// audio-fp.jsx — Audio Fingerprinting
// ─────────────────────────────────────────────────────────────────
// Acoustic-fingerprint matching across the catalog. Generates
// deterministic 32-band spectral fingerprints from recording
// metadata (in lieu of real audio), provides a query interface,
// and surfaces detection workflows:
//
//   01 IDENTIFY     — paste/upload audio → ranked catalog matches
//   02 MONITOR      — DSP/UGC scan log: where & when was each
//                     recording detected (Spotify / YouTube / TikTok
//                     / Instagram / Twitch / radio / podcast)
//   03 COVERS       — same-work different-recording detection
//                     (cover, remix, sample, interpolation)
//   04 UNAUTH       — unauthorized uses requiring a claim
//                     (UGC video, sync, broadcast)
//   05 CLAIMS       — claim queue: take action against detections
//
// EXPORT:
//   window.AudioFP.generate(recording)     — synthesize fingerprint
//   window.AudioFP.match(fingerprint)      — rank catalog matches
//   window.AudioFP.scan()                  — full corpus scan
//   window.ScreenAudioFP                   — full screen
//   window.AudioFPCard({rec})              — embed for recording page
// ─────────────────────────────────────────────────────────────────
(function () {
  if (typeof window === 'undefined' || !window.React) return;
  const _S = React.useState, _M = React.useMemo, _E = React.useEffect, _R = React.useRef;

  const fmtNum = (n) => {
    if (n == null) return '—';
    const a = Math.abs(n);
    if (a >= 1e9) return (n/1e9).toFixed(1) + 'B';
    if (a >= 1e6) return (n/1e6).toFixed(1) + 'M';
    if (a >= 1e3) return (n/1e3).toFixed(1) + 'k';
    return Math.round(n).toLocaleString();
  };
  const fmtDur = (s) => {
    if (!s) return '—';
    const m = Math.floor(s / 60), ss = String(s % 60).padStart(2, '0');
    return m + ':' + ss;
  };

  function Mono({ children, upper, size, color, style, ...rest }) {
    return <span className={'ff-mono' + (upper?' upper':'')} style={{ fontSize: size||11, color: color||'var(--ink)', letterSpacing: upper?'.08em':0, ...style }} {...rest}>{children}</span>;
  }

  // ─── deterministic RNG ─────────────────────────────────────────
  function pseed(seed) {
    let s = 0; for (let i = 0; i < seed.length; i++) s = (s * 31 + seed.charCodeAt(i)) | 0;
    let a = s ^ 0x9e3779b9, b = s ^ 0xdeadbeef, c = s ^ 0x41c6ce57, d = s ^ 0x6b79f5d3;
    return function () {
      a |= 0; b |= 0; c |= 0; d |= 0;
      const t = (((a + b) | 0) + d) | 0; d = (d + 1) | 0;
      a = b ^ (b >>> 9); b = (c + (c << 3)) | 0; c = (c << 21 | c >>> 11);
      c = (c + t) | 0;
      return (t >>> 0) / 4294967296;
    };
  }

  // ─── FINGERPRINT GENERATION ────────────────────────────────────
  // Generate a deterministic 32-band × N-frame fingerprint from
  // recording metadata. Real audio fingerprinting (Chromaprint /
  // Echoprint) hashes spectral peaks; we simulate equivalent
  // structure: 32-element vectors per ~3-second frame, plus a
  // compact "hash" derived from peak indices.
  const BANDS = 32;
  function generate(rec) {
    if (!rec) return null;
    const rng = pseed((rec.id || '') + '·' + (rec.isrc || '') + '·' + (rec.title || ''));
    const dur = rec.duration || 180;
    const frames = Math.max(8, Math.floor(dur / 3));

    // Generate band energies. Use rec id to drive a pseudo-spectrum
    // with characteristic peaks.
    const peaks = [];
    for (let i = 0; i < 5; i++) peaks.push(Math.floor(rng() * BANDS));
    const matrix = [];
    for (let f = 0; f < frames; f++) {
      const row = new Array(BANDS).fill(0);
      for (let b = 0; b < BANDS; b++) {
        let v = rng() * 0.3;
        peaks.forEach((p, pi) => {
          const dist = Math.abs(b - p);
          v += Math.exp(-dist * 0.5) * (0.6 + Math.sin(f / 6 + pi) * 0.2);
        });
        row[b] = Math.min(1, v);
      }
      matrix.push(row);
    }

    // Compact hash: top-2 peak indices per frame, packed into 32-bit chunks.
    const hash = [];
    matrix.forEach(row => {
      const indexed = row.map((v, i) => [v, i]).sort((a, b) => b[0] - a[0]);
      hash.push((indexed[0][1] << 5) | indexed[1][1]);
    });

    return { id: rec.id, isrc: rec.isrc, hash, matrix, peaks, frames, dur, signature: hashToSignature(hash) };
  }

  function hashToSignature(hash) {
    // Compact 16-char hex signature for display
    let h = 0xcbf29ce4 >>> 0;
    for (const v of hash) {
      h = ((h ^ v) >>> 0);
      h = ((h * 0x01000193) >>> 0);
    }
    return h.toString(16).padStart(8, '0').toUpperCase();
  }

  // ─── MATCHING ──────────────────────────────────────────────────
  // Score two fingerprints by Hamming-like distance over hash
  // arrays + matrix correlation. Returns 0..1 (1 = identical).
  function similarity(fpA, fpB) {
    if (!fpA || !fpB) return 0;
    const minLen = Math.min(fpA.hash.length, fpB.hash.length);
    if (minLen === 0) return 0;

    // Hash overlap: rolling alignment best score
    let bestOverlap = 0;
    const maxShift = Math.min(8, Math.abs(fpA.hash.length - fpB.hash.length) + 4);
    for (let shift = -maxShift; shift <= maxShift; shift++) {
      let matches = 0, compared = 0;
      for (let i = 0; i < minLen; i++) {
        const j = i + shift;
        if (j < 0 || j >= fpB.hash.length) continue;
        const a = fpA.hash[i] || 0;
        const b = fpB.hash[j] || 0;
        const popcount = bitDiff(a ^ b);
        if (popcount <= 2) matches++;
        compared++;
      }
      if (compared > 0) bestOverlap = Math.max(bestOverlap, matches / compared);
    }

    // Peak overlap
    const peakOverlap = (fpA.peaks || []).filter(p => (fpB.peaks || []).includes(p)).length / 5;

    return 0.7 * bestOverlap + 0.3 * peakOverlap;
  }
  function bitDiff(n) { let c = 0; while (n) { c += n & 1; n >>>= 1; } return c; }

  // Catalog fingerprint cache (lazy-built)
  let __FP_CACHE = null;
  function fpCache() {
    if (__FP_CACHE) return __FP_CACHE;
    const recs = window.RECORDINGS || [];
    __FP_CACHE = recs.map(r => generate(r));
    return __FP_CACHE;
  }

  function match(query, opts) {
    if (!query) return [];
    const recs = window.RECORDINGS || [];
    const cache = fpCache();
    return cache.map((fp, i) => ({
      rec: recs[i],
      fp,
      score: similarity(query, fp),
    }))
      .filter(r => r.score > 0.35)
      .sort((a, b) => b.score - a.score)
      .slice(0, (opts && opts.limit) || 12);
  }

  // ─── DETECTION SOURCES ─────────────────────────────────────────
  const PLATFORMS = [
    { k: 'spotify',   l: 'Spotify',     c: '#1DB954', kind: 'DSP',     weight: 0.30 },
    { k: 'apple',     l: 'Apple Music', c: '#FA243C', kind: 'DSP',     weight: 0.20 },
    { k: 'youtube',   l: 'YouTube',     c: '#FF0000', kind: 'UGC',     weight: 0.18 },
    { k: 'tiktok',    l: 'TikTok',      c: '#000000', kind: 'UGC',     weight: 0.15 },
    { k: 'instagram', l: 'Instagram',   c: '#E4405F', kind: 'UGC',     weight: 0.08 },
    { k: 'twitch',    l: 'Twitch',      c: '#9146FF', kind: 'BCAST',   weight: 0.04 },
    { k: 'radio',     l: 'Radio (BMAT)',c: '#a35418', kind: 'BCAST',   weight: 0.03 },
    { k: 'podcast',   l: 'Podcast',     c: '#7a3a8c', kind: 'BCAST',   weight: 0.02 },
  ];

  const DETECTION_KIND = {
    'authorized':    { l: 'AUTHORIZED', c: '#0a8754' },
    'cover':         { l: 'COVER',      c: '#1a4ed8' },
    'sample':        { l: 'SAMPLE',     c: '#7a3a8c' },
    'interpolation': { l: 'INTERP',     c: '#7a3a8c' },
    'unauth-ugc':    { l: 'UGC · UNCLAIMED', c: '#d4881f' },
    'unauth-sync':   { l: 'SYNC · UNCLAIMED', c: '#a32a18' },
    'unauth-bcast':  { l: 'BROADCAST · UNCLAIMED', c: '#a32a18' },
  };

  // ─── SCAN: build detection log per-recording ───────────────────
  // Synthesizes a realistic scan log: each recording gets N detections
  // across multiple platforms with varied kinds. Deterministic per recording.
  function scanRecording(rec, rng) {
    rng = rng || pseed('scan·' + (rec.id || rec.title));
    const detections = [];
    const detCount = 8 + Math.floor(rng() * 22); // 8-30 hits
    for (let i = 0; i < detCount; i++) {
      // weighted platform pick
      const r = rng();
      let acc = 0, plat = PLATFORMS[0];
      for (const p of PLATFORMS) { acc += p.weight; if (r < acc) { plat = p; break; } }
      // kind: most are authorized; some unauth/cover/sample
      const kindRoll = rng();
      let kind = 'authorized';
      if (kindRoll < 0.05) kind = 'unauth-ugc';
      else if (kindRoll < 0.07) kind = 'unauth-sync';
      else if (kindRoll < 0.10) kind = 'cover';
      else if (kindRoll < 0.115) kind = 'sample';
      else if (kindRoll < 0.125) kind = 'interpolation';
      // confidence
      const conf = 0.6 + rng() * 0.39;
      // detected age in days
      const days = Math.floor(rng() * 365);
      // value at risk (dollars) for unauth
      const unauth = kind.startsWith('unauth');
      const valAtRisk = unauth ? Math.floor(50 + rng() * 4500) : 0;
      // detected URL / title fragment
      const fragments = ['compilation-mix-2024', 'top-tracks-vibes', 'workout-energy', 'studyfocus-2025', 'late-night', 'cinematic-cuts', 'fan-edit-clean', 'video-edit-2025'];
      detections.push({
        platform: plat,
        kind,
        conf,
        days,
        plays: Math.floor(rng() * 80000),
        valAtRisk,
        urlFragment: plat.k + '/' + fragments[Math.floor(rng() * fragments.length)] + '-' + Math.floor(rng() * 9000 + 1000),
        title: kind === 'cover' ? 'Cover by ' + ['user_42', 'IndieJam', 'BedroomCovers', 'Acoustic Ana'][Math.floor(rng() * 4)] : null,
      });
    }
    return detections.sort((a, b) => a.days - b.days);
  }

  function scan(opts) {
    opts = opts || {};
    const recs = window.RECORDINGS || [];
    const sliced = recs.slice(0, opts.limit || 60);
    const all = [];
    sliced.forEach(rec => {
      const dets = scanRecording(rec);
      dets.forEach(d => all.push({ ...d, rec }));
    });

    // Aggregations
    const byKind = {};
    const byPlatform = {};
    let totalDetections = all.length;
    let valAtRisk = 0;
    let unauthCount = 0;

    all.forEach(d => {
      byKind[d.kind] = (byKind[d.kind] || 0) + 1;
      byPlatform[d.platform.k] = (byPlatform[d.platform.k] || 0) + 1;
      valAtRisk += d.valAtRisk;
      if (d.kind.startsWith('unauth')) unauthCount++;
    });

    return {
      detections: all,
      byKind,
      byPlatform,
      totalDetections,
      unauthCount,
      valAtRisk,
      recsScanned: sliced.length,
      ts: Date.now(),
    };
  }

  window.AudioFP = { generate, match, scan, scanRecording, similarity, PLATFORMS, DETECTION_KIND, BANDS };

  // ────────────────────── UI COMPONENTS ─────────────────────────

  // Spectrogram: render a fingerprint matrix as a tiny canvas-ish grid
  function FPGrid({ fp, width, height }) {
    if (!fp) return null;
    const W = width || 360, H = height || 80;
    const fr = fp.matrix.length, bd = BANDS;
    const cw = W / fr, ch = H / bd;
    return (
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" style={{ width: '100%', height: H, display: 'block', background: '#0a0a0a' }}>
        {fp.matrix.map((row, f) =>
          row.map((v, b) => {
            const intensity = Math.min(1, v);
            const hue = 200 - intensity * 50; // teal → orange
            const sat = 60 + intensity * 30;
            const lit = 5 + intensity * 50;
            return <rect key={f+'-'+b} x={f*cw} y={(bd-1-b)*ch} width={cw+0.5} height={ch+0.5} fill={`hsl(${hue} ${sat}% ${lit}%)`}/>;
          })
        )}
      </svg>
    );
  }

  function PlatformChip({ platform, count }) {
    return (
      <span className="ff-mono" style={{
        display: 'inline-flex', alignItems: 'center', gap: 5,
        padding: '3px 7px', fontSize: 10, letterSpacing: '0.06em',
        background: platform.c + '15', color: platform.c, border: '1px solid ' + platform.c + '33',
      }}>
        <span style={{ width: 6, height: 6, background: platform.c, borderRadius: '50%' }}/>
        <span style={{ textTransform: 'uppercase' }}>{platform.l}</span>
        {count != null && <span style={{ opacity: 0.7 }}>{count}</span>}
      </span>
    );
  }

  function KindChip({ kind }) {
    const m = DETECTION_KIND[kind];
    if (!m) return null;
    return (
      <span className="ff-mono" style={{
        fontSize: 9, letterSpacing: '0.08em', padding: '2px 6px',
        background: m.c, color: '#fff',
      }}>{m.l}</span>
    );
  }

  function MatchBar({ score }) {
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, width: 130 }}>
        <div style={{ flex: 1, height: 6, background: 'var(--bg-2)' }}>
          <div style={{ height: '100%', width: (score * 100) + '%', background: score > 0.85 ? '#0a8754' : score > 0.65 ? '#d4881f' : 'var(--ink-3)' }}/>
        </div>
        <Mono size={10} color="var(--ink-2)" style={{ minWidth: 32, textAlign: 'right' }}>{Math.round(score * 100)}%</Mono>
      </div>
    );
  }

  // ─── 01 IDENTIFY ──────────────────────────────────────────────
  function IdentifyTab() {
    const [seed, setSeed] = _S('');
    const [fileName, setFileName] = _S('');
    const [matches, setMatches] = _S([]);
    const [running, setRunning] = _S(false);
    const [queryFP, setQueryFP] = _S(null);
    const recs = window.RECORDINGS || [];

    const runIdentify = (input) => {
      setRunning(true);
      setTimeout(() => {
        // Generate a query fingerprint from the seed string, optionally
        // anchored on a real catalog recording (so we get hits)
        let queryRec = null;
        if (input.startsWith('rec:')) {
          queryRec = recs.find(r => r.id === input.slice(4));
        } else if (recs.length > 0) {
          // Use seed as a hash → pick a recording for plausible matches
          const h = pseed(input + 'demo')();
          queryRec = recs[Math.floor(h * Math.min(recs.length, 100))];
        }
        const q = queryRec ? generate(queryRec) : { id: 'query', hash: [], matrix: [], peaks: [], frames: 0, dur: 0, signature: '00000000' };
        setQueryFP(q);
        const m = match(q, { limit: 10 });
        setMatches(m);
        setRunning(false);
      }, 600);
    };

    const onFile = (e) => {
      const f = e.target.files && e.target.files[0];
      if (!f) return;
      setFileName(f.name);
      runIdentify('file:' + f.name);
    };

    return (
      <div>
        <div style={{ border: '1px solid var(--rule)', padding: '24px 28px', marginBottom: 22 }}>
          <Mono upper size={9} color="var(--ink-3)" style={{ display: 'block', marginBottom: 12 }}>
            QUERY · upload audio or paste a sample fragment
          </Mono>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 10, alignItems: 'center' }}>
            <input
              value={seed} onChange={(e) => setSeed(e.target.value)}
              placeholder="paste an excerpt, ISRC, YouTube URL, or audio fingerprint string…"
              style={{
                padding: '12px 14px', fontSize: 13,
                border: '1px solid var(--rule)', background: 'var(--paper)', color: 'var(--ink)',
                outline: 'none', fontFamily: 'inherit',
              }}
            />
            <button onClick={() => runIdentify(seed || 'demo')} className="ff-mono upper" style={{
              fontSize: 11, padding: '12px 20px', letterSpacing: '0.08em',
              background: 'var(--ink)', color: 'var(--bg)', border: 0, cursor: 'pointer',
            }}>{running ? 'matching…' : 'identify'}</button>
          </div>
          <div style={{ display: 'flex', gap: 10, marginTop: 14, alignItems: 'center' }}>
            <label style={{ fontSize: 11, color: 'var(--ink-2)', cursor: 'pointer' }}>
              <input type="file" accept="audio/*" onChange={onFile} style={{ display: 'none' }}/>
              <span className="ff-mono upper" style={{
                fontSize: 10, padding: '6px 12px', border: '1px dashed var(--rule)', display: 'inline-block',
              }}>↑ UPLOAD AUDIO FILE</span>
            </label>
            {fileName && <Mono size={10} color="var(--ink-3)">{fileName}</Mono>}
            <Mono size={10} color="var(--ink-3)" style={{ marginLeft: 'auto' }}>or pick a sample below to test</Mono>
          </div>

          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 12 }}>
            {recs.slice(0, 6).map(r => (
              <button key={r.id} onClick={() => { setSeed(r.title); runIdentify('rec:' + r.id); }} className="ff-mono upper" style={{
                fontSize: 9, padding: '4px 8px',
                background: 'transparent', color: 'var(--ink-2)',
                border: '1px solid var(--rule)', cursor: 'pointer',
              }}>{(r.title || '').slice(0, 30)}</button>
            ))}
          </div>
        </div>

        {queryFP && (
          <div style={{ display: 'grid', gridTemplateColumns: '380px 1fr', gap: 22 }}>
            <div>
              <Mono upper size={9} color="var(--ink-3)" style={{ display: 'block', marginBottom: 8 }}>QUERY FINGERPRINT</Mono>
              <div style={{ border: '1px solid var(--rule)', padding: 8 }}>
                <FPGrid fp={queryFP}/>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginTop: 10 }}>
                <div>
                  <Mono upper size={8} color="var(--ink-3)">SIGNATURE</Mono>
                  <div className="ff-mono" style={{ fontSize: 13, marginTop: 2, letterSpacing: '0.04em' }}>{queryFP.signature}</div>
                </div>
                <div>
                  <Mono upper size={8} color="var(--ink-3)">FRAMES · BANDS</Mono>
                  <div className="ff-mono" style={{ fontSize: 13, marginTop: 2 }}>{queryFP.frames} × {BANDS}</div>
                </div>
                <div>
                  <Mono upper size={8} color="var(--ink-3)">DURATION</Mono>
                  <div className="ff-mono" style={{ fontSize: 13, marginTop: 2 }}>{fmtDur(queryFP.dur)}</div>
                </div>
                <div>
                  <Mono upper size={8} color="var(--ink-3)">PEAKS</Mono>
                  <div className="ff-mono" style={{ fontSize: 13, marginTop: 2 }}>{(queryFP.peaks || []).join(', ')}</div>
                </div>
              </div>
            </div>

            <div>
              <Mono upper size={9} color="var(--ink-3)" style={{ display: 'block', marginBottom: 8 }}>
                CATALOG MATCHES · {matches.length}
              </Mono>
              {matches.map((m, i) => (
                <div key={m.rec.id} style={{
                  display: 'grid', gridTemplateColumns: '30px 1fr 130px 140px 60px',
                  gap: 10, alignItems: 'center', padding: '10px 14px',
                  borderBottom: '1px solid var(--rule-soft)',
                  background: i === 0 ? '#0a875408' : 'var(--paper)',
                  borderLeft: i === 0 ? '3px solid #0a8754' : 'none',
                  marginLeft: i === 0 ? -3 : 0,
                }}>
                  <Mono size={10} color="var(--ink-3)">{(i+1).toString().padStart(2,'0')}</Mono>
                  <div>
                    <div style={{ fontSize: 13, fontWeight: 500 }}>{m.rec.title}</div>
                    <div style={{ fontSize: 10, color: 'var(--ink-2)', marginTop: 1 }}>{m.rec.artist || '—'} · {m.rec.isrc || '—'}</div>
                  </div>
                  <MatchBar score={m.score}/>
                  <Mono size={10} color="var(--ink-3)" style={{ letterSpacing: '0.04em' }}>{m.fp.signature}</Mono>
                  {i === 0 && <Mono upper size={9} color="#fff" style={{ background: '#0a8754', padding: '3px 7px', textAlign: 'center' }}>BEST</Mono>}
                </div>
              ))}
              {matches.length === 0 && !running && <div style={{ padding: 30, textAlign: 'center', color: 'var(--ink-3)', fontSize: 12 }}>No matches above 35% similarity threshold.</div>}
              {running && <div style={{ padding: 30, textAlign: 'center', color: 'var(--ink-3)', fontSize: 12 }}>Computing fingerprints…</div>}
            </div>
          </div>
        )}
      </div>
    );
  }

  // ─── 02 MONITOR (DSP/UGC scan log) ─────────────────────────────
  function MonitorTab() {
    const [filter, setFilter] = _S('all');
    const [platFilter, setPlatFilter] = _S('all');
    const [search, setSearch] = _S('');
    const result = _M(() => scan({ limit: 60 }), []);

    const visible = _M(() => {
      let v = result.detections;
      if (filter !== 'all') v = v.filter(d => filter === 'unauth' ? d.kind.startsWith('unauth') : d.kind === filter);
      if (platFilter !== 'all') v = v.filter(d => d.platform.k === platFilter);
      if (search) {
        const s = search.toLowerCase();
        v = v.filter(d => (d.rec.title || '').toLowerCase().includes(s) || (d.rec.artist || '').toLowerCase().includes(s));
      }
      return v.slice(0, 200);
    }, [result, filter, platFilter, search]);

    return (
      <div>
        {/* Headline metrics */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', borderTop: '1px solid var(--rule)', borderBottom: '1px solid var(--rule)', marginBottom: 22 }}>
          <Cell label="RECORDINGS SCANNED" value={fmtNum(result.recsScanned)} sub="catalog coverage"/>
          <Cell label="DETECTIONS" value={fmtNum(result.totalDetections)} sub="across 8 platforms"/>
          <Cell label="UNAUTHORIZED" value={fmtNum(result.unauthCount)} sub="action recommended" tone={result.unauthCount > 0 ? '#a32a18' : undefined}/>
          <Cell label="VALUE AT RISK" value={'$' + fmtNum(result.valAtRisk)} sub="recoverable royalties" tone={result.valAtRisk > 0 ? '#d4881f' : undefined}/>
        </div>

        {/* Platform distribution */}
        <div style={{ border: '1px solid var(--rule)', padding: '14px 18px', marginBottom: 18 }}>
          <Mono upper size={9} color="var(--ink-3)" style={{ display: 'block', marginBottom: 10 }}>BY PLATFORM</Mono>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {PLATFORMS.map(p => <PlatformChip key={p.k} platform={p} count={result.byPlatform[p.k] || 0}/>)}
          </div>
        </div>

        {/* Filters */}
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, marginBottom: 12, flexWrap: 'wrap' }}>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {[
              { k: 'all', l: 'All' },
              { k: 'authorized', l: 'Authorized' },
              { k: 'unauth', l: 'Unauthorized' },
              { k: 'cover', l: 'Covers' },
              { k: 'sample', l: 'Samples' },
              { k: 'interpolation', l: 'Interpolations' },
            ].map(f => (
              <button key={f.k} onClick={() => setFilter(f.k)} className="ff-mono upper" style={{
                fontSize: 10, padding: '5px 10px',
                background: filter === f.k ? 'var(--ink)' : 'transparent',
                color: filter === f.k ? '#fff' : 'var(--ink-2)',
                border: '1px solid ' + (filter === f.k ? 'var(--ink)' : 'var(--rule)'),
                cursor: 'pointer',
              }}>{f.l}</button>
            ))}
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            <select value={platFilter} onChange={(e) => setPlatFilter(e.target.value)} className="ff-mono" style={{
              fontSize: 11, padding: '6px 10px', border: '1px solid var(--rule)', background: 'var(--paper)', color: 'var(--ink)', fontFamily: 'inherit',
            }}>
              <option value="all">all platforms</option>
              {PLATFORMS.map(p => <option key={p.k} value={p.k}>{p.l}</option>)}
            </select>
            <input value={search} onChange={(e) => setSearch(e.target.value)} placeholder="search title/artist…" style={{
              fontSize: 11, padding: '6px 10px', border: '1px solid var(--rule)', background: 'var(--paper)', color: 'var(--ink)', fontFamily: 'inherit',
            }}/>
          </div>
        </div>

        {/* Detection log */}
        <div style={{ border: '1px solid var(--rule)' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '110px 1fr 130px 130px 70px 80px 90px', gap: 10, padding: '10px 14px', borderBottom: '1px solid var(--rule)', background: 'var(--bg-2)' }}>
            <Mono upper size={9} color="var(--ink-3)">PLATFORM</Mono>
            <Mono upper size={9} color="var(--ink-3)">RECORDING · DETECTION</Mono>
            <Mono upper size={9} color="var(--ink-3)">KIND</Mono>
            <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>CONFIDENCE</Mono>
            <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>PLAYS</Mono>
            <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>RISK $</Mono>
            <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>WHEN</Mono>
          </div>
          {visible.map((d, i) => (
            <div key={i} style={{
              display: 'grid', gridTemplateColumns: '110px 1fr 130px 130px 70px 80px 90px',
              gap: 10, padding: '8px 14px', borderBottom: '1px solid var(--rule-soft)',
              alignItems: 'center',
              background: d.kind.startsWith('unauth') ? '#a32a1808' : 'transparent',
            }}>
              <PlatformChip platform={d.platform}/>
              <div>
                <div style={{ fontSize: 12, fontWeight: 500 }}>{d.rec.title}</div>
                <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 1 }}>{d.urlFragment}{d.title ? ' · ' + d.title : ''}</div>
              </div>
              <KindChip kind={d.kind}/>
              <MatchBar score={d.conf}/>
              <Mono size={11} color="var(--ink-2)" style={{ textAlign: 'right' }}>{fmtNum(d.plays)}</Mono>
              <Mono size={11} style={{ textAlign: 'right', color: d.valAtRisk > 0 ? '#a32a18' : 'var(--ink-3)', fontWeight: d.valAtRisk > 0 ? 500 : 400 }}>{d.valAtRisk > 0 ? '$' + d.valAtRisk : '—'}</Mono>
              <Mono size={10} color="var(--ink-3)" style={{ textAlign: 'right' }}>{d.days}d ago</Mono>
            </div>
          ))}
          {visible.length === 0 && <div style={{ padding: 30, textAlign: 'center', color: 'var(--ink-3)', fontSize: 12 }}>No detections match these filters.</div>}
        </div>
      </div>
    );
  }

  // ─── 03 COVERS / SAMPLES ───────────────────────────────────────
  function CoversTab() {
    const result = _M(() => scan({ limit: 60 }), []);
    const groups = _M(() => {
      // Bucket by recording, only show those with cover/sample/interp detections
      const m = new Map();
      result.detections.filter(d => d.kind === 'cover' || d.kind === 'sample' || d.kind === 'interpolation').forEach(d => {
        const k = d.rec.id;
        if (!m.has(k)) m.set(k, { rec: d.rec, dets: [] });
        m.get(k).dets.push(d);
      });
      return [...m.values()].sort((a, b) => b.dets.length - a.dets.length);
    }, [result]);

    return (
      <div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', borderTop: '1px solid var(--rule)', borderBottom: '1px solid var(--rule)', marginBottom: 22 }}>
          <Cell label="WORKS WITH COVERS/SAMPLES" value={groups.length} sub="cross-rec matches"/>
          <Cell label="TOTAL COVER DETECTIONS" value={(result.byKind['cover'] || 0)} sub="re-recorded versions"/>
          <Cell label="SAMPLE/INTERP DETECTIONS" value={(result.byKind['sample'] || 0) + (result.byKind['interpolation'] || 0)} sub="potential incoming royalties"/>
        </div>

        {groups.map(g => (
          <div key={g.rec.id} style={{ border: '1px solid var(--rule)', marginBottom: 12 }}>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 90px', gap: 10, padding: '12px 18px', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center', background: 'var(--bg-2)' }}>
              <div>
                <div style={{ fontSize: 14, fontWeight: 500 }}>{g.rec.title}</div>
                <div style={{ fontSize: 10, color: 'var(--ink-2)', marginTop: 1 }}>{g.rec.artist || '—'} · ISRC {g.rec.isrc || '—'} · ISWC {(window.WORKS || []).find(w => w.id === g.rec.workId)?.iswc || '—'}</div>
              </div>
              <Mono size={11} style={{ textAlign: 'right' }}>{g.dets.length} version{g.dets.length === 1 ? '' : 's'}</Mono>
            </div>
            {g.dets.map((d, i) => (
              <div key={i} style={{
                display: 'grid', gridTemplateColumns: '110px 1fr 130px 130px 60px',
                gap: 10, padding: '8px 18px', borderBottom: i < g.dets.length - 1 ? '1px solid var(--rule-soft)' : 0, alignItems: 'center',
              }}>
                <PlatformChip platform={d.platform}/>
                <div>
                  <div style={{ fontSize: 12 }}>{d.title || (d.kind === 'sample' ? 'Sample detected in another track' : 'Interpolation detected')}</div>
                  <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 1 }}>{d.urlFragment}</div>
                </div>
                <KindChip kind={d.kind}/>
                <MatchBar score={d.conf}/>
                <button className="ff-mono upper" style={{
                  fontSize: 9, padding: '4px 8px',
                  background: 'transparent', color: 'var(--ink)',
                  border: '1px solid var(--rule)', cursor: 'pointer',
                }}>FILE CLAIM</button>
              </div>
            ))}
          </div>
        ))}
        {groups.length === 0 && <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-3)', fontSize: 12 }}>No cover/sample detections in the current scan window.</div>}
      </div>
    );
  }

  // ─── 04 UNAUTHORIZED USES ─────────────────────────────────────
  function UnauthTab() {
    const result = _M(() => scan({ limit: 60 }), []);
    const unauth = _M(() => result.detections.filter(d => d.kind.startsWith('unauth')).sort((a, b) => b.valAtRisk - a.valAtRisk), [result]);
    const totalRisk = unauth.reduce((s, d) => s + d.valAtRisk, 0);

    return (
      <div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', borderTop: '1px solid var(--rule)', borderBottom: '1px solid var(--rule)', marginBottom: 22 }}>
          <Cell label="UNAUTHORIZED DETECTIONS" value={unauth.length} sub="across all platforms" tone={unauth.length > 0 ? '#a32a18' : undefined}/>
          <Cell label="TOTAL VALUE AT RISK" value={'$' + fmtNum(totalRisk)} sub="recoverable royalties"/>
          <Cell label="AVG CASE VALUE" value={unauth.length ? '$' + Math.round(totalRisk / unauth.length).toLocaleString() : '—'} sub="per detection"/>
        </div>

        <div style={{ padding: '14px 18px', background: '#a32a1808', borderLeft: '3px solid #a32a18', marginBottom: 18 }}>
          <Mono upper size={10} color="#a32a18">RECOMMENDED ACTION</Mono>
          <div style={{ fontSize: 12, marginTop: 4, color: 'var(--ink-2)', lineHeight: 1.5 }}>
            File Content ID claims for top-value UGC detections. Issue takedown notices for unlicensed sync uses. Dispatch broadcast claims through BMI/PRS/SACEM.
          </div>
        </div>

        <div style={{ border: '1px solid var(--rule)' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '110px 1fr 150px 80px 90px 90px', gap: 10, padding: '10px 14px', borderBottom: '1px solid var(--rule)', background: 'var(--bg-2)' }}>
            <Mono upper size={9} color="var(--ink-3)">PLATFORM</Mono>
            <Mono upper size={9} color="var(--ink-3)">RECORDING / URL</Mono>
            <Mono upper size={9} color="var(--ink-3)">KIND</Mono>
            <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>PLAYS</Mono>
            <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>VALUE</Mono>
            <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>ACTION</Mono>
          </div>
          {unauth.map((d, i) => (
            <div key={i} style={{ display: 'grid', gridTemplateColumns: '110px 1fr 150px 80px 90px 90px', gap: 10, padding: '8px 14px', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center' }}>
              <PlatformChip platform={d.platform}/>
              <div>
                <div style={{ fontSize: 12, fontWeight: 500 }}>{d.rec.title}</div>
                <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 1 }}>{d.urlFragment}</div>
              </div>
              <KindChip kind={d.kind}/>
              <Mono size={11} color="var(--ink-2)" style={{ textAlign: 'right' }}>{fmtNum(d.plays)}</Mono>
              <Mono size={12} style={{ textAlign: 'right', color: '#a32a18', fontWeight: 600 }}>${d.valAtRisk.toLocaleString()}</Mono>
              <button className="ff-mono upper" style={{
                fontSize: 9, padding: '5px 10px',
                background: '#a32a18', color: '#fff', border: 0, cursor: 'pointer',
              }}>CLAIM</button>
            </div>
          ))}
          {unauth.length === 0 && <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-3)', fontSize: 12 }}>No unauthorized uses detected.</div>}
        </div>
      </div>
    );
  }

  // ─── 05 CLAIMS QUEUE ──────────────────────────────────────────
  function ClaimsTab() {
    // Fake a claims-in-flight queue
    const result = _M(() => scan({ limit: 60 }), []);
    const claims = _M(() => {
      const rng = pseed('claims');
      return result.detections
        .filter(d => d.kind.startsWith('unauth'))
        .slice(0, 24)
        .map((d, i) => ({
          ...d,
          claimId: 'CL-' + String(2025000 + i).padStart(7, '0'),
          status: rng() < 0.3 ? 'filed' : rng() < 0.55 ? 'review' : rng() < 0.78 ? 'approved' : rng() < 0.92 ? 'rejected' : 'paid',
          filedDate: 365 - Math.floor(rng() * 90),
        }))
        .sort((a, b) => b.valAtRisk - a.valAtRisk);
    }, [result]);

    const statusMeta = {
      filed:    { c: '#7a8590', l: 'FILED' },
      review:   { c: '#d4881f', l: 'IN REVIEW' },
      approved: { c: '#1a4ed8', l: 'APPROVED' },
      paid:     { c: '#0a8754', l: 'PAID' },
      rejected: { c: '#a32a18', l: 'REJECTED' },
    };
    const counts = {};
    claims.forEach(c => counts[c.status] = (counts[c.status] || 0) + 1);
    const recovered = claims.filter(c => c.status === 'paid').reduce((s, c) => s + c.valAtRisk, 0);

    return (
      <div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', borderTop: '1px solid var(--rule)', borderBottom: '1px solid var(--rule)', marginBottom: 22 }}>
          <Cell label="TOTAL CLAIMS" value={claims.length} sub="filed + in-flight"/>
          <Cell label="IN REVIEW" value={counts.review || 0} sub="awaiting platform" tone={(counts.review || 0) > 0 ? '#d4881f' : undefined}/>
          <Cell label="APPROVED" value={counts.approved || 0} sub="payout pending" tone="#1a4ed8"/>
          <Cell label="PAID" value={counts.paid || 0} sub="recovered to date" tone="#0a8754"/>
          <Cell label="RECOVERED" value={'$' + fmtNum(recovered)} sub="cumulative collections"/>
        </div>

        <div style={{ border: '1px solid var(--rule)' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '120px 100px 1fr 100px 90px 90px 80px', gap: 10, padding: '10px 14px', borderBottom: '1px solid var(--rule)', background: 'var(--bg-2)' }}>
            <Mono upper size={9} color="var(--ink-3)">CLAIM ID</Mono>
            <Mono upper size={9} color="var(--ink-3)">PLATFORM</Mono>
            <Mono upper size={9} color="var(--ink-3)">RECORDING</Mono>
            <Mono upper size={9} color="var(--ink-3)">STATUS</Mono>
            <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>VALUE</Mono>
            <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>FILED</Mono>
            <Mono upper size={9} color="var(--ink-3)" style={{ textAlign: 'right' }}>ACTIONS</Mono>
          </div>
          {claims.map((c, i) => {
            const sm = statusMeta[c.status];
            return (
              <div key={i} style={{ display: 'grid', gridTemplateColumns: '120px 100px 1fr 100px 90px 90px 80px', gap: 10, padding: '8px 14px', borderBottom: '1px solid var(--rule-soft)', alignItems: 'center' }}>
                <Mono size={11} color="var(--ink-2)">{c.claimId}</Mono>
                <PlatformChip platform={c.platform}/>
                <div>
                  <div style={{ fontSize: 12 }}>{c.rec.title}</div>
                  <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 1 }}>{c.urlFragment}</div>
                </div>
                <span className="ff-mono" style={{ fontSize: 9, padding: '2px 6px', background: sm.c, color: '#fff', letterSpacing: '0.08em', display: 'inline-block', width: 'fit-content' }}>{sm.l}</span>
                <Mono size={12} style={{ textAlign: 'right', fontWeight: 500 }}>${c.valAtRisk.toLocaleString()}</Mono>
                <Mono size={10} color="var(--ink-3)" style={{ textAlign: 'right' }}>{c.filedDate}d ago</Mono>
                <button className="ff-mono upper" style={{
                  fontSize: 9, padding: '4px 8px',
                  background: 'transparent', color: 'var(--ink)',
                  border: '1px solid var(--rule)', cursor: 'pointer',
                }}>VIEW</button>
              </div>
            );
          })}
          {claims.length === 0 && <div style={{ padding: 40, textAlign: 'center', color: 'var(--ink-3)', fontSize: 12 }}>No claims filed yet. Open the Unauthorized tab to file your first claim.</div>}
        </div>
      </div>
    );
  }

  function Cell({ label, value, sub, tone }) {
    return (
      <div style={{ padding: '18px 22px', borderRight: '1px solid var(--rule)' }}>
        <Mono upper size={9} color="var(--ink-3)">{label}</Mono>
        <div className="ff-display" style={{ fontSize: 24, fontWeight: 600, letterSpacing: '-0.02em', marginTop: 4, color: tone || 'var(--ink)' }}>{value}</div>
        <div style={{ fontSize: 11, color: 'var(--ink-2)', marginTop: 3 }}>{sub}</div>
      </div>
    );
  }

  // ─── MAIN SCREEN ───────────────────────────────────────────────
  function ScreenAudioFP({ go, payload }) {
    const PageHeader = window.PageHeader;
    const [tab, setTab] = _S(payload?.tab || 'identify');

    const TABS = [
      { k: 'identify', l: 'Identify' },
      { k: 'monitor',  l: 'Monitor' },
      { k: 'covers',   l: 'Covers / Samples' },
      { k: 'unauth',   l: 'Unauthorized' },
      { k: 'claims',   l: 'Claims Queue' },
    ];

    return (
      <div>
        {PageHeader && (
          <PageHeader
            eyebrow={['DETECTION', 'AUDIO FINGERPRINTING', 'CONTENT ID']}
            title="audio fp."
            highlight="audio fp."
            sub="Acoustic identification across DSPs, UGC, and broadcast. 32-band spectral fingerprints match catalog recordings to detections, surface unauthorized use, and route claims."
          />
        )}

        <div style={{ borderBottom: '1px solid var(--rule)', display: 'flex', gap: 0, marginBottom: 24 }}>
          {TABS.map(t => (
            <button key={t.k} onClick={() => setTab(t.k)} style={{
              padding: '14px 22px', background: 'transparent', border: 0,
              borderBottom: '2px solid ' + (tab === t.k ? 'var(--ink)' : 'transparent'),
              cursor: 'pointer', color: tab === t.k ? 'var(--ink)' : 'var(--ink-3)',
              fontSize: 13, fontWeight: tab === t.k ? 600 : 400,
            }}>{t.l}</button>
          ))}
        </div>

        {tab === 'identify' && <IdentifyTab/>}
        {tab === 'monitor'  && <MonitorTab/>}
        {tab === 'covers'   && <CoversTab/>}
        {tab === 'unauth'   && <UnauthTab/>}
        {tab === 'claims'   && <ClaimsTab/>}
      </div>
    );
  }

  // ─── EMBED: per-recording fingerprint card ────────────────────
  function AudioFPCard({ rec }) {
    const fp = _M(() => rec ? generate(rec) : null, [rec && rec.id]);
    const dets = _M(() => rec ? scanRecording(rec) : [], [rec && rec.id]);
    if (!fp) return null;

    const byKind = {};
    dets.forEach(d => byKind[d.kind] = (byKind[d.kind] || 0) + 1);
    const unauthCt = Object.entries(byKind).filter(([k]) => k.startsWith('unauth')).reduce((s, [, v]) => s + v, 0);

    return (
      <div style={{ border: '1px solid var(--rule)' }}>
        <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--rule-soft)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <Mono upper size={9} color="var(--ink-3)">AUDIO FINGERPRINT</Mono>
          <Mono size={11} style={{ letterSpacing: '0.04em' }}>{fp.signature}</Mono>
        </div>
        <FPGrid fp={fp} height={70}/>
        <div style={{ padding: '10px 16px', borderTop: '1px solid var(--rule-soft)', display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 10 }}>
          <div>
            <Mono upper size={8} color="var(--ink-3)">DETECTIONS</Mono>
            <div className="ff-display" style={{ fontSize: 18, fontWeight: 600, marginTop: 2 }}>{dets.length}</div>
          </div>
          <div>
            <Mono upper size={8} color="var(--ink-3)">PLATFORMS</Mono>
            <div className="ff-display" style={{ fontSize: 18, fontWeight: 600, marginTop: 2 }}>{new Set(dets.map(d => d.platform.k)).size}</div>
          </div>
          <div>
            <Mono upper size={8} color="var(--ink-3)">UNAUTH</Mono>
            <div className="ff-display" style={{ fontSize: 18, fontWeight: 600, marginTop: 2, color: unauthCt > 0 ? '#a32a18' : 'var(--ink)' }}>{unauthCt}</div>
          </div>
          <div>
            <Mono upper size={8} color="var(--ink-3)">FRAMES × BANDS</Mono>
            <div className="ff-mono" style={{ fontSize: 13, marginTop: 4 }}>{fp.frames}×{BANDS}</div>
          </div>
        </div>
      </div>
    );
  }

  window.ScreenAudioFP = ScreenAudioFP;
  window.AudioFPCard = AudioFPCard;

  console.log('[AudioFP] loaded · ' + BANDS + '-band fingerprints · ' + PLATFORMS.length + ' platforms');
})();
