// releases-id.jsx — Releases identifier-health screen.
// Operational layer over RELEASE_GROUPS / RELEASES that issues, validates,
// deduplicates, and pushes UPC / EAN / GRid / catalog # / ICPN identifiers
// to DSPs with full audit trail and lifecycle.
//
// Tabs: Audit · Prefix blocks · Conflicts · Lifecycle · DDEX queue
// EXPORT: window.ScreenReleasesId
// ============================================================================
(() => {
  const { useState, useMemo, useEffect, useRef } = React;
  const useTweaks = window.useTweaks || ((d) => useState(d));
  const TweaksPanel = window.TweaksPanel;
  const TweakSection = window.TweakSection;
  const TweakRadio = window.TweakRadio;
  const TweakToggle = window.TweakToggle;
  const TweakSelect = window.TweakSelect;
  const Ic = window.Ic || {};

  // ── id math ─────────────────────────────────────────────────────────
  // GS1 mod-10: weights alternate 3/1 from rightmost data digit (UPC-A 12d, EAN-13 13d).
  function gs1CheckDigit(digits) {
    // digits: string of N-1 numeric chars (UPC: 11, EAN: 12)
    const arr = String(digits).split('').map(Number);
    let sum = 0;
    // last data digit gets weight 3, then alternating
    for (let i = 0; i < arr.length; i++) {
      const fromRight = arr.length - 1 - i;
      sum += arr[i] * (fromRight % 2 === 0 ? 3 : 1);
    }
    return (10 - (sum % 10)) % 10;
  }
  function isValidUPC(upc) {
    if (!upc) return false;
    const s = String(upc).replace(/\D/g, '');
    if (s.length !== 12) return false;
    return gs1CheckDigit(s.slice(0, 11)) === Number(s[11]);
  }
  function isValidEAN13(ean) {
    if (!ean) return false;
    const s = String(ean).replace(/\D/g, '');
    if (s.length !== 13) return false;
    return gs1CheckDigit(s.slice(0, 12)) === Number(s[12]);
  }
  function classifyBarcode(code) {
    const s = String(code || '').replace(/\D/g, '');
    if (s.length === 12) return { kind: 'UPC-A', valid: isValidUPC(s), normalized: s };
    if (s.length === 13) return { kind: 'EAN-13', valid: isValidEAN13(s), normalized: s };
    if (s.length === 11) return { kind: 'UPC-A (no check)', valid: false, normalized: s + gs1CheckDigit(s) };
    return { kind: 'unknown', valid: false, normalized: s };
  }
  // GRid format: A1-XXXXX-NNNNNNNNNN-C (18 chars no hyphens)
  // identifier scheme '-' issuer '-' release ref '-' check
  // Check char: ISO/IEC 7064 mod-37,2 over the 17 preceding alnum chars.
  function gridCheckChar(seventeen) {
    const ALPHA = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    let p = 36;
    for (let i = 0; i < 17; i++) {
      const c = seventeen[i].toUpperCase();
      const v = ALPHA.indexOf(c);
      if (v < 0) return null;
      let s = p + v;
      if (s > 36) s -= 36;
      let d = s * 2;
      if (d >= 37) d -= 37;
      p = d;
    }
    let check = 37 - p;
    if (check === 36) check = 0;
    return ALPHA[check];
  }
  function isValidGRid(grid) {
    if (!grid) return false;
    const s = String(grid).replace(/-/g, '').toUpperCase();
    if (s.length !== 18) return false;
    if (!s.startsWith('A1')) return false;
    if (!/^[0-9A-Z]+$/.test(s)) return false;
    const expected = gridCheckChar(s.slice(0, 17));
    return expected === s[17];
  }
  function formatGrid(s) {
    const c = String(s || '').replace(/-/g, '').toUpperCase();
    if (c.length !== 18) return s;
    return `${c.slice(0, 2)}-${c.slice(2, 7)}-${c.slice(7, 17)}-${c.slice(17)}`;
  }
  function isValidISRC(isrc) {
    if (!isrc) return false;
    const s = String(isrc).replace(/-/g, '').toUpperCase();
    return /^[A-Z]{2}[A-Z0-9]{3}\d{7}$/.test(s);
  }

  // ── prefix blocks (GS1 company prefixes the label owns) ────────────
  // Each block: prefix (digits), capacity = 10^(11 - prefix.length),
  // a watermark of how many we've issued, and a notes/ledger of holes.
  const SEED_BLOCKS = [
    { id: 'pb_8864',  prefix: '886447',     owner: 'Saint / Columbia',          tier: 'distributor', issued: 142,  reserved: 8,  hole: [], note: 'Allocated by Sony' },
    { id: 'pb_5054',  prefix: '5054429',    owner: 'Ninja Tune',                tier: 'label',       issued: 86,   reserved: 4,  hole: ['5054429143970'], note: '' },
    { id: 'pb_1962',  prefix: '196292',     owner: 'Perpetual Novice',          tier: 'distributor', issued: 41,   reserved: 12, hole: [], note: 'Cherry-picked range' },
    { id: 'pb_5060',  prefix: '5060766',    owner: 'Domino',                    tier: 'label',       issued: 57,   reserved: 3,  hole: [], note: '' },
    { id: 'pb_1914',  prefix: '191404',     owner: '4AD',                       tier: 'label',       issued: 33,   reserved: 0,  hole: [], note: 'Block exhausted in Q3' },
    { id: 'pb_1969',  prefix: '196925',     owner: 'RCA',                       tier: 'distributor', issued: 312,  reserved: 22, hole: ['196925100231'], note: '' },
    { id: 'pb_1903',  prefix: '190296',     owner: 'Godmode',                   tier: 'label',       issued: 12,   reserved: 0,  hole: [], note: '' },
    { id: 'pb_5060_854', prefix: '5060854', owner: 'Forever Living Originals',  tier: 'label',       issued: 28,   reserved: 6,  hole: [], note: '' },
    { id: 'pb_4988',  prefix: '4988001',    owner: 'Sony Japan',                tier: 'distributor', issued: 4203, reserved: 0,  hole: [], note: 'JP territory' },
    { id: 'pb_6349',  prefix: '634904',     owner: 'XL Recordings',             tier: 'label',       issued: 188,  reserved: 9,  hole: [], note: '' },
    { id: 'pb_1849',  prefix: '184923',     owner: 'Mexican Summer',            tier: 'label',       issued: 22,   reserved: 1,  hole: [], note: '' },
    { id: 'pb_0755',  prefix: '075597',     owner: 'Nonesuch',                  tier: 'label',       issued: 1611, reserved: 0,  hole: [], note: '' },
  ];
  // GRid issuer codes — a label/aggregator owns one
  const SEED_GRID_ISSUERS = [
    { id: 'gi_a1qz', code: 'A1QZ8', owner: 'Saint / Columbia',  tier: 'distributor', issued: 89 },
    { id: 'gi_a1k3', code: 'A1K3M', owner: 'Ninja Tune',        tier: 'label',       issued: 41 },
    { id: 'gi_a1bx', code: 'A1BX2', owner: 'XL Recordings',     tier: 'label',       issued: 73 },
    { id: 'gi_a1nv', code: 'A1NV7', owner: 'Domino',            tier: 'label',       issued: 22 },
    { id: 'gi_a1rc', code: 'A1RC9', owner: 'RCA',               tier: 'distributor', issued: 218 },
    { id: 'gi_a1mx', code: 'A1MX4', owner: 'Mexican Summer',    tier: 'label',       issued: 14 },
  ];
  function blockCapacity(prefix) {
    return Math.pow(10, 11 - String(prefix).length);
  }
  function blockUtilization(b) {
    return ((b.issued + b.reserved) / blockCapacity(b.prefix));
  }
  function nextUPCFromPrefix(prefix, issued) {
    const remaining = 11 - prefix.length;
    const seq = String(issued + 1).padStart(remaining, '0');
    const eleven = prefix + seq;
    return eleven + gs1CheckDigit(eleven);
  }
  function nextGridFromIssuer(code, issued) {
    const seq = String(issued + 1).padStart(10, '0');
    const seventeen = 'A1' + code.slice(2, 7) + seq;
    const c = gridCheckChar(seventeen);
    return seventeen + c;
  }

  // ── enrich every release with id-health derived state ──────────────
  // We store a parallel index keyed by release id with extra ops fields
  // (status, lifecycle, last DSP push, source block) — without mutating the
  // upstream RELEASES catalog.
  const STATUSES = [
    { v: 'reserved',     label: 'Reserved',    tone: '#7a7a7a' },
    { v: 'provisional',  label: 'Provisional', tone: '#c79538' },
    { v: 'locked',       label: 'Locked',      tone: '#3a8a52' },
    { v: 'conflict',     label: 'Conflict',    tone: '#a04432' },
    { v: 'withdrawn',    label: 'Withdrawn',   tone: '#5a5a5a' },
    { v: 'variant',      label: 'Variant',     tone: '#5a8aa0' },
  ];
  const STATUS_BY = Object.fromEntries(STATUSES.map(s => [s.v, s]));

  function blockForUPC(blocks, upc) {
    const s = String(upc || '').replace(/\D/g, '').slice(0, 11);
    return blocks.find(b => s.startsWith(b.prefix)) || null;
  }

  function deriveOps(rel, blocks, allRels) {
    // Lifecycle status — derived from data + a tiny synthetic spread so the
    // table looks like real life, where most are locked, some provisional.
    const upc = rel.upc;
    const upcMeta = classifyBarcode(upc);
    const block = blockForUPC(blocks, upc);
    const dupe = allRels.filter(r => r.upc === upc && r.id !== rel.id).map(r => r.id);

    // Pseudo-stable hash for variation
    let h = 0;
    for (const c of String(rel.id)) h = (h * 31 + c.charCodeAt(0)) | 0;
    h = Math.abs(h);

    let status;
    if (dupe.length) status = 'conflict';
    else if (!upcMeta.valid) status = 'conflict';
    else if (h % 23 === 0) status = 'withdrawn';
    else if (h % 13 === 0) status = 'reserved';
    else if (h % 7 === 0) status = 'provisional';
    else if ((rel.editionLabel || '').match(/Remix|Deluxe|Japan|Variant/i)) status = 'variant';
    else status = 'locked';

    // ICPN: industry alias for the same UPC/EAN — same value, different label.
    const icpn = upc;
    // Catalog number — synthesize from label + sequence.
    const labelCode = (rel.label || '').replace(/\W+/g, '').slice(0, 3).toUpperCase() || 'CAT';
    const catNo = `${labelCode}-${String((h % 9000) + 1000)}`;
    // GRid for digital-eligible releases (Digital, EP/Album)
    const gridIssuer = SEED_GRID_ISSUERS.find(i => i.owner === rel.label);
    let grid = null;
    if (gridIssuer && rel.format === 'Digital') {
      grid = formatGrid(nextGridFromIssuer(gridIssuer.code, (h % 9999)));
    }
    // DSP push state
    const DSPS = ['Spotify', 'Apple', 'Amazon', 'YouTube', 'Tidal', 'Deezer'];
    const pushed = DSPS.filter((_, i) => ((h >> i) & 1) === 1);
    const lastPush = (status === 'locked' || status === 'variant')
      ? new Date(Date.now() - (h % 90) * 86400000).toISOString().slice(0, 10)
      : null;

    // ISRC presence (we do not have track-level ISRCs, so simulate)
    const trackCount = rel.tracks || (rel.type === 'Single' ? 1 : (rel.type === 'EP' ? 5 : 12));
    const isrcMissing = (h % 11 === 0) ? Math.min(trackCount, (h % 4) + 1) : 0;

    // health score 0-100
    let score = 100;
    if (!upcMeta.valid) score -= 35;
    if (dupe.length) score -= 25;
    if (!grid && rel.format === 'Digital') score -= 8;
    if (isrcMissing) score -= Math.min(20, isrcMissing * 4);
    if (status === 'provisional') score -= 6;
    if (status === 'reserved') score -= 12;
    score = Math.max(0, score);

    return {
      upc, upcMeta, block, dupe, status, icpn, catNo, grid, gridIssuer,
      pushed, lastPush, trackCount, isrcMissing, score,
    };
  }

  // ── chrome bits ─────────────────────────────────────────────────────
  function StatusPill({ status }) {
    const s = STATUS_BY[status] || STATUS_BY.locked;
    return (
      <span className="ff-mono upper" style={{
        fontSize: 9, letterSpacing: '.08em', fontWeight: 600,
        padding: '3px 7px', borderRadius: 2,
        color: s.tone, border: `1px solid ${s.tone}33`, background: `${s.tone}10`,
      }}>{s.label}</span>
    );
  }
  function ScoreRing({ value, size = 28 }) {
    const r = (size - 4) / 2;
    const c = 2 * Math.PI * r;
    const dash = (value / 100) * c;
    const tone = value >= 90 ? '#3a8a52' : value >= 70 ? '#c79538' : '#a04432';
    return (
      <svg width={size} height={size} style={{ display: 'block' }}>
        <circle cx={size / 2} cy={size / 2} r={r} stroke="var(--rule)" strokeWidth="2" fill="none" />
        <circle cx={size / 2} cy={size / 2} r={r} stroke={tone} strokeWidth="2" fill="none"
          strokeDasharray={`${dash} ${c}`} transform={`rotate(-90 ${size / 2} ${size / 2})`} strokeLinecap="round" />
        <text x={size / 2} y={size / 2 + 3} textAnchor="middle" fontSize="9" fill="var(--ink-1)" fontWeight="600" fontFamily="ui-monospace,monospace">{value}</text>
      </svg>
    );
  }
  function CopyMono({ value, dim }) {
    const [hit, setHit] = useState(false);
    if (!value) return <span className="ff-mono" style={{ color: 'var(--ink-3)', fontSize: 11 }}>—</span>;
    return (
      <span
        className="ff-mono"
        title="Click to copy"
        onClick={(e) => { e.stopPropagation(); navigator.clipboard?.writeText(value); setHit(true); setTimeout(() => setHit(false), 700); }}
        style={{ fontSize: 11, color: dim ? 'var(--ink-3)' : 'var(--ink-1)', cursor: 'pointer', borderBottom: hit ? '1px solid #3a8a52' : '1px dashed transparent' }}>
        {hit ? '✓ copied' : value}
      </span>
    );
  }

  // ── audit list ──────────────────────────────────────────────────────
  function AuditList({ rows, onPick, selectedId, sort, setSort, density }) {
    const headerRow = density === 'compact' ? '28px 1fr 110px 120px 92px 64px 80px 70px 70px 56px' : '32px 1.4fr 130px 140px 100px 70px 90px 84px 84px 60px';
    const cellH = density === 'compact' ? 38 : 50;
    const cols = [
      { k: 'score',   l: '' },
      { k: 'title',   l: 'Title / artist' },
      { k: 'upc',     l: 'UPC' },
      { k: 'grid',    l: 'GRid' },
      { k: 'cat',     l: 'Cat #' },
      { k: 'tracks',  l: 'Trks' },
      { k: 'block',   l: 'Block' },
      { k: 'pushed',  l: 'DSPs' },
      { k: 'date',    l: 'Last push' },
      { k: 'status',  l: 'State' },
    ];
    function head(k, l) {
      const active = sort.k === k;
      return (
        <button key={k} onClick={() => setSort(s => ({ k, dir: s.k === k && s.dir === 'asc' ? 'desc' : 'asc' }))}
          className="ff-mono upper" style={{
            background: 'transparent', border: 0, padding: 0, fontSize: 9, letterSpacing: '.08em',
            color: active ? 'var(--ink-1)' : 'var(--ink-3)', textAlign: 'left', cursor: 'pointer', fontWeight: active ? 600 : 500,
          }}>
          {l}{active && <span style={{ marginLeft: 4 }}>{sort.dir === 'asc' ? '↑' : '↓'}</span>}
        </button>
      );
    }
    return (
      <div style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)' }}>
        <div style={{ display: 'grid', gridTemplateColumns: headerRow, gap: 8, padding: '10px 14px', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center', position: 'sticky', top: 0, background: 'var(--paper)', zIndex: 1 }}>
          {cols.map(c => head(c.k, c.l))}
        </div>
        <div>
          {rows.map(r => {
            const isSel = selectedId === r.id;
            const ops = r._ops;
            const hasIssue = ops.dupe.length || !ops.upcMeta.valid || ops.isrcMissing;
            return (
              <div key={r.id} onClick={() => onPick(r.id)}
                style={{
                  display: 'grid', gridTemplateColumns: headerRow, gap: 8, padding: '0 14px',
                  height: cellH, alignItems: 'center', borderBottom: '1px solid var(--rule-soft)',
                  background: isSel ? 'var(--surface-2)' : 'transparent',
                  cursor: 'pointer', position: 'relative',
                }}
                onMouseEnter={(e) => { if (!isSel) e.currentTarget.style.background = 'var(--surface-2)'; }}
                onMouseLeave={(e) => { if (!isSel) e.currentTarget.style.background = 'transparent'; }}>
                {hasIssue && <span style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: 2, background: ops.dupe.length || !ops.upcMeta.valid ? '#a04432' : '#c79538' }} />}
                <ScoreRing value={ops.score} size={26} />
                <div style={{ overflow: 'hidden' }}>
                  <div style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.title}</div>
                  <div style={{ fontSize: 11, color: 'var(--ink-3)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    {r.artist} · <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.06em' }}>{r.editionLabel || r.format}</span>
                  </div>
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
                  <CopyMono value={ops.upc} />
                  {!ops.upcMeta.valid && <span title="Invalid check digit" style={{ color: '#a04432', fontSize: 11 }}>⚠</span>}
                  {ops.dupe.length > 0 && <span title="Duplicate" style={{ color: '#a04432', fontSize: 11 }}>×{ops.dupe.length + 1}</span>}
                </div>
                <CopyMono value={ops.grid} dim={!ops.grid} />
                <CopyMono value={ops.catNo} dim />
                <span className="ff-mono" style={{ fontSize: 11, color: ops.isrcMissing ? '#a04432' : 'var(--ink-2)' }}>
                  {ops.trackCount}{ops.isrcMissing ? ` −${ops.isrcMissing}` : ''}
                </span>
                <span className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                  {ops.block ? ops.block.prefix : '—'}
                </span>
                <div style={{ display: 'flex', gap: 2 }}>
                  {['S', 'A', 'Z', 'Y', 'T', 'D'].map((ch, i) => {
                    const n = ['Spotify', 'Apple', 'Amazon', 'YouTube', 'Tidal', 'Deezer'][i];
                    const on = ops.pushed.includes(n);
                    return (
                      <span key={ch} title={n} className="ff-mono"
                        style={{
                          width: 12, height: 12, fontSize: 8, lineHeight: '12px', textAlign: 'center', fontWeight: 600,
                          background: on ? '#3a8a52' : 'transparent',
                          color: on ? '#fff' : 'var(--ink-3)',
                          border: on ? 0 : '1px solid var(--rule)',
                        }}>{ch}</span>
                    );
                  })}
                </div>
                <span className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{ops.lastPush || '—'}</span>
                <StatusPill status={ops.status} />
              </div>
            );
          })}
          {rows.length === 0 && (
            <div style={{ padding: '40px 20px', textAlign: 'center', color: 'var(--ink-3)', fontSize: 13 }}>No releases match these filters.</div>
          )}
        </div>
      </div>
    );
  }

  // ── detail panel ────────────────────────────────────────────────────
  function DetailPanel({ rel, onClose, onAction, allRels }) {
    if (!rel) return null;
    const ops = rel._ops;
    const dupes = ops.dupe.map(id => allRels.find(r => r.id === id)).filter(Boolean);
    const dspList = ['Spotify', 'Apple', 'Amazon Music', 'YouTube Music', 'Tidal', 'Deezer'];

    function Row({ label, children, mono }) {
      return (
        <div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 12, padding: '8px 0', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center' }}>
          <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>{label}</span>
          <div className={mono ? 'ff-mono' : ''} style={{ fontSize: mono ? 12 : 13 }}>{children}</div>
        </div>
      );
    }

    return (
      <aside style={{
        position: 'fixed', top: 0, right: 0, bottom: 0, width: 480, background: 'var(--paper)',
        borderLeft: '1px solid var(--rule)', boxShadow: '-12px 0 32px rgba(0,0,0,.06)', zIndex: 60,
        display: 'flex', flexDirection: 'column',
      }}>
        <header style={{ padding: '16px 20px', borderBottom: '1px solid var(--rule)', display: 'flex', alignItems: 'flex-start', gap: 12 }}>
          <div style={{ width: 40, height: 40, background: rel.art || 'var(--surface-2)', borderRadius: 2, flexShrink: 0 }} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 15, fontWeight: 600, lineHeight: 1.2 }}>{rel.title}</div>
            <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 2 }}>{rel.artist} · <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.06em' }}>{rel.editionLabel || rel.format}</span></div>
          </div>
          <button onClick={onClose} className="ff-mono" style={{ background: 'transparent', border: 0, color: 'var(--ink-3)', cursor: 'pointer', fontSize: 14, padding: 4 }}>×</button>
        </header>

        <div style={{ flex: 1, overflowY: 'auto', padding: '8px 20px' }}>
          {/* Health */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px 0', borderBottom: '1px solid var(--rule-soft)' }}>
            <ScoreRing value={ops.score} size={48} />
            <div style={{ flex: 1 }}>
              <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', marginBottom: 4 }}>ID HEALTH</div>
              <StatusPill status={ops.status} />
            </div>
          </div>

          {/* IDs */}
          <div style={{ paddingTop: 8 }}>
            <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', padding: '12px 0 4px' }}>IDENTIFIERS</div>
            <Row label="UPC-A" mono>
              <span>{ops.upc} </span>
              {ops.upcMeta.valid
                ? <span style={{ color: '#3a8a52', marginLeft: 6, fontSize: 11 }}>✓ check digit</span>
                : <span style={{ color: '#a04432', marginLeft: 6, fontSize: 11 }}>✗ check digit</span>}
            </Row>
            <Row label="EAN-13" mono>
              {ops.upcMeta.kind === 'EAN-13' ? ops.upc : `0${ops.upc}`}
            </Row>
            <Row label="ICPN" mono>{ops.icpn}</Row>
            <Row label="GRid" mono>{ops.grid || <span style={{ color: 'var(--ink-3)' }}>not issued</span>}</Row>
            <Row label="Catalog #" mono>{ops.catNo}</Row>
            <Row label="Tracks / ISRC" mono>
              {ops.trackCount} tracks
              {ops.isrcMissing
                ? <span style={{ color: '#a04432', marginLeft: 8 }}>{ops.isrcMissing} missing ISRC</span>
                : <span style={{ color: '#3a8a52', marginLeft: 8 }}>✓ all assigned</span>}
            </Row>
          </div>

          {/* Source block */}
          {ops.block && (
            <div style={{ paddingTop: 8 }}>
              <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', padding: '12px 0 4px' }}>PREFIX BLOCK</div>
              <Row label="Prefix" mono>{ops.block.prefix}</Row>
              <Row label="Owner">{ops.block.owner}</Row>
              <Row label="Tier" mono><span className="upper" style={{ fontSize: 10, letterSpacing: '.06em' }}>{ops.block.tier}</span></Row>
              <Row label="Utilization">
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <div style={{ flex: 1, height: 6, background: 'var(--surface-2)' }}>
                    <div style={{ width: `${blockUtilization(ops.block) * 100}%`, height: '100%', background: blockUtilization(ops.block) > 0.85 ? '#a04432' : '#3a8a52' }} />
                  </div>
                  <span className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{(blockUtilization(ops.block) * 100).toFixed(2)}%</span>
                </div>
              </Row>
            </div>
          )}

          {/* Conflicts */}
          {dupes.length > 0 && (
            <div style={{ paddingTop: 8 }}>
              <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: '#a04432', padding: '12px 0 4px' }}>⚠ CONFLICTS · {dupes.length}</div>
              {dupes.map(d => (
                <div key={d.id} style={{ padding: '8px 10px', border: '1px solid #a0443233', background: '#a0443210', marginBottom: 6, fontSize: 12 }}>
                  <div style={{ fontWeight: 500 }}>{d.title}</div>
                  <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{d.artist} · {d.editionLabel || d.format}</div>
                </div>
              ))}
            </div>
          )}

          {/* DSP push state */}
          <div style={{ paddingTop: 8 }}>
            <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', padding: '12px 0 4px' }}>DSP PUSH STATE</div>
            {dspList.map(d => {
              const on = ops.pushed.includes(d.split(' ')[0]);
              return (
                <div key={d} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 0', borderBottom: '1px solid var(--rule-soft)' }}>
                  <span style={{ fontSize: 12 }}>{d}</span>
                  {on
                    ? <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: '#3a8a52', fontWeight: 600 }}>✓ DELIVERED · {ops.lastPush}</span>
                    : <span className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>not delivered</span>}
                </div>
              );
            })}
          </div>

          {/* Actions */}
          <div style={{ padding: '20px 0' }}>
            <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', padding: '4px 0 8px' }}>ACTIONS</div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
              <ActionBtn onClick={() => onAction('lock', rel.id)}>Lock identifiers</ActionBtn>
              <ActionBtn onClick={() => onAction('reissue', rel.id)}>Reissue UPC</ActionBtn>
              <ActionBtn onClick={() => onAction('grid', rel.id)} disabled={!!ops.grid}>{ops.grid ? 'GRid issued' : 'Issue GRid'}</ActionBtn>
              <ActionBtn onClick={() => onAction('variant', rel.id)}>Issue variant</ActionBtn>
              <ActionBtn onClick={() => onAction('push', rel.id)}>Push to DDEX</ActionBtn>
              <ActionBtn onClick={() => onAction('withdraw', rel.id)} danger>Withdraw</ActionBtn>
            </div>
          </div>
        </div>
      </aside>
    );
  }

  function ActionBtn({ children, onClick, danger, disabled }) {
    return (
      <button onClick={onClick} disabled={disabled} className="ff-mono upper"
        style={{
          fontSize: 10, letterSpacing: '.06em', fontWeight: 600,
          padding: '8px 10px',
          background: disabled ? 'var(--surface-2)' : 'transparent',
          color: disabled ? 'var(--ink-3)' : (danger ? '#a04432' : 'var(--ink-1)'),
          border: `1px solid ${danger ? '#a0443244' : 'var(--rule)'}`,
          cursor: disabled ? 'not-allowed' : 'pointer',
          borderRadius: 0, textAlign: 'center',
        }}>{children}</button>
    );
  }

  // ── Prefix-blocks tab ───────────────────────────────────────────────
  function BlocksTab({ blocks, setBlocks }) {
    const [adding, setAdding] = useState(false);
    const [newPrefix, setNewPrefix] = useState('');
    const [newOwner, setNewOwner] = useState('');

    function add() {
      if (!/^\d{6,11}$/.test(newPrefix)) { window.toast?.('Prefix must be 6-11 digits', 'soft'); return; }
      if (!newOwner.trim()) return;
      setBlocks(bs => [...bs, { id: 'pb_' + Date.now(), prefix: newPrefix, owner: newOwner.trim(), tier: 'label', issued: 0, reserved: 0, hole: [], note: 'New block' }]);
      setAdding(false); setNewPrefix(''); setNewOwner('');
    }

    return (
      <div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 14, fontWeight: 500 }}>GS1 prefix blocks</div>
            <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>UPC capacity allocated to imprints. Sequence advances per release.</div>
          </div>
          <button onClick={() => setAdding(a => !a)} className="ff-mono upper" style={{
            fontSize: 10, letterSpacing: '.08em', padding: '7px 12px', border: '1px solid var(--rule)',
            background: adding ? 'var(--ink-1)' : 'transparent', color: adding ? '#fff' : 'var(--ink-1)',
            cursor: 'pointer', fontWeight: 600,
          }}>+ ADD BLOCK</button>
        </div>

        {adding && (
          <div style={{ display: 'flex', gap: 8, padding: 12, border: '1px solid var(--rule-soft)', background: 'var(--surface-2)', marginBottom: 14, alignItems: 'center' }}>
            <input value={newPrefix} onChange={e => setNewPrefix(e.target.value)} placeholder="Prefix (6-11 digits)" className="ff-mono"
              style={{ flex: '0 0 180px', padding: '7px 10px', border: '1px solid var(--rule)', background: 'var(--paper)', fontSize: 12 }} />
            <input value={newOwner} onChange={e => setNewOwner(e.target.value)} placeholder="Owner / imprint"
              style={{ flex: 1, padding: '7px 10px', border: '1px solid var(--rule)', background: 'var(--paper)', fontSize: 12 }} />
            <button onClick={add} className="ff-mono upper" style={{ fontSize: 10, letterSpacing: '.08em', padding: '7px 12px', border: 0, background: 'var(--ink-1)', color: '#fff', cursor: 'pointer', fontWeight: 600 }}>SAVE</button>
          </div>
        )}

        <div style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '120px 1.4fr 90px 80px 80px 1fr 100px', gap: 10, padding: '10px 14px', borderBottom: '1px solid var(--rule-soft)' }} className="ff-mono upper">
            {['Prefix', 'Owner', 'Tier', 'Issued', 'Reserved', 'Utilization', 'Next UPC'].map(l => (
              <span key={l} style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>{l}</span>
            ))}
          </div>
          {blocks.map(b => {
            const u = blockUtilization(b);
            const next = nextUPCFromPrefix(b.prefix, b.issued);
            const tone = u > 0.95 ? '#a04432' : u > 0.85 ? '#c79538' : '#3a8a52';
            return (
              <div key={b.id} style={{ display: 'grid', gridTemplateColumns: '120px 1.4fr 90px 80px 80px 1fr 100px', gap: 10, padding: '12px 14px', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center' }}>
                <span className="ff-mono" style={{ fontSize: 12, fontWeight: 600 }}>{b.prefix}</span>
                <div>
                  <div style={{ fontSize: 13 }}>{b.owner}</div>
                  {b.note && <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 1 }}>{b.note}</div>}
                </div>
                <span className="ff-mono upper" style={{ fontSize: 10, letterSpacing: '.06em', color: 'var(--ink-3)' }}>{b.tier}</span>
                <span className="ff-mono" style={{ fontSize: 12 }}>{b.issued.toLocaleString()}</span>
                <span className="ff-mono" style={{ fontSize: 12, color: 'var(--ink-3)' }}>{b.reserved}</span>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <div style={{ flex: 1, height: 5, background: 'var(--surface-2)' }}>
                    <div style={{ width: `${Math.min(100, u * 100)}%`, height: '100%', background: tone }} />
                  </div>
                  <span className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-3)', minWidth: 45 }}>{(u * 100).toFixed(1)}%</span>
                </div>
                <span className="ff-mono" style={{ fontSize: 11, color: tone, fontWeight: 600 }}>{next}</span>
              </div>
            );
          })}
        </div>

        <div style={{ marginTop: 24 }}>
          <div style={{ fontSize: 14, fontWeight: 500, marginBottom: 4 }}>GRid issuer codes</div>
          <div style={{ fontSize: 12, color: 'var(--ink-3)', marginBottom: 14 }}>Allocated by IFPI for digital release identifiers.</div>
          <div style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)' }}>
            <div style={{ display: 'grid', gridTemplateColumns: '100px 1.4fr 100px 90px 1fr', gap: 10, padding: '10px 14px', borderBottom: '1px solid var(--rule-soft)' }} className="ff-mono upper">
              {['Code', 'Owner', 'Tier', 'Issued', 'Sample next'].map(l => (
                <span key={l} style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>{l}</span>
              ))}
            </div>
            {SEED_GRID_ISSUERS.map(g => (
              <div key={g.id} style={{ display: 'grid', gridTemplateColumns: '100px 1.4fr 100px 90px 1fr', gap: 10, padding: '12px 14px', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center' }}>
                <span className="ff-mono" style={{ fontSize: 12, fontWeight: 600 }}>{g.code}</span>
                <span style={{ fontSize: 13 }}>{g.owner}</span>
                <span className="ff-mono upper" style={{ fontSize: 10, letterSpacing: '.06em', color: 'var(--ink-3)' }}>{g.tier}</span>
                <span className="ff-mono" style={{ fontSize: 12 }}>{g.issued}</span>
                <span className="ff-mono" style={{ fontSize: 11, color: '#3a8a52' }}>{formatGrid(nextGridFromIssuer(g.code, g.issued))}</span>
              </div>
            ))}
          </div>
        </div>
      </div>
    );
  }

  // ── Conflicts tab ───────────────────────────────────────────────────
  function ConflictsTab({ rows, onPick }) {
    // Group conflicts: invalid check digit, duplicate UPC, missing GRid (digital), missing ISRC.
    const dupeMap = new Map();
    rows.forEach(r => {
      if (!dupeMap.has(r._ops.upc)) dupeMap.set(r._ops.upc, []);
      dupeMap.get(r._ops.upc).push(r);
    });
    const dupes = [...dupeMap.entries()].filter(([_, list]) => list.length > 1);
    const invalid = rows.filter(r => !r._ops.upcMeta.valid);
    const missingGRid = rows.filter(r => r.format === 'Digital' && !r._ops.grid);
    const missingISRC = rows.filter(r => r._ops.isrcMissing > 0);

    function Section({ title, count, tone, children }) {
      return (
        <div style={{ marginBottom: 24 }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, marginBottom: 10 }}>
            <span style={{ fontSize: 14, fontWeight: 500 }}>{title}</span>
            <span className="ff-mono upper" style={{
              fontSize: 9, letterSpacing: '.08em', padding: '2px 7px', fontWeight: 600,
              color: tone, border: `1px solid ${tone}33`, background: `${tone}10`,
            }}>{count}</span>
          </div>
          {count === 0
            ? <div style={{ fontSize: 12, color: 'var(--ink-3)', padding: '12px 14px', border: '1px dashed var(--rule)' }}>None — clean.</div>
            : children}
        </div>
      );
    }

    function MiniRow({ rel, extra, tone }) {
      return (
        <div onClick={() => onPick(rel.id)} style={{
          display: 'grid', gridTemplateColumns: '1.6fr 130px 1fr 80px', gap: 10, padding: '10px 14px',
          borderBottom: '1px solid var(--rule-soft)', alignItems: 'center', cursor: 'pointer',
        }}
          onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--surface-2)'; }}
          onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}>
          <div>
            <div style={{ fontSize: 13 }}>{rel.title}</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>{rel.artist} · {rel.editionLabel || rel.format}</div>
          </div>
          <span className="ff-mono" style={{ fontSize: 11 }}>{rel._ops.upc}</span>
          <span className="ff-mono" style={{ fontSize: 11, color: tone }}>{extra}</span>
          <StatusPill status={rel._ops.status} />
        </div>
      );
    }

    return (
      <div>
        <Section title="Duplicate UPCs" count={dupes.length} tone="#a04432">
          <div style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)' }}>
            {dupes.map(([upc, list]) => (
              <div key={upc} style={{ borderBottom: '1px solid var(--rule)' }}>
                <div style={{ padding: '8px 14px', background: 'var(--surface-2)', fontSize: 11 }} className="ff-mono">
                  <strong style={{ color: '#a04432' }}>{upc}</strong>
                  <span style={{ color: 'var(--ink-3)', marginLeft: 8 }}>used by {list.length} releases</span>
                </div>
                {list.map(r => <MiniRow key={r.id} rel={r} extra={r.label} tone="#a04432" />)}
              </div>
            ))}
          </div>
        </Section>
        <Section title="Invalid check digit" count={invalid.length} tone="#a04432">
          <div style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)' }}>
            {invalid.map(r => <MiniRow key={r.id} rel={r} extra={r._ops.upcMeta.kind} tone="#a04432" />)}
          </div>
        </Section>
        <Section title="Digital releases missing GRid" count={missingGRid.length} tone="#c79538">
          <div style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)' }}>
            {missingGRid.slice(0, 30).map(r => <MiniRow key={r.id} rel={r} extra="no issuer code" tone="#c79538" />)}
            {missingGRid.length > 30 && <div style={{ padding: '10px 14px', fontSize: 11, color: 'var(--ink-3)' }} className="ff-mono">+{missingGRid.length - 30} more</div>}
          </div>
        </Section>
        <Section title="Releases with missing ISRC" count={missingISRC.length} tone="#c79538">
          <div style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)' }}>
            {missingISRC.slice(0, 30).map(r => <MiniRow key={r.id} rel={r} extra={`${r._ops.isrcMissing} of ${r._ops.trackCount}`} tone="#c79538" />)}
            {missingISRC.length > 30 && <div style={{ padding: '10px 14px', fontSize: 11, color: 'var(--ink-3)' }} className="ff-mono">+{missingISRC.length - 30} more</div>}
          </div>
        </Section>
      </div>
    );
  }

  // ── Lifecycle tab ───────────────────────────────────────────────────
  function LifecycleTab({ rows }) {
    const groups = {};
    STATUSES.forEach(s => groups[s.v] = []);
    rows.forEach(r => { groups[r._ops.status]?.push(r); });
    return (
      <div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: 10, marginBottom: 20 }}>
          {STATUSES.map(s => (
            <div key={s.v} style={{ padding: '14px 14px 12px', border: '1px solid var(--rule-soft)', background: 'var(--paper)' }}>
              <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: s.tone, fontWeight: 600 }}>{s.label}</div>
              <div style={{ fontSize: 28, fontWeight: 300, marginTop: 4, fontVariantNumeric: 'tabular-nums' }}>{groups[s.v].length}</div>
              <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 4 }} className="ff-mono">{((groups[s.v].length / Math.max(rows.length, 1)) * 100).toFixed(1)}% of catalog</div>
            </div>
          ))}
        </div>

        {/* Sankey-ish lifecycle map */}
        <div style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)', padding: '20px 24px' }}>
          <div style={{ fontSize: 13, fontWeight: 500, marginBottom: 18 }}>Lifecycle flow</div>
          <svg viewBox="0 0 800 220" style={{ width: '100%', height: 220 }}>
            {/* nodes */}
            {[
              { x: 30,  y: 100, w: 110, label: 'Reserved',    n: groups.reserved.length,    tone: STATUS_BY.reserved.tone },
              { x: 200, y: 100, w: 110, label: 'Provisional', n: groups.provisional.length, tone: STATUS_BY.provisional.tone },
              { x: 370, y: 60,  w: 110, label: 'Locked',      n: groups.locked.length,      tone: STATUS_BY.locked.tone },
              { x: 370, y: 140, w: 110, label: 'Variant',     n: groups.variant.length,     tone: STATUS_BY.variant.tone },
              { x: 540, y: 30,  w: 110, label: 'Conflict',    n: groups.conflict.length,    tone: STATUS_BY.conflict.tone },
              { x: 660, y: 100, w: 110, label: 'Withdrawn',   n: groups.withdrawn.length,   tone: STATUS_BY.withdrawn.tone },
            ].map((n, i) => (
              <g key={i}>
                <rect x={n.x} y={n.y} width={n.w} height={50} fill={`${n.tone}10`} stroke={n.tone} strokeWidth="1" />
                <text x={n.x + n.w / 2} y={n.y + 22} textAnchor="middle" fontSize="11" fill="var(--ink-1)" fontWeight="500">{n.label}</text>
                <text x={n.x + n.w / 2} y={n.y + 38} textAnchor="middle" fontSize="14" fill={n.tone} fontWeight="600" fontFamily="ui-monospace,monospace">{n.n}</text>
              </g>
            ))}
            {/* arrows */}
            {[
              ['M140,125 L200,125', 'reserved → provisional'],
              ['M310,125 L370,90',  'provisional → locked'],
              ['M310,125 L370,165', 'provisional → variant'],
              ['M480,90 L540,60',   'locked → conflict'],
              ['M480,90 L660,115',  'locked → withdrawn'],
              ['M480,165 L660,135', 'variant → withdrawn'],
            ].map((a, i) => (
              <path key={i} d={a[0]} stroke="var(--ink-3)" strokeWidth="1" fill="none" markerEnd="url(#arrow)" opacity="0.55" />
            ))}
            <defs>
              <marker id="arrow" viewBox="0 -4 8 8" refX="7" refY="0" markerWidth="7" markerHeight="7" orient="auto">
                <path d="M0,-4L8,0L0,4" fill="var(--ink-3)" />
              </marker>
            </defs>
          </svg>
        </div>
      </div>
    );
  }

  // ── DDEX queue tab ──────────────────────────────────────────────────
  function DdexTab({ rows }) {
    // Synthesize a queue from any release in 'locked' or 'variant' state.
    const queue = rows.filter(r => r._ops.status === 'locked' || r._ops.status === 'variant').slice(0, 40);
    const PARTNERS = ['Spotify', 'Apple', 'Amazon Music', 'YouTube Music', 'Tidal', 'Deezer'];
    const states = ['QUEUED', 'BUILDING', 'SIGNED', 'TRANSMITTED', 'ACK PENDING', 'ACCEPTED', 'REJECTED'];

    function pickState(rel, partner) {
      let h = 0; for (const c of rel.id + partner) h = (h * 31 + c.charCodeAt(0)) | 0;
      h = Math.abs(h);
      const on = rel._ops.pushed.includes(partner.split(' ')[0]);
      if (on) return h % 11 === 0 ? 'REJECTED' : 'ACCEPTED';
      const idx = h % 5;
      return states[idx];
    }
    function stateTone(s) {
      if (s === 'ACCEPTED') return '#3a8a52';
      if (s === 'REJECTED') return '#a04432';
      if (s === 'TRANSMITTED' || s === 'SIGNED') return '#5a8aa0';
      if (s === 'ACK PENDING') return '#c79538';
      return '#7a7a7a';
    }

    return (
      <div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 14, fontWeight: 500 }}>DDEX outbound queue</div>
            <div style={{ fontSize: 12, color: 'var(--ink-3)' }}>ERN 4.3 ResourceList → ReleaseList → DealList per partner.</div>
          </div>
          <button className="ff-mono upper" style={{
            fontSize: 10, letterSpacing: '.08em', padding: '7px 12px', border: '1px solid var(--rule)',
            background: 'transparent', color: 'var(--ink-1)', cursor: 'pointer', fontWeight: 600,
          }}>↻ REFRESH ACKS</button>
          <button className="ff-mono upper" style={{
            fontSize: 10, letterSpacing: '.08em', padding: '7px 12px', border: 0,
            background: 'var(--ink-1)', color: '#fff', cursor: 'pointer', fontWeight: 600,
          }}>SHIP BATCH</button>
        </div>

        <div style={{ background: 'var(--paper)', border: '1px solid var(--rule-soft)' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '1.6fr 130px repeat(6, 1fr)', gap: 8, padding: '10px 14px', borderBottom: '1px solid var(--rule-soft)' }} className="ff-mono upper">
            <span style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>Release</span>
            <span style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>UPC</span>
            {PARTNERS.map(p => <span key={p} style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)' }}>{p}</span>)}
          </div>
          {queue.map(r => (
            <div key={r.id} style={{ display: 'grid', gridTemplateColumns: '1.6fr 130px repeat(6, 1fr)', gap: 8, padding: '12px 14px', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center' }}>
              <div>
                <div style={{ fontSize: 13 }}>{r.title}</div>
                <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>{r.artist}</div>
              </div>
              <span className="ff-mono" style={{ fontSize: 11 }}>{r._ops.upc}</span>
              {PARTNERS.map(p => {
                const s = pickState(r, p);
                return (
                  <span key={p} className="ff-mono upper" style={{
                    fontSize: 9, letterSpacing: '.06em', fontWeight: 600,
                    color: stateTone(s),
                  }}>{s}</span>
                );
              })}
            </div>
          ))}
        </div>
      </div>
    );
  }

  // ── header KPI strip ────────────────────────────────────────────────
  function KpiStrip({ rows, blocks }) {
    const total = rows.length;
    const conflict = rows.filter(r => r._ops.status === 'conflict').length;
    const locked = rows.filter(r => r._ops.status === 'locked' || r._ops.status === 'variant').length;
    const provisional = rows.filter(r => r._ops.status === 'provisional').length;
    const reserved = rows.filter(r => r._ops.status === 'reserved').length;
    const avgScore = rows.reduce((s, r) => s + r._ops.score, 0) / Math.max(total, 1);
    const blockUtil = blocks.reduce((s, b) => s + blockUtilization(b), 0) / Math.max(blocks.length, 1);
    const KPIS = [
      { l: 'CATALOG',     v: total,                  s: 'editions tracked' },
      { l: 'AVG HEALTH',  v: avgScore.toFixed(0),    s: 'across catalog',   tone: avgScore < 70 ? '#a04432' : avgScore < 90 ? '#c79538' : '#3a8a52' },
      { l: 'CONFLICTS',   v: conflict,               s: 'need review',      tone: conflict ? '#a04432' : null },
      { l: 'LOCKED',      v: locked,                 s: 'DSP-ready' },
      { l: 'PROVISIONAL', v: provisional,            s: 'awaiting lock' },
      { l: 'RESERVED',    v: reserved,               s: 'pre-allocated' },
      { l: 'BLOCK UTIL',  v: (blockUtil * 100).toFixed(1) + '%', s: 'across all prefixes', tone: blockUtil > 0.85 ? '#c79538' : null },
    ];
    return (
      <div style={{ display: 'grid', gridTemplateColumns: `repeat(${KPIS.length}, 1fr)`, gap: 1, background: 'var(--rule-soft)', marginBottom: 16, border: '1px solid var(--rule-soft)' }}>
        {KPIS.map((k, i) => (
          <div key={i} style={{ background: 'var(--paper)', padding: '14px 16px' }}>
            <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.08em', color: 'var(--ink-3)', fontWeight: 600 }}>{k.l}</div>
            <div style={{ fontSize: 26, fontWeight: 300, marginTop: 4, fontVariantNumeric: 'tabular-nums', color: k.tone || 'var(--ink-1)' }}>{k.v}</div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 2 }} className="ff-mono">{k.s}</div>
          </div>
        ))}
      </div>
    );
  }

  // ── main screen ─────────────────────────────────────────────────────
  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "density": "comfortable",
    "groupBy": "none",
    "showOnlyIssues": false,
    "showVariants": true,
    "showWithdrawn": false
  }/*EDITMODE-END*/;

  function ScreenReleasesId({ go, payload }) {
    const allRels = (window.RELEASES || []);
    const [blocks, setBlocks] = useState(SEED_BLOCKS);
    const [tab, setTab] = useState(payload?.tab || 'audit');
    const [search, setSearch] = useState('');
    const [filterStatus, setFilterStatus] = useState('all');
    const [filterFormat, setFilterFormat] = useState('all');
    const [filterLabel, setFilterLabel] = useState('all');
    const [sort, setSort] = useState({ k: 'score', dir: 'asc' });
    const [selectedId, setSelectedId] = useState(payload?.releaseId || null);
    const [tweaksOpen, setTweaksOpen] = useState(false);
    const [t, setT] = useTweaks(TWEAK_DEFAULTS);

    useEffect(() => {
      const onMsg = (e) => {
        if (e.data?.type === '__activate_edit_mode') setTweaksOpen(true);
        if (e.data?.type === '__deactivate_edit_mode') setTweaksOpen(false);
      };
      window.addEventListener('message', onMsg);
      window.parent.postMessage({ type: '__edit_mode_available' }, '*');
      return () => window.removeEventListener('message', onMsg);
    }, []);

    // attach _ops to every release (memoized)
    const enriched = useMemo(() => {
      return allRels.map(r => ({ ...r, _ops: deriveOps(r, blocks, allRels) }));
    }, [allRels, blocks]);

    const filtered = useMemo(() => {
      let out = enriched;
      if (search.trim()) {
        const q = search.trim().toLowerCase();
        out = out.filter(r => (r.title + ' ' + r.artist + ' ' + r._ops.upc + ' ' + r.label + ' ' + r._ops.catNo).toLowerCase().includes(q));
      }
      if (filterStatus !== 'all') out = out.filter(r => r._ops.status === filterStatus);
      if (filterFormat !== 'all') out = out.filter(r => r.format === filterFormat);
      if (filterLabel !== 'all')  out = out.filter(r => r.label === filterLabel);
      if (t.showOnlyIssues) out = out.filter(r => r._ops.dupe.length || !r._ops.upcMeta.valid || r._ops.isrcMissing);
      if (!t.showVariants) out = out.filter(r => r._ops.status !== 'variant');
      if (!t.showWithdrawn) out = out.filter(r => r._ops.status !== 'withdrawn');

      const dir = sort.dir === 'asc' ? 1 : -1;
      out = [...out].sort((a, b) => {
        const va = ({
          score: a._ops.score, title: a.title, upc: a._ops.upc, grid: a._ops.grid || '',
          cat: a._ops.catNo, tracks: a._ops.trackCount, block: (a._ops.block?.prefix || ''),
          pushed: a._ops.pushed.length, date: a._ops.lastPush || '', status: a._ops.status,
        })[sort.k];
        const vb = ({
          score: b._ops.score, title: b.title, upc: b._ops.upc, grid: b._ops.grid || '',
          cat: b._ops.catNo, tracks: b._ops.trackCount, block: (b._ops.block?.prefix || ''),
          pushed: b._ops.pushed.length, date: b._ops.lastPush || '', status: b._ops.status,
        })[sort.k];
        if (va < vb) return -1 * dir;
        if (va > vb) return  1 * dir;
        return 0;
      });
      return out;
    }, [enriched, search, filterStatus, filterFormat, filterLabel, sort, t.showOnlyIssues, t.showVariants, t.showWithdrawn]);

    const labels = useMemo(() => [...new Set(enriched.map(r => r.label).filter(Boolean))].sort(), [enriched]);
    const formats = useMemo(() => [...new Set(enriched.map(r => r.format).filter(Boolean))].sort(), [enriched]);

    const selected = enriched.find(r => r.id === selectedId) || null;

    function onAction(verb, id) {
      const tip = ({
        lock: 'Identifiers locked. UPC / GRid / Cat # frozen.',
        reissue: 'Reissued UPC from next available block slot. Prior is now WITHDRAWN.',
        grid: 'GRid issued from next-available issuer slot.',
        variant: 'Variant edition created with new UPC.',
        push: 'Queued for DDEX ERN 4.3 transmission to all 6 partners.',
        withdraw: 'Release withdrawn. Will be propagated as ERN takedown.',
      })[verb] || 'Action queued.';
      window.toast?.(tip, verb === 'withdraw' ? 'warn' : 'soft');
    }

    const TABS = [
      { v: 'audit',     l: 'Audit',          n: filtered.length },
      { v: 'blocks',    l: 'Prefix blocks',  n: blocks.length },
      { v: 'conflicts', l: 'Conflicts',      n: enriched.filter(r => r._ops.status === 'conflict').length },
      { v: 'lifecycle', l: 'Lifecycle',      n: null },
      { v: 'ddex',      l: 'DDEX queue',     n: enriched.filter(r => r._ops.status === 'locked' || r._ops.status === 'variant').length },
    ];

    return (
      <div style={{ padding: '20px 28px 60px', minHeight: '100vh', background: 'var(--bg)' }}>
        {/* header */}
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 16, marginBottom: 18 }}>
          <div style={{ flex: 1 }}>
            <div className="ff-mono upper" style={{ fontSize: 9, letterSpacing: '.12em', color: 'var(--ink-3)', fontWeight: 600 }}>OPERATIONS · IDENTIFIERS</div>
            <h1 style={{ fontSize: 26, fontWeight: 400, margin: '4px 0 2px', letterSpacing: '-.01em' }}>Releases ID tracker</h1>
            <div style={{ fontSize: 13, color: 'var(--ink-3)' }}>
              UPC, EAN-13, GRid, ICPN, catalog number — issued from prefix blocks, validated against GS1 & ISO/IEC 7064, deduplicated, and pushed to DSPs.
            </div>
          </div>
        </div>

        <KpiStrip rows={enriched} blocks={blocks} />

        {/* tabs */}
        <div style={{ display: 'flex', gap: 24, borderBottom: '1px solid var(--rule)', marginBottom: 18 }}>
          {TABS.map(tb => (
            <button key={tb.v} onClick={() => setTab(tb.v)}
              className="ff-mono upper"
              style={{
                background: 'transparent', border: 0, padding: '8px 0', cursor: 'pointer',
                fontSize: 11, letterSpacing: '.08em', fontWeight: 600,
                color: tab === tb.v ? 'var(--ink-1)' : 'var(--ink-3)',
                borderBottom: tab === tb.v ? '2px solid var(--ink-1)' : '2px solid transparent',
                marginBottom: -1,
              }}>
              {tb.l}{tb.n != null && <span className="ff-mono" style={{ marginLeft: 6, color: 'var(--ink-3)', fontWeight: 400 }}>{tb.n}</span>}
            </button>
          ))}
        </div>

        {tab === 'audit' && (
          <>
            {/* filters */}
            <div style={{ display: 'flex', gap: 8, marginBottom: 14, alignItems: 'center', flexWrap: 'wrap' }}>
              <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search title, artist, UPC, cat #…"
                style={{ flex: '1 1 280px', padding: '8px 12px', border: '1px solid var(--rule)', background: 'var(--paper)', fontSize: 13 }} />
              <select value={filterStatus} onChange={e => setFilterStatus(e.target.value)} className="ff-mono"
                style={{ padding: '8px 10px', border: '1px solid var(--rule)', background: 'var(--paper)', fontSize: 11 }}>
                <option value="all">All states</option>
                {STATUSES.map(s => <option key={s.v} value={s.v}>{s.label}</option>)}
              </select>
              <select value={filterFormat} onChange={e => setFilterFormat(e.target.value)} className="ff-mono"
                style={{ padding: '8px 10px', border: '1px solid var(--rule)', background: 'var(--paper)', fontSize: 11 }}>
                <option value="all">All formats</option>
                {formats.map(f => <option key={f}>{f}</option>)}
              </select>
              <select value={filterLabel} onChange={e => setFilterLabel(e.target.value)} className="ff-mono"
                style={{ padding: '8px 10px', border: '1px solid var(--rule)', background: 'var(--paper)', fontSize: 11, maxWidth: 200 }}>
                <option value="all">All labels</option>
                {labels.map(l => <option key={l}>{l}</option>)}
              </select>
              <button onClick={() => { setSearch(''); setFilterStatus('all'); setFilterFormat('all'); setFilterLabel('all'); }}
                className="ff-mono upper" style={{
                  fontSize: 10, letterSpacing: '.08em', padding: '8px 12px', border: '1px solid var(--rule)',
                  background: 'transparent', color: 'var(--ink-3)', cursor: 'pointer', fontWeight: 600,
                }}>RESET</button>
            </div>
            <AuditList rows={filtered} onPick={setSelectedId} selectedId={selectedId} sort={sort} setSort={setSort} density={t.density} />
          </>
        )}
        {tab === 'blocks'    && <BlocksTab blocks={blocks} setBlocks={setBlocks} />}
        {tab === 'conflicts' && <ConflictsTab rows={enriched} onPick={(id) => { setTab('audit'); setSelectedId(id); }} />}
        {tab === 'lifecycle' && <LifecycleTab rows={enriched} />}
        {tab === 'ddex'      && <DdexTab rows={enriched} />}

        {/* detail */}
        {selected && tab === 'audit' && (
          <DetailPanel rel={selected} allRels={enriched} onClose={() => setSelectedId(null)} onAction={onAction} />
        )}

        {/* tweaks */}
        {tweaksOpen && TweaksPanel && (
          <TweaksPanel onClose={() => setTweaksOpen(false)}>
            <TweakSection title="Density">
              <TweakRadio value={t.density} onChange={(v) => setT('density', v)} options={[
                { value: 'compact', label: 'Compact' },
                { value: 'comfortable', label: 'Comfortable' },
              ]} />
            </TweakSection>
            <TweakSection title="Filters">
              <TweakToggle value={t.showOnlyIssues} onChange={(v) => setT('showOnlyIssues', v)} label="Issues only" />
              <TweakToggle value={t.showVariants} onChange={(v) => setT('showVariants', v)} label="Show variants" />
              <TweakToggle value={t.showWithdrawn} onChange={(v) => setT('showWithdrawn', v)} label="Show withdrawn" />
            </TweakSection>
          </TweaksPanel>
        )}
      </div>
    );
  }

  Object.assign(window, {
    ScreenReleasesId,
    ReleasesIdValidate: { isValidUPC, isValidEAN13, isValidGRid, isValidISRC, gs1CheckDigit, gridCheckChar, formatGrid, classifyBarcode },
  });
})();
