// globals.jsx — App-level overlays available from anywhere via custom events:
//   window.dispatchEvent(new CustomEvent('astro-open-recording', {detail:{id}}))
//   window.dispatchEvent(new CustomEvent('astro-add-recording',  {detail:{workId}}))

const { useState: useGS, useEffect: useGE } = React;

// ── Global recording drawer ────────────────────────────────────
function GlobalRecordingDrawer({ go }) {
  const [recId, setRecId] = useGS(null);
  useGE(() => {
    // Default behaviour: navigate to the recording PAGE.
    // If `peek: true` is passed in the event detail (or the user alt/cmd/meta-clicks
    // a row that dispatched it), open as a quick-peek drawer instead.
    const open  = (e) => {
      if (!e.detail?.id) return;
      if (e.detail.peek) { setRecId(e.detail.id); return; }
      if (typeof go === 'function') go('recording', { id: e.detail.id });
      else setRecId(e.detail.id);
    };
    const close = () => setRecId(null);
    window.addEventListener('astro-open-recording', open);
    window.addEventListener('astro-close-recording', close);
    return () => {
      window.removeEventListener('astro-open-recording', open);
      window.removeEventListener('astro-close-recording', close);
    };
  }, [go]);
  if (!recId) return null;
  // Prefer rich graph; fall back to plain RECORDINGS list (legacy)
  const r = (typeof REC_BY_ID !== 'undefined' && REC_BY_ID[recId])
    || ((typeof RECORDINGS !== 'undefined') ? RECORDINGS.find(x=>x.id===recId) : null);
  if (!r) return null;
  return <RecordingDrawer r={r} onClose={()=>setRecId(null)} go={(name, payload) => { setRecId(null); go && go(name, payload); }}/>;
}

// ── Global "Add recording" modal ───────────────────────────────
// ── PerformersField — structured artist list with role + credited-as ───────
// Each row: { aid, name, role: primary|featuring|remixer|producer, creditedAs }
// Typeahead is backed by window.ARTISTS — selecting a profile sets `aid`.
// "Credited as" is the display string for this recording (e.g. "Solange" when
// the underlying profile is "Solange Knowles", or "Sault feat. Cleo Sol").
function PerformersField({ value, onChange }) {
  const list = Array.isArray(value) && value.length > 0 ? value : [{ aid:null, name:'', role:'primary', creditedAs:'' }];
  const update = (i, patch) => onChange(list.map((r, j) => j === i ? { ...r, ...patch } : r));
  const remove = (i) => onChange(list.length === 1 ? [{aid:null,name:'',role:'primary',creditedAs:''}] : list.filter((_, j) => j !== i));
  const add = () => onChange([...list, { aid:null, name:'', role:'featuring', creditedAs:'' }]);

  const ROLES = [
    {k:'primary',   l:'Primary'},
    {k:'featuring', l:'Featuring'},
    {k:'remixer',   l:'Remixer'},
    {k:'producer',  l:'Producer'},
  ];

  return (
    <div style={{marginBottom:14}}>
      <div style={{display:'grid',gridTemplateColumns:'minmax(0,1.4fr) 130px minmax(0,1fr) 24px',gap:8,padding:'4px 0 6px',
        fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',textTransform:'uppercase',fontFamily:'var(--ff-mono, ui-monospace)'}}>
        <span>Artist profile</span>
        <span>Role</span>
        <span>Credited as</span>
        <span/>
      </div>
      {list.map((row, i) => (
        <div key={i} style={{display:'grid',gridTemplateColumns:'minmax(0,1.4fr) 130px minmax(0,1fr) 32px',gap:8,marginBottom:6,alignItems:'stretch'}}>
          <ArtistTypeahead
            value={row.name}
            aid={row.aid}
            onSelect={({aid, name})=>update(i, { aid, name, creditedAs: row.creditedAs || name })}/>
          <select value={row.role} onChange={e=>update(i, { role: e.target.value })}
            className="ff-mono"
            style={{height:32,padding:'0 8px',background:'var(--bg-2)',border:'1px solid var(--rule)',fontSize:11,color:'var(--ink)',cursor:'pointer',boxSizing:'border-box'}}>
            {ROLES.map(r=><option key={r.k} value={r.k}>{r.l}</option>)}
          </select>
          <input value={row.creditedAs} onChange={e=>update(i, { creditedAs: e.target.value })}
            placeholder={row.name || 'Display name on this recording'}
            style={{height:32,padding:'0 8px',background:'var(--bg-2)',border:'1px solid var(--rule)',fontSize:12,color:'var(--ink)',minWidth:0,boxSizing:'border-box',width:'100%'}}/>
          <button onClick={()=>remove(i)} aria-label="Remove performer"
            style={{height:32,width:32,padding:0,background:'transparent',border:'1px solid var(--rule)',cursor:'pointer',color:'var(--ink-3)',fontSize:14,lineHeight:1,boxSizing:'border-box'}}>×</button>
        </div>
      ))}
      <button onClick={add} className="ff-mono upper"
        style={{display:'inline-flex',alignItems:'center',gap:6,padding:'6px 10px',marginTop:2,
          background:'transparent',color:'var(--ink-2)',border:'1px dashed var(--rule)',cursor:'pointer',
          fontSize:10,letterSpacing:'.08em'}}>
        + Add performer
      </button>
    </div>
  );
}

// ── ArtistTypeahead — profile-linked artist name input ────────────────────
// Mirrors WriterTypeahead but pulls EVERY artist (persons + acts) from
// window.ARTISTS — recordings can credit a band ("Sault") as well as a person.
function ArtistTypeahead({ value, aid, onSelect, onDraft }) {
  const [open, setOpen] = React.useState(false);
  const [draft, setDraft] = React.useState(value || '');
  React.useEffect(()=>{ setDraft(value || ''); }, [value, aid]);

  const all = (window.ARTISTS || []);
  const q = draft.trim().toLowerCase();
  const matches = q
    ? all.filter(p => p.name.toLowerCase().includes(q) || (p.aliases||[]).some(a => a.toLowerCase().includes(q))).slice(0, 8)
    : all.slice(0, 8);
  const exact = q ? all.find(p => p.name.toLowerCase() === q) : null;

  const inputRef = React.useRef(null);
  const [rect, setRect] = React.useState(null);
  React.useEffect(()=>{
    if (!open || !inputRef.current) return;
    const update = () => { if (inputRef.current) setRect(inputRef.current.getBoundingClientRect()); };
    update();
    window.addEventListener('scroll', update, true);
    window.addEventListener('resize', update);
    return () => { window.removeEventListener('scroll', update, true); window.removeEventListener('resize', update); };
  }, [open]);

  const closeBox = () => setTimeout(()=>setOpen(false), 120);

  const pick = (p) => {
    onSelect({ aid: p.id, name: p.name });
    setDraft(p.name);
    setOpen(false);
  };
  const acceptDraft = () => {
    if (exact) pick(exact);
    else if (draft.trim()) onSelect({ aid: null, name: draft.trim() });
  };

  return (
    <div style={{position:'relative',minWidth:0,display:'flex',alignItems:'stretch'}}>
      <input
        ref={inputRef}
        value={draft}
        onChange={e=>{ setDraft(e.target.value); setOpen(true); if (onDraft) onDraft(e.target.value); }}
        onFocus={()=>setOpen(true)}
        onBlur={()=>{ acceptDraft(); closeBox(); }}
        onKeyDown={e=>{ if(e.key==='Enter'){ e.preventDefault(); acceptDraft(); setOpen(false);} if(e.key==='Escape'){ setOpen(false);} }}
        placeholder="Solange, Caroline Polachek, Sault…"
        style={{flex:1,height:32,padding:'0 8px',background:'var(--bg-2)',border:'1px solid var(--rule)',fontSize:12,color:'var(--ink)',boxSizing:'border-box',minWidth:0}}/>
      {aid && (
        <span title="Linked to artist profile" className="ff-mono"
          style={{display:'inline-flex',alignItems:'center',padding:'0 6px',marginLeft:-1,
            background:'var(--ink)',color:'var(--bg)',fontSize:9,letterSpacing:'.08em',textTransform:'uppercase',fontWeight:600}}>
          ✓ profile
        </span>
      )}
      {open && rect && ReactDOM.createPortal(
        <div style={{position:'fixed',top:rect.bottom+2,left:rect.left,width:rect.width,maxHeight:240,overflowY:'auto',
          background:'var(--bg)',border:'1px solid var(--ink)',boxShadow:'4px 4px 0 rgba(0,0,0,.18)',zIndex:200}}>
          {matches.length > 0 ? matches.map((p,i)=>(
            <button key={p.id} onMouseDown={e=>e.preventDefault()} onClick={()=>pick(p)}
              style={{display:'grid',gridTemplateColumns:'1fr auto',gap:8,width:'100%',padding:'7px 10px',
                background:'transparent',border:0,borderBottom: i<matches.length-1?'1px solid var(--rule-soft)':'none',
                cursor:'pointer',textAlign:'left',alignItems:'baseline'}}
              onMouseEnter={e=>e.currentTarget.style.background='var(--bg-2)'}
              onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
              <div>
                <div style={{fontSize:12,color:'var(--ink)',fontWeight:500}}>{p.name}</div>
                {(p.aliases||[]).length > 0 && <div className="ff-mono" style={{fontSize:9,color:'var(--ink-3)',marginTop:1}}>aka {p.aliases.slice(0,3).join(', ')}</div>}
              </div>
              <span className="ff-mono upper" style={{fontSize:8,letterSpacing:'.08em',color:'var(--ink-3)'}}>{p.type || 'Person'}</span>
            </button>
          )) : (
            <div className="ff-mono" style={{padding:'10px 12px',fontSize:11,color:'var(--ink-3)'}}>No matching profile.</div>
          )}
          {q && !exact && (
            <button onMouseDown={e=>e.preventDefault()} onClick={()=>{ onSelect({ aid:null, name:draft.trim() }); setOpen(false); }}
              style={{display:'flex',alignItems:'center',gap:6,width:'100%',padding:'8px 10px',
                background:'var(--bg-2)',border:0,borderTop:'1px solid var(--rule)',cursor:'pointer',textAlign:'left'}}>
              <span style={{fontSize:11,color:'var(--ink-2)'}}>+ Use "<b style={{color:'var(--ink)'}}>{draft.trim()}</b>" as an unlinked credit</span>
            </button>
          )}
        </div>,
        document.body
      )}
    </div>
  );
}

// ── LabelTypeahead — record-label combobox with "+ create new" affordance ─
// Mirrors ArtistTypeahead. Searches window.LABELS by name AND code.
// Selecting a known label sets {labelId, name, code}. Typing a new name and
// confirming creates a new entry on window.LABELS (so it shows up everywhere
// labels are listed) with a generated code, and returns the same shape.
function LabelTypeahead({ value, labelId, onSelect, placeholder, allowCreate = true }) {
  const [open, setOpen] = React.useState(false);
  const [draft, setDraft] = React.useState(value || '');
  React.useEffect(()=>{ setDraft(value || ''); }, [value, labelId]);

  const all = (window.LABELS || []);
  const q = draft.trim().toLowerCase();
  const matches = q
    ? all.filter(l => l.name.toLowerCase().includes(q) || (l.code||'').toLowerCase().includes(q)).slice(0, 8)
    : all.slice(0, 8);
  const exact = q ? all.find(l => l.name.toLowerCase() === q) : null;

  const inputRef = React.useRef(null);
  const [rect, setRect] = React.useState(null);
  React.useEffect(()=>{
    if (!open || !inputRef.current) return;
    const update = () => { if (inputRef.current) setRect(inputRef.current.getBoundingClientRect()); };
    update();
    window.addEventListener('scroll', update, true);
    window.addEventListener('resize', update);
    return () => { window.removeEventListener('scroll', update, true); window.removeEventListener('resize', update); };
  }, [open]);

  const closeBox = () => setTimeout(()=>setOpen(false), 120);

  const pick = (l) => {
    onSelect({ labelId: l.id, name: l.name, code: l.code });
    setDraft(l.name);
    setOpen(false);
  };

  // Generate a 3-char code from a new label name (first letters of words, padded).
  const codeFromName = (n) => {
    const parts = n.toUpperCase().replace(/[^A-Z0-9 ]/g,'').split(/\s+/).filter(Boolean);
    const initials = parts.map(p => p[0]).join('').slice(0, 3);
    return (initials.length >= 2 ? initials : (parts[0] || 'NEW').slice(0, 3)).padEnd(3, 'X');
  };

  const createNew = () => {
    const name = draft.trim();
    if (!name) return;
    const code = codeFromName(name);
    const id = 'lb_' + Date.now().toString(36);
    const newLabel = { id, name, code, releases: 0, artists: 0, parent: 'Independent', kind: 'indie', deals: '—', color: '#1a1a1a' };
    if (!window.LABELS) window.LABELS = [];
    window.LABELS.push(newLabel);
    onSelect({ labelId: id, name, code, _isNew: true });
    setOpen(false);
    if (window.toast) window.toast(`Created label "${name}" · ${code}`);
  };

  const acceptDraft = () => {
    if (exact) pick(exact);
    // otherwise leave the draft alone — user must explicitly pick or create
  };

  return (
    <div style={{position:'relative',minWidth:0,display:'flex',alignItems:'stretch'}}>
      <input
        ref={inputRef}
        value={draft}
        onChange={e=>{ setDraft(e.target.value); setOpen(true); onSelect({ labelId: null, name: e.target.value, code: null }); }}
        onFocus={()=>setOpen(true)}
        onBlur={()=>{ acceptDraft(); closeBox(); }}
        onKeyDown={e=>{ if(e.key==='Enter'){ e.preventDefault(); if (exact) pick(exact); else if (allowCreate) createNew(); setOpen(false);} if(e.key==='Escape'){ setOpen(false);} }}
        placeholder={placeholder || 'Search labels…'}
        style={{flex:1,height:32,padding:'0 8px',background:'var(--bg-2)',border:'1px solid var(--rule)',fontSize:12,color:'var(--ink)',boxSizing:'border-box',minWidth:0}}/>
      {labelId && (
        <span title="Linked to label record" className="ff-mono"
          style={{display:'inline-flex',alignItems:'center',padding:'0 6px',marginLeft:-1,
            background:'var(--ink)',color:'var(--bg)',fontSize:9,letterSpacing:'.08em',textTransform:'uppercase',fontWeight:600}}>
          ✓ linked
        </span>
      )}
      {open && rect && ReactDOM.createPortal(
        <div style={{position:'fixed',top:rect.bottom+2,left:rect.left,width:rect.width,maxHeight:260,overflowY:'auto',
          background:'var(--bg)',border:'1px solid var(--ink)',boxShadow:'4px 4px 0 rgba(0,0,0,.18)',zIndex:200}}>
          {matches.length > 0 ? matches.map((l,i)=>(
            <button key={l.id} onMouseDown={e=>e.preventDefault()} onClick={()=>pick(l)}
              style={{display:'grid',gridTemplateColumns:'48px 1fr auto',gap:10,width:'100%',padding:'8px 10px',
                background:'transparent',border:0,borderBottom: i<matches.length-1?'1px solid var(--rule-soft)':'none',
                cursor:'pointer',textAlign:'left',alignItems:'center'}}
              onMouseEnter={e=>e.currentTarget.style.background='var(--bg-2)'}
              onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
              <span className="ff-mono num" style={{fontSize:9,fontWeight:600,padding:'3px 6px',background:l.color || 'var(--ink)',color:'#fff',letterSpacing:'.06em',textAlign:'center'}}>{l.code}</span>
              <div style={{minWidth:0}}>
                <div style={{fontSize:12,color:'var(--ink)',fontWeight:500,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>{l.name}</div>
                <div className="ff-mono" style={{fontSize:9,color:'var(--ink-3)',marginTop:1}}>{l.parent || '—'} · {(l.releases||0).toLocaleString()} releases</div>
              </div>
              <span className="ff-mono upper" style={{fontSize:8,letterSpacing:'.08em',color:'var(--ink-3)'}}>{l.kind || 'label'}</span>
            </button>
          )) : (
            <div className="ff-mono" style={{padding:'10px 12px',fontSize:11,color:'var(--ink-3)'}}>No matching label.</div>
          )}
          {q && !exact && allowCreate && (
            <button onMouseDown={e=>e.preventDefault()} onClick={createNew}
              style={{display:'flex',alignItems:'center',justifyContent:'space-between',gap:10,width:'100%',padding:'14px 14px',
                background:'var(--accent)',color:'var(--accent-ink)',border:0,borderTop:'2px solid var(--ink)',cursor:'pointer',textAlign:'left',fontWeight:600}}>
              <span className="ff-mono upper" style={{fontSize:11,letterSpacing:'.1em'}}>
                + CREATE NEW LABEL
              </span>
              <span style={{display:'flex',alignItems:'center',gap:8}}>
                <span style={{fontSize:13,fontWeight:700}}>{draft.trim()}</span>
                <span className="ff-mono num" style={{fontSize:10,padding:'3px 6px',background:'var(--ink)',color:'var(--bg)',letterSpacing:'.06em'}}>{codeFromName(draft.trim())}</span>
              </span>
            </button>
          )}
        </div>,
        document.body
      )}
    </div>
  );
}

function GlobalAddRecordingModal() {
  const [open, setOpen] = useGS(null); // null | { workId, onCreated, returnLabel }
  const [step, setStep] = useGS('match'); // match → details → confirm
  const [form, setForm] = useGS({});

  useGE(() => {
    const onOpen = (e) => {
      setOpen({
        workId: e.detail?.workId || null,
        onCreated: e.detail?.onCreated || null,
        returnLabel: e.detail?.returnLabel || null,
      });
      setStep('match');
      const w = e.detail?.workId && (typeof WORK_BY_ID!=='undefined') ? WORK_BY_ID[e.detail.workId] : null;
      setForm({
        title: w?.title || '',
        subTitle: '',
        versionType: 'Original',  // Original | Edit | Remix | Live | Acoustic | Karaoke | Instrumental | Cover
        versionLabel: '',          // e.g. "Radio Edit", "Extended Mix"
        artist: '', // legacy; derived from performers[0] for display
        performers: [{ aid: null, name: '', role: 'primary', creditedAs: '' }],
        contributors: [],          // [{role, name, isni}] — producer, engineer, mixer, etc.
        isrc: '',
        catalogNumber: '',         // label catalog #
        label: '',                 // recording label (P-line owner)
        marketingLabel: '',        // imprint that markets — may differ
        pLine: '',                 // ℗ year + owner
        year: new Date().getFullYear(),
        recordingDate: '',         // when recorded (YYYY-MM-DD)
        firstReleaseDate: '',      // when first commercially released
        countryOfRecording: '',    // ISO 3166-1 alpha-2
        countryOfMastering: '',
        durationMin: w?.duration ? Math.floor(w.duration/60) : 3,
        durationSec: w?.duration ? (w.duration%60) : 30,
        // DDEX SoundRecording details
        languageOfPerformance: '', // vocals language; blank for instrumental
        parentalAdvisory: 'NotExplicit', // Explicit | NotExplicit | ExplicitContentEdited
        explicit: false,           // legacy mirror of parentalAdvisory
        genre: '',
        subGenre: '',
        // Audio profile
        sampleRate: '44.1 kHz',
        bitDepth: '16',
        channels: 'Stereo',
        audioFormat: 'WAV',
        // Lifecycle
        kind: 'original',          // original | cover | remix | live | sample
        clearance: 'pending',
        // Rights / ownership
        recordingOwners: [],       // [{name, territory, share}]
      });
    };
    window.addEventListener('astro-add-recording', onOpen);
    return () => window.removeEventListener('astro-add-recording', onOpen);
  }, []);

  // Sync derived artist string for legacy display — MUST be before early return
  React.useEffect(() => {
    if (!open || !form.performers) return;
    const display = form.performers
      .filter(p => p && (p.creditedAs || p.name))
      .map(p => {
        const label = p.creditedAs || p.name;
        if (p.role === 'featuring') return `feat. ${label}`;
        if (p.role === 'remixer') return `${label} (Remix)`;
        return label;
      })
      .join(', ');
    if (display !== form.artist) setForm(f => ({...f, artist: display}));
  }, [form.performers, open]);

  if (!open) return null;
  const w = open.workId && (typeof WORK_BY_ID!=='undefined') ? WORK_BY_ID[open.workId] : null;
  const close = () => setOpen(null);
  const set = (patch) => setForm(f => ({...f, ...patch}));

  // Mock candidate matches the user might pick from — DSP / metadata sources
  const candidates = form.title || form.artist ? [
    {src:'Spotify',     id:'5kxJYKWa…', isrc:'USNEW2600118', title: form.title || '(unknown)', artist: form.artist || 'Unknown', label:'—', year: form.year, score: 92},
    {src:'MusicBrainz', id:'mbid:7f3…', isrc:'USNEW2600119', title: (form.title||'') + ' (alt take)', artist: form.artist || 'Unknown', label:'—', year: form.year-1, score: 78},
    {src:'Apple Music', id:'1623345…', isrc:'USNEW2600120', title: form.title || '(unknown)', artist: form.artist || 'Unknown', label:'—', year: form.year, score: 71},
  ] : [];

  return (
    <>
      <div onClick={close} style={{position:'fixed',inset:0,background:'rgba(0,0,0,.42)',zIndex:90}}/>
      <div role="dialog" aria-modal="true" style={{
        position:'fixed',top:'50%',left:'50%',transform:'translate(-50%,-50%)',
        width: step==='edit' ? 'min(940px,96vw)' : 'min(640px,94vw)',
        maxHeight:'90vh',
        background:'var(--bg)',border:'1px solid var(--rule)',zIndex:91,overflowY:'auto',
        boxShadow:'0 30px 80px rgba(0,0,0,.34)'}}>

        {/* Header */}
        <div style={{padding:'18px 24px',borderBottom:'1px solid var(--rule)',display:'flex',justifyContent:'space-between',alignItems:'flex-start',gap:14}}>
          <div>
            <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.14em',color:'var(--ink-3)',marginBottom:4}}>NEW RECORDING</div>
            <div className="ff-display" style={{fontSize:24,fontWeight:700,letterSpacing:'-0.02em',lineHeight:1.05}}>
              Add recording{w ? <> to <span style={{background:'var(--ink)',color:'var(--bg)',padding:'0 6px',marginLeft:4}}>{w.title}</span></> : ''}
            </div>
            {w && <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:6}}>ISWC {w.iswc} · {w.writers.join(', ')}</div>}
          </div>
          <button onClick={close} aria-label="Close" style={{width:30,height:30,background:'transparent',border:'1px solid var(--rule)',cursor:'pointer',fontSize:16,lineHeight:1}}>×</button>
        </div>

        {/* Step indicator */}
        <div style={{display:'flex',padding:'10px 24px',gap:0,borderBottom:'1px solid var(--rule-soft)',background:'var(--bg-2)'}}>
          {[
            {k:'match',l:'0 · MATCH'},
            {k:'basics',l:'1 · BASICS'},
            {k:'details',l:'2 · DETAILS'},
            {k:'rights',l:'3 · RIGHTS'},
            {k:'confirm',l:'4 · CONFIRM'},
          ].map((s,i,arr) => (
            <button key={s.k} onClick={()=>setStep(s.k)}
              className="ff-mono upper" style={{
              fontSize:10,letterSpacing:'.1em',padding:'6px 12px',marginRight:6,
              background:'transparent', border:0, cursor:'pointer',
              color: step===s.k ? 'var(--ink)' : 'var(--ink-3)',
              fontWeight: step===s.k ? 600 : 400,
              borderBottom: step===s.k ? '2px solid var(--ink)' : '2px solid transparent'}}>
              {s.l}
            </button>
          ))}
        </div>

        {step === 'match' && (
          <div style={{padding:'20px 24px'}}>
            <Lbl>Title</Lbl>
            <Inp value={form.title} onChange={v=>set({title:v})} placeholder="Recording title" autoFocus/>
            <Lbl>Performing artist</Lbl>
            <Inp value={form.artist} onChange={v=>set({artist:v})} placeholder="Solange, Caroline Polachek..."/>
            <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',margin:'-8px 0 14px',lineHeight:1.4}}>
              Search-only — used to query MusicBrainz / IFPI / Spotify. You'll structure performer credits with roles on the next step.
            </div>
            <Lbl>Type</Lbl>
            <div style={{display:'flex',gap:6,marginBottom:14}}>
              {['original','cover','remix','live','sample'].map(k=>(
                <button key={k} onClick={()=>set({kind:k})} className="ff-mono upper" style={{
                  padding:'6px 12px',fontSize:10,letterSpacing:'.08em',cursor:'pointer',
                  background: form.kind===k ? 'var(--ink)' : 'transparent',
                  color: form.kind===k ? 'var(--bg)' : 'var(--ink-2)',
                  border:'1px solid var(--rule)'}}>{k}</button>
              ))}
            </div>

            <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',margin:'18px 0 8px',display:'flex',justifyContent:'space-between',alignItems:'baseline'}}>
              <span>SUGGESTED MATCHES</span>
              <span style={{color:'var(--ink-4)',fontSize:9}}>SPOTIFY · APPLE MUSIC · MUSICBRAINZ · IFPI</span>
            </div>
            {candidates.length === 0 && (
              <div className="ff-mono" style={{padding:'14px 14px',border:'1px dashed var(--rule)',fontSize:11,color:'var(--ink-3)',marginBottom:6}}>
                Enter a title or artist to search DSPs and metadata sources.
              </div>
            )}
            <div>
              {candidates.map((c,i)=>(
                <button key={i} onClick={()=>{
                  // Try to link the candidate's artist string to a real profile
                  const all = (window.ARTISTS || []);
                  const norm = (s)=>String(s||'').toLowerCase().trim();
                  const found = all.find(p => norm(p.name) === norm(c.artist) || (p.aliases||[]).some(a=>norm(a)===norm(c.artist)));
                  set({
                    isrc:c.isrc, title:c.title, label:c.label, year:c.year,
                    performers: [{
                      aid: found ? found.id : null,
                      name: found ? found.name : c.artist,
                      role: 'primary',
                      creditedAs: c.artist,
                    }],
                  });
                  setStep('basics');
                }}
                  style={{display:'grid',gridTemplateColumns:'80px 1fr 80px 18px',gap:12,width:'100%',padding:'12px 14px',marginBottom:6,
                    border:'1px solid var(--rule)',background:'transparent',cursor:'pointer',textAlign:'left',alignItems:'center'}}>
                  <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'var(--ink-2)',background:'var(--bg-2)',padding:'3px 6px',justifySelf:'start'}}>{c.src}</span>
                  <div>
                    <div style={{fontSize:13,fontWeight:500}}>{c.title}</div>
                    <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2}}>{c.artist} · {c.year} · {c.isrc} · {c.id}</div>
                  </div>
                  <div className="ff-mono num" style={{fontSize:11,textAlign:'right'}}>{c.score}% match</div>
                  <Ic.Right width={12} height={12} style={{color:'var(--ink-3)',justifySelf:'end'}}/>
                </button>
              ))}
              <button onClick={()=>setStep('basics')}
                style={{display:'flex',gap:10,alignItems:'center',width:'100%',padding:'12px 14px',
                  border:'1px dashed var(--rule)',background:'transparent',cursor:'pointer',textAlign:'left'}}>
                <Ic.Plus width={12} height={12} style={{color:'var(--ink-3)'}}/>
                <span style={{fontSize:12,color:'var(--ink-2)'}}>Skip — enter details manually</span>
              </button>
            </div>
          </div>
        )}

        {(step === 'basics' || step === 'details') && window.RecordingEditPane && (
          <window.RecordingEditPane
            form={form}
            set={set}
            w={w}
            openCtx={open}
            visibleStep={step}
          />
        )}

        {step === 'rights' && (
          <div style={{padding:'18px 24px 28px',background:'var(--bg)',minHeight:'40vh'}}>
            {window.RightsTab
              ? <window.RightsTab form={form} set={set} />
              : <div className="ff-mono" style={{padding:'40px 20px',textAlign:'center',color:'var(--ink-3)',fontSize:11}}>Rights registry component not loaded.</div>}
          </div>
        )}

        {step === 'confirm' && (
          <div style={{padding:'20px 24px'}}>
            <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:10}}>REVIEW</div>
            <div style={{border:'1px solid var(--rule)',padding:'16px 18px',background:'var(--bg-2)'}}>
              <div style={{fontSize:18,fontWeight:600,letterSpacing:'-0.01em'}}>{form.title || '—'}</div>
              <div style={{fontSize:13,color:'var(--ink-2)',marginTop:3}}>{form.artist || '—'}</div>
              {(form.performers||[]).filter(p=>p && (p.creditedAs || p.name)).length > 0 && (
                <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:8,display:'flex',flexDirection:'column',gap:3}}>
                  {form.performers.filter(p=>p && (p.creditedAs || p.name)).map((p,i)=>{
                    const profile = p.aid && (window.ARTISTS||[]).find(x=>x.id===p.aid);
                    return (
                      <div key={i} style={{display:'flex',gap:8,alignItems:'baseline'}}>
                        <span className="upper" style={{fontSize:9,letterSpacing:'.08em',color:'var(--ink-3)',minWidth:64}}>{p.role}</span>
                        <span style={{color:'var(--ink-2)'}}>{p.creditedAs || p.name}</span>
                        {profile && <span style={{color:'var(--ink-4)'}}>· linked to {profile.name}</span>}
                        {!profile && p.name && <span style={{color:'var(--ink-4)'}}>· new profile</span>}
                      </div>
                    );
                  })}
                </div>
              )}
              <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',marginTop:10,display:'grid',gridTemplateColumns:'1fr 1fr',gap:'6px 18px'}}>
                <span>ISRC · {form.isrc || '—'}</span>
                <span>Catalog # · {form.catalogNumber || '—'}</span>
                <span>Recording label · {form.label || '—'}</span>
                <span>Marketing label · {form.marketingLabel || form.label || '—'}</span>
                <span>P-line · {form.pLine || `℗ ${form.year} —`}</span>
                <span>Duration · {form.durationMin}:{String(form.durationSec).padStart(2,'0')}</span>
                <span>Version · {form.versionType}{form.versionLabel?` (${form.versionLabel})`:''}</span>
                <span>Genre · {form.genre || '—'}{form.subGenre?` / ${form.subGenre}`:''}</span>
                <span>Recorded · {form.recordingDate || '—'}{form.countryOfRecording?` · ${form.countryOfRecording}`:''}</span>
                <span>First release · {form.firstReleaseDate || '—'}</span>
                <span>Audio · {form.audioFormat} · {form.sampleRate} · {form.bitDepth}-bit · {form.channels}</span>
                <span>Language · {form.languageOfPerformance || (form.versionType==='Instrumental'?'Instrumental':'—')}</span>
                <span>Advisory · {form.parentalAdvisory==='NotExplicit'?'Clean':form.parentalAdvisory==='Explicit'?'Explicit':'Edited'}</span>
                <span>Type · {form.kind}{form.kind!=='original'?` · ${form.clearance}`:''}</span>
                {(form.contributors||[]).length>0 && <span style={{gridColumn:'1 / -1'}}>Contributors · {form.contributors.map(c=>`${c.name} (${c.role})`).join(', ')}</span>}
                {(form.recordingOwners||[]).length>0 && <span style={{gridColumn:'1 / -1'}}>Owners · {form.recordingOwners.map(o=>`${o.name} ${o.share}% ${o.territory||'WW'}`).join(', ')}</span>}
              </div>
              {w && (
                <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',marginTop:14,paddingTop:12,borderTop:'1px solid var(--rule)'}}>
                  Will link to work <b style={{color:'var(--ink)'}}>{w.title}</b> · ISWC {w.iswc}
                </div>
              )}
            </div>
            <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',marginTop:12,lineHeight:1.5}}>
              On save we'll attempt automatic society notification (ASCAP/BMI/PRS) and queue a DDEX delivery to all paired DSPs.
            </div>
          </div>
        )}

        {/* Footer */}
        <div style={{padding:'14px 24px',borderTop:'1px solid var(--rule)',display:'flex',justifyContent:'space-between',alignItems:'center',background:'var(--bg-2)',gap:10}}>
          <button onClick={close} className="ff-mono upper" style={{padding:'8px 14px',fontSize:11,letterSpacing:'.08em',background:'transparent',color:'var(--ink-2)',border:'1px solid var(--rule)',cursor:'pointer'}}>Cancel</button>
          <div style={{display:'flex',gap:8}}>
            {step!=='match' && (
              <button onClick={()=>setStep(
                  step==='confirm' ? 'rights' :
                  step==='rights' ? 'details' :
                  step==='details' ? 'basics' :
                  'match')}
                className="ff-mono upper" style={{padding:'8px 14px',fontSize:11,letterSpacing:'.08em',background:'transparent',color:'var(--ink-2)',border:'1px solid var(--rule)',cursor:'pointer'}}>← Back</button>
            )}
            {step!=='confirm' ? (
              <button onClick={()=>setStep(
                  step==='match' ? 'basics' :
                  step==='basics' ? 'details' :
                  step==='details' ? 'rights' :
                  'confirm')}
                className="ff-mono upper" style={{padding:'8px 16px',fontSize:11,letterSpacing:'.08em',background:'var(--ink)',color:'var(--bg)',border:0,cursor:'pointer',fontWeight:600}}>
                Next →
              </button>
            ) : (
              <button onClick={()=>{
                  // Build the new recording record and push it into the catalog graph
                  const id = 'r_new_' + Date.now().toString(36);
                  const dur = (+form.durationMin||0)*60 + (+form.durationSec||0);
                  const isrc = form.isrc || ('USNEW' + String(Date.now()).slice(-7));
                  const primary = (form.performers||[]).find(p=>p && p.role==='primary');
                  const newRec = {
                    id,
                    workId: open.workId || null,
                    isrc,
                    title: form.title || 'Untitled',
                    artist: form.artist || (primary ? (primary.creditedAs||primary.name) : ''),
                    album: '',
                    label: form.label || '',
                    duration: dur,
                    year: form.year,
                    plays: 0,
                    art: '#1a1a1a',
                    explicit: form.parentalAdvisory === 'Explicit',
                    isPrimary: true,
                    derivesFrom: [],
                    releaseIds: [],
                    _isNew: true,
                  };
                  // Push into the live catalog so pickers + drawer can find it
                  if (window.RECORDING_GRAPH) window.RECORDING_GRAPH.unshift(newRec);
                  if (window.REC_BY_ID) window.REC_BY_ID[id] = newRec;
                  // Hand back to the opener (e.g. the release flow) before closing
                  if (open.onCreated) {
                    try { open.onCreated(newRec); } catch (err) { console.error('onCreated handler', err); }
                  }
                  window.dispatchEvent(new CustomEvent('astro-toast', {detail:{
                    msg: open.returnLabel
                      ? `Recording "${newRec.title}" added · returning to ${open.returnLabel}`
                      : `Recording "${newRec.title}" added${w?` to "${w.title}"`:''}`,
                    tone:'ok'}}));
                  close();
                }}
                className="ff-mono upper" style={{padding:'8px 16px',fontSize:11,letterSpacing:'.08em',background:'var(--accent)',color:'var(--ink)',border:0,cursor:'pointer',fontWeight:600}}>
                {open.returnLabel ? `Save & return to ${open.returnLabel} →` : 'Save recording ✓'}
              </button>
            )}
          </div>
        </div>
      </div>
    </>
  );
}

// ── Global "Add song" (new work) modal ─────────────────────────
class _SongErrBoundary extends React.Component {
  constructor(p){super(p); this.state={err:null};}
  static getDerivedStateFromError(err){ return {err}; }
  componentDidCatch(err, info){ window.__SONG_ERR = {msg: err?.message, stack: err?.stack, info: info?.componentStack}; console.error('[SongModal]', err?.message, err?.stack); }
  render(){
    if(this.state.err) return (
      <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,.6)',zIndex:81,display:'grid',placeItems:'center'}}>
        <div style={{background:'#fff',padding:24,maxWidth:600,fontFamily:'monospace',fontSize:11,whiteSpace:'pre-wrap'}}>
          <strong>SongModal error:</strong>{'\n'}{this.state.err.message}{'\n\n'}{(this.state.err.stack||'').split('\n').slice(0,8).join('\n')}
          <div style={{marginTop:12}}><button onClick={()=>this.setState({err:null})}>Dismiss</button></div>
        </div>
      </div>
    );
    return this.props.children;
  }
}
function GlobalAddSongModal(props) { return <_SongErrBoundary><_GlobalAddSongModalInner {...props}/></_SongErrBoundary>; }

// ───────────── IPI input — digits only, 9–11 length, auto-pads to 11 on blur ─────────────
function IPIInput({ value, onChange, placeholder = '00000000000', size = 'md', invalid }) {
  const [touched, setTouched] = React.useState(false);
  const handleChange = (e) => {
    const digits = (e.target.value || '').replace(/\D/g, '').slice(0, 11);
    onChange(digits);
  };
  const handleBlur = () => {
    setTouched(true);
    const d = (value || '').replace(/\D/g, '');
    if (d.length === 9 || d.length === 10) onChange(d.padStart(11, '0'));
    else if (d.length > 11) onChange(d.slice(0, 11));
  };
  const len = (value || '').length;
  const showError = invalid || (touched && len > 0 && len < 9);
  const baseStyle = size === 'sm'
    ? {height:28,padding:'0 6px',fontSize:10}
    : size === 'md-tight'
      ? {height:32,padding:'0 8px',fontSize:11}
      : {height:32,padding:'0 8px',fontSize:11};
  return (
    <input value={value || ''} onChange={handleChange} onBlur={handleBlur}
      inputMode="numeric" pattern="[0-9]*" maxLength={11} placeholder={placeholder}
      className="ff-mono"
      style={{...baseStyle,background:'var(--bg-2)',
        border:`1px solid ${showError?'#c0392b':'var(--rule)'}`,color:'var(--ink)',
        minWidth:0,boxSizing:'border-box',width:'100%'}}/>
  );
}

// ───────── Stable UUID for profiles (artists + publishers) ─────────
// We expose a real UUIDv4-format string in URLs (aid=… / pid=…) so internal short
// ids like "p_01" / "pb_03" never leak into shareable links.
//
// CRITICAL: the UUID must be **deterministic** from the internal id, because the
// "↗ open in new tab" flow opens a fresh document with its own in-memory ARTISTS /
// __PUBLISHERS arrays. Random UUIDs generated in tab A would be unknown to tab B.
// We hash the internal id (FNV-1a 32-bit, repeated 4× with salts) into the canonical
// 8-4-4-4-12 hex shape with the v4 marker. This is not cryptographically a UUIDv4 —
// it's a UUID-shaped stable identifier that is safe to put in URLs.
function profileUuid(rec) {
  if (!rec || !rec.id) return null;
  if (rec.uuid) return rec.uuid;
  // FNV-1a 32-bit
  const fnv = (s, salt) => {
    let h = 0x811c9dc5 ^ salt;
    for (let i = 0; i < s.length; i++) {
      h ^= s.charCodeAt(i);
      h = Math.imul(h, 0x01000193);
    }
    return (h >>> 0).toString(16).padStart(8, '0');
  };
  const a = fnv(rec.id, 0x00000000);
  const b = fnv(rec.id, 0x12345678);
  const c = fnv(rec.id, 0x9abcdef0);
  const d = fnv(rec.id, 0xfedcba98);
  // Shape: aaaaaaaa-bbbb-4bbb-Vccc-ccccdddddddd  (V = 8|9|a|b for v4 variant bits)
  const v = '89ab'[parseInt(c[0],16) & 3];
  rec.uuid = `${a}-${b.slice(0,4)}-4${b.slice(4,7)}-${v}${c.slice(1,4)}-${c.slice(4,8)}${d}`;
  return rec.uuid;
}
function findProfileByUuid(list, uuid) {
  if (!uuid) return null;
  for (const r of (list||[])) {
    if (profileUuid(r) === uuid) return r;
  }
  return null;
}

// ───────── Profile-diff warning — when an existing profile is selected and user edits PRO/IPI ─────────
// Compares current row values vs the directory record they were pulled from. If diverged, offers:
//   • Update the directory profile (writes the new values onto window.ARTISTS / window.__PUBLISHERS)
//   • Keep just for this song (a song-level override; clears the link to the directory)
function ProfileDiffWarning({ kind, aid, current, onClearLink }) {
  if (!aid) return null;
  const list = kind === 'writer' ? (window.ARTISTS || []) : (window.__PUBLISHERS || []);
  const rec = list.find(x => x.id === aid);
  if (!rec) return null;
  const recIpi = (kind === 'writer' ? rec.ipi : rec.cae) || '';
  const recPro = rec.pro || '';
  const curIpi = (current.ipi || '').replace(/\D/g,'');
  const curPro = current.pro || '';
  // Only flag if current values are "filled" (don't nag mid-edit)
  const ipiDiverged = curIpi.length >= 9 && curIpi !== recIpi;
  const proDiverged = curPro && curPro !== recPro;
  if (!ipiDiverged && !proDiverged) return null;

  const updateProfile = () => {
    if (ipiDiverged) {
      if (kind === 'writer') rec.ipi = curIpi; else rec.cae = curIpi;
    }
    if (proDiverged) rec.pro = curPro;
    window.dispatchEvent(new CustomEvent('astro-toast',{detail:{
      msg:`${rec.name}'s ${kind==='writer'?'profile':'directory record'} updated · ${[ipiDiverged&&'IPI',proDiverged&&'PRO'].filter(Boolean).join(' + ')} synced`,
      tone:'ok'}}));
    // Force re-render via a no-op tick — easiest is to call onClearLink with a flag the parent ignores
    if (onClearLink) onClearLink({ kept:true });
  };
  const keepLocal = () => {
    onClearLink && onClearLink({ kept:false }); // clears _id / _pubId — this row no longer linked to a profile
    window.dispatchEvent(new CustomEvent('astro-toast',{detail:{
      msg:`Override saved on this work only · ${rec.name}'s profile is unchanged`, tone:'warn'}}));
  };

  return (
    <div className="ff-mono" style={{display:'flex',alignItems:'center',gap:10,marginTop:6,marginLeft:14,paddingLeft:12,
      borderLeft:`2px solid #c0392b`,fontSize:10,color:'var(--ink-2)',flexWrap:'wrap'}}>
      <span style={{color:'#c0392b',letterSpacing:'.04em'}}>
        ◇ Edited fields differ from {rec.name}'s saved profile
        {ipiDiverged && <span style={{marginLeft:8,color:'var(--ink-3)'}}>IPI {recIpi||'—'} → {curIpi}</span>}
        {proDiverged && <span style={{marginLeft:8,color:'var(--ink-3)'}}>PRO {recPro} → {curPro}</span>}
      </span>
      <span style={{flex:1}}/>
      <button type="button" onClick={updateProfile} className="ff-mono upper"
        style={{padding:'4px 10px',background:'var(--ink)',color:'var(--bg)',border:'1px solid var(--ink)',
          fontSize:9,letterSpacing:'.08em',cursor:'pointer'}}>
        Update profile
      </button>
      <button type="button" onClick={keepLocal} className="ff-mono upper"
        style={{padding:'4px 10px',background:'transparent',color:'var(--ink-2)',border:'1px solid var(--rule)',
          fontSize:9,letterSpacing:'.08em',cursor:'pointer'}}>
        Keep just for this work
      </button>
    </div>
  );
}

function PublisherTypeahead({ value, onSelect, autosaveLabel = 'Save to directory', compact = false, fieldMap, aid }) {
  const fm = fieldMap || { name:'pub', pro:'pubPro', ipi:'pubIpi', id:'_pubId' };
  const [open, setOpen] = React.useState(false);
  const [draft, setDraft] = React.useState(value || '');
  React.useEffect(()=>{ setDraft(value||''); }, [value]);

  const pubName = (p) => (p?.name || p?.Name || p?.display_name || p?.legal_name || p?.['Interested Party'] || '').trim();
  const pubPro = (p) => p?.pro || p?.PRO || p?.pr_society || p?.society || '';
  const pubIpi = (p) => p?.cae || p?.ipi || p?.IPI || p?.ipi_cae_number || p?.['IPI Name Number'] || '';
  const pubId = (p) => p?.id || p?.['Publisher ID'] || p?.aid || p?.custom_id || pubName(p);
  const pubAliases = (p) => [
    p?.aliases,
    p?.Aliases,
    p?.['Interested Party'],
    p?.['Admin/Co-Publisher'],
    p?.parent,
    p?.Parent
  ].filter(Boolean).join(' ');

  const rawList = [
    ...(Array.isArray(window.__PUBLISHERS) ? window.__PUBLISHERS : []),
    ...(Array.isArray(window.PUBLISHERS) && window.PUBLISHERS !== window.__PUBLISHERS ? window.PUBLISHERS : []),
    ...(Array.isArray(window.RS?.publishers) ? window.RS.publishers : []),
    ...(Array.isArray(window.__RS_DIRECTORY?.publishers) ? window.__RS_DIRECTORY.publishers : [])
  ];
  const seen = new Set();
  const list = rawList
    .map(p => ({ raw:p, name:pubName(p), pro:pubPro(p), ipi:pubIpi(p), id:pubId(p), aliases:pubAliases(p), _new:p?._new }))
    .filter(p => p.name && !seen.has(p.id) && seen.add(p.id));
  const q = draft.trim().toLowerCase();
  const matches = q.length===0 ? list.slice(0, 80) : list.filter(p =>
    p.name.toLowerCase().includes(q) ||
    p.aliases.toLowerCase().includes(q) ||
    p.ipi.toLowerCase().includes(q) ||
    p.id.toLowerCase().includes(q)
  ).slice(0, 80);
  const exact = list.find(p => p.name.toLowerCase() === q);
  const closeBox = () => setTimeout(()=>setOpen(false), 150);

  // Match an existing publisher (by id passed in OR by exact name) — gates the "↗ open" button.
  const linkPub = aid
    ? list.find(p => p.id === aid)
    : exact;

  const pick = (p) => {
    onSelect({[fm.name]:p.name, [fm.pro]:p.pro, [fm.ipi]:p.ipi, [fm.id]:p.id});
    setDraft(p.name);
    setOpen(false);
  };
  const saveNew = () => {
    const name = draft.trim();
    if (!name || exact) return;
    const newPub = {
      id: 'pb_'+Math.random().toString(36).slice(2,7),
      'Publisher ID': null,
      name,
      Name: name,
      aliases: '',
      cae: '',
      IPI: '',
      pro: 'ASCAP',
      PRO: 'ASCAP',
      territory: 'Worldwide',
      works: 0,
      parent: '—',
      since: String(new Date().getFullYear()),
      _new: true,
    };
    list.push(newPub);
    onSelect({[fm.name]:newPub.name, [fm.pro]:newPub.pro, [fm.ipi]:newPub.cae, [fm.id]:newPub.id, _newPub:true});
    setOpen(false);
    window.dispatchEvent(new CustomEvent('astro-toast',{detail:{msg:`"${name}" saved to publisher directory · pending IPI lookup`,tone:'ok'}}));
  };

  const inputStyle = compact
    ? {flex:1,height:28,padding:'0 18px 0 6px',background:'var(--bg-2)',border:'1px solid var(--rule)',fontSize:11,color:'var(--ink)',boxSizing:'border-box',minWidth:0}
    : {flex:1,height:32,padding:'0 28px 0 8px',background:'var(--bg-2)',border:'1px solid var(--rule)',fontSize:12,color:'var(--ink)',boxSizing:'border-box',minWidth:0};

  const inputRef = React.useRef(null);
  const [rect, setRect] = React.useState(null);
  React.useEffect(()=>{
    if (!open || !inputRef.current) return;
    const update = () => { if (inputRef.current) setRect(inputRef.current.getBoundingClientRect()); };
    update();
    window.addEventListener('scroll', update, true);
    window.addEventListener('resize', update);
    return () => { window.removeEventListener('scroll', update, true); window.removeEventListener('resize', update); };
  }, [open]);

  return (
    <div style={{position:'relative',minWidth:0,display:'flex',alignItems:'stretch'}}>
      <input
        ref={inputRef}
        value={draft}
        onChange={e=>{ setDraft(e.target.value); setOpen(true); }}
        onFocus={()=>setOpen(true)}
        onBlur={closeBox}
        placeholder={compact ? 'Search publishers' : 'Publisher name · type to search'}
        style={{...inputStyle, borderRight: linkPub ? 'none' : inputStyle.border ? undefined : '1px solid var(--rule)'}}/>
      {linkPub ? (
        <button
          type="button"
          title={`Open ${linkPub.name} profile in new tab`}
          onMouseDown={e=>{ e.preventDefault();
            const url = new URL(window.location.href);
            url.search = '?aid=' + encodeURIComponent(profileUuid(linkPub)) + '&kind=publisher';
            url.hash = '';
            window.open(url.toString(), '_blank', 'noopener');
          }}
          style={{padding: compact ? '0 6px' : '0 8px',background:'var(--bg)',border:'1px solid var(--rule)',cursor:'pointer',
            color:'var(--ink)',fontSize: compact ? 10 : 11,fontFamily:'IBM Plex Mono, monospace',letterSpacing:'.04em'}}>↗</button>
      ) : (
        <span style={{position:'absolute',right:compact?5:8,top:'50%',transform:'translateY(-50%)',fontSize:9,color:'var(--ink-3)',pointerEvents:'none'}}>▾</span>
      )}
      {open && rect && ReactDOM.createPortal(
        <div style={{position:'fixed',top:rect.bottom+2,left:rect.left,
          width:compact?Math.max(260,rect.width):rect.width,maxWidth:`calc(100vw - ${rect.left+8}px)`,zIndex:9999,boxSizing:'border-box',
          background:'var(--bg)',border:'1px solid var(--ink)',boxShadow:'0 12px 32px rgba(0,0,0,.18)',
          maxHeight:240,overflowY:'auto',overflowX:'hidden'}}>
          {list.length > 0 && (
            <div className="ff-mono upper" style={{position:'sticky',top:0,zIndex:1,padding:'7px 12px',background:'var(--bg-2)',borderBottom:'1px solid var(--rule-soft)',fontSize:9,letterSpacing:'.08em',color:'var(--ink-3)'}}>
              {matches.length.toLocaleString()} shown · {list.length.toLocaleString()} publishers loaded
            </div>
          )}
          {matches.length === 0 && (
            <div className="ff-mono" style={{padding:'10px 12px',fontSize:11,color:'var(--ink-3)'}}>
              {list.length === 0
                ? 'No publishers are loaded yet'
                : <>No publishers match "{draft}" · {list.length.toLocaleString()} loaded</>}
            </div>
          )}
          {matches.map(p => (
            <button key={p.id} onMouseDown={e=>{e.preventDefault(); pick(p);}}
              style={{display:'block',width:'100%',textAlign:'left',padding:'8px 12px',background:'transparent',border:0,
                borderBottom:'1px solid var(--rule-soft)',cursor:'pointer',boxSizing:'border-box',overflow:'hidden'}}>
              <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',gap:10,minWidth:0}}>
                <span style={{fontSize:12,fontWeight:500,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',minWidth:0,flex:1}}>{p.name}{p._new && <span className="ff-mono" style={{marginLeft:6,fontSize:9,padding:'1px 5px',background:'var(--accent)',color:'var(--ink)'}}>NEW</span>}</span>
                <span className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',whiteSpace:'nowrap',flexShrink:0}}><SocietyLink code={p.pro} style={{fontSize:10}}/> · {p.ipi||'—'}</span>
              </div>
              {p.aliases && <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{p.aliases}</div>}
            </button>
          ))}
          {draft.trim() && !exact && (
            <button onMouseDown={e=>{e.preventDefault(); saveNew();}}
              className="ff-mono upper"
              style={{display:'flex',alignItems:'center',gap:8,width:'100%',textAlign:'left',padding:'10px 12px',
                background:'var(--bg-2)',border:0,borderTop:'1px solid var(--rule)',cursor:'pointer',
                fontSize:10,letterSpacing:'.08em',color:'var(--ink)',boxSizing:'border-box',overflow:'hidden'}}>
              <span style={{width:14,height:14,border:'1px solid var(--ink)',display:'inline-flex',alignItems:'center',justifyContent:'center',fontSize:11,lineHeight:1,flexShrink:0}}>+</span>
              <span style={{overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',minWidth:0}}>{autosaveLabel}: <span style={{fontWeight:600,letterSpacing:0,textTransform:'none'}}>"{draft.trim()}"</span></span>
            </button>
          )}
        </div>,
        document.body
      )}
    </div>
  );
}

// Writer-name typeahead — pulls from window.ARTISTS, but only people with full legal names.
// Stage names ("Solange", "Sault", "Yaeji", "Floating Points", "Helado Negro") are excluded;
// only profiles flagged legal:true (Solange Knowles, Caroline Polachek, …) are offered.
// Lets the user add brand-new people (they get pushed onto window.ARTISTS as type:'Person', legal:true).
function WriterTypeahead({ value, onSelect, aid, autoFocus }) {
  const [open, setOpen] = React.useState(false);
  const [draft, setDraft] = React.useState(value || '');
  const [dirty, setDirty] = React.useState(false);
  React.useEffect(()=>{ setDraft(value||''); }, [value]);

  const realName = (p) => {
    const explicit = (p.realName || p.legalName || p.fullName || '').trim();
    if (explicit) return explicit;
    return p.legal === true ? (p.name || '').trim() : '';
  };
  const displayName = (p) => realName(p);
  const aliases = (p) => Array.isArray(p.aliases) ? p.aliases.join(' ') : (p.aliases || '');

  // Source list: legal-name people only. Groups and stage-name-only profiles are
  // deliberately excluded from songwriter workflows.
  const all = (window.ARTISTS || []);
  const seen = new Set();
  const eligible = all
    .filter(p => (p.type === 'Person' || p.kind === 'Person') && realName(p) && /\s/.test(realName(p)))
    .map(p => ({ ...p, name: displayName(p), _searchAliases: aliases(p) }))
    .filter(p => !seen.has(p.id) && seen.add(p.id));
  const q = draft.trim().toLowerCase();
  const matches = q.length===0
    ? eligible
    : eligible.filter(p => p.name.toLowerCase().includes(q) || (p._searchAliases || '').toLowerCase().includes(q) || (p.ipi || '').toLowerCase().includes(q));
  const exact = eligible.find(p => p.name.toLowerCase() === q);
  const closeBox = () => setTimeout(()=>setOpen(false), 150);

  const pick = (p) => {
    onSelect({ name:p.name, pro:p.pro, ipi:p.ipi, _id:p.id });
    setDraft(p.name);
    setDirty(false);
    setOpen(false);
  };
  const saveNew = () => {
    const name = draft.trim();
    if (!name || exact) return;
    if (!/\s/.test(name)) {
      window.dispatchEvent(new CustomEvent('astro-toast',{detail:{
        msg:`Profiles need a full name (first + last). "${name}" looks like a stage name.`, tone:'warn'}}));
      return;
    }
    const newP = {
      id: 'p_'+Math.random().toString(36).slice(2,7),
      name, ipi:'', pro:'ASCAP', type:'Person', country:'—',
      roster:0, claims:0, share:0, color:'#aaa', legal:true, _new:true,
    };
    all.push(newP);
    onSelect({ name:newP.name, pro:newP.pro, ipi:newP.ipi, _id:newP.id, _newPerson:true });
    setDirty(false);
    setOpen(false);
    window.dispatchEvent(new CustomEvent('astro-toast',{detail:{
      msg:`"${name}" added to people directory · pending IPI lookup`, tone:'ok'}}));
  };

  // If draft matches an existing profile, expose the link icon
  const linkProfile = aid
    ? all.find(a => a.id === aid)
    : (q ? eligible.find(p => p.name.toLowerCase() === q) : null);

  const inputRef = React.useRef(null);
  const [rect, setRect] = React.useState(null);
  React.useEffect(()=>{
    if (!open || !inputRef.current) return;
    const update = () => { if (inputRef.current) setRect(inputRef.current.getBoundingClientRect()); };
    update();
    window.addEventListener('scroll', update, true);
    window.addEventListener('resize', update);
    return () => { window.removeEventListener('scroll', update, true); window.removeEventListener('resize', update); };
  }, [open]);

  return (
    <div style={{position:'relative',minWidth:0,display:'flex',alignItems:'stretch'}}>
      <input
        ref={inputRef}
        value={draft}
        autoFocus={autoFocus}
        onChange={e=>{ setDraft(e.target.value); setDirty(true); setOpen(true); }}
        onClick={()=>setOpen(true)}
        onKeyDown={e=>{
          if (e.key==='Enter' && matches[0]) { e.preventDefault(); pick(matches[0]); }
          else if (e.key==='Escape') { setDraft(value || ''); setDirty(false); setOpen(false); }
          else if (!open) setOpen(true);
        }}
        onBlur={()=>{ if (dirty) setDraft(value || ''); setDirty(false); closeBox(); }}
        placeholder="Search legal songwriter name"
        style={{flex:1,height:32,padding:'0 28px 0 8px',background:'var(--bg)',border:'1px solid var(--rule)',
          fontSize:12,color:'var(--ink)',boxSizing:'border-box',minWidth:0,
          borderRight: linkProfile ? 'none' : '1px solid var(--rule)'}}/>
      {linkProfile ? (
        <button
          type="button"
          title={`Open ${linkProfile.name} profile in new tab`}
          onMouseDown={e=>{ e.preventDefault();
            const url = new URL(window.location.href);
            url.search = '?aid=' + encodeURIComponent(profileUuid(linkProfile)) + '&kind=writer';
            url.hash = '';
            window.open(url.toString(), '_blank', 'noopener');
          }}
          style={{padding:'0 8px',background:'var(--bg-2)',border:'1px solid var(--rule)',cursor:'pointer',
            color:'var(--ink)',fontSize:11,fontFamily:'IBM Plex Mono, monospace',letterSpacing:'.04em'}}>↗</button>
      ) : (
        <span style={{position:'absolute',right:8,top:'50%',transform:'translateY(-50%)',fontSize:9,color:'var(--ink-3)',pointerEvents:'none'}}>▾</span>
      )}
      {open && rect && ReactDOM.createPortal(
        <div style={{position:'fixed',top:rect.bottom+2,left:rect.left,width:rect.width,zIndex:9999,
          background:'var(--bg)',border:'1px solid var(--ink)',boxShadow:'0 12px 32px rgba(0,0,0,.18)',
          maxHeight:240,overflowY:'auto',overflowX:'hidden',boxSizing:'border-box'}}>
          <div className="ff-mono upper" style={{position:'sticky',top:0,zIndex:1,padding:'7px 12px',background:'var(--bg-2)',borderBottom:'1px solid var(--rule-soft)',fontSize:9,letterSpacing:'.08em',color:'var(--ink-3)'}}>
            legal people only · groups excluded
          </div>
          {matches.length === 0 && (
            <div className="ff-mono" style={{padding:'10px 12px',fontSize:11,color:'var(--ink-3)'}}>
              No legal songwriter matches "{draft}"
            </div>
          )}
          {matches.map(p => (
            <button key={p.id} type="button" onMouseDown={e=>{e.preventDefault(); pick(p);}}
              style={{display:'block',width:'100%',textAlign:'left',padding:'8px 10px',background:'transparent',border:0,
                borderBottom:'1px solid var(--rule-soft)',cursor:'pointer',boxSizing:'border-box',overflow:'hidden'}}>
              <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',gap:8,minWidth:0}}>
                <span style={{fontSize:12,fontWeight:500,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',minWidth:0,flex:1}}>
                  {p.name}
                  {p._new && <span className="ff-mono" style={{marginLeft:6,fontSize:9,padding:'1px 5px',background:'var(--accent)',color:'var(--ink)'}}>NEW</span>}
                </span>
                <span className="ff-mono" style={{fontSize:9,color:'var(--ink-3)',whiteSpace:'nowrap',flexShrink:0}}><SocietyLink code={p.pro} style={{fontSize:9}}/></span>
              </div>
              <div className="ff-mono" style={{fontSize:9,color:'var(--ink-3)',marginTop:2,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>
                IPI {(window.ipiDisplayCompact ? window.ipiDisplayCompact(p.ipi) : p.ipi)||'—'} · {p.country} · {p.roster} works
              </div>
            </button>
          ))}
          {draft.trim() && !exact && (
            <button type="button" onMouseDown={e=>{e.preventDefault(); saveNew();}}
              className="ff-mono upper"
              style={{display:'flex',alignItems:'center',gap:8,width:'100%',textAlign:'left',padding:'10px 10px',
                background:'var(--bg-2)',border:0,borderTop:'1px solid var(--rule)',cursor:'pointer',
                fontSize:10,letterSpacing:'.08em',color:'var(--ink)',boxSizing:'border-box',overflow:'hidden'}}>
              <span style={{width:14,height:14,border:'1px solid var(--ink)',display:'inline-flex',alignItems:'center',justifyContent:'center',fontSize:11,lineHeight:1,flexShrink:0}}>+</span>
              <span style={{overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',minWidth:0}}>
                Add new person: <span style={{fontWeight:600,letterSpacing:0,textTransform:'none'}}>"{draft.trim()}"</span>
              </span>
            </button>
          )}
        </div>,
        document.body
      )}
    </div>
  );
}

// SharesField — single share with optional split into P/M/S.
// `value` is {share, splitShares, perfShare, mechShare, syncShare}.
// `onChange(patch)` merges the patch into the parent.
function SharesField({ value, onChange, max=100, error=false, size='md' }) {
  const v = value || {};
  const fz = size==='sm' ? 11 : 12;
  // Square out heights so input + SPLIT/MERGE button line up perfectly with
  // adjacent selects (which are forced to 32px globally). md=32, sm=28.
  const H = size==='sm' ? 28 : 32;
  const pad = size==='sm' ? '0 14px 0 6px' : '0 16px 0 8px';
  const bg = size==='sm' ? 'var(--bg-2)' : 'var(--bg)';

  // Parse free-typed text into a 0–max number, keeping up to 2 decimals.
  // Strips anything but digits and one '.', truncates to 2 fractional digits, clamps,
  // then rounds to 2 decimals to avoid float drift (99.999 → 100, 33.336 → 33.34).
  const parsePct = (n) => {
    let s = String(n).replace(/[^\d.]/g,'');
    const firstDot = s.indexOf('.');
    if (firstDot !== -1) {
      s = s.slice(0, firstDot+1) + s.slice(firstDot+1).replace(/\./g,'');
      const [whole, frac=''] = s.split('.');
      s = whole + '.' + frac.slice(0,2);
    }
    if (s === '' || s === '.') return 0;
    const num = +s;
    if (!isFinite(num)) return 0;
    return Math.round(Math.max(0, Math.min(max, num)) * 100) / 100;
  };
  const setEqual = (n) => {
    const x = parsePct(n);
    onChange({share:x, perfShare:x, mechShare:x, syncShare:x});
  };
  const setOne = (k, n) => {
    const x = parsePct(n);
    const p = {[k]:x};
    // when split, also set the canonical 'share' to the perf value (most-used downstream)
    if (k==='perfShare') p.share = x;
    onChange(p);
  };
  // Display helper: show what the user has, but never with phantom trailing zeros (5 not 5.00),
  // and never longer than 2 decimals.
  const fmt = (n) => {
    const x = +n || 0;
    return Number.isInteger(x) ? String(x) : String(Math.round(x*100)/100);
  };
  const toggleSplit = () => {
    if (v.splitShares) {
      // collapsing — only allowed if all 3 equal (to 2 decimals)
      const p = +v.perfShare||0, m = +v.mechShare||0, s = +v.syncShare||0;
      const eq = (a,b) => Math.round(a*100) === Math.round(b*100);
      if (eq(p,m) && eq(m,s)) {
        onChange({splitShares:false, share:p});
      } else {
        window.dispatchEvent(new CustomEvent('astro-toast',{detail:{
          msg:`Cannot collapse — P ${fmt(p)}% · M ${fmt(m)}% · S ${fmt(s)}% must match`,tone:'warn'}}));
      }
    } else {
      const x = +v.share||0;
      onChange({splitShares:true, perfShare:x, mechShare:x, syncShare:x});
    }
  };

  const inputStyle = (errOn) => ({
    width:'100%',height:H,padding:pad,background:bg,
    border:`1px solid ${errOn?'#c0392b':'var(--rule)'}`,fontSize:fz,textAlign:'right',color:'var(--ink)',boxSizing:'border-box',lineHeight:`${H-2}px`
  });

  if (!v.splitShares) {
    return (
      <div style={{display:'flex',alignItems:'center',gap:6}}>
        <div style={{position:'relative',flex:1,minWidth:0}}>
          <input value={fmt(v.share)} onChange={e=>setEqual(e.target.value)}
            inputMode="decimal" className="ff-mono num" style={inputStyle(error)}/>
          <span className="ff-mono" style={{position:'absolute',right:5,top:'50%',transform:'translateY(-50%)',fontSize:10,color:'var(--ink-3)',pointerEvents:'none'}}>%</span>
        </div>
        <button type="button" onClick={toggleSplit} className="ff-mono upper" title="Split into Performance / Mechanical / Sync"
          style={{height:H,boxSizing:'border-box',background:'transparent',border:'1px solid var(--rule)',padding:'0 8px',fontSize:8,letterSpacing:'.08em',cursor:'pointer',color:'var(--ink-3)',whiteSpace:'nowrap'}}>
          ⊟ SPLIT
        </button>
      </div>
    );
  }
  return (
    <div style={{display:'flex',alignItems:'flex-end',gap:6}}>
      {[['perfShare','PERF'],['mechShare','MECH'],['syncShare','SYNC']].map(([k,lbl])=>(
        <div key={k} style={{flex:1,minWidth:0}}>
          <div className="ff-mono upper" style={{fontSize:8,letterSpacing:'.1em',color:'var(--ink-3)',marginBottom:2}}>{lbl}</div>
          <div style={{position:'relative'}}>
            <input value={fmt(v[k])} onChange={e=>setOne(k,e.target.value)}
              inputMode="decimal" className="ff-mono num" style={inputStyle(error)}/>
            <span className="ff-mono" style={{position:'absolute',right:5,top:'50%',transform:'translateY(-50%)',fontSize:10,color:'var(--ink-3)',pointerEvents:'none'}}>%</span>
          </div>
        </div>
      ))}
      <button type="button" onClick={toggleSplit} className="ff-mono upper" title="Collapse to single share"
        style={{height:H,boxSizing:'border-box',alignSelf:'flex-end',background:'transparent',border:'1px solid var(--rule)',padding:'0 8px',fontSize:8,letterSpacing:'.08em',cursor:'pointer',color:'var(--ink-3)',whiteSpace:'nowrap'}}>
        ⊞ MERGE
      </button>
    </div>
  );
}

// TerritoriesField — multi-select chip input. Backed by an array of strings.
const TERRITORY_PRESETS = ['World','US','Canada','UK / Ireland','EU','Latin America','Japan','Brazil','Mexico','Australia / NZ','Asia ex-Japan','MENA','Sub-Saharan Africa'];
function TerritoriesField({ value, onChange, size='md' }) {
  const list = (value && value.length) ? value : ['World'];
  const [draft, setDraft] = React.useState('');
  const [open, setOpen] = React.useState(false);
  const fz = size==='sm' ? 10 : 11;
  const remove = (t) => onChange(list.filter(x=>x!==t));
  const add = (t) => { const tt = (t||'').trim(); if (!tt || list.includes(tt)) return; onChange([...list, tt]); setDraft(''); };
  const matches = TERRITORY_PRESETS.filter(t => !list.includes(t) && (!draft || t.toLowerCase().includes(draft.toLowerCase())));
  return (
    <div style={{position:'relative'}}>
      <div style={{display:'flex',flexWrap:'wrap',alignItems:'center',gap:4,padding:'4px 6px',background:'var(--bg)',border:'1px solid var(--rule)',minHeight:size==='sm'?26:30,boxSizing:'border-box'}}>
        {list.map(t => (
          <span key={t} className="ff-mono" style={{display:'inline-flex',alignItems:'center',gap:4,padding:'2px 4px 2px 6px',
            background:'var(--bg-2)',border:'1px solid var(--rule)',fontSize:fz,color:'var(--ink-2)'}}>
            {t}
            {list.length>1 && <button type="button" onClick={()=>remove(t)} style={{background:'transparent',border:0,cursor:'pointer',color:'var(--ink-3)',fontSize:fz+1,padding:0,lineHeight:1}}>×</button>}
          </span>
        ))}
        <input value={draft}
          onChange={e=>{setDraft(e.target.value); setOpen(true);}}
          onFocus={()=>setOpen(true)}
          onBlur={()=>setTimeout(()=>setOpen(false),150)}
          onKeyDown={e=>{
            if (e.key==='Enter'){ e.preventDefault(); add(draft); }
            else if (e.key==='Backspace' && !draft && list.length>1){ remove(list[list.length-1]); }
          }}
          placeholder={list.length?'+ add':'Territory'}
          style={{flex:1,minWidth:60,border:0,background:'transparent',outline:'none',fontSize:fz,padding:'2px 4px',color:'var(--ink)'}}/>
      </div>
      {open && matches.length>0 && (
        <div style={{position:'absolute',top:'calc(100% + 2px)',left:0,right:0,zIndex:60,background:'var(--bg)',border:'1px solid var(--ink)',
          boxShadow:'0 8px 20px rgba(0,0,0,.14)',maxHeight:180,overflowY:'auto'}}>
          {matches.slice(0,10).map(t=>(
            <button key={t} type="button" onMouseDown={e=>{e.preventDefault(); add(t);}}
              className="ff-mono" style={{display:'block',width:'100%',textAlign:'left',padding:'5px 8px',background:'transparent',border:0,
                borderBottom:'1px solid var(--rule-soft)',cursor:'pointer',fontSize:fz,color:'var(--ink)'}}>{t}</button>
          ))}
        </div>
      )}
    </div>
  );
}

function _GlobalAddSongModalInner() {
  const [open, setOpen] = useGS(false);
  const [step, setStep] = useGS('basics'); // basics → writers → ids → confirm
  const [form, setForm] = useGS(null);
  const [editing, setEditing] = useGS(false);

  useGE(() => {
    const onOpen = (e) => {
      setOpen(true);
      const workId = e?.detail?.workId || null;
      const existing = workId && (typeof WORK_BY_ID !== 'undefined') ? WORK_BY_ID[workId] : null;
      setEditing(!!existing);
      setStep('basics');

      // Defaults — used as the base for both new and edit
      const baseForm = {
        title: '',
        altTitles: [],  // [{title, type, language}]
        writers: [{
          name:'', pro:'', cae:'', _id:null,
          unknown:false,
          role:'Composer/Author',
          share:100, splitShares:false, perfShare:100, mechShare:100, syncShare:100,
          territories:['World'],
          workForHire:false, reversionary:false, firstRecRefusal:false,
          pubs:[],
        }],
        iswc: '',
        societyIds: [], // [{society, workId}] — per-PRO work codes
        catalog: 'PluralPub',
        copyright: `© ${new Date().getFullYear()} Pluralis Music`,
        durationMin: 3, durationSec: 30,
        musicalKey: '',
        tempo: '',
        language: 'English',
        category: 'Original',
        instrumental: false,
        unrecorded: true,
        lyricsLanguage: 'English',
        lyrics: '',
        cwr: {
          compositeType: '',
          compositeCount: '',
          txtMusicRel: 'Music and Text (same creation)',
          versionType: 'Original Work',
          distCategory: 'Popular',
          musicArrangement: 'Original',
          lyricAdaptation: 'Original',
          intendedPurpose: 'General Usage',
          excerptType: '',
          workType: 'Pop Music',
          grandRights: false,
          priority: false,
          traditional: false,
          publicDomain: false,
          exceptionalClause: false,
          workOrigin: '',
          instrumentation: '',
        },
        notes: '',
        rights: null, // built by RightsTab on first open
      };

      if (existing) {
        // Hydrate from a static WORKS row — these have summary fields, not the
        // full structure the modal owns. Map what we can; leave the rest to defaults.
        const splits = (existing.shares||'').includes('/')
          ? existing.shares.split('/').map(Number)
          : [100];
        const writers = (existing.writers||['']).map((name, i) => ({
          ...baseForm.writers[0],
          name,
          pro: existing.pro || '',
          share: splits[i] != null ? splits[i] : Math.round(100 / existing.writers.length),
          perfShare: splits[i] != null ? splits[i] : Math.round(100 / existing.writers.length),
          mechShare: splits[i] != null ? splits[i] : Math.round(100 / existing.writers.length),
          syncShare: splits[i] != null ? splits[i] : Math.round(100 / existing.writers.length),
        }));
        const dur = existing.duration || 0;
        setForm({
          ...baseForm,
          title: existing.title || '',
          iswc: existing.iswc || '',
          catalog: existing.catalog || baseForm.catalog,
          copyright: existing.copyright || baseForm.copyright,
          durationMin: Math.floor(dur / 60),
          durationSec: dur % 60,
          writers,
          unrecorded: !existing.duration,
          // Pre-seed a single society ID row using the work's primary PRO
          societyIds: existing.pro ? [{ society: existing.pro, workId: '' }] : [],
        });
      } else {
        setForm(baseForm);
      }
    };
    window.addEventListener('astro-add-song', onOpen);
    const onClose = () => setOpen(false);
    window.addEventListener('astro-add-song-close', onClose);
    return () => {
      window.removeEventListener('astro-add-song', onOpen);
      window.removeEventListener('astro-add-song-close', onClose);
    };
  }, []);

  if (!open || !form) return null;
  const close = () => setOpen(false);
  const set = (patch) => setForm(f => ({...f, ...patch}));
  const newWriter = (share=0) => ({
    name:'', pro:'', cae:'', _id:null,
    unknown:false, role:'Composer/Author',
    share, splitShares:false, perfShare:share, mechShare:share, syncShare:share,
    territories:['World'],
    workForHire:false, reversionary:false, firstRecRefusal:false,
    pubs:[],
  });
  const newPub = (share=0, parentPro='') => ({
    name:'', pro:parentPro, ipi:'', _pubId:null,
    selfPub:false, unknown:false, controlled:true,
    role:'Original Publisher',
    share, splitShares:false, perfShare:share, mechShare:share, syncShare:share,
    territories:['World'],
    firstRecRefusal:false,
    admins:[],
  });
  const newAdmin = (share=0) => ({
    role:'Sub-pub',
    name:'', pro:'PRS', ipi:'', _pubId:null,
    selfPub:false, unknown:false, controlled:true,
    pubRole:'Original Publisher',
    share, splitShares:false, perfShare:share, mechShare:share, syncShare:share,
    territories:['UK / Ireland'],
    firstRecRefusal:false,
  });

  const setWriter = (i, patch) => setForm(f => ({...f, writers: f.writers.map((w,j)=>j===i?{...w,...patch}:w)}));
  const addWriter = () => setForm(f => ({...f, writers:[...f.writers, newWriter(0)]}));
  const removeWriter = (i) => setForm(f => ({...f, writers: f.writers.filter((_,j)=>j!==i)}));
  const setPub = (wi, pi, patch) => setWriter(wi, {pubs: form.writers[wi].pubs.map((p,j)=>j===pi?{...p,...patch}:p)});
  const addPub = (wi) => {
    const w = form.writers[wi];
    const used = (w.pubs||[]).reduce((s,p)=>s+(+p.share||0),0);
    const remain = Math.max(0, (+w.share||0) - used);
    setWriter(wi, {pubs:[...(w.pubs||[]), newPub(remain, w.pro||'')]});
  };
  const removePub = (wi, pi) => setWriter(wi, {pubs: form.writers[wi].pubs.filter((_,j)=>j!==pi)});
  const setAdmin = (wi, pi, ai, patch) => setPub(wi, pi, {admins: form.writers[wi].pubs[pi].admins.map((a,j)=>j===ai?{...a,...patch}:a)});
  const addAdmin = (wi, pi) => {
    const p = form.writers[wi].pubs[pi];
    const used = (p.admins||[]).reduce((s,a)=>s+(+a.share||0),0);
    const remain = Math.max(0, (+p.share||0) - used);
    setPub(wi, pi, {admins:[...(p.admins||[]), newAdmin(remain)]});
  };
  const removeAdmin = (wi, pi, ai) => setPub(wi, pi, {admins: form.writers[wi].pubs[pi].admins.filter((_,j)=>j!==ai)});

  const totalShare = form.writers.reduce((s,w)=>s+(+w.share||0),0);
  const shareOk = totalShare === 100;

  return (
    <>
      <div onClick={close} style={{position:'fixed',inset:0,background:'rgba(0,0,0,.42)',zIndex:80}}/>
      <div role="dialog" aria-modal="true" style={{
        position:'fixed',top:'50%',left:'50%',transform:'translate(-50%,-50%)',
        width: step==='rights' ? 'min(1080px,96vw)' : 'min(720px,94vw)',
        maxHeight:'90vh',
        background:'var(--bg)',border:'1px solid var(--rule)',zIndex:81,
        display:'flex',flexDirection:'column',overflow:'hidden',
        boxShadow:'0 30px 80px rgba(0,0,0,.34)',
        transition:'width .25s ease'}}>

        {/* Header */}
        <div style={{flex:'0 0 auto',padding:'18px 24px',borderBottom:'1px solid var(--rule)',display:'flex',justifyContent:'space-between',alignItems:'flex-start',gap:14}}>
          <div>
            <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.14em',color:'var(--ink-3)',marginBottom:4}}>{editing ? 'EDIT WORK' : 'NEW WORK'}</div>
            <div className="ff-display" style={{fontSize:28,fontWeight:700,letterSpacing:'-0.02em',lineHeight:1.05}}>
              {form.title ? form.title : 'Untitled work'}
            </div>
            <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:6}}>
              Register the work itself · add recordings later
            </div>
          </div>
          <button onClick={close} aria-label="Close" style={{width:30,height:30,background:'transparent',border:'1px solid var(--rule)',cursor:'pointer',fontSize:16,lineHeight:1}}>×</button>
        </div>

        {/* Step indicator */}
        <div style={{flex:'0 0 auto',display:'flex',padding:'10px 24px',gap:0,borderBottom:'1px solid var(--rule-soft)',background:'var(--bg-2)'}}>
          {[
            {k:'basics',l:'1 · BASICS'},
            {k:'writers',l:'2 · SPLITS'},
            {k:'lyrics',l:'3 · LYRICS', disabled: form.instrumental},
            {k:'pub',l:'4 · DETAILS'},
            {k:'ids',l:'5 · IDs'},
            {k:'rights',l:'6 · RIGHTS'},
            {k:'confirm',l:'7 · REVIEW'},
          ].map((s)=>(
            <div key={s.k} className="ff-mono upper" style={{
              fontSize:10,letterSpacing:'.1em',padding:'6px 12px',marginRight:6,
              color: s.disabled ? 'var(--ink-4)' : (step===s.k ? 'var(--ink)' : 'var(--ink-3)'),
              opacity: s.disabled ? 0.5 : 1,
              fontWeight: step===s.k ? 600 : 400,
              textDecoration: s.disabled ? 'line-through' : 'none',
              borderBottom: step===s.k ? '2px solid var(--ink)' : '2px solid transparent'}}
              title={s.disabled ? 'Disabled — work is marked instrumental' : undefined}>
              {s.l}
            </div>
          ))}
        </div>

        {/* Scrollable body — header + step indicator stay fixed at top, footer fixed at bottom */}
        <div style={{flex:'1 1 auto',overflowY:'auto',minHeight:0}}>

        {step==='basics' && (
          <div style={{padding:'20px 24px'}}>
            <div style={{display:'grid',gridTemplateColumns:'1fr 220px',gap:14}}>
              <div>
                <Lbl>Title</Lbl>
                <Inp value={form.title} onChange={v=>set({title:v})} placeholder="Cranes In The Sky"/>
              </div>
              <div>
                <Lbl>ISWC</Lbl>
                <Inp value={form.iswc} onChange={v=>set({iswc:v})} placeholder="T-000.000.000-0" mono/>
              </div>
            </div>
            <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginTop:14,marginBottom:6}}>
              <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)'}}>ALTERNATE TITLES</div>
            </div>
            {form.altTitles.length === 0 ? (
              <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',padding:'10px 0',borderTop:'1px solid var(--rule-soft)',borderBottom:'1px solid var(--rule-soft)',fontStyle:'italic'}}>No alternate titles</div>
            ) : (
              <>
                <div className="ff-mono upper" style={{display:'grid',gridTemplateColumns:'1fr 200px 130px 24px',gap:8,
                  fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',padding:'0 0 6px',borderBottom:'1px solid var(--rule)'}}>
                  <span>TITLE</span><span>TITLE TYPE</span><span>LANGUAGE</span><span/>
                </div>
                {form.altTitles.map((a,i)=>(
                  <div key={i} style={{display:'grid',gridTemplateColumns:'1fr 200px 130px 24px',gap:8,padding:'8px 0',borderBottom:'1px solid var(--rule-soft)',alignItems:'center'}}>
                    <input value={a.title} onChange={e=>set({altTitles: form.altTitles.map((x,j)=>j===i?{...x,title:e.target.value}:x)})}
                      placeholder="Alternate title"
                      style={{padding:'6px 8px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:12,color:'var(--ink)'}}/>
                    <select value={a.type} onChange={e=>set({altTitles: form.altTitles.map((x,j)=>j===i?{...x,type:e.target.value}:x)})}
                      className="ff-mono" style={{padding:'6px 8px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:11,color:'var(--ink)'}}>
                      {['Alternative Title','First Line of Text','Formal Title','Incorrect Title','Original Title','Original Title Translated','Part Title','Restricted Title','Extra Search Title','Original Title with National Characters','Alternative Title with National Characters'].map(t=><option key={t}>{t}</option>)}
                    </select>
                    <select value={a.language} onChange={e=>set({altTitles: form.altTitles.map((x,j)=>j===i?{...x,language:e.target.value}:x)})}
                      className="ff-mono" style={{padding:'6px 8px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:11,color:'var(--ink)'}}>
                      {['English','Spanish','French','Portuguese','German','Italian','Japanese','Korean','Mandarin','Arabic','Hindi','Russian','Other'].map(l=><option key={l}>{l}</option>)}
                    </select>
                    <button onClick={()=>set({altTitles: form.altTitles.filter((_,j)=>j!==i)})}
                      style={{background:'transparent',border:0,cursor:'pointer',color:'var(--ink-3)',fontSize:14}}>×</button>
                  </div>
                ))}
              </>
            )}
            <button onClick={()=>set({altTitles:[...form.altTitles,{title:'',type:'Alternative Title',language:'English'}]})}
              style={{display:'flex',gap:8,alignItems:'center',marginTop:10,padding:'7px 12px',
                border:'1px dashed var(--rule)',background:'transparent',cursor:'pointer'}}>
              <Ic.Plus width={11} height={11} style={{color:'var(--ink-3)'}}/>
              <span style={{fontSize:12,color:'var(--ink-2)'}}>Add alternate title</span>
            </button>
            <div style={{height:14}}/>
            <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:14}}>
              <div>
                <Lbl>Metadata language</Lbl>
                <select className="ff-mono" value={form.language} onChange={e=>set({language:e.target.value})}
                  style={{width:'100%',padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,color:'var(--ink)'}}>
                  {(typeof refLanguageOptions==='function'?refLanguageOptions():['English','Spanish','French','Portuguese','German','Italian','Japanese','Korean','Mandarin','Arabic','Hindi','Russian','Other']).map(l=><option key={l}>{l}</option>)}
                </select>
              </div>
              <div>
                <Lbl>Approx. duration (m:s)</Lbl>
                <div style={{display:'flex',gap:6,alignItems:'center'}}>
                  <input value={String(form.durationMin)} onChange={e=>set({durationMin:+e.target.value.replace(/\D/g,'')||0})}
                    inputMode="numeric" className="ff-mono"
                    style={{width:56,padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,color:'var(--ink)',textAlign:'center'}}/>
                  <span className="ff-mono" style={{color:'var(--ink-3)'}}>:</span>
                  <input value={String(form.durationSec).padStart(2,'0')} onChange={e=>set({durationSec:Math.min(59,+e.target.value.replace(/\D/g,'')||0)})}
                    inputMode="numeric" className="ff-mono"
                    style={{width:56,padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,color:'var(--ink)',textAlign:'center'}}/>
                </div>
              </div>
              <div>
                <Lbl>Musical key</Lbl>
                <select className="ff-mono" value={form.musicalKey} onChange={e=>set({musicalKey:e.target.value})}
                  style={{width:'100%',padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,color: form.musicalKey ? 'var(--ink)' : 'var(--ink-3)'}}>
                  <option value="">— Unspecified —</option>
                  <optgroup label="Major">
                    {['C','C♯ / D♭','D','D♯ / E♭','E','F','F♯ / G♭','G','G♯ / A♭','A','A♯ / B♭','B'].map(k=><option key={'maj'+k} value={k+' major'}>{k} major</option>)}
                  </optgroup>
                  <optgroup label="Minor">
                    {['C','C♯ / D♭','D','D♯ / E♭','E','F','F♯ / G♭','G','G♯ / A♭','A','A♯ / B♭','B'].map(k=><option key={'min'+k} value={k+' minor'}>{k} minor</option>)}
                  </optgroup>
                </select>
              </div>
              <div>
                <Lbl>Tempo (BPM)</Lbl>
                <div style={{display:'flex',gap:6,alignItems:'center'}}>
                  <input value={form.tempo} onChange={e=>set({tempo:e.target.value.replace(/\D/g,'').slice(0,3)})}
                    inputMode="numeric" placeholder="—" className="ff-mono num"
                    style={{width:80,padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,color:'var(--ink)',textAlign:'center'}}/>
                  <span className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',letterSpacing:'.06em'}}>BPM
                    {form.tempo && (
                      <span style={{marginLeft:8,color:'var(--ink-2)'}}>
                        · {(+form.tempo<70)?'Slow':(+form.tempo<100)?'Moderate':(+form.tempo<130)?'Up-tempo':'Fast'}
                      </span>
                    )}
                  </span>
                </div>
              </div>
              <div style={{gridColumn:'1/3'}}>
                <Lbl>Copyright line</Lbl>
                <Inp value={form.copyright} onChange={v=>set({copyright:v})} placeholder="© 2026 Pluralis Music"/>
              </div>
              <div style={{gridColumn:'1/3',display:'flex',alignItems:'center',gap:18,paddingTop:4}}>
                <label style={{display:'inline-flex',alignItems:'center',gap:6,fontSize:12,cursor:'pointer'}}>
                  <input type="checkbox" checked={form.instrumental} onChange={e=>set({instrumental:e.target.checked})}/>
                  Instrumental
                </label>
                <label style={{display:'inline-flex',alignItems:'center',gap:6,fontSize:12,cursor:'pointer'}}>
                  <input type="checkbox" checked={form.unrecorded} onChange={e=>set({unrecorded:e.target.checked})}/>
                  Unrecorded · pitch-only
                </label>
              </div>
            </div>
          </div>
        )}

        {step==='writers' && (
          <div style={{padding:'20px 24px'}}>
            <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginBottom:12}}>
              <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)'}}>SPLITS · TOTAL MUST EQUAL 100%</div>
              <span className="ff-mono num" style={{fontSize:12,fontWeight:600,
                color: shareOk ? 'var(--ink)' : '#c0392b'}}>
                {totalShare}% {shareOk?'✓':`· ${totalShare<100?`${100-totalShare}% remaining`:`over by ${totalShare-100}%`}`}
              </span>
            </div>
            {form.writers.map((w,i)=>(
              <WriterCard key={i} w={w} idx={i} total={form.writers.length}
                setWriter={setWriter} removeWriter={removeWriter}
                addPub={addPub} setPub={setPub} removePub={removePub}
                addAdmin={addAdmin} setAdmin={setAdmin} removeAdmin={removeAdmin}/>
            ))}
            <button onClick={addWriter} style={{display:'flex',gap:8,alignItems:'center',marginTop:10,padding:'8px 12px',
              border:'1px dashed var(--rule)',background:'transparent',cursor:'pointer'}}>
              <Ic.Plus width={11} height={11} style={{color:'var(--ink-3)'}}/>
              <span style={{fontSize:12,color:'var(--ink-2)'}}>Add co-writer</span>
            </button>
          </div>
        )}


        {step==='ids' && (() => {
          // Pulled live from the central Societies directory — only kinds that accept CWR (PRO / MRO / CMO / HUB; not pure NRO)
          const SOCIETIES = [...(window.SOC_CWR || ['ASCAP','BMI','SESAC','PRS','GEMA','SACEM','SOCAN','APRA','JASRAC']), 'Other'];
          // Suggested societies = unique PROs across writers (only writers with a name), minus ones already added
          const writerPros = Array.from(new Set((form.writers||[]).filter(w=>w.name && w.name.trim()).map(w=>w.pro).filter(Boolean)));
          const existing = new Set((form.societyIds||[]).map(r=>r.society));
          const suggestions = writerPros.filter(p=>!existing.has(p));

          const addRow = (soc='') => set({societyIds:[...(form.societyIds||[]), {society:soc, workId:''}]});
          const setRow = (i, patch) => set({societyIds: form.societyIds.map((r,j)=>j===i?{...r,...patch}:r)});
          const removeRow = (i) => set({societyIds: form.societyIds.filter((_,j)=>j!==i)});

          return (
            <div style={{padding:'20px 24px',maxHeight:'58vh',overflowY:'auto'}}>
              {/* Society work IDs */}
              <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginBottom:8}}>
                <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)'}}>SOCIETY WORK IDs</div>
                <span className="ff-mono" style={{fontSize:10,color:'var(--ink-3)'}}>Each PRO assigns its own work code on registration</span>
              </div>

              {(form.societyIds||[]).length === 0 ? (
                <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',padding:'12px 0',borderTop:'1px solid var(--rule-soft)',borderBottom:'1px solid var(--rule-soft)',fontStyle:'italic'}}>
                  No society IDs yet. They’ll be filled in once each writer’s society confirms registration — add any you already have below.
                </div>
              ) : (
                <>
                  <div className="ff-mono upper" style={{display:'grid',gridTemplateColumns:'180px 1fr 24px',gap:8,fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',padding:'0 0 6px',borderBottom:'1px solid var(--rule)'}}>
                    <span>SOCIETY</span><span>WORK ID</span><span/>
                  </div>
                  {form.societyIds.map((r,i)=>(
                    <div key={i} style={{display:'grid',gridTemplateColumns:'180px 1fr 24px',gap:8,padding:'8px 0',borderBottom:'1px solid var(--rule-soft)',alignItems:'center'}}>
                      <select value={r.society} onChange={e=>setRow(i,{society:e.target.value})}
                        className="ff-mono" style={{padding:'7px 8px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:11,color: r.society?'var(--ink)':'var(--ink-3)'}}>
                        <option value="">— select society —</option>
                        {SOCIETIES.map(s=><option key={s} value={s}>{s}</option>)}
                      </select>
                      <input value={r.workId} onChange={e=>setRow(i,{workId:e.target.value})}
                        placeholder={
                          r.society==='ASCAP' ? 'e.g. 884127392' :
                          r.society==='BMI' ? 'e.g. 12345678' :
                          r.society==='GEMA' ? 'e.g. 1234567-001' :
                          r.society==='SACEM' ? 'e.g. 09876543' :
                          r.society==='PRS' ? 'e.g. 098765BX' :
                          'Work code'
                        }
                        className="ff-mono num"
                        style={{padding:'7px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:12,color:'var(--ink)'}}/>
                      <button onClick={()=>removeRow(i)} style={{background:'transparent',border:0,cursor:'pointer',color:'var(--ink-3)',fontSize:14}}>×</button>
                    </div>
                  ))}
                </>
              )}

              <div style={{display:'flex',gap:6,flexWrap:'wrap',marginTop:10}}>
                {suggestions.map(p=>(
                  <button key={p} onClick={()=>addRow(p)} className="ff-mono upper"
                    style={{padding:'6px 10px',fontSize:10,letterSpacing:'.06em',cursor:'pointer',background:'transparent',color:'var(--ink-2)',border:'1px dashed var(--rule)'}}>
                    + {p}
                  </button>
                ))}
                <button onClick={()=>addRow('')}
                  style={{display:'flex',gap:8,alignItems:'center',padding:'6px 12px',border:'1px dashed var(--rule)',background:'transparent',cursor:'pointer'}}>
                  <Ic.Plus width={11} height={11} style={{color:'var(--ink-3)'}}/>
                  <span style={{fontSize:12,color:'var(--ink-2)'}}>Add society</span>
                </button>
              </div>

              {/* Catalog assignment — internal grouping */}
              <div style={{marginTop:24,paddingTop:18,borderTop:'1px solid var(--rule)'}}>
                <div style={{maxWidth:380}}>
                  <Lbl>Catalog</Lbl>
                  <select className="ff-mono" value={form.catalog} onChange={e=>set({catalog:e.target.value})}
                    style={{width:'100%',padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,color:'var(--ink)'}}>
                    {['PluralPub','SK Catalog','Saint Music','Perpetual Novice','TM Works','KAY Publishing','Forever Living'].map(c=><option key={c}>{c}</option>)}
                  </select>
                  <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:6}}>
                    Internal sub-imprint / catalog grouping for accounting and reporting.
                  </div>
                </div>
              </div>
            </div>
          );
        })()}

        {step==='rights' && (() => {
          const RT = window.RightsTab;
          return RT
            ? <RT form={form} set={set}/>
            : <div style={{padding:'40px 24px',textAlign:'center'}} className="ff-mono">Rights module loading…</div>;
        })()}

        {step==='pub' && (() => {
          const cwr = form.cwr;
          const setCwr = (patch) => set({cwr: {...cwr, ...patch}});
          const SectHdr = ({title, hint}) => (
            <div style={{display:'flex',alignItems:'baseline',gap:10,marginTop:18,marginBottom:10,paddingBottom:6,borderBottom:'1px solid var(--rule)'}}>
              <span className="ff-display" style={{fontSize:14,fontWeight:600,letterSpacing:'-0.01em'}}>{title}</span>
              {hint && <span className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginLeft:'auto'}}>{hint}</span>}
            </div>
          );
          const Field = ({label, children, span=1}) => (
            <div style={{gridColumn:`span ${span}`}}>
              <Lbl>{label}</Lbl>
              {children}
            </div>
          );
          const Sel = ({value, onChange, options, placeholder}) => (
            <select className="ff-mono" value={value} onChange={e=>onChange(e.target.value)}
              style={{width:'100%',padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:12,color:value?'var(--ink)':'var(--ink-3)'}}>
              {placeholder && <option value="">{placeholder}</option>}
              {options.map(o=><option key={o} value={o}>{o}</option>)}
            </select>
          );
          const Chk = ({k, label, hint}) => (
            <label style={{display:'flex',alignItems:'flex-start',gap:8,padding:'10px 12px',border:'1px solid var(--rule)',background: cwr[k] ? 'var(--bg-2)' : 'var(--bg)',cursor:'pointer'}}>
              <input type="checkbox" checked={cwr[k]} onChange={e=>setCwr({[k]:e.target.checked})} style={{marginTop:2}}/>
              <div style={{flex:1}}>
                <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.06em',fontWeight:600}}>{label}</div>
                {hint && <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:2,lineHeight:1.5}}>{hint}</div>}
              </div>
            </label>
          );
          return (
            <div style={{padding:'4px 24px 20px',maxHeight:'58vh',overflowY:'auto'}}>
              <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',padding:'12px 0 4px',lineHeight:1.6}}>
                Detailed metadata sent to the writers' societies when the work is delivered. Defaults are fine for most pop / commercial works.
              </div>

              <SectHdr title="Composition Type"/>
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:12}}>
                <Field label="Category">
                  <div style={{display:'flex',gap:4,flexWrap:'wrap'}}>
                    {(typeof refWorkTypeLabels==='function'?refWorkTypeLabels(['Original','Arrangement','Translation','Medley']):['Original','Arrangement','Translation','Medley']).map(k=>(
                      <button key={k} onClick={()=>set({category:k})} className="ff-mono upper" style={{
                        padding:'6px 10px',fontSize:10,letterSpacing:'.06em',cursor:'pointer',
                        background: form.category===k ? 'var(--ink)' : 'transparent',
                        color: form.category===k ? 'var(--bg)' : 'var(--ink-2)',
                        border:'1px solid var(--rule)'}}>{k}</button>
                    ))}
                  </div>
                </Field>
                <Field label="Composite Type">
                  <Sel value={cwr.compositeType} onChange={v=>setCwr({compositeType:v, compositeCount: v?cwr.compositeCount:''})}
                    placeholder="— Non-composite —"
                    options={(typeof refCwrLabels==='function'?refCwrLabels('cwr_composite_types',['Composite of Samples','Medley','Potpourri','Unspecified Composite']):['Composite of Samples','Medley','Potpourri','Unspecified Composite'])}/>
                </Field>
                <Field label={`Component count${cwr.compositeType?'':' · n/a'}`}>
                  <input value={cwr.compositeCount} onChange={e=>setCwr({compositeCount:e.target.value.replace(/\D/g,'')})}
                    inputMode="numeric" disabled={!cwr.compositeType} placeholder={cwr.compositeType?'e.g. 3':'—'}
                    className="ff-mono"
                    style={{width:'100%',padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:12,
                      color: cwr.compositeType ? 'var(--ink)' : 'var(--ink-4)',
                      opacity: cwr.compositeType ? 1 : 0.5,
                      cursor: cwr.compositeType ? 'text' : 'not-allowed'}}/>
                </Field>
              </div>

              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12,marginTop:12}}>
                <Field label="Text/Music Relationship">
                  <Sel value={cwr.txtMusicRel} onChange={v=>setCwr({txtMusicRel:v})}
                    options={(typeof refCwrLabels==='function'?refCwrLabels('cwr_text_music_relationships',['Music','Music and Text (same creation)','Music and Text (separate creation)','Text']):['Music','Music and Text (same creation)','Music and Text (separate creation)','Text'])}/>
                </Field>
                <Field label="Version Type">
                  <Sel value={cwr.versionType} onChange={v=>setCwr({versionType:v})}
                    options={(typeof refCwrLabels==='function'?refCwrLabels('cwr_version_types',['Original Work','Modified Version of a musical work']):['Original Work','Modified Version of a musical work'])}/>
                </Field>
                <Field label="Music Arrangement">
                  <Sel value={cwr.musicArrangement} onChange={v=>setCwr({musicArrangement:v})}
                    options={['Original','New','Arrangement','Addition','Unspecified arrangement']}/>
                </Field>
                <Field label="Lyric Adaptation">
                  <Sel value={cwr.lyricAdaptation} onChange={v=>setCwr({lyricAdaptation:v})}
                    options={['Original','New','Modification','Replacement','Addition','Translation','None','Unspecified']}/>
                </Field>
              </div>

              <SectHdr title="Classification & Intent"/>
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:12}}>
                <Field label="Distribution Category">
                  <Sel value={cwr.distCategory} onChange={v=>setCwr({distCategory:v})}
                    options={(typeof refCwrLabels==='function'?refCwrLabels('cwr_work_categories',['Jazz','Popular','Serious','Unclassified Distribution Category']):['Jazz','Popular','Serious','Unclassified Distribution Category'])}/>
                </Field>
                <Field label="Intended Purpose">
                  <Sel value={cwr.intendedPurpose} onChange={v=>setCwr({intendedPurpose:v})}
                    options={(typeof refCwrLabels==='function'?refCwrLabels('cwr_intended_purposes',['Commercial/Jingle/Trailer','Film','General Usage','Library Work','Multimedia','Radio','Television','Theatre','Video']):['Commercial/Jingle/Trailer','Film','General Usage','Library Work','Multimedia','Radio','Television','Theatre','Video'])}/>
                </Field>
                <Field label="Excerpt Type">
                  <Sel value={cwr.excerptType} onChange={v=>setCwr({excerptType:v})}
                    placeholder="— Not an excerpt —"
                    options={['Movement','Unspecified Excerpt']}/>
                </Field>
                <Field label="Work Type" span={3}>
                  <Sel value={cwr.workType} onChange={v=>setCwr({workType:v})}
                    options={['AAA (Triple A)','Adult Contemporary','Album Oriented Rock (AOR)','Alternative Music','Americana','Band','Bluegrass Music',"Children's Music",'Classical Music','Contemporary Christian','Country Music','Dance','Film/Television Music','Folk Music','Gospel (Black)','Gospel (Southern)','Jazz Music','Jingles','Latin','Latina','New Age','Opera','Polka Music','Pop Music','Rap Music','Rock Music','Rhythm and Blues','Sacred','Symphonic']}/>
                </Field>
              </div>

              <SectHdr title="Rights & Status"/>
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:8}}>
                <Chk k="grandRights" label="Grand Rights" hint="Dramatic performance rights apply (theatrical use)"/>
                <Chk k="priority" label="Priority Flag" hint="Expedite registration with societies"/>
                <Chk k="traditional" label="Traditional" hint="Based on traditional / folk material"/>
                <Chk k="publicDomain" label="Public Domain" hint="Underlying work is in the public domain"/>
                <Chk k="exceptionalClause" label="Exceptional Clause" hint="Society-specific exceptional treatment requested"/>
              </div>

              <SectHdr title="Origin & Instrumentation"/>
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12}}>
                <Field label="Work origin">
                  <input value={cwr.workOrigin} onChange={e=>setCwr({workOrigin:e.target.value})}
                    placeholder="e.g. Library cue · Film score for &quot;Title&quot;"
                    style={{width:'100%',padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:12,color:'var(--ink)',fontFamily:'inherit'}}/>
                </Field>
                <Field label="Instrumentation">
                  <input value={cwr.instrumentation} onChange={e=>setCwr({instrumentation:e.target.value})}
                    placeholder="e.g. SATB choir, piano · String quartet"
                    style={{width:'100%',padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:12,color:'var(--ink)',fontFamily:'inherit'}}/>
                </Field>
              </div>
            </div>
          );
        })()}

        {step==='lyrics' && (
          <div style={{padding:'20px 24px'}}>
            {form.instrumental ? (
              <div style={{padding:'40px 20px',textAlign:'center',border:'1px dashed var(--rule)',background:'var(--bg-2)'}}>
                <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:8}}>NO LYRICS</div>
                <div className="ff-mono" style={{fontSize:12,color:'var(--ink-2)'}}>This work is marked instrumental. Uncheck “Instrumental” in Basics to add lyrics.</div>
              </div>
            ) : (
              <>
                <div style={{marginBottom:14,maxWidth:260}}>
                  <Lbl>Lyrics language</Lbl>
                  <select className="ff-mono" value={form.lyricsLanguage} onChange={e=>set({lyricsLanguage:e.target.value})}
                    style={{width:'100%',padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,color:'var(--ink)'}}>
                    {(typeof refLanguageOptions==='function'?refLanguageOptions():['English','Spanish','French','Portuguese','German','Italian','Japanese','Korean','Mandarin','Arabic','Hindi','Russian','Other']).map(l=><option key={l}>{l}</option>)}
                  </select>
                </div>
                <Lbl>Lyrics</Lbl>
                <textarea value={form.lyrics} onChange={e=>set({lyrics:e.target.value})} rows={14}
                  placeholder={'[Verse 1]\n\n\n[Chorus]\n\n\n[Verse 2]\n\n\n[Bridge]\n'}
                  style={{width:'100%',padding:'12px 14px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,resize:'vertical',color:'var(--ink)',fontFamily:'inherit',lineHeight:1.6}}/>
                <div style={{display:'flex',justifyContent:'space-between',marginTop:6}}>
                  <span className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',letterSpacing:'.06em'}}>{form.lyrics.trim() ? `${form.lyrics.trim().split(/\s+/).length} words · ${form.lyrics.length} chars` : 'Empty'}</span>
                  <span className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',letterSpacing:'.06em'}}>Tip: bracket section labels like [Verse], [Chorus]</span>
                </div>
              </>
            )}
          </div>
        )}

        {step==='confirm' && (
          <div style={{padding:'20px 24px'}}>
            <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:10}}>{editing ? 'REVIEW CHANGES' : 'REVIEW NEW WORK'}</div>
            <div style={{border:'1px solid var(--rule)',padding:'18px 20px',background:'var(--bg-2)'}}>
              <div className="ff-display" style={{fontSize:24,fontWeight:600,letterSpacing:'-0.02em'}}>{form.title || '— untitled —'}</div>
              <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',marginTop:6}}>
                {form.category} · {form.instrumental ? 'Instrumental' : form.language} · {form.durationMin}:{String(form.durationSec).padStart(2,'0')}
                {form.unrecorded && ' · unrecorded · pitch-only'}
              </div>

              <div style={{marginTop:14,paddingTop:14,borderTop:'1px solid var(--rule)'}}>
                <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',marginBottom:8}}>WRITERS</div>
                {form.writers.map((w,i)=>(
                  <div key={i} style={{display:'grid',gridTemplateColumns:'1fr 60px 60px',padding:'4px 0',fontSize:12}}>
                    <span>{w.name || <em style={{color:'var(--ink-3)'}}>(no name)</em>}</span>
                    <span className="ff-mono" style={{color:'var(--ink-3)'}}><SocietyLink code={w.pro} dim/></span>
                    <span className="ff-mono num" style={{textAlign:'right',fontWeight:600}}>{w.share}%</span>
                  </div>
                ))}
              </div>

              <div style={{marginTop:14,paddingTop:14,borderTop:'1px solid var(--rule)',display:'grid',gridTemplateColumns:'1fr 1fr',gap:'8px 14px'}}>
                <span className="ff-mono" style={{fontSize:11,color:'var(--ink-3)'}}>ISWC · {form.iswc || 'AUTO'}</span>
                <span className="ff-mono" style={{fontSize:11,color:'var(--ink-3)'}}>CATALOG · {form.catalog}</span>
                <span className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',gridColumn:'1/3'}}>{form.copyright}</span>
              </div>
            </div>
            {!shareOk && (
              <div className="ff-mono" style={{marginTop:12,padding:'10px 14px',background:'#fde7e2',color:'#8a2419',fontSize:11,border:'1px solid #c0392b'}}>
                ⚠ Writer shares total {totalShare}%. Adjust to 100% before saving.
              </div>
            )}
          </div>
        )}

        </div>{/* /scrollable body */}

        {/* Footer */}
        <div style={{flex:'0 0 auto',padding:'14px 24px',borderTop:'1px solid var(--rule)',display:'flex',justifyContent:'space-between',alignItems:'center',background:'var(--bg-2)',gap:10}}>
          <button onClick={close} className="ff-mono upper" style={{padding:'8px 14px',fontSize:11,letterSpacing:'.08em',background:'transparent',color:'var(--ink-2)',border:'1px solid var(--rule)',cursor:'pointer'}}>Cancel</button>
          <div style={{display:'flex',gap:8}}>
            {step!=='basics' && (
              <button onClick={()=>setStep(
                  step==='confirm' ? 'rights' :
                  step==='rights' ? 'ids' :
                  step==='ids' ? 'pub' :
                  step==='pub' ? (form.instrumental?'writers':'lyrics') :
                  step==='lyrics' ? 'writers' :
                  'basics')}
                className="ff-mono upper" style={{padding:'8px 14px',fontSize:11,letterSpacing:'.08em',background:'transparent',color:'var(--ink-2)',border:'1px solid var(--rule)',cursor:'pointer'}}>← Back</button>
            )}
            {step!=='confirm' ? (
              <button onClick={()=>setStep(
                  step==='basics' ? 'writers' :
                  step==='writers' ? (form.instrumental?'pub':'lyrics') :
                  step==='lyrics' ? 'pub' :
                  step==='pub' ? 'ids' :
                  step==='ids' ? 'rights' :
                  'confirm')}
                disabled={step==='basics' && !form.title}
                className="ff-mono upper" style={{padding:'8px 16px',fontSize:11,letterSpacing:'.08em',
                  background: (step==='basics' && !form.title) ? 'var(--ink-4)' : 'var(--ink)',
                  color:'var(--bg)',border:0,
                  cursor:(step==='basics' && !form.title)?'default':'pointer',fontWeight:600}}>
                Next →
              </button>
            ) : (
              <button onClick={()=>{
                  if (!shareOk) return;
                  window.dispatchEvent(new CustomEvent('astro-toast', {detail:{
                    msg:`Work "${form.title}" registered · ISWC reserved`, tone:'ok'}}));
                  close();
                }}
                disabled={!shareOk}
                className="ff-mono upper" style={{padding:'8px 16px',fontSize:11,letterSpacing:'.08em',
                  background: shareOk ? 'var(--accent)' : 'var(--ink-4)',
                  color: shareOk ? 'var(--ink)' : 'var(--bg)',
                  border:0,cursor: shareOk ? 'pointer' : 'default',fontWeight:600}}>
                {editing ? 'Save changes ✓' : 'Register work ✓'}
              </button>
            )}
          </div>
        </div>
      </div>
    </>
  );
}

// ── CountrySelect — ISO 3166-1 alpha-2 picker ─────────────────
const _COUNTRIES = [
  ['US','United States'],['GB','United Kingdom'],['CA','Canada'],['DE','Germany'],
  ['FR','France'],['IT','Italy'],['ES','Spain'],['NL','Netherlands'],['SE','Sweden'],
  ['NO','Norway'],['DK','Denmark'],['FI','Finland'],['IE','Ireland'],['BE','Belgium'],
  ['CH','Switzerland'],['AT','Austria'],['PL','Poland'],['CZ','Czechia'],['PT','Portugal'],
  ['JP','Japan'],['KR','South Korea'],['CN','China'],['AU','Australia'],['NZ','New Zealand'],
  ['BR','Brazil'],['AR','Argentina'],['MX','Mexico'],['CO','Colombia'],['ZA','South Africa'],
  ['IN','India'],['NG','Nigeria'],['JM','Jamaica'],['IS','Iceland'],
];
function CountrySelect({ value, onChange }) {
  return (
    <select className="ff-mono" value={value||''} onChange={e=>onChange(e.target.value)}
      style={{width:'100%',padding:'8px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:12,color: value?'var(--ink)':'var(--ink-3)'}}>
      <option value="">— select country —</option>
      {_COUNTRIES.map(([k,n])=><option key={k} value={k}>{k} · {n}</option>)}
    </select>
  );
}

// ── ContributorsField — studio personnel (producer, mixer, engineer…) ─────
function ContributorsField({ value, onChange }) {
  const list = Array.isArray(value) ? value : [];
  const ROLES = ['Producer','Co-Producer','Executive Producer','Vocal Producer','Mixer','Mastering Engineer','Recording Engineer','Programmer','Arranger','Conductor','A&R','Studio'];
  const add = () => onChange([...list, { role: 'Producer', name: '', isni: '' }]);
  const update = (i, patch) => onChange(list.map((r,j)=>j===i?{...r,...patch}:r));
  const remove = (i) => onChange(list.filter((_,j)=>j!==i));

  if (list.length === 0) {
    return (
      <button onClick={add} style={{display:'flex',gap:8,alignItems:'center',width:'100%',padding:'10px 14px',
        border:'1px dashed var(--rule)',background:'transparent',cursor:'pointer'}}>
        <Ic.Plus width={11} height={11} style={{color:'var(--ink-3)'}}/>
        <span style={{fontSize:12,color:'var(--ink-2)'}}>Add producer, engineer, or other contributor</span>
      </button>
    );
  }

  return (
    <div>
      <div className="ff-mono upper" style={{display:'grid',gridTemplateColumns:'180px 1fr 160px 24px',gap:8,fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',padding:'0 0 6px',borderBottom:'1px solid var(--rule)'}}>
        <span>ROLE</span><span>NAME</span><span>ISNI</span><span/>
      </div>
      {list.map((r,i)=>(
        <div key={i} style={{display:'grid',gridTemplateColumns:'180px 1fr 160px 24px',gap:8,padding:'8px 0',borderBottom:'1px solid var(--rule-soft)',alignItems:'center'}}>
          <select value={r.role} onChange={e=>update(i,{role:e.target.value})}
            className="ff-mono" style={{padding:'7px 8px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:11,color:'var(--ink)'}}>
            {ROLES.map(s=><option key={s}>{s}</option>)}
          </select>
          <input value={r.name} onChange={e=>update(i,{name:e.target.value})}
            placeholder="Full name"
            style={{padding:'7px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,color:'var(--ink)'}}/>
          <input value={r.isni} onChange={e=>update(i,{isni:e.target.value})}
            placeholder="0000 0000 1234 567X"
            className="ff-mono num"
            style={{padding:'7px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:11,color:'var(--ink)'}}/>
          <button onClick={()=>remove(i)} style={{background:'transparent',border:0,cursor:'pointer',color:'var(--ink-3)',fontSize:14}}>×</button>
        </div>
      ))}
      <button onClick={add} style={{display:'flex',gap:8,alignItems:'center',marginTop:10,padding:'8px 12px',
        border:'1px dashed var(--rule)',background:'transparent',cursor:'pointer'}}>
        <Ic.Plus width={11} height={11} style={{color:'var(--ink-3)'}}/>
        <span style={{fontSize:12,color:'var(--ink-2)'}}>Add contributor</span>
      </button>
    </div>
  );
}

// ── RecordingOwnersField — master ownership by territory ──────
function RecordingOwnersField({ value, onChange, defaultOwner }) {
  const list = Array.isArray(value) ? value : [];
  const total = list.reduce((s,r)=>s+(+r.share||0),0);
  const ok = total === 100 || list.length === 0;
  const add = () => onChange([...list, { name: list.length===0 ? (defaultOwner||'') : '', territory: 'Worldwide', share: list.length===0 ? 100 : 0 }]);
  const update = (i, patch) => onChange(list.map((r,j)=>j===i?{...r,...patch}:r));
  const remove = (i) => onChange(list.filter((_,j)=>j!==i));

  if (list.length === 0) {
    return (
      <div>
        <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',marginBottom:8,fontStyle:'italic'}}>
          Defaults to 100% recording label, worldwide. Add rows to split ownership by territory or co-owner.
        </div>
        <button onClick={add} style={{display:'flex',gap:8,alignItems:'center',width:'100%',padding:'10px 14px',
          border:'1px dashed var(--rule)',background:'transparent',cursor:'pointer'}}>
          <Ic.Plus width={11} height={11} style={{color:'var(--ink-3)'}}/>
          <span style={{fontSize:12,color:'var(--ink-2)'}}>Add ownership row</span>
        </button>
      </div>
    );
  }

  return (
    <div>
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',marginBottom:8}}>
        <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)'}}>OWNERS · TOTAL MUST = 100%</div>
        <span className="ff-mono num" style={{fontSize:11,fontWeight:600,color: ok?'var(--ink)':'#c0392b'}}>
          {total}% {ok?'✓':total<100?`· ${100-total}% remaining`:`· over by ${total-100}%`}
        </span>
      </div>
      <div className="ff-mono upper" style={{display:'grid',gridTemplateColumns:'1fr 200px 80px 24px',gap:8,fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',padding:'0 0 6px',borderBottom:'1px solid var(--rule)'}}>
        <span>OWNER</span><span>TERRITORY</span><span>SHARE</span><span/>
      </div>
      {list.map((r,i)=>(
        <div key={i} style={{display:'grid',gridTemplateColumns:'1fr 200px 80px 24px',gap:8,padding:'8px 0',borderBottom:'1px solid var(--rule-soft)',alignItems:'center'}}>
          <input value={r.name} onChange={e=>update(i,{name:e.target.value})}
            placeholder="Label / company"
            style={{padding:'7px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:13,color:'var(--ink)'}}/>
          <select value={r.territory} onChange={e=>update(i,{territory:e.target.value})}
            className="ff-mono" style={{padding:'7px 8px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:11,color:'var(--ink)'}}>
            {['Worldwide','North America','Europe','UK','US','Asia','Latin America','Oceania','Africa','Worldwide ex. US'].map(s=><option key={s}>{s}</option>)}
          </select>
          <div style={{position:'relative'}}>
            <input type="number" value={r.share} onChange={e=>update(i,{share:+e.target.value||0})}
              className="ff-mono num"
              style={{width:'100%',padding:'7px 22px 7px 10px',background:'var(--bg)',border:'1px solid var(--rule)',fontSize:12,color:'var(--ink)'}}/>
            <span className="ff-mono" style={{position:'absolute',right:8,top:'50%',transform:'translateY(-50%)',fontSize:11,color:'var(--ink-3)',pointerEvents:'none'}}>%</span>
          </div>
          <button onClick={()=>remove(i)} style={{background:'transparent',border:0,cursor:'pointer',color:'var(--ink-3)',fontSize:14}}>×</button>
        </div>
      ))}
      <button onClick={add} style={{display:'flex',gap:8,alignItems:'center',marginTop:10,padding:'8px 12px',
        border:'1px dashed var(--rule)',background:'transparent',cursor:'pointer'}}>
        <Ic.Plus width={11} height={11} style={{color:'var(--ink-3)'}}/>
        <span style={{fontSize:12,color:'var(--ink-2)'}}>Add territory</span>
      </button>
    </div>
  );
}

// ── Date helpers ───────────────────────────────────────────────
// Single source of truth for date display + entry across the app.
// Storage format: ISO 'YYYY-MM-DD' (matches HTML <input type="date">).
// Display format: 'Month DD, YYYY' (e.g. June 24, 1986).
// Legacy values that aren't strict ISO (like '2018' or '2020-12') still
// pass through unchanged so historical data renders without breakage.
const _MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
function formatDate(iso) {
  if (!iso) return '';
  const s = String(iso).trim();
  if (!s) return '';
  // Strict YYYY-MM-DD (or ...THH:MM optional)
  const m = s.match(/^(\d{4})-(\d{2})-(\d{2})/);
  if (!m) return s; // legacy / partial — display as-is
  const y = +m[1], mo = +m[2], d = +m[3];
  if (mo < 1 || mo > 12 || d < 1 || d > 31) return s;
  return `${_MONTHS[mo-1]} ${d}, ${y}`;
}
window.formatDate = formatDate;

// DateField — native date picker in edit mode, formatted text in read mode.
//   <DateField value={iso} editing={bool} onChange={fn} placeholder="—"/>
// In read mode renders a <span>; in edit mode renders <input type="date">.
function DateField({ value, editing = true, onChange, placeholder = '—', size = 'md', style = {}, className = '', readOnlyAs = 'span' }) {
  const fz = size === 'sm' ? 11 : 12;
  const h  = size === 'sm' ? 26 : 30;
  if (!editing) {
    const display = formatDate(value) || placeholder;
    const Tag = readOnlyAs;
    return <Tag className={`num ${className}`} style={{fontSize:fz,...style}}>{display}</Tag>;
  }
  // Native <input type="date"> only accepts strict YYYY-MM-DD. If we hand it
  // a legacy/partial value (year-only "2003", year-month "2020-12"), it renders
  // blank — silently appearing to lose the data on first edit click. Detect
  // that case and render a plain text input as a fallback so the value remains
  // visible and editable; the user can either keep the legacy form or type a
  // full ISO date and the picker will take over on the next render.
  const v = value || '';
  const isStrictIso = /^\d{4}-\d{2}-\d{2}$/.test(v);
  if (v && !isStrictIso) {
    return (
      <input type="text" value={v} onChange={e => onChange && onChange(e.target.value)}
        placeholder="YYYY-MM-DD"
        className={`ff-mono num ${className}`}
        style={{height:h, padding:'0 8px', fontSize:fz,
          background:'var(--bg)', border:'1px solid var(--rule)',
          color:'var(--ink)', boxSizing:'border-box', fontFamily:'inherit', ...style}}/>
    );
  }
  return (
    <input type="date" value={v} onChange={e => onChange && onChange(e.target.value)}
      className={`ff-mono num ${className}`}
      style={{height:h, padding:'0 8px', fontSize:fz,
        background:'var(--bg)', border:'1px solid var(--rule)',
        color: v ? 'var(--ink)' : 'var(--ink-3)',
        boxSizing:'border-box', fontFamily:'inherit', ...style}}/>
  );
}
window.DateField = DateField;
window.formatDate = formatDate;

// ── Tiny form helpers ──────────────────────────────────────────
function Lbl({ children }) {
  return <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,marginTop:2}}>{children}</div>;
}
function Inp({ value, onChange, placeholder, type='text', mono=false, inline=false, autoFocus=false }) {
  const safeValue = value == null ? '' : value;
  return (
    <input value={safeValue} onChange={e=>onChange(e.target.value)} placeholder={placeholder} type={type} autoFocus={autoFocus}
      className={mono?'ff-mono':''}
      style={{
        width: inline ? 56 : '100%',
        padding:'8px 10px',
        background:'var(--bg)',
        border:'1px solid var(--rule)',
        fontSize:13, fontFamily: mono ? 'ui-monospace,monospace' : 'inherit',
        color:'var(--ink)', outline:'none',
        marginBottom: inline ? 0 : 12,
      }}
      onFocus={e=>e.target.style.borderColor='var(--ink)'}
      onBlur={e=>e.target.style.borderColor='var(--rule)'}/>
  );
}

// ── Toast (simple) ─────────────────────────────────────────────
function ToastHost() {
  const [toast, setToast] = useGS(null);
  useGE(() => {
    const h = (e) => {
      setToast(e.detail);
      setTimeout(()=>setToast(null), 3200);
    };
    window.addEventListener('astro-toast', h);
    return () => window.removeEventListener('astro-toast', h);
  }, []);
  if (!toast) return null;
  return (
    <div style={{
      position:'fixed',bottom:24,left:'50%',transform:'translateX(-50%)',zIndex:90,
      padding:'12px 18px',background:'var(--ink)',color:'var(--bg)',
      border:`2px solid ${toast.tone==='ok' ? 'var(--accent)' : 'var(--ink)'}`,
      fontSize:13,fontWeight:500,boxShadow:'0 12px 30px rgba(0,0,0,.3)'}}>
      {toast.tone==='ok' && <span style={{marginRight:8,color:'var(--accent)'}}>✓</span>}
      {toast.msg}
    </div>
  );
}

Object.assign(window, { GlobalRecordingDrawer, GlobalAddRecordingModal, GlobalAddSongModal, ToastHost,
  WriterTypeahead, PublisherTypeahead, IPIInput, ProfileDiffWarning, SharesField, TerritoriesField,
  PerformersField, ArtistTypeahead, LabelTypeahead,
  profileUuid, findProfileByUuid });
