// ───────────────────────────────────────────────────────────── CATALOG SAVED VIEWS
// Saves the catalog's full filter+search+sort+tab state under a name and lets the
// user switch between them with one click. Persists to localStorage.
//
// State shape: { id, name, pinned, tab, view, sort, q, labelFilter, pubFilter,
//                filters: { status, societies, yearMin, yearMax, sharesComplete, hasRecordings } }

const SV_KEY = 'astro:catalog-views';
const SV_ACTIVE_KEY = 'astro:catalog-view-active';

const SV_DEFAULTS = [
  {
    id: 'sv-conflicts',
    name: 'Conflicts to resolve',
    pinned: true,
    tab: 'songs', view: 'table', sort: 'edited', q: '',
    labelFilter: '', pubFilter: '',
    filters: { status: ['Conflict'], societies: [], yearMin: 2015, yearMax: 2025, sharesComplete: 'incomplete', hasRecordings: 'any' },
  },
  {
    id: 'sv-unmatched',
    name: 'Unmatched recordings',
    pinned: true,
    tab: 'recordings', view: 'table', sort: 'edited', q: '',
    labelFilter: '', pubFilter: '',
    filters: { status: ['Unmatched'], societies: [], yearMin: 2015, yearMax: 2025, sharesComplete: 'any', hasRecordings: 'no' },
  },
  {
    id: 'sv-cwr-recent',
    name: 'Recent CWR · ASCAP',
    pinned: false,
    tab: 'songs', view: 'table', sort: 'created', q: '',
    labelFilter: '', pubFilter: '',
    filters: { status: ['Registered','Pending'], societies: ['ASCAP'], yearMin: 2023, yearMax: 2025, sharesComplete: 'any', hasRecordings: 'any' },
  },
  {
    id: 'sv-prs',
    name: 'PRS · UK pipeline',
    pinned: false,
    tab: 'songs', view: 'table', sort: 'edited', q: '',
    labelFilter: '', pubFilter: '',
    filters: { status: [], societies: ['PRS'], yearMin: 2015, yearMax: 2025, sharesComplete: 'any', hasRecordings: 'any' },
  },
  {
    id: 'sv-incomplete-shares',
    name: 'Incomplete share splits',
    pinned: false,
    tab: 'songs', view: 'table', sort: 'edited', q: '',
    labelFilter: '', pubFilter: '',
    filters: { status: [], societies: [], yearMin: 2015, yearMax: 2025, sharesComplete: 'incomplete', hasRecordings: 'any' },
  },
];

function svLoad() {
  try {
    const raw = localStorage.getItem(SV_KEY);
    if (!raw) {
      localStorage.setItem(SV_KEY, JSON.stringify(SV_DEFAULTS));
      return SV_DEFAULTS.slice();
    }
    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) return SV_DEFAULTS.slice();
    return parsed;
  } catch { return SV_DEFAULTS.slice(); }
}
function svSave(views) {
  try { localStorage.setItem(SV_KEY, JSON.stringify(views)); } catch {}
}
function svGetActive() {
  try { return localStorage.getItem(SV_ACTIVE_KEY) || ''; } catch { return ''; }
}
function svSetActive(id) {
  try {
    if (id) localStorage.setItem(SV_ACTIVE_KEY, id);
    else localStorage.removeItem(SV_ACTIVE_KEY);
  } catch {}
}

// Deep equality for filter blob — small + flat enough that JSON.stringify suffices
function svEq(a, b) {
  try { return JSON.stringify(a) === JSON.stringify(b); } catch { return false; }
}

function svSnapshot(state) {
  return {
    tab: state.tab,
    view: state.view,
    sort: state.sort,
    q: state.q || '',
    labelFilter: state.labelFilter || '',
    pubFilter: state.pubFilter || '',
    filters: {
      status:          (state.filters?.status || []).slice().sort(),
      societies:       (state.filters?.societies || []).slice().sort(),
      yearMin:         state.filters?.yearMin ?? 2015,
      yearMax:         state.filters?.yearMax ?? 2025,
      sharesComplete:  state.filters?.sharesComplete ?? 'any',
      hasRecordings:   state.filters?.hasRecordings ?? 'any',
    },
  };
}

function svMatches(view, state) {
  return svEq(svSnapshot(view), svSnapshot(state));
}

// ── UI ────────────────────────────────────────────────────────────────────

function CatalogSavedViews({ state, applyView }) {
  const [views, setViews] = React.useState(() => svLoad());
  const [activeId, setActiveId] = React.useState(() => svGetActive());
  const [saveOpen, setSaveOpen] = React.useState(false);
  const [menuFor, setMenuFor] = React.useState(null); // view id w/ open kebab
  const [editingFor, setEditingFor] = React.useState(null); // view id being renamed
  const [editName, setEditName] = React.useState('');

  // Whenever a view is "active" but the user has since changed something, surface dirty
  const activeView = views.find(v => v.id === activeId) || null;
  const isDirty = activeView ? !svMatches(activeView, state) : false;

  React.useEffect(() => {
    if (!menuFor) return;
    const close = () => setMenuFor(null);
    window.addEventListener('click', close);
    return () => window.removeEventListener('click', close);
  }, [menuFor]);

  const persist = (next) => { setViews(next); svSave(next); };
  const setActive = (id) => { setActiveId(id); svSetActive(id); };

  const apply = (v) => {
    applyView(v);
    setActive(v.id);
    window.dispatchEvent(new CustomEvent('astro-toast', { detail: { msg: `View "${v.name}" applied`, tone: 'ok' }}));
  };

  const saveCurrent = (name) => {
    const n = (name || '').trim();
    if (!n) return;
    const snap = svSnapshot(state);
    const id = 'sv-' + Math.random().toString(36).slice(2, 9);
    const next = [...views, { id, name: n, pinned: false, ...snap }];
    persist(next);
    setActive(id);
    setSaveOpen(false);
    window.dispatchEvent(new CustomEvent('astro-toast', { detail: { msg: `Saved view "${n}"`, tone: 'ok' }}));
  };

  const updateActive = () => {
    if (!activeView) return;
    const snap = svSnapshot(state);
    const next = views.map(v => v.id === activeView.id ? { ...v, ...snap } : v);
    persist(next);
    window.dispatchEvent(new CustomEvent('astro-toast', { detail: { msg: `Updated "${activeView.name}"`, tone: 'ok' }}));
  };

  const togglePin = (id) => {
    const next = views.map(v => v.id === id ? { ...v, pinned: !v.pinned } : v);
    persist(next);
    setMenuFor(null);
  };

  const remove = (id) => {
    const target = views.find(v => v.id === id);
    const next = views.filter(v => v.id !== id);
    persist(next);
    if (activeId === id) setActive('');
    setMenuFor(null);
    window.dispatchEvent(new CustomEvent('astro-toast', { detail: { msg: `Deleted "${target?.name || 'view'}"`, tone: 'warn' }}));
  };

  const rename = (id) => {
    const v = views.find(x => x.id === id);
    if (!v) return;
    setEditingFor(id);
    setEditName(v.name);
    setMenuFor(null);
  };
  const commitRename = () => {
    if (!editingFor) return;
    const n = editName.trim();
    if (n) {
      const next = views.map(v => v.id === editingFor ? { ...v, name: n } : v);
      persist(next);
    }
    setEditingFor(null);
  };

  // Sort: pinned first, then insertion order
  const ordered = [...views].sort((a, b) => Number(!!b.pinned) - Number(!!a.pinned));

  const Ic = window.Ic;
  const pinIc = (filled) => (
    <svg width={11} height={11} viewBox="0 0 24 24" fill={filled ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="1.5">
      <path d="M12 2v8l4 4v3H8v-3l4-4V2z M9 2h6 M12 17v5"/>
    </svg>
  );

  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap',
      padding: '12px 0', borderBottom: '1px solid var(--rule-soft)', marginBottom: 14,
    }}>
      <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.14em', color: 'var(--ink-3)', marginRight: 4 }}>VIEWS</span>

      {ordered.map(v => {
        const active = v.id === activeId;
        const matches = svMatches(v, state);
        const showDirty = active && isDirty;
        return (
          <div key={v.id} style={{ position: 'relative' }}>
            {editingFor === v.id ? (
              <input autoFocus value={editName}
                onChange={e => setEditName(e.target.value)}
                onBlur={commitRename}
                onKeyDown={e => { if (e.key === 'Enter') commitRename(); if (e.key === 'Escape') setEditingFor(null); }}
                style={{
                  padding: '5px 9px', fontSize: 11, fontFamily: 'inherit',
                  border: '1px solid var(--ink)', background: 'var(--bg)', color: 'var(--ink)',
                  outline: 'none', minWidth: 140,
                }} />
            ) : (
              <div style={{ display: 'inline-flex', alignItems: 'stretch', border: '1px solid var(--rule)' }}>
                <button onClick={() => apply(v)}
                  className="ff-mono"
                  title={matches ? `Active: ${v.name}` : `Apply view "${v.name}"`}
                  style={{
                    display: 'inline-flex', alignItems: 'center', gap: 6,
                    padding: '5px 10px',
                    background: active ? 'var(--ink)' : (matches ? 'var(--bg-2)' : 'transparent'),
                    color: active ? 'var(--bg)' : 'var(--ink)',
                    border: 0, fontSize: 11, fontWeight: active ? 600 : 500,
                    cursor: 'pointer', letterSpacing: '.01em',
                  }}>
                  {v.pinned && <span style={{ opacity: active ? 1 : .55, display: 'inline-flex' }}>{pinIc(true)}</span>}
                  <span>{v.name}</span>
                  {showDirty && <span className="ff-mono" title="Modified — click 'Update' to save changes"
                    style={{ fontSize: 9, padding: '1px 4px', background: '#b78113', color: '#fff', letterSpacing: '.06em' }}>·</span>}
                </button>
                <button onClick={(e) => { e.stopPropagation(); setMenuFor(menuFor === v.id ? null : v.id); }}
                  aria-label={`Options for ${v.name}`}
                  className="ff-mono"
                  style={{
                    padding: '0 7px', background: active ? 'var(--ink)' : 'transparent',
                    color: active ? 'var(--bg)' : 'var(--ink-2)',
                    border: 0, borderLeft: `1px solid ${active ? 'rgba(255,255,255,.2)' : 'var(--rule)'}`,
                    fontSize: 12, cursor: 'pointer', lineHeight: 1,
                  }}>⋯</button>
              </div>
            )}
            {menuFor === v.id && (
              <div onClick={e => e.stopPropagation()}
                style={{
                  position: 'absolute', top: '100%', left: 0, marginTop: 4, zIndex: 30,
                  background: 'var(--bg)', border: '1px solid var(--ink)', boxShadow: '4px 4px 0 rgba(0,0,0,.14)',
                  minWidth: 180,
                }}>
                <SvMenuItem onClick={() => togglePin(v.id)}>{v.pinned ? 'Unpin' : 'Pin'}</SvMenuItem>
                <SvMenuItem onClick={() => rename(v.id)}>Rename…</SvMenuItem>
                {active && isDirty && (
                  <SvMenuItem onClick={() => { updateActive(); setMenuFor(null); }}>Update from current</SvMenuItem>
                )}
                <SvMenuItem onClick={() => { apply(v); setMenuFor(null); }}>Apply</SvMenuItem>
                <SvMenuDivider/>
                <SvMenuItem danger onClick={() => remove(v.id)}>Delete view</SvMenuItem>
              </div>
            )}
          </div>
        );
      })}

      {activeId && isDirty && (
        <button onClick={updateActive}
          className="ff-mono upper"
          title="Save current state into the active view"
          style={{
            padding: '5px 10px', fontSize: 10, letterSpacing: '.1em',
            background: '#b78113', color: '#fff', border: 0, cursor: 'pointer', fontWeight: 600,
          }}>UPDATE VIEW</button>
      )}
      {activeId && (
        <button onClick={() => setActive('')}
          className="ff-mono upper"
          title="Detach from this view"
          style={{
            padding: '5px 8px', fontSize: 10, letterSpacing: '.1em',
            background: 'transparent', color: 'var(--ink-3)', border: '1px solid var(--rule)', cursor: 'pointer',
          }}>×</button>
      )}

      <span style={{ flex: 1 }}/>

      <button onClick={() => setSaveOpen(true)}
        className="ff-mono upper"
        title="Save current filters as a new view"
        style={{
          display: 'inline-flex', alignItems: 'center', gap: 6,
          padding: '5px 10px', fontSize: 10, letterSpacing: '.1em',
          background: 'transparent', color: 'var(--ink)', border: '1px solid var(--rule)', cursor: 'pointer', fontWeight: 600,
        }}>
        <Ic.Plus width={10} height={10}/>
        SAVE VIEW
      </button>

      {saveOpen && (
        <SvSaveModal
          onClose={() => setSaveOpen(false)}
          onSave={saveCurrent}
          snapshot={svSnapshot(state)}
        />
      )}
    </div>
  );
}

function SvMenuItem({ children, onClick, danger }) {
  return (
    <button onClick={onClick} className="ff-mono"
      style={{
        display: 'block', width: '100%', textAlign: 'left',
        padding: '8px 12px', background: 'transparent',
        color: danger ? '#c0392b' : 'var(--ink)',
        border: 0, fontSize: 11, cursor: 'pointer',
      }}
      onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-2)'}
      onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
      {children}
    </button>
  );
}
function SvMenuDivider() {
  return <div style={{ height: 1, background: 'var(--rule)' }}/>;
}

function SvSaveModal({ onClose, onSave, snapshot }) {
  const [name, setName] = React.useState('');

  // Build a tiny human summary of what's captured
  const f = snapshot.filters;
  const bits = [];
  bits.push(`tab: ${snapshot.tab}`);
  if (snapshot.q) bits.push(`search: "${snapshot.q}"`);
  if (f.status.length) bits.push(`status: ${f.status.join(', ')}`);
  if (f.societies.length) bits.push(`societies: ${f.societies.join(', ')}`);
  if (f.sharesComplete !== 'any') bits.push(`shares: ${f.sharesComplete}`);
  if (f.hasRecordings !== 'any') bits.push(`has recordings: ${f.hasRecordings}`);
  if (f.yearMin !== 2015 || f.yearMax !== 2025) bits.push(`years: ${f.yearMin}–${f.yearMax}`);
  if (snapshot.labelFilter) bits.push(`label: ${snapshot.labelFilter}`);
  if (snapshot.pubFilter) bits.push(`publisher: ${snapshot.pubFilter}`);
  bits.push(`sort: ${snapshot.sort}`);

  return (
    <>
      <div onClick={onClose} style={{position:'fixed',inset:0,background:'rgba(0,0,0,.42)',zIndex:80}}/>
      <div style={{
        position:'fixed', top:'50%', left:'50%', transform:'translate(-50%, -50%)',
        width:'min(520px, 92vw)',
        background:'var(--bg)', border:'1px solid var(--ink)', boxShadow:'8px 8px 0 rgba(0,0,0,.18)',
        zIndex:81, color:'var(--ink)'
      }}>
        <div style={{padding:'16px 20px',borderBottom:'1px solid var(--rule)'}}>
          <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.14em',color:'var(--ink-3)',marginBottom:6}}>SAVED VIEW</div>
          <div className="ff-display" style={{fontSize:22,fontWeight:700,letterSpacing:'-0.01em'}}>Save current view</div>
        </div>
        <div style={{padding:'18px 20px', display:'flex', flexDirection:'column', gap: 14}}>
          <div>
            <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:8}}>NAME</div>
            <input autoFocus value={name} onChange={e=>setName(e.target.value)}
              onKeyDown={e => { if (e.key === 'Enter' && name.trim()) onSave(name); }}
              placeholder="e.g. Q1 conflict review · ASCAP"
              style={{width:'100%',padding:'10px 12px',fontSize:13,background:'transparent',color:'var(--ink)',border:'1px solid var(--rule)'}}/>
          </div>
          <div>
            <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:8}}>CAPTURED</div>
            <div style={{
              border:'1px solid var(--rule)', background:'var(--bg-2)',
              padding:'10px 12px', fontSize:11, lineHeight:1.7,
            }} className="ff-mono">
              {bits.map((b, i) => (
                <div key={i} style={{color: 'var(--ink-2)'}}>{b}</div>
              ))}
            </div>
          </div>
          <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',lineHeight:1.5}}>
            Views are stored in your browser. Pin from the ⋯ menu to keep them at the front of the row.
          </div>
        </div>
        <div style={{display:'flex',gap:8,justifyContent:'flex-end',padding:'12px 16px',borderTop:'1px solid var(--rule)',background:'var(--bg-2)'}}>
          <button onClick={onClose}
            style={{padding:'8px 14px',background:'transparent',color:'var(--ink)',border:'1px solid var(--rule)',fontSize:12,fontWeight:500,cursor:'pointer'}}>Cancel</button>
          <button onClick={() => name.trim() && onSave(name)}
            disabled={!name.trim()}
            style={{padding:'8px 14px',background:'var(--ink)',color:'var(--bg)',border:0,fontSize:12,fontWeight:600,cursor: name.trim() ? 'pointer' : 'not-allowed',letterSpacing:'.02em',opacity: name.trim() ? 1 : .4}}>
            Save view
          </button>
        </div>
      </div>
    </>
  );
}

Object.assign(window, { CatalogSavedViews, svLoad, svSave, svSnapshot, svMatches });
