// ── add-release-modal.jsx ──────────────────────────────────────
// Global "+ NEW RELEASE" flow. Triggered by:
//   window.dispatchEvent(new CustomEvent('astro-add-release'))
//
// Releases are CONTAINERS for already-existing recordings. This flow does
// not create or edit recordings — the tracklist step links existing tracks
// out of the catalog (RECORDING_GRAPH). All metadata required for a clean
// DDEX 4.x ERN delivery to DSPs is captured up front.
//
// Steps:
//   1 IDENTIFIERS  – title, version, type, UPC, catalog #, label, marketing
//   2 ARTIST + COPY – primary artist, P-line, ©-line, language, genre,
//                     parental advisory, original / digital release dates
//   3 TRACKLIST    – pick existing recordings, drag-reorder, disc / track #,
//                    side, single flag
//   4 ARTWORK + TERR – cover art swatch (placeholder), territories,
//                      excluded territories
//   5 DISTRIBUTION – DSP delivery targets (the 14 DSPs), preview clip,
//                    Hi-Res / Atmos flags
//   6 CONFIRM      – review summary
// ──────────────────────────────────────────────────────────────

const { useState: useArS, useEffect: useArE, useMemo: useArM, useRef: useArR } = React;

// 14 DSPs we model in the prototype
const _AR_DSPS = ['Spotify', 'Apple Music', 'YouTube Music', 'Amazon', 'Tidal', 'Deezer',
'Pandora', 'SoundCloud', 'Anghami', 'Boomplay', 'NetEase', 'QQ Music', 'Yandex', 'VK'];

// Curated cover-art swatches that match the existing release card aesthetic.
const _AR_ART_SWATCHES = [
'#f4d34a', '#2a4d8f', '#e8693a', '#3d2614', '#a83232', '#5b3aa8', '#0d4a2f', '#d4a02a',
'#1a1a1a', '#f0f0f0', '#7a8c5c', '#c45a8c'];


// Release types — drives ERN ReleaseType + display.
// Only Album / EP / Single ship to commercial DSPs; the rest are
// sub-classifications recorded for catalog reporting and rights mgmt.
// Release-type cards for step 1 BASICS. The DB (ref.release_types) is the
// source of truth for which codes exist; we overlay a small UI map for the
// human-friendly hints (track counts, "non-comm", etc.) and the
// distributable-vs-other split, neither of which the schema carries.
//
// `code`  : ref.release_types.code  (used internally + for downstream tagging)
// `label` : ref.release_types.label (display string — what's stored in form.type)
// `hint`  : UI-only short subtitle
// `group` : 'distributable' (Album/EP/Single — the three commercial DSP types)
//           or 'other' (catalog sub-classifications)
const _AR_RT_UI = {
  ALBUM:       { hint: '12+ tracks · ≥30 min',  group: 'distributable' },
  EP:          { hint: '4–8 tracks · ≤30 min',  group: 'distributable' },
  SINGLE:      { hint: '1–3 tracks',            group: 'distributable' },
  COMPILATION: { hint: 'Mixed sources',         group: 'other' },
  MIXTAPE:     { hint: 'Non-comm mix',          group: 'other' },
  LIVE:        { hint: 'Live recording',        group: 'other' },
  SOUNDTRACK:  { hint: 'Film / game OST',       group: 'other' },
  REMIX_ALBUM: { hint: 'Remix package',         group: 'other' },
  PROMO:       { hint: 'Promotional · non-comm',group: 'other' },
};
function _arReleaseTypes() {
  const REF = window.REF;
  let rows = [];
  if (REF && REF.ready && Array.isArray(REF.releaseTypes) && REF.releaseTypes.length) {
    rows = REF.releaseTypes.map((r) => {
      const ui = _AR_RT_UI[r.code] || { hint: '', group: 'other' };
      return { code: r.code, k: r.label, hint: ui.hint, group: ui.group };
    });
  } else {
    // Fallback (matches DB shape exactly so behavior is identical pre-load)
    rows = [
      { code: 'ALBUM',       k: 'Album',       hint: '12+ tracks · ≥30 min',  group: 'distributable' },
      { code: 'EP',          k: 'EP',          hint: '4–8 tracks · ≤30 min',  group: 'distributable' },
      { code: 'SINGLE',      k: 'Single',      hint: '1–3 tracks',            group: 'distributable' },
      { code: 'COMPILATION', k: 'Compilation', hint: 'Mixed sources',         group: 'other' },
      { code: 'MIXTAPE',     k: 'Mixtape',     hint: 'Non-comm mix',          group: 'other' },
      { code: 'LIVE',        k: 'Live Album',  hint: 'Live recording',        group: 'other' },
      { code: 'SOUNDTRACK',  k: 'Soundtrack',  hint: 'Film / game OST',       group: 'other' },
      { code: 'REMIX_ALBUM', k: 'Remix Album', hint: 'Remix package',         group: 'other' },
      { code: 'PROMO',       k: 'Promotional', hint: 'Promotional · non-comm',group: 'other' },
    ];
  }
  // Order: distributable first (Single → EP → Album), then everything else.
  const order = ['SINGLE', 'EP', 'ALBUM', 'COMPILATION', 'MIXTAPE', 'LIVE', 'SOUNDTRACK', 'REMIX_ALBUM', 'PROMO'];
  return rows.slice().sort((a, b) => {
    const ai = order.indexOf(a.code); const bi = order.indexOf(b.code);
    return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
  });
}


// Physical / digital configurations the release ships in.
// DDEX TechnicalResourceDetails distinguishes between these, and
// we need at minimum one config selected so catalogers know whether
// to expect physical assets, digital uploads, or both.
//
// Configurations the release ships in. Source of truth is ref.configurations
// (REF.configurations); we overlay short UX hints by code so the cards still
// read at a glance. Stored value (form.configurations) uses the DB label
// (e.g. "Digital", "Blu-ray") to stay readable downstream.
const _AR_CFG_HINT = {
  DIGITAL:  'streaming · download',
  CD:       'compact disc',
  VINYL:    'LP / 7" / 12"',
  CASSETTE: 'tape',
  DVD:      'DVD-A / video',
  BLU_RAY:  'high-def video',
};
function _arConfigs() {
  const REF = window.REF;
  if (REF && REF.ready && Array.isArray(REF.configurations) && REF.configurations.length) {
    return REF.configurations.map((r) => ({
      code: r.code, k: r.label, l: r.label, hint: _AR_CFG_HINT[r.code] || '',
    }));
  }
  // Fallback mirrors the DB rows so UI is identical pre-load.
  return [
    { code: 'DIGITAL',  k: 'Digital',  l: 'Digital',  hint: 'streaming · download' },
    { code: 'CD',       k: 'CD',       l: 'CD',       hint: 'compact disc' },
    { code: 'VINYL',    k: 'Vinyl',    l: 'Vinyl',    hint: 'LP / 7" / 12"' },
    { code: 'CASSETTE', k: 'Cassette', l: 'Cassette', hint: 'tape' },
    { code: 'DVD',      k: 'DVD',      l: 'DVD',      hint: 'DVD-A / video' },
    { code: 'BLU_RAY',  k: 'Blu-ray',  l: 'Blu-ray',  hint: 'high-def video' },
  ];
}


// Release-level explicit-content rating. Source of truth: ref.explicit_content_types
// (REF.explicitContentTypes). We expose only the 3 user-pickable options on the
// creation form (NOT_EXPLICIT / EXPLICIT / CLEAN); NOT_APPLICABLE and UNKNOWN
// are catch-alls for ingested data, not authoring choices.
function _arParentalOpts() {
  const opts = (window.refExplicitContentTypes ? window.refExplicitContentTypes({ userPickable: true }) : [
    { v: 'NOT_EXPLICIT', l: 'Not Explicit' },
    { v: 'EXPLICIT',     l: 'Explicit'     },
    { v: 'CLEAN',        l: 'Clean'        },
  ]);
  // _ArSeg expects {k, l} shape
  return opts.map(o => ({ k: o.v, l: o.l }));
}


// A monospace label, copied from the AddRecording flow.
function _ArLbl({ children }) {
  return <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--ink-3)', marginBottom: 6, marginTop: 2 }}>{children}</div>;
}
function _ArInp({ value, onChange, placeholder, type = 'text', mono = false, autoFocus = false }) {
  const safe = value == null ? '' : value;
  return (
    <input
      type={type}
      value={safe}
      autoFocus={autoFocus}
      onChange={(e) => onChange(e.target.value)}
      placeholder={placeholder}
      className={mono ? 'ff-mono' : ''}
      style={{ width: '100%', padding: '8px 10px', background: 'var(--bg)', border: '1px solid var(--rule)',
        fontSize: 13, color: 'var(--ink)', marginBottom: 14, boxSizing: 'border-box' }} />);

}
function _ArSeg({ options, value, onChange, mono = true, full = false }) {
  return (
    <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 14 }}>
      {options.map((o) => {
        const k = typeof o === 'string' ? o : o.k;
        const l = typeof o === 'string' ? o : o.l || o.k;
        const active = value === k;
        return (
          <button key={k} onClick={() => onChange(k)} className={mono ? 'ff-mono upper' : ''}
          style={{ padding: '7px 12px', fontSize: mono ? 10 : 12, letterSpacing: mono ? '.08em' : 0,
            background: active ? 'var(--ink)' : 'transparent',
            color: active ? 'var(--bg)' : 'var(--ink-2)',
            border: '1px solid ' + (active ? 'var(--ink)' : 'var(--rule)'),
            cursor: 'pointer', flex: full ? '1 1 auto' : '0 0 auto',
            fontWeight: active ? 600 : 400 }}>{l}</button>);

      })}
    </div>);

}

// ── Multi-artist field — array of {name, aid, role: 'primary'|'featured'|'remixer'} ──
// Releases routinely credit several artists (Beyoncé & Jay-Z; DJ Khaled feat.
// Drake & Lil Baby). DDEX ERN distinguishes between MainArtist, FeaturedArtist,
// and Remixer contributor — we capture all three. Roles come from
// ref.artist_roles via window.refArtistRoles().
function _ArArtistsField({ value, onChange, autoFocus = false }) {
  const list = Array.isArray(value) && value.length ? value : [{ name: '', aid: null, role: 'primary', creditedAs: '' }];
  const update = (i, patch) => onChange(list.map((r, k) => k === i ? { ...r, ...patch } : r));
  const remove = (i) => onChange(list.filter((_, k) => k !== i).length ? list.filter((_, k) => k !== i) : [{ name: '', aid: null, role: 'primary', creditedAs: '' }]);
  const add = (role = 'primary') => onChange(list.concat({ name: '', aid: null, role, creditedAs: '' }));
  const roles = (window.refArtistRoles ? window.refArtistRoles() : [
    { v: 'primary',  l: 'Primary Artist'  },
    { v: 'featured', l: 'Featured Artist' },
    { v: 'remixer',  l: 'Remixer'         },
  ]);

  return (
    <div style={{ marginBottom: 14 }}>
      {list.map((row, i) =>
      <div key={i} style={{ display: 'grid', gridTemplateColumns: 'minmax(0,1fr) minmax(0,1fr) 110px 26px', gap: 6, marginBottom: 6, alignItems: 'stretch' }}>
          {window.ArtistTypeahead ?
        <window.ArtistTypeahead
          value={row.name}
          aid={row.aid}
          onDraft={(txt) => update(i, { name: txt, aid: null })}
          onSelect={(o) => update(i, { name: o.name, aid: o.aid || null })} /> :

        <input value={row.name || ''} autoFocus={autoFocus && i === 0}
        onChange={(e) => update(i, { name: e.target.value })}
        placeholder={i === 0 ? 'Solange' : 'Featured artist…'}
        style={{ padding: '8px 10px', background: 'var(--bg)', border: '1px solid var(--rule)', fontSize: 13, color: 'var(--ink)', boxSizing: 'border-box' }} />
        }
          <input value={row.creditedAs || ''}
        onChange={(e) => update(i, { creditedAs: e.target.value })}
        placeholder={row.name ? `Credited as · ${row.name}` : 'Credited as…'}
        title="How this artist appears on this release (e.g. nickname, alias, abbreviation). Defaults to the artist name."
        style={{ padding: '8px 10px', background: 'var(--bg)', border: '1px solid var(--rule)', fontSize: 13, color: 'var(--ink)', boxSizing: 'border-box', fontStyle: row.creditedAs ? 'normal' : 'italic' }} />
          <select value={row.role} onChange={(e) => update(i, { role: e.target.value })} className="ff-mono upper"
        style={{ padding: '0 6px', background: 'var(--bg)', border: '1px solid var(--rule)',
          fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-2)', cursor: 'pointer' }}>
            {roles.map((r) => <option key={r.v} value={r.v}>{r.l.replace(/ Artist$/i, '').toUpperCase()}</option>)}
          </select>
          <button onClick={() => remove(i)} title="Remove"
        disabled={list.length === 1 && !row.name}
        style={{ border: '1px solid var(--rule)', background: 'var(--bg)', color: 'var(--ink-3)', cursor: 'pointer', fontSize: 14, padding: 0 }}>×</button>
        </div>
      )}
      <div style={{ display: 'flex', gap: 6 }}>
        <button onClick={() => add('primary')} className="ff-mono upper"
        style={{ padding: '5px 10px', fontSize: 9, letterSpacing: '.08em', background: 'transparent', color: 'var(--ink-2)', border: '1px dashed var(--rule)', cursor: 'pointer' }}>+ PRIMARY</button>
        <button onClick={() => add('featured')} className="ff-mono upper"
        style={{ padding: '5px 10px', fontSize: 9, letterSpacing: '.08em', background: 'transparent', color: 'var(--ink-2)', border: '1px dashed var(--rule)', cursor: 'pointer' }}>+ FEATURED</button>
        <button onClick={() => add('remixer')} className="ff-mono upper"
        style={{ padding: '5px 10px', fontSize: 9, letterSpacing: '.08em', background: 'transparent', color: 'var(--ink-2)', border: '1px dashed var(--rule)', cursor: 'pointer' }}>+ REMIXER</button>
      </div>
    </div>);

}

// Render an artists[] array as a credit string for headers / cards.
//   PRIMARY rows  → joined with " & "  (Beyoncé & Jay-Z)
//   FEATURED rows → " feat. A, B"      (Solange feat. Lil Wayne)
//   REMIXER rows  → " (A Remix)"       (Solange — Cranes In The Sky (Maceo Plex Remix))
function _arCreditLine(artists) {
  if (!Array.isArray(artists)) return artists || '';
  const filled = artists.filter((a) => a && a.name);
  if (!filled.length) return '';
  // Use the per-release `creditedAs` override when present; fall back to the canonical artist name.
  const display = (a) => (a.creditedAs && String(a.creditedAs).trim()) || a.name;
  const primaries = filled.filter((a) => a.role === 'primary');
  const feats = filled.filter((a) => a.role === 'featured');
  const remixers = filled.filter((a) => a.role === 'remixer');
  let s = '';
  if (primaries.length) {
    s = primaries.map(display).join(' & ');
  } else {
    s = display(filled[0]);
  }
  if (feats.length) s += ' feat. ' + feats.map(display).join(', ');
  if (remixers.length) {
    s += ' (' + remixers.map(display).join(' & ') + (remixers.length === 1 ? ' Remix)' : ' Remixes)');
  }
  return s;
}

// ── Tracklist row (sortable + linked to a real recording id) ───
//
// MusicBrainz model: a `track` is the appearance of a `recording` on a
// release/edition. The recording is the master (one canonical ISRC, title,
// duration, artists). The track row may override any of:
//   - track.trackTitle           (string; null = use recording.title)
//   - track.durationOverride     (seconds; null = use recording.duration)
//   - track.isrcs[]              ([{isrc, primary}]; first/primary used in UI;
//                                 if empty, fall back to recording.isrc)
//   - track.artistOverrides[]    ([{aid, role, displayNameOverride}];
//                                 each override targets one of the recording's
//                                 performers by aid + role and renames it for
//                                 *this* track only — `display_name_override`
//                                 in the schema)
//
// When any of those override fields is present and non-empty, render an "OVR"
// chip on the row. Click the ✎ (edit) button to expand the inline editor.

// Has any override been applied to this track?
function _arTrackHasOverrides(t) {
  if (!t) return false;
  if (t.trackTitle && String(t.trackTitle).trim()) return true;
  if (typeof t.durationOverride === 'number' && t.durationOverride > 0) return true;
  if (Array.isArray(t.isrcs) && t.isrcs.some((x) => x && x.isrc && String(x.isrc).trim())) return true;
  if (Array.isArray(t.artistOverrides) && t.artistOverrides.some((x) =>
    x && (
      (x.displayNameOverride && String(x.displayNameOverride).trim()) ||
      // an artist-only-on-this-track row counts as an override too
      (x.role && x.aid && x._extra)
    )
  )) return true;
  return false;
}

function _ArTrackRow({ track, idx, onRemove, onMoveUp, onMoveDown, onTogglePromo, onUpdate, isFirst, isLast }) {
  const [open, setOpen] = useArS(false);
  const REC = (window.REC_BY_ID || {})[track.recId] || {};
  // Title / duration / ISRC: prefer override, else recording.
  const title = (track.trackTitle && String(track.trackTitle).trim()) || track.title || REC.title || '(missing)';
  const dur = (typeof track.durationOverride === 'number' && track.durationOverride > 0)
    ? track.durationOverride
    : (track.duration || REC.duration || 0);
  const isrcRow = Array.isArray(track.isrcs) ? track.isrcs.find((x) => x && x.primary) || track.isrcs[0] : null;
  const isrc = (isrcRow && isrcRow.isrc) || track.isrc || REC.isrc || '';
  // Credit string: use the recording's artist as base, and apply any per-track
  // displayNameOverrides. We don't have full artist-credit objects in seed
  // data yet, so this falls back to the recording's `artist` string and tags
  // any extra-only override rows on the end.
  const artistBase = track.artist || REC.artist || '';
  const overrides = Array.isArray(track.artistOverrides) ? track.artistOverrides : [];
  let artist = artistBase;
  // If an override targets the primary artist's name, apply it.
  // Heuristic: an override with role 'primary' and no aid/extra acts as a
  // bulk replacement of the primary credit string.
  const primaryOverride = overrides.find((o) => o && o.role === 'primary' && o.displayNameOverride);
  if (primaryOverride) artist = primaryOverride.displayNameOverride;
  // Append any extra featured/remixer overrides not already on the recording.
  const extras = overrides.filter((o) => o && o._extra && (o.displayNameOverride || '').trim());
  if (extras.length) {
    const feats = extras.filter((o) => o.role === 'featured').map((o) => o.displayNameOverride.trim());
    const remixers = extras.filter((o) => o.role === 'remixer').map((o) => o.displayNameOverride.trim());
    if (feats.length && !/feat\.|featuring/i.test(artist)) {
      artist += ' feat. ' + feats.join(', ');
    }
    if (remixers.length) {
      artist += ' (' + remixers.join(' & ') + (remixers.length === 1 ? ' Remix)' : ' Remixes)');
    }
  }
  const hasOvr = _arTrackHasOverrides(track);

  return (
    <div style={{ borderBottom: '1px solid var(--rule-soft)', background: 'var(--bg)' }}>
      <div style={{ display: 'grid', gridTemplateColumns: '24px 32px 1fr 110px 60px 26px 26px 26px 22px', gap: 10,
        padding: '10px 12px', alignItems: 'center' }}>
        <span className="ff-mono num" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{String(idx + 1).padStart(2, '0')}</span>
        <button onClick={onTogglePromo} title={track.promo ? 'Lead single' : 'Mark as lead single'}
          style={{ width: 24, height: 24, border: '1px solid ' + (track.promo ? 'var(--accent)' : 'var(--rule)'),
            background: track.promo ? 'var(--accent)' : 'transparent',
            color: track.promo ? 'var(--bg)' : 'var(--ink-3)', fontSize: 11, cursor: 'pointer', padding: 0, fontWeight: 700 }}>★</button>
        <div style={{ minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>{title}</span>
            {hasOvr &&
              <span className="ff-mono upper" title="This track has per-edition overrides applied"
                style={{ fontSize: 8, letterSpacing: '.08em', padding: '1px 5px', border: '1px solid var(--accent)', color: 'var(--accent)', flexShrink: 0 }}>OVR</span>}
          </div>
          <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{artist}</div>
        </div>
        <span className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-2)', whiteSpace: 'nowrap' }}>
          {window.isrcDisplay ? window.isrcDisplay(isrc) : isrc}
        </span>
        <span className="ff-mono num" style={{ fontSize: 11, color: 'var(--ink-2)', textAlign: 'right' }}>
          {Math.floor(dur / 60)}:{String(dur % 60).padStart(2, '0')}
        </span>
        <button onClick={() => setOpen(!open)} title={open ? 'Close track editor' : 'Edit track overrides'}
          style={{ width: 24, height: 24, border: '1px solid ' + (open || hasOvr ? 'var(--accent)' : 'var(--rule)'), background: open ? 'var(--accent)' : 'var(--bg)',
            color: open ? 'var(--bg)' : (hasOvr ? 'var(--accent)' : 'var(--ink-2)'), cursor: 'pointer', padding: 0, fontSize: 11 }}>✎</button>
        <button disabled={isFirst} onClick={onMoveUp} title="Move up"
          style={{ width: 24, height: 24, border: '1px solid var(--rule)', background: 'var(--bg)',
            color: isFirst ? 'var(--ink-4)' : 'var(--ink-2)', cursor: isFirst ? 'default' : 'pointer', padding: 0, fontSize: 11 }}>↑</button>
        <button disabled={isLast} onClick={onMoveDown} title="Move down"
          style={{ width: 24, height: 24, border: '1px solid var(--rule)', background: 'var(--bg)',
            color: isLast ? 'var(--ink-4)' : 'var(--ink-2)', cursor: isLast ? 'default' : 'pointer', padding: 0, fontSize: 11 }}>↓</button>
        <button onClick={onRemove} title="Remove track"
          style={{ width: 22, height: 22, border: 0, background: 'transparent', color: 'var(--ink-3)', cursor: 'pointer', fontSize: 14, lineHeight: 1 }}>×</button>
      </div>
      {open && <_ArTrackEditPanel track={track} REC={REC} onUpdate={onUpdate} onClose={() => setOpen(false)} />}
    </div>);
}

// Inline overrides editor. Mounts under the track row when ✎ is clicked.
// All four override fields plus a "reset all overrides" CTA.
function _ArTrackEditPanel({ track, REC, onUpdate, onClose }) {
  const patch = (p) => onUpdate({ ...track, ...p });
  // Normalise ISRCs to a list view; first row is "primary".
  const isrcs = Array.isArray(track.isrcs) && track.isrcs.length ? track.isrcs : [];
  const setIsrcs = (arr) => {
    // Re-mark exactly one primary; default to row 0 if nothing flagged.
    const cleaned = arr.map((r, i) => ({ isrc: (r && r.isrc) || '', primary: false }));
    const flagged = arr.findIndex((r) => r && r.primary);
    const primaryIdx = flagged >= 0 ? flagged : 0;
    if (cleaned[primaryIdx]) cleaned[primaryIdx].primary = true;
    patch({ isrcs: cleaned });
  };
  const addIsrc = () => setIsrcs(isrcs.concat({ isrc: '', primary: !isrcs.length }));
  const removeIsrc = (i) => setIsrcs(isrcs.filter((_, k) => k !== i));
  const updateIsrc = (i, p) => setIsrcs(isrcs.map((r, k) => k === i ? { ...r, ...p } : r));
  const setPrimary = (i) => setIsrcs(isrcs.map((r, k) => ({ ...r, primary: k === i })));

  // Artist overrides — start with one row for the recording's primary artist
  // (so the user has somewhere obvious to type a "credited as" string).
  const recArtistName = REC.artist || track.artist || '';
  const overrides = Array.isArray(track.artistOverrides) ? track.artistOverrides : [];
  const ensureRow = (role, displayDefault, _extra = false) => {
    if (overrides.find((o) => o && o.role === role && !!o._extra === !!_extra)) return;
    patch({ artistOverrides: overrides.concat({ aid: null, role, displayNameOverride: '', _extra }) });
  };
  const updateOverride = (i, p) => patch({ artistOverrides: overrides.map((o, k) => k === i ? { ...o, ...p } : o) });
  const removeOverride = (i) => patch({ artistOverrides: overrides.filter((_, k) => k !== i) });

  const resetAll = () => onUpdate({
    recId: track.recId,
    promo: track.promo,
    // Strip overrides; keep linkage + promo flag.
    title: track.title, artist: track.artist, duration: track.duration, isrc: track.isrc,
    trackTitle: undefined,
    durationOverride: undefined,
    isrcs: undefined,
    artistOverrides: undefined,
  });

  const fmtDur = (s) => `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
  const recDur = REC.duration || track.duration || 0;
  const durDraft = (typeof track.durationOverride === 'number' && track.durationOverride > 0) ? track.durationOverride : '';

  // Roles for "extra" featured/remixer rows that don't exist on the recording.
  const roles = (window.refArtistRoles ? window.refArtistRoles() : [
    { v: 'primary',  l: 'Primary'  },
    { v: 'featured', l: 'Featured' },
    { v: 'remixer',  l: 'Remixer'  },
  ]);

  const lblStyle = { display: 'block', fontSize: 9, letterSpacing: '.1em', textTransform: 'uppercase', color: 'var(--ink-3)', marginBottom: 4, fontFamily: 'var(--ff-mono, ui-monospace)' };
  const inpStyle = { width: '100%', padding: '7px 9px', background: 'var(--bg)', border: '1px solid var(--rule)', fontSize: 12, color: 'var(--ink)', boxSizing: 'border-box' };
  const ovr = !!_arTrackHasOverrides(track);

  return (
    <div style={{ background: 'var(--rule-soft)', padding: '12px 16px 14px', borderTop: '1px solid var(--rule-soft)' }}>
      <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.1em', color: 'var(--ink-3)', marginBottom: 10, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <span>Per-edition overrides · linked to <span style={{ color: 'var(--ink-2)' }}>{REC.title || '(recording)'}</span></span>
        {ovr &&
          <button onClick={resetAll} className="ff-mono upper"
            style={{ fontSize: 9, letterSpacing: '.08em', padding: '4px 8px', border: '1px solid var(--rule)', background: 'transparent', color: 'var(--ink-2)', cursor: 'pointer' }}>
            ↺ Reset all overrides
          </button>}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 12 }}>
        <div>
          <label style={lblStyle}>Track title</label>
          <input value={track.trackTitle || ''}
            onChange={(e) => patch({ trackTitle: e.target.value })}
            placeholder={REC.title || track.title || 'Track title…'}
            style={{ ...inpStyle, fontStyle: track.trackTitle ? 'normal' : 'italic' }} />
        </div>
        <div>
          <label style={lblStyle}>Duration override (mm:ss or seconds)</label>
          <input value={durDraft === '' ? '' : (typeof durDraft === 'number' ? fmtDur(durDraft) : String(durDraft))}
            onChange={(e) => {
              const v = (e.target.value || '').trim();
              if (!v) { patch({ durationOverride: undefined }); return; }
              const m = v.match(/^(\d+):(\d{1,2})$/);
              const secs = m ? (Number(m[1]) * 60 + Number(m[2])) : (Number(v) || 0);
              patch({ durationOverride: secs > 0 ? secs : undefined });
            }}
            placeholder={recDur ? fmtDur(recDur) : '0:00'}
            style={{ ...inpStyle, fontStyle: track.durationOverride ? 'normal' : 'italic' }} />
        </div>
      </div>

      {/* ISRC overrides — multi w/ primary flag */}
      <div style={{ marginBottom: 12 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 4 }}>
          <label style={lblStyle}>Track ISRC{isrcs.length > 1 ? 's' : ''}</label>
          <span className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>
            {isrcs.length === 0 && REC.isrc ? `Inheriting · ${window.isrcDisplay ? window.isrcDisplay(REC.isrc) : REC.isrc}` : ''}
          </span>
        </div>
        {isrcs.map((row, i) =>
          <div key={i} style={{ display: 'grid', gridTemplateColumns: '1fr 80px 26px', gap: 6, marginBottom: 6, alignItems: 'center' }}>
            <input value={row.isrc || ''} onChange={(e) => updateIsrc(i, { isrc: e.target.value.toUpperCase() })}
              placeholder={REC.isrc || 'USRC1XXXXXXX'} maxLength={12}
              className="ff-mono" style={{ ...inpStyle, fontSize: 11, letterSpacing: '.04em' }} />
            <button onClick={() => setPrimary(i)} className="ff-mono upper" title={row.primary ? 'Primary ISRC' : 'Mark as primary'}
              style={{ padding: '6px 8px', fontSize: 9, letterSpacing: '.08em',
                background: row.primary ? 'var(--accent)' : 'transparent',
                color: row.primary ? 'var(--bg)' : 'var(--ink-2)',
                border: '1px solid ' + (row.primary ? 'var(--accent)' : 'var(--rule)'),
                cursor: 'pointer' }}>
              {row.primary ? '✓ PRIMARY' : 'PRIMARY'}
            </button>
            <button onClick={() => removeIsrc(i)} title="Remove"
              style={{ border: '1px solid var(--rule)', background: 'var(--bg)', color: 'var(--ink-3)', cursor: 'pointer', fontSize: 14, padding: 0, height: '100%' }}>×</button>
          </div>
        )}
        <button onClick={addIsrc} className="ff-mono upper"
          style={{ padding: '5px 10px', fontSize: 9, letterSpacing: '.08em', background: 'transparent', color: 'var(--ink-2)', border: '1px dashed var(--rule)', cursor: 'pointer' }}>
          + ADD ISRC
        </button>
      </div>

      {/* Per-artist credited-as overrides */}
      <div>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 4 }}>
          <label style={lblStyle}>Credited as · per artist on this track</label>
          <span className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>
            Recording: {recArtistName}
          </span>
        </div>
        {/* Always-present row for the recording's primary artist credit. */}
        {(() => {
          const rowIdx = overrides.findIndex((o) => o && o.role === 'primary' && !o._extra);
          const rowVal = rowIdx >= 0 ? overrides[rowIdx] : { aid: null, role: 'primary', displayNameOverride: '' };
          const onChange = (v) => {
            if (rowIdx >= 0) updateOverride(rowIdx, { displayNameOverride: v });
            else patch({ artistOverrides: overrides.concat({ aid: null, role: 'primary', displayNameOverride: v }) });
          };
          return (
            <div style={{ display: 'grid', gridTemplateColumns: 'minmax(0,1fr) minmax(0,1fr) 90px 26px', gap: 6, marginBottom: 6, alignItems: 'center' }}>
              <input value={recArtistName} disabled
                style={{ ...inpStyle, color: 'var(--ink-3)', cursor: 'not-allowed' }} />
              <input value={rowVal.displayNameOverride || ''}
                onChange={(e) => onChange(e.target.value)}
                placeholder={recArtistName ? `Credited as · ${recArtistName}` : 'Credited as…'}
                style={{ ...inpStyle, fontStyle: rowVal.displayNameOverride ? 'normal' : 'italic' }} />
              <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', textAlign: 'center' }}>PRIMARY</span>
              <span />
            </div>
          );
        })()}
        {/* Extra featured/remixer rows added only on this track. */}
        {overrides.map((o, i) => o && o._extra ? (
          <div key={i} style={{ display: 'grid', gridTemplateColumns: 'minmax(0,1fr) minmax(0,1fr) 90px 26px', gap: 6, marginBottom: 6, alignItems: 'center' }}>
            <input value={o.name || ''} onChange={(e) => updateOverride(i, { name: e.target.value, displayNameOverride: o.displayNameOverride || e.target.value })}
              placeholder="Artist name" style={inpStyle} />
            <input value={o.displayNameOverride || ''} onChange={(e) => updateOverride(i, { displayNameOverride: e.target.value })}
              placeholder="Credited as…" style={{ ...inpStyle, fontStyle: o.displayNameOverride ? 'normal' : 'italic' }} />
            <select value={o.role} onChange={(e) => updateOverride(i, { role: e.target.value })} className="ff-mono upper"
              style={{ padding: '7px 6px', fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-2)',
                background: 'var(--bg)', border: '1px solid var(--rule)', cursor: 'pointer' }}>
              {roles.filter((r) => r.v !== 'primary').map((r) => <option key={r.v} value={r.v}>{(r.l || '').replace(/ Artist$/i, '').toUpperCase()}</option>)}
            </select>
            <button onClick={() => removeOverride(i)} title="Remove"
              style={{ border: '1px solid var(--rule)', background: 'var(--bg)', color: 'var(--ink-3)', cursor: 'pointer', fontSize: 14, padding: 0, height: '100%' }}>×</button>
          </div>
        ) : null)}
        <div style={{ display: 'flex', gap: 6, marginTop: 4 }}>
          <button onClick={() => patch({ artistOverrides: overrides.concat({ aid: null, role: 'featured', name: '', displayNameOverride: '', _extra: true }) })}
            className="ff-mono upper"
            style={{ padding: '5px 10px', fontSize: 9, letterSpacing: '.08em', background: 'transparent', color: 'var(--ink-2)', border: '1px dashed var(--rule)', cursor: 'pointer' }}>
            + FEATURED (this track only)
          </button>
          <button onClick={() => patch({ artistOverrides: overrides.concat({ aid: null, role: 'remixer', name: '', displayNameOverride: '', _extra: true }) })}
            className="ff-mono upper"
            style={{ padding: '5px 10px', fontSize: 9, letterSpacing: '.08em', background: 'transparent', color: 'var(--ink-2)', border: '1px dashed var(--rule)', cursor: 'pointer' }}>
            + REMIXER (this track only)
          </button>
        </div>
      </div>
    </div>
  );
}

// Expose the helper so the modal hook (useArS, etc.) can use it without re-importing.
window._arTrackHasOverrides = _arTrackHasOverrides;

// ── Recording picker — search existing catalog ─────────────────
function _ArPicker({ excludeIds, onPick, primaryArtistName, onCreateNew }) {
  const [q, setQ] = useArS('');
  const [scope, setScope] = useArS('all'); // all | byArtist
  const all = useArM(() => window.RECORDING_GRAPH || [], []);
  const filtered = useArM(() => {
    const qn = q.trim().toLowerCase();
    let xs = all.filter((r) => !excludeIds.includes(r.id));
    if (scope === 'byArtist' && primaryArtistName) {
      xs = xs.filter((r) => (r.artist || '').toLowerCase() === primaryArtistName.toLowerCase());
    }
    if (qn) {
      xs = xs.filter((r) =>
      (r.title || '').toLowerCase().includes(qn) ||
      (r.artist || '').toLowerCase().includes(qn) ||
      (r.isrc || '').toLowerCase().includes(qn));
    }
    return xs.slice(0, 24);
  }, [q, scope, all, excludeIds, primaryArtistName]);

  return (
    <div style={{ border: '1px solid var(--rule)', background: 'var(--bg-2)', padding: '12px 12px 4px' }}>
      <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--ink-3)', marginBottom: 8 }}>
        ADD EXISTING RECORDING <span style={{ color: 'var(--ink-4)' }}>· LINK ONLY · {(window.RECORDING_GRAPH || []).length} IN CATALOG</span>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 8, marginBottom: 8 }}>
        <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search title, artist, ISRC…"
        style={{ padding: '8px 10px', background: 'var(--bg)', border: '1px solid var(--rule)', fontSize: 12, color: 'var(--ink)', boxSizing: 'border-box' }} />
        {primaryArtistName &&
        <button onClick={() => setScope((s) => s === 'byArtist' ? 'all' : 'byArtist')} className="ff-mono upper"
        style={{ padding: '7px 10px', fontSize: 9, letterSpacing: '.1em',
          background: scope === 'byArtist' ? 'var(--ink)' : 'var(--bg)',
          color: scope === 'byArtist' ? 'var(--bg)' : 'var(--ink-2)',
          border: '1px solid var(--rule)', cursor: 'pointer', fontWeight: 600, whiteSpace: 'nowrap' }}>
            BY {primaryArtistName.toUpperCase().slice(0, 18)}
          </button>
        }
      </div>
      <div style={{ maxHeight: 200, overflowY: 'auto', border: '1px solid var(--rule-soft)', background: 'var(--bg)' }}>
        {filtered.length === 0 ?
        <div className="ff-mono" style={{ fontSize: 11, color: 'var(--ink-3)', padding: '18px 14px', textAlign: 'center' }}>
            {q ? `No recordings matching "${q}".` : 'Start typing to search the catalog.'}
          </div> :

        filtered.map((r, i, arr) =>
        <button key={r.id} onClick={() => onPick(r)}
        style={{ display: 'grid', gridTemplateColumns: '1fr 110px 50px 18px', gap: 10, width: '100%', padding: '9px 12px',
          border: 0, borderBottom: i < arr.length - 1 ? '1px solid var(--rule-soft)' : 'none',
          background: 'transparent', cursor: 'pointer', textAlign: 'left', alignItems: 'center' }}
        onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-2)'}
        onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontSize: 12, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.title}</div>
                <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.artist} · {r.album || '—'}</div>
              </div>
              <span className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-2)' }}>{window.isrcDisplay ? window.isrcDisplay(r.isrc) : r.isrc}</span>
              <span className="ff-mono num" style={{ fontSize: 11, textAlign: 'right', color: 'var(--ink-2)' }}>
                {Math.floor((r.duration || 0) / 60)}:{String((r.duration || 0) % 60).padStart(2, '0')}
              </span>
              <span style={{ color: 'var(--ink-3)', fontSize: 14 }}>+</span>
            </button>
        )
        }
      </div>
      <div className="ff-mono" style={{ fontSize: 9, color: 'var(--ink-3)', padding: '8px 2px 4px', letterSpacing: '.04em', lineHeight: 1.5,
        display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10 }}>
        <span>Releases LINK existing recordings.</span>
        <button onClick={onCreateNew} className="ff-mono upper"
        style={{ padding: '6px 10px', fontSize: 9, letterSpacing: '.1em', fontWeight: 600,
          background: 'transparent', color: 'var(--ink)', border: '1px solid var(--ink)', cursor: 'pointer' }}>
          + CREATE NEW RECORDING
        </button>
      </div>
    </div>);

}

// ── MAIN MODAL ─────────────────────────────────────────────────
function GlobalAddReleaseModal() {
  const [open, setOpen] = useArS(false);
  const [step, setStep] = useArS('id');
  const [form, setForm] = useArS({});

  useArE(() => {
    const onOpen = (e) => {
      const today = new Date();
      const yyyy = today.getFullYear();
      const seedRecId = e.detail?.recordingId || null;
      const seedTracks = [];
      if (seedRecId) {
        const r = (window.REC_BY_ID || {})[seedRecId];
        if (r) seedTracks.push({ recId: r.id, title: r.title, artist: r.artist, duration: r.duration, isrc: r.isrc, promo: true });
      }
      setOpen(true);
      setStep(seedRecId ? 'basics' : 'match');
      setForm({
        // 0 — match (search to import existing release metadata)
        searchTitle: '',
        searchUpc: '',
        searchArtist: '',
        importedFrom: null, // {src, id, title, artist, year, upc} once imported
        // 1 — identifiers
        title: '',
        versionSubtitle: '',
        type: 'Album',
        configurations: ['Digital'],
        upc: '',
        catalog: '',
        label: '',
        marketingLabel: '',
        // 2 — artist + copy
        artists: [{ name: '', aid: null, role: 'primary' }], // [{name, aid, role}]
        artist: '', // derived display string
        artistAid: null, // first primary's aid (legacy)
        variousArtists: false, // auto-applied when 5+ primary artists on a non-single (compilation rule)
        featured: '', // legacy mirror
        pLine: `℗ ${yyyy} `,
        cLine: `© ${yyyy} `,
        language: 'English',
        genre: '',
        subGenre: '',
        parental: 'NOT_EXPLICIT',
        originalDate: '',
        digitalDate: today.toISOString().slice(0, 10),
        preorderDate: '',
        // 3 — tracks
        tracks: seedTracks,
        showPicker: seedTracks.length === 0,
        // 4 — artwork + territory
        art: _AR_ART_SWATCHES[0],
        artwork: [], // [{ id, file, url, type_code, is_primary, kind: 'image'|'video', meta: { width, height, duration, size, format } }]
        territoryMode: 'world', // world | include | exclude
        territories: ['World'],
        // 5 — distribution
        dsps: new Set(_AR_DSPS), // all by default
        previewStart: 30,
        previewLength: 30,
        hiRes: false,
        atmos: false,
        deliveryDate: today.toISOString().slice(0, 10)
      });
    };
    window.addEventListener('astro-add-release', onOpen);
    return () => window.removeEventListener('astro-add-release', onOpen);
  }, []);

  // Keep the legacy display string + first-primary aid in sync with artists[]
  useArE(() => {
    if (!open || !form.artists) return;
    const display = _arCreditLine(form.artists);
    const firstPrimary = form.artists.find((a) => a && a.role === 'primary' && a.aid);
    if (display !== form.artist || firstPrimary && firstPrimary.aid !== form.artistAid) {
      setForm((f) => ({ ...f, artist: display, artistAid: firstPrimary ? firstPrimary.aid : f.artistAid }));
    }
  }, [form.artists, open]);

  if (!open) return null;
  const close = () => setOpen(false);
  const set = (patch) => setForm((f) => ({ ...f, ...patch }));

  const tracks = form.tracks || [];
  const totalSeconds = tracks.reduce((a, t) => {
    const d = t.duration || (window.REC_BY_ID || {})[t.recId]?.duration || 0;
    return a + d;
  }, 0);
  const totalMin = Math.floor(totalSeconds / 60);
  const dspCount = form.dsps && form.dsps.size || 0;

  // Track ops
  const removeTrack = (i) => set({ tracks: tracks.filter((_, k) => k !== i) });
  const moveTrack = (i, dir) => {
    const j = i + dir;
    if (j < 0 || j >= tracks.length) return;
    const next = tracks.slice();
    [next[i], next[j]] = [next[j], next[i]];
    set({ tracks: next });
  };
  const togglePromo = (i) => {
    set({ tracks: tracks.map((t, k) => k === i ? { ...t, promo: !t.promo } : t) });
  };
  // Replace track at index i. The TrackEditPanel calls this with a fresh
  // object every change. We accept either a partial patch (object) or a full
  // replacement; here we always pass a full object so just swap.
  const updateTrack = (i, next) => {
    set({ tracks: tracks.map((t, k) => k === i ? next : t) });
  };
  const addTrackFromRec = (r) => {
    const next = tracks.concat({ recId: r.id, title: r.title, artist: r.artist, duration: r.duration, isrc: r.isrc, promo: false });
    set({ tracks: next });
  };
  const openCreateRecording = () => {
    // Hand off to the global recording modal; come back here on save.
    window.dispatchEvent(new CustomEvent('astro-add-recording', { detail: {
        returnLabel: 'release',
        onCreated: (newRec) => {
          // Add to release tracklist immediately
          setForm((f) => ({
            ...f,
            tracks: (f.tracks || []).concat({
              recId: newRec.id,
              title: newRec.title,
              artist: newRec.artist,
              duration: newRec.duration,
              isrc: newRec.isrc,
              promo: (f.tracks || []).length === 0,
              _isNew: true
            }),
            showPicker: true
          }));
        }
      } }));
  };
  const toggleDsp = (d) => {
    const next = new Set(form.dsps);next.has(d) ? next.delete(d) : next.add(d);
    set({ dsps: next });
  };

  // Validation per step — most fields are optional. Block CONFIRM only if
  // title, artists, label, and at least one config are missing somewhere.
  const stepValid = (() => {
    if (step === 'match') return true;
    if (step === 'basics') {
      const hasArtist = form.artists && form.artists.some((a) => a.name && a.name.trim()) ||
      !!(form.artist && form.artist.trim());
      return !!form.title && hasArtist;
    }
    if (step === 'details' || step === 'rights' || step === 'tracks' ||
        step === 'art' || step === 'dist') return true;
    if (step === 'confirm') {
      const hasArtist = form.artists && form.artists.some((a) => a.name && a.name.trim()) ||
      !!(form.artist && form.artist.trim());
      return !!form.title && hasArtist && !!form.label && (form.configurations || []).length > 0;
    }
    return true;
  })();

  // Step navigation — 8-step wizard:
  //   MATCH (search to prefill)
  //   BASICS (title, artist, type, configuration)
  //   DETAILS (identifiers, label & rights, dates, classification)
  //   RIGHTS (copyright registrations — Works-style RightsTab)
  //   TRACKS (tracklist picker)
  //   ART (cover art swatches)
  //   DIST (territory + DSP delivery)
  //   CONFIRM (review)
  const STEPS = [
  { k: 'match',   l: '0 · MATCH' },
  { k: 'basics',  l: '1 · BASICS' },
  { k: 'art',     l: '2 · ARTWORK' },
  { k: 'details', l: '3 · DETAILS' },
  { k: 'rights',  l: '4 · RIGHTS' },
  { k: 'tracks',  l: '5 · TRACKS' },
  { k: 'dist',    l: '6 · DIST' },
  { k: 'confirm', l: '7 · CONFIRM' }];

  const idx = STEPS.findIndex((s) => s.k === step);
  const nextStep = () => idx < STEPS.length - 1 ? setStep(STEPS[idx + 1].k) : null;
  const prevStep = () => idx > 0 ? setStep(STEPS[idx - 1].k) : null;

  const finish = () => {
    // Create the parent Release Group + one Edition per selected configuration.
    // The earliest delivered format becomes the "Original" edition; the rest are
    // labeled by format ("CD", "Vinyl LP", etc).
    const yyyy = (form.digitalDate || '').slice(0, 4) || String(new Date().getFullYear());
    const groupId = 'rg_new_' + Date.now().toString(36);
    const group = {
      id: groupId, title: form.title, artist: form.artist,
      label: form.label, marketingLabel: form.marketingLabel || form.label,
      type: form.type, genre: form.genre, subGenre: form.subGenre,
      language: form.language, parental: form.parental,
      pLineYear: parseInt(yyyy, 10), cLineYear: parseInt(yyyy, 10),
      art: form.art, originalDate: form.digitalDate
    };
    if (window.RELEASE_GROUPS) window.RELEASE_GROUPS.unshift(group);

    const formats = form.configurations && form.configurations.length ? form.configurations : ['Digital'];
    const editionLabelFor = (fmt, isFirst) => {
      if (isFirst && fmt === 'Digital') return 'Digital Original';
      if (fmt === 'Vinyl') return 'Vinyl LP';
      if (fmt === 'CD') return 'CD';
      if (fmt === 'Cassette') return 'Cassette';
      if (fmt === 'DVD') return 'DVD';
      return fmt;
    };
    const territoryStr = form.territoryMode === 'world' ? 'Worldwide' :
    (form.territories || []).slice(0, 3).join(' · ') + ((form.territories || []).length > 3 ? ` +${form.territories.length - 3}` : '');
    const dspList = Array.from(form.dsps || []);
    const baseDsps = dspList.length;
    formats.forEach((fmt, i) => {
      const isFirst = i === 0;
      const isDigital = fmt === 'Digital';
      const id = 'rl_new_' + Date.now().toString(36) + '_' + i;
      const ed = {
        id, groupId,
        editionLabel: editionLabelFor(fmt, isFirst),
        upc: (form.upc || '') + (i > 0 ? String(i) : ''),
        title: form.title, artist: form.artist,
        label: form.label, type: form.type,
        format: fmt,
        territory: territoryStr,
        tracks: tracks.length,
        date: form.digitalDate,
        retailPrice: isDigital ? 9.99 : fmt === 'Vinyl' ? 27.99 : fmt === 'CD' ? 14.99 : 9.99,
        pLine: form.pLine || `℗ ${yyyy} ${form.label}`,
        cLine: form.cLine || `© ${yyyy} ${form.label}`,
        status: 'live',
        art: form.art,
        ddex: 'queued',
        // Digital editions go to all DSPs; physical editions to none.
        dsps: isDigital ? baseDsps : 0,
        dspList: isDigital ? dspList : []
      };
      if (window.RELEASES) window.RELEASES.unshift(ed);
    });

    if (window.toast) window.toast(`Release "${form.title}" created · ${formats.length} edition${formats.length > 1 ? 's' : ''} · queued for DDEX delivery`, 'ok');
    close();
  };

  const genreOpts = window.refGenreLabels && window.refGenreLabels() || ['Pop', 'R&B', 'Soul', 'Indie', 'Electronic', 'Hip-Hop', 'Jazz', 'Folk', 'Rock', 'Alternative', 'Country', 'Classical'];
  const langOpts = window.refLanguageOptions && window.refLanguageOptions() || ['English', 'Spanish', 'French', 'Portuguese', 'German', 'Italian', 'Japanese'];
  const labelOpts = (window.LABELS || []).map((l) => l.name);

  return (
    <>
      <div onClick={close} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.42)', zIndex: 80 }} />
      <div role="dialog" aria-modal="true" aria-label="New release" style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
        width: 'min(940px,96vw)', maxHeight: '92vh', display: 'flex', flexDirection: 'column',
        background: 'var(--bg)', border: '1px solid var(--rule)', zIndex: 81,
        boxShadow: '0 30px 80px rgba(0,0,0,.34)' }}>

        {/* HEADER — uses chosen art swatch as a gradient */}
        <div style={{ flex: '0 0 auto', padding: '18px 24px', borderBottom: '1px solid var(--rule)',
          display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 14,
          background: `linear-gradient(90deg, ${form.art || '#f4d34a'} 0%, ${form.art || '#f4d34a'} 6px, var(--bg) 6px)` }}>
          <div>
            <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.14em', color: 'var(--ink-3)', marginBottom: 4 }}>NEW RELEASE</div>
            <div className="ff-display" style={{ fontSize: 26, fontWeight: 700, letterSpacing: '-0.02em', lineHeight: 1.05 }}>
              {form.title ? form.title : 'Untitled release'}
              {form.versionSubtitle && <span style={{ color: 'var(--ink-3)', fontWeight: 400 }}> · {form.versionSubtitle}</span>}
            </div>
            <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 6 }}>
              {form.type} · {tracks.length} track{tracks.length === 1 ? '' : 's'}
              {totalSeconds > 0 && <> · {totalMin}:{String(totalSeconds % 60).padStart(2, '0')}</>}
              {form.artist && <> · {form.artist}</>}
              {form.upc && <> · UPC {form.upc}</>}
            </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 TABS */}
        <div style={{ flex: '0 0 auto', display: 'flex', padding: '8px 18px', gap: 0, borderBottom: '1px solid var(--rule-soft)', background: 'var(--bg-2)' }}>
          {STEPS.map((s) =>
          <button key={s.k} onClick={() => setStep(s.k)}
          className="ff-mono upper" style={{
            fontSize: 9, letterSpacing: '.08em', padding: '8px 7px', marginRight: 2,
            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',
            whiteSpace: 'nowrap' }}>
              {s.l}
            </button>
          )}
        </div>

        {/* BODY (scrollable) */}
        <div style={{ flex: '1 1 auto', overflowY: 'auto' }}>

          {/* ── 0 MATCH ── search + import existing release metadata ── */}
          {step === 'match' && (() => {
            const q = (form.searchTitle || '').trim();
            const qa = (form.searchArtist || '').trim();
            const qu = (form.searchUpc || '').trim();
            const hasQuery = q.length > 1 || qa.length > 1 || qu.length >= 8;

            // ── Internal: search YOUR catalog first ─────────────────
            const allReleases = (window.RELEASES_X || []).concat(window.RELEASES || []);
            // Dedup by id
            const seen = new Set();
            const internalAll = allReleases.filter((r) => {
              if (!r || !r.id || seen.has(r.id)) return false;
              seen.add(r.id);return true;
            });
            const matchScore = (r) => {
              let s = 0;
              if (q && (r.title || '').toLowerCase().includes(q.toLowerCase())) s += 60;
              if (qa && (r.artist || '').toLowerCase().includes(qa.toLowerCase())) s += 30;
              if (qu && r.upc && r.upc.replace(/\D/g, '').includes(qu)) s = 100; // exact UPC overrides
              return s;
            };
            const internalHits = hasQuery ?
            internalAll.
            map((r) => ({ rec: r, score: matchScore(r) })).
            filter((x) => x.score > 0).
            sort((a, b) => b.score - a.score).
            slice(0, 4).
            map((x) => x.rec) :
            [];

            // ── External: mock DSP / metadata source candidates ──
            const candidates = hasQuery ? [
            { src: 'Spotify', id: '5kx1pP…', upc: qu || '886447988104', title: q || 'A Seat at the Table', artist: qa || 'Solange', year: 2016, label: 'Saint / Columbia', tracks: 21, art: '#f4d34a', score: 94 },
            { src: 'MusicBrainz', id: 'mbid:f3a…', upc: qu || '886447988104', title: q || 'A Seat at the Table', artist: qa || 'Solange', year: 2016, label: 'Columbia', tracks: 21, art: '#f4d34a', score: 88 },
            { src: 'Apple Music', id: '1109714933', upc: qu || '886447988104', title: q || 'A Seat at the Table', artist: qa || 'Solange', year: 2016, label: 'Saint Records', tracks: 21, art: '#f4d34a', score: 84 },
            { src: 'Discogs', id: '9412210', upc: qu || '886447988104', title: (q || 'A Seat at the Table') + ' (Vinyl)', artist: qa || 'Solange', year: 2016, label: 'Saint Records', tracks: 21, art: '#3d2614', score: 71 }] :
            [];

            const importCandidate = (c) => {
              const yyyy = c.year || new Date().getFullYear();
              setForm((f) => ({
                ...f,
                importedFrom: { src: c.src, id: c.id },
                title: c.title,
                upc: c.upc,
                label: c.label,
                marketingLabel: c.label,
                artist: c.artist,
                // Pre-fill artists[] with the primary; user can add featured / additional in DETAILS
                artists: [{ name: c.artist, aid: f.artistAid || null, role: 'primary' }],
                pLine: `℗ ${yyyy} ${c.label}`,
                cLine: `© ${yyyy} ${c.label}`,
                originalDate: yyyy + '-01-01',
                art: c.art || f.art,
                type: c.tracks >= 7 ? 'Album' : c.tracks >= 4 ? 'EP' : 'Single'
              }));
              setStep('basics');
              if (window.toast) window.toast(`Imported metadata from ${c.src} · ${c.tracks} tracks pre-filled`, 'ok');
            };

            const openInternal = (r) => {
              // It's already in your catalog — close this and open the existing release drawer
              setOpen(false);
              if (window.toast) window.toast(`"${r.title}" is already in your catalog · opening detail`, 'soft');
              window.dispatchEvent(new CustomEvent('astro-open-release', { detail: { id: r.id } }));
            };

            return (
              <div style={{ padding: '20px 24px' }}>
                <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--ink-3)', paddingBottom: 6, borderBottom: '1px solid var(--rule)', marginBottom: 12, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
                  <span>FIND EXISTING RELEASE</span>
                  <span style={{ color: 'var(--ink-4)', letterSpacing: '.06em' }}>YOUR CATALOG · SPOTIFY · APPLE · MUSICBRAINZ · DISCOGS</span>
                </div>

                <div className="ff-mono" style={{ fontSize: 11, color: 'var(--ink-3)', marginBottom: 14, lineHeight: 1.55 }}>
                  Search by UPC, title or artist. We check your <b style={{ color: 'var(--ink-2)' }}>own catalog</b> first to avoid
                  duplicates, then DSPs and external metadata sources. Pick a match to prefill identifiers,
                  copy lines, dates, and artwork — you can review and edit each step.
                </div>

                <div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr 1fr', gap: 12, marginBottom: 14 }}>
                  <div>
                    <_ArLbl>Release title</_ArLbl>
                    <_ArInp value={form.searchTitle} onChange={(v) => set({ searchTitle: v })} placeholder="A Seat at the Table" autoFocus />
                  </div>
                  <div>
                    <_ArLbl>Primary artist <span style={{ color: 'var(--ink-3)', fontWeight: 400 }}></span></_ArLbl>
                    {window.ArtistTypeahead ?
                    <div style={{ marginBottom: 14 }}>
                        <window.ArtistTypeahead
                        value={form.searchArtist}
                        aid={form.artistAid}
                        onSelect={(o) => set({
                          searchArtist: o.name,
                          artist: o.name,
                          artistAid: o.aid || null,
                          artists: [{ name: o.name, aid: o.aid || null, role: 'primary' }]
                        })} />
                      </div> :

                    <_ArInp value={form.searchArtist} onChange={(v) => set({ searchArtist: v, artist: v, artists: [{ name: v, aid: null, role: 'primary' }] })} placeholder="Solange" />
                    }
                  </div>
                  <div>
                    <_ArLbl>UPC / EAN <span style={{ color: 'var(--ink-3)', fontWeight: 400 }}></span></_ArLbl>
                    <_ArInp value={form.searchUpc} onChange={(v) => set({ searchUpc: (v || '').replace(/\D/g, '').slice(0, 13) })} placeholder="886447988104" mono />
                  </div>
                </div>

                {/* INTERNAL HITS — your catalog */}
                {internalHits.length > 0 &&
                <>
                    <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--accent)', marginBottom: 8, fontWeight: 600, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
                      <span>⚠ ALREADY IN YOUR CATALOG · {internalHits.length}</span>
                      <span style={{ color: 'var(--ink-4)', fontWeight: 400 }}>OPENS DETAIL · NO DUPLICATE</span>
                    </div>
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 14 }}>
                      {internalHits.map((r, i) =>
                    <button key={i} onClick={() => openInternal(r)}
                    style={{ display: 'grid', gridTemplateColumns: '48px 96px 1fr 70px 18px', gap: 14, width: '100%', padding: '12px 14px',
                      border: '1px solid var(--accent)', background: 'rgba(196,162,82,.06)', cursor: 'pointer', textAlign: 'left', alignItems: 'center' }}>
                          <div style={{ width: 48, height: 48, background: r.art || '#1a1a1a', flexShrink: 0 }} />
                          <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.1em', color: 'var(--ink)', background: 'var(--accent)', padding: '4px 8px', justifySelf: 'start', fontWeight: 700 }}>YOUR DB</span>
                          <div style={{ minWidth: 0 }}>
                            <div style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.title}</div>
                            <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 3, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                              {r.artist || '—'} · {r.kind || r.type || 'Release'}{r.upc ? ' · UPC ' + r.upc : ''}{r.date ? ' · ' + r.date : ''}{r.label ? ' · ' + r.label : ''}
                            </div>
                          </div>
                          <span className="ff-mono upper" style={{ fontSize: 9, color: 'var(--ink-2)', textAlign: 'right', letterSpacing: '.08em' }}>OPEN →</span>
                          <span style={{ color: 'var(--ink-3)', fontSize: 14 }}>↗</span>
                        </button>
                    )}
                    </div>
                  </>
                }

                {/* EXTERNAL HITS — DSPs */}
                <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--ink-3)', marginBottom: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
                  <span>EXTERNAL MATCHES{hasQuery && <span style={{ color: 'var(--ink-4)' }}> · {candidates.length} FOUND</span>}</span>
                  {form.importedFrom &&
                  <span style={{ color: 'var(--accent)', fontWeight: 600 }}>IMPORTED · {form.importedFrom.src}</span>
                  }
                </div>

                {!hasQuery ?
                <div className="ff-mono" style={{ padding: '24px 16px', border: '1px dashed var(--rule)',
                  fontSize: 11, color: 'var(--ink-3)', textAlign: 'center', lineHeight: 1.6, marginBottom: 14 }}>
                    Type a title, artist or UPC above to find matching releases<br />
                    in your catalog and across metadata partners.
                  </div> :

                <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 14 }}>
                    {candidates.map((c, i) =>
                  <button key={i} onClick={() => importCandidate(c)}
                  style={{ display: 'grid', gridTemplateColumns: '48px 96px 1fr 60px 18px', gap: 14, width: '100%', padding: '12px 14px',
                    border: '1px solid var(--rule)', background: 'transparent', cursor: 'pointer', textAlign: 'left', alignItems: 'center' }}
                  onMouseEnter={(e) => {e.currentTarget.style.background = 'var(--bg-2)';e.currentTarget.style.borderColor = 'var(--ink-3)';}}
                  onMouseLeave={(e) => {e.currentTarget.style.background = 'transparent';e.currentTarget.style.borderColor = 'var(--rule)';}}>
                        <div style={{ width: 48, height: 48, background: c.art, flexShrink: 0 }} />
                        <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.1em', color: 'var(--ink-2)', background: 'var(--bg-2)', padding: '4px 8px', justifySelf: 'start', fontWeight: 600 }}>{c.src}</span>
                        <div style={{ minWidth: 0 }}>
                          <div style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.title}</div>
                          <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 3, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                            {c.artist} · {c.year} · {c.label} · {c.tracks} tracks · UPC {c.upc} · {c.id}
                          </div>
                        </div>
                        <div className="ff-mono num" style={{ fontSize: 11, textAlign: 'right', color: c.score >= 85 ? 'var(--accent)' : 'var(--ink-2)', fontWeight: c.score >= 85 ? 600 : 400 }}>{c.score}%</div>
                        <span style={{ color: 'var(--ink-3)', fontSize: 14 }}>↓</span>
                      </button>
                  )}
                  </div>
                }

                <button onClick={() => setStep('basics')}
                style={{ display: 'flex', gap: 10, alignItems: 'center', justifyContent: 'center', width: '100%', padding: '12px 14px',
                  border: '1px dashed var(--rule)', background: 'transparent', cursor: 'pointer', color: 'var(--ink-2)', fontSize: 12 }}>
                  <span style={{ color: 'var(--ink-3)' }}>+</span> Skip — enter release details from scratch
                </button>

                {form.importedFrom &&
                <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', padding: '12px 0 0', lineHeight: 1.55 }}>
                    <span style={{ color: 'var(--accent)', fontWeight: 600 }}>✓</span> Metadata pre-filled from {form.importedFrom.src}.
                    Continue to DETAILS to review and edit, or pick a different source above to re-import.
                  </div>
                }
              </div>);

          })()}

          {/* ── BASICS / DETAILS / TRACKS / ART / DIST ── pane in wizard mode ── */}
          {(step === 'basics' || step === 'details' ||
            step === 'tracks' || step === 'art' || step === 'dist') &&
            window.ReleaseEditionEditPane &&
            <window.ReleaseEditionEditPane
              form={form}
              set={set}
              visibleStep={step}
              AR={{
                Lbl: _ArLbl, Inp: _ArInp, Seg: _ArSeg,
                ArtistsField: _ArArtistsField, TrackRow: _ArTrackRow, Picker: _ArPicker,
                ART_SWATCHES: _AR_ART_SWATCHES, DSPS: _AR_DSPS,
                releaseTypes: _arReleaseTypes, configs: _arConfigs, parentalOpts: _arParentalOpts,
                creditLine: _arCreditLine,
                tracks, totalSeconds, totalMin, dspCount,
                removeTrack, moveTrack, togglePromo, updateTrack, addTrackFromRec, openCreateRecording, toggleDsp,
                genreOpts, langOpts
              }}
            />
          }

          {/* ── 4 RIGHTS ── Works RightsTab — copyright registrations ── */}
          {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>
          }

          {/* ── 7 CONFIRM ── */}
          {step === 'confirm' &&
          <div style={{ padding: '20px 24px' }}>
              <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--ink-3)', marginBottom: 10 }}>REVIEW NEW RELEASE</div>
              <div style={{ border: '1px solid var(--rule)', padding: '18px 20px', background: 'var(--bg-2)', marginBottom: 14 }}>
                <div style={{ display: 'flex', gap: 18, alignItems: 'flex-start' }}>
                  <div style={{ width: 96, height: 96, background: form.art, flexShrink: 0,
                  display: 'flex', alignItems: 'flex-end', padding: 8,
                  color: form.art === '#f0f0f0' || form.art === '#d4a02a' || form.art === '#f4d34a' ? '#0b0b0b' : 'rgba(255,255,255,.85)' }}>
                    <span className="ff-display" style={{ fontSize: 14, fontWeight: 700, letterSpacing: '-0.03em', lineHeight: .95, opacity: .85 }}>{form.title || 'Untitled'}</span>
                  </div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div className="ff-display" style={{ fontSize: 24, fontWeight: 700, letterSpacing: '-0.02em' }}>
                      {form.title || '— untitled —'}
                      {form.versionSubtitle && <span style={{ color: 'var(--ink-3)', fontWeight: 400, fontSize: 16 }}> · {form.versionSubtitle}</span>}
                    </div>
                    <div className="ff-mono" style={{ fontSize: 11, color: 'var(--ink-2)', marginTop: 4 }}>{form.artist || '—'}</div>
                    <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 6, lineHeight: 1.7 }}>
                      {form.type} · {tracks.length} tracks · {totalMin}:{String(totalSeconds % 60).padStart(2, '0')} · {form.label || 'no label'}<br />
                      {(form.configurations || []).join(' · ') || 'no config'} · UPC {form.upc || '— TBA'} · CAT {form.catalog || '—'}<br />
                      {form.pLine} · {form.cLine}<br />
                      {form.genre || 'no genre'} · {form.language} · {form.parental === 'Explicit' ? 'EXPLICIT' : form.parental === 'ExplicitContentEdited' ? 'EDITED' : 'CLEAN'}
                    </div>
                  </div>
                </div>
              </div>

              {/* Tracklist preview */}
              <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--ink-3)', marginBottom: 8 }}>TRACKLIST · {tracks.length}</div>
              <div style={{ border: '1px solid var(--rule-soft)', marginBottom: 14, maxHeight: 160, overflowY: 'auto' }}>
                {tracks.length === 0 ?
              <div style={{ padding: '14px 14px', color: 'var(--ink-3)', fontSize: 12, fontStyle: 'italic' }}>No tracks linked. Go back to step 3 to add at least one recording.</div> :
              tracks.map((t, i) => {
                const REC = (window.REC_BY_ID || {})[t.recId] || {};
                const dur = t.duration || REC.duration || 0;
                return (
                  <div key={i} style={{ display: 'grid', gridTemplateColumns: '24px 14px 1fr 60px', gap: 8, padding: '7px 12px', borderBottom: i < tracks.length - 1 ? '1px solid var(--rule-soft)' : 'none', alignItems: 'center' }}>
                      <span className="ff-mono num" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{String(i + 1).padStart(2, '0')}</span>
                      <span style={{ color: t.promo ? 'var(--accent)' : 'transparent', fontSize: 11, fontWeight: 700 }}>★</span>
                      <span style={{ fontSize: 12, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{t.title || REC.title}</span>
                      <span className="ff-mono num" style={{ fontSize: 10, color: 'var(--ink-3)', textAlign: 'right' }}>
                        {Math.floor(dur / 60)}:{String(dur % 60).padStart(2, '0')}
                      </span>
                    </div>);

              })}
              </div>

              {/* Distribution */}
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
                <div>
                  <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--ink-3)', marginBottom: 6 }}>TERRITORIES</div>
                  <div className="ff-mono" style={{ fontSize: 11 }}>
                    {form.territoryMode === 'world' ? 'Worldwide' :
                  form.territoryMode === 'include' ? 'Only: ' + (form.territories.join(', ') || '—') :
                  'World except: ' + (form.territories.join(', ') || '—')}
                  </div>
                </div>
                <div>
                  <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--ink-3)', marginBottom: 6 }}>DELIVERY · {dspCount} DSPs · {form.deliveryDate}</div>
                  <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-2)', lineHeight: 1.55 }}>
                    {[...form.dsps].slice(0, 8).join(', ')}{dspCount > 8 ? ` … (+${dspCount - 8})` : ''}
                    {(form.hiRes || form.atmos) && <div style={{ marginTop: 4, color: 'var(--accent)', fontWeight: 600 }}>{form.hiRes && 'Hi-Res'} {form.atmos && '· Atmos'}</div>}
                  </div>
                </div>
              </div>
            </div>
          }

        </div>

        {/* FOOTER NAV */}
        <div style={{ flex: '0 0 auto', padding: '14px 24px', borderTop: '1px solid var(--rule)',
          display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10, background: 'var(--bg)' }}>
          <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>
            Step {idx + 1} of {STEPS.length}
            {!stepValid && <span style={{ color: 'var(--danger)', marginLeft: 10, fontWeight: 600 }}>· complete required fields to advance</span>}
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <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>
            {idx > 0 &&
            <button onClick={prevStep} 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={nextStep} disabled={!stepValid} className="ff-mono upper"
            style={{ padding: '8px 16px', fontSize: 11, letterSpacing: '.08em',
              background: stepValid ? 'var(--ink)' : 'var(--ink-4)',
              color: 'var(--bg)', border: 0, cursor: stepValid ? 'pointer' : 'not-allowed', fontWeight: 600 }}>
                CONTINUE →
              </button> :

            <button onClick={finish} disabled={tracks.length === 0} className="ff-mono upper"
            style={{ padding: '8px 18px', fontSize: 11, letterSpacing: '.08em',
              background: tracks.length > 0 ? 'var(--ink)' : 'var(--ink-4)',
              color: 'var(--bg)', border: 0, cursor: tracks.length > 0 ? 'pointer' : 'not-allowed', fontWeight: 600 }}>
                CREATE RELEASE →
              </button>
            }
          </div>
        </div>

      </div>
    </>);

}

Object.assign(window, { GlobalAddReleaseModal });