// subpubs.jsx — Sub-publishing network
// ─────────────────────────────────────────────────────────────────────
// Sub-publishers are local administrators we appoint in non-controlled
// territories (e.g. JP, KR, LATAM) — they collect at the local society,
// take a fee, and remit to us. This screen surfaces the network as:
//   01 PARTNERS      — list of sub-pub partners + scorecards
//   02 TERRITORY MAP — coverage per ISO territory + gaps
//   03 CASH FLOW     — incoming statements, retention, advances outstanding
//   04 ISSUES        — unmatched cue sheets, late statements, fee disputes
//
// Schema (mock — would map to astro.subpub_partners, subpub_deals,
// subpub_territories, subpub_statements):
//   partner: id, name, hq_iso, kind (major|indie|admin), works, deals[], score
//   deal:    id, partner_id, territory_iso[], rights[], fee_pct, advance,
//            term_start, term_end, status, statements[]

const { useState: useSPS, useMemo: useSPM } = React;

// ─────────── partners (deterministic seed)
const SP_PARTNERS = [
  {
    id:'sp_kobalt_jp', name:'Kobalt Music Tokyo', kind:'major', hqIso:'JP', hqCity:'Tokyo',
    contact:'h.tanaka@kobaltmusic.jp', acct:'Hiro Tanaka', since:'2019-04-01',
    pro:'JASRAC · NexTone', ipi:'00919012245',
    score: 94, healthLabel:'Strong',
  },
  {
    id:'sp_peer_latam', name:'Peermusic Latin America', kind:'major', hqIso:'MX', hqCity:'Mexico City',
    contact:'r.cruz@peermusic.com', acct:'Renata Cruz', since:'2017-09-15',
    pro:'SACM · ECAD · SADAIC', ipi:'00731298011',
    score: 88, healthLabel:'Strong',
  },
  {
    id:'sp_warner_kr', name:'Warner Chappell Korea', kind:'major', hqIso:'KR', hqCity:'Seoul',
    contact:'minjun.park@warnerchappell.kr', acct:'Min-jun Park', since:'2021-01-10',
    pro:'KOMCA',  ipi:'00822914477',
    score: 81, healthLabel:'Healthy',
  },
  {
    id:'sp_budde_de', name:'Budde Music', kind:'indie', hqIso:'DE', hqCity:'Berlin',
    contact:'office@buddemusic.de', acct:'Lena Hoffmann', since:'2018-06-20',
    pro:'GEMA · SUISA · AKM', ipi:'00540998312',
    score: 76, healthLabel:'Healthy',
  },
  {
    id:'sp_mushroom_au', name:'Mushroom Music', kind:'indie', hqIso:'AU', hqCity:'Melbourne',
    contact:'sub@mushroomgroup.com', acct:'Tom Ellery', since:'2020-03-01',
    pro:'APRA · AMCOS', ipi:'00611234560',
    score: 71, healthLabel:'Watch',
  },
  {
    id:'sp_subpop_br', name:'Bossa Nova Pub', kind:'indie', hqIso:'BR', hqCity:'Rio de Janeiro',
    contact:'admin@bossanovapub.br', acct:'Fernanda Lima', since:'2022-08-12',
    pro:'ECAD · UBC', ipi:'00558440011',
    score: 64, healthLabel:'Watch',
  },
  {
    id:'sp_strictly_in', name:'Strictly Music India', kind:'admin', hqIso:'IN', hqCity:'Mumbai',
    contact:'a.iyer@strictlymusic.in', acct:'Aarav Iyer', since:'2023-02-01',
    pro:'IPRS', ipi:'00911878811',
    score: 52, healthLabel:'At risk',
  },
  {
    id:'sp_sentric_emea', name:'Sentric EMEA', kind:'admin', hqIso:'GB', hqCity:'Liverpool',
    contact:'admin@sentricmusic.com', acct:'Owen Pritchard', since:'2019-11-04',
    pro:'PRS · MCPS · STIM · TONO', ipi:'00440812499',
    score: 87, healthLabel:'Strong',
  },
];

// ─────────── deals — territorial scope + commercial terms
//
// Each sub-pub deal IS an agreement (kind='Sub-publishing · Admin'), so each row
// here mirrors itself into window.AGREEMENTS at boot. The deal object retains
// operational fields the sub-pub dashboard needs (feePct, advance recoupment,
// statement schedule, audit calendar) — those don't belong on the legal contract
// record. The `agreementId` field is the single canonical link between the two.
const SP_DEALS = [
  {
    id:'spd_001', agreementId:'AG-2024-0511', partnerId:'sp_kobalt_jp', name:'JP/KR · Mech+Perf · 3yr',
    territories:['JP','KR'], rights:['MECH','PERF','SYNC'],
    feePct: 12, advance: 35000, advanceRecouped: 28400,
    termStart:'2024-04-01', termEnd:'2027-03-31', signedDate:'2024-03-22',
    works: 412, status:'active',
    sunsetClause:'12mo post-term collection', sunsetActive:false,
    audit:{ lastDate:'2025-09-12', findings:'Clean', dueDate:'2026-09-12' },
  },
  {
    id:'spd_002', agreementId:'AG-2022-0488', partnerId:'sp_peer_latam', name:'LATAM · Full · 5yr',
    territories:['MX','BR','AR','CL','CO','PE'], rights:['MECH','PERF','SYNC','PRINT'],
    feePct: 15, advance: 60000, advanceRecouped: 60000,
    termStart:'2022-09-15', termEnd:'2027-09-14', signedDate:'2022-08-30',
    works: 312, status:'active',
    sunsetClause:'Standard 18mo', sunsetActive:false,
    audit:{ lastDate:'2025-06-04', findings:'Minor — under-reported on 2 cuesheets', dueDate:'2026-06-04' },
  },
  {
    id:'spd_003', agreementId:'AG-2023-0902', partnerId:'sp_warner_kr', name:'KR · Mech+Perf · 3yr',
    territories:['KR'], rights:['MECH','PERF'],
    feePct: 14, advance: 25000, advanceRecouped: 18900,
    termStart:'2024-01-10', termEnd:'2027-01-09', signedDate:'2023-12-22',
    works: 188, status:'active',
    sunsetClause:'9mo post-term collection', sunsetActive:false,
    audit:{ lastDate:null, findings:null, dueDate:'2026-01-10' },
  },
  {
    id:'spd_004', agreementId:'AG-2023-0617', partnerId:'sp_budde_de', name:'DACH · Full · 4yr',
    territories:['DE','AT','CH','LI'], rights:['MECH','PERF','SYNC'],
    feePct: 13, advance: 40000, advanceRecouped: 31200,
    termStart:'2023-06-20', termEnd:'2027-06-19', signedDate:'2023-05-30',
    works: 274, status:'active',
    sunsetClause:'12mo', sunsetActive:false,
    audit:{ lastDate:'2025-03-15', findings:'Clean', dueDate:'2026-03-15' },
  },
  {
    id:'spd_005', agreementId:'AG-2024-0205', partnerId:'sp_mushroom_au', name:'ANZ · Mech+Perf · 3yr',
    territories:['AU','NZ'], rights:['MECH','PERF'],
    feePct: 15, advance: 18000, advanceRecouped: 12100,
    termStart:'2024-03-01', termEnd:'2027-02-28', signedDate:'2024-02-10',
    works: 142, status:'active',
    sunsetClause:'12mo', sunsetActive:false,
    audit:{ lastDate:'2025-11-22', findings:'Reporting cadence slow', dueDate:'2026-11-22' },
  },
  {
    id:'spd_006', agreementId:'AG-2024-0788', partnerId:'sp_subpop_br', name:'BR · Sync · 2yr',
    territories:['BR'], rights:['SYNC'],
    feePct: 20, advance: 0, advanceRecouped: 0,
    termStart:'2024-08-12', termEnd:'2026-08-11', signedDate:'2024-08-01',
    works: 64, status:'expiring', // <90d
    sunsetClause:'6mo', sunsetActive:false,
    audit:{ lastDate:null, findings:null, dueDate:'2026-08-12' },
  },
  {
    id:'spd_007', agreementId:'AG-2025-0033', partnerId:'sp_strictly_in', name:'IN · Mech+Perf · 3yr',
    territories:['IN','BD','LK','NP','PK'], rights:['MECH','PERF'],
    feePct: 18, advance: 8000, advanceRecouped: 1200,
    termStart:'2025-02-01', termEnd:'2028-01-31', signedDate:'2025-01-22',
    works: 88, status:'active',
    sunsetClause:'12mo', sunsetActive:false,
    audit:{ lastDate:null, findings:null, dueDate:'2027-02-01' },
    flags:['Late Q4 statement','Cuesheet match rate 71% (target 90%)'],
  },
  {
    id:'spd_008', agreementId:'AG-2021-0974', partnerId:'sp_sentric_emea', name:'Nordics+UK ex. SACEM · Full · 5yr',
    territories:['GB','IE','SE','NO','DK','FI','IS','EE','LV','LT'],
    rights:['MECH','PERF','SYNC','PRINT'],
    feePct: 10, advance: 0, advanceRecouped: 0,
    termStart:'2021-11-04', termEnd:'2026-11-03', signedDate:'2021-10-15',
    works: 612, status:'expiring',
    sunsetClause:'18mo', sunsetActive:false,
    audit:{ lastDate:'2025-04-08', findings:'Clean', dueDate:'2026-04-08' },
  },
  // Historical / sunset
  {
    id:'spd_h01', agreementId:'AG-2019-0212', partnerId:'sp_peer_latam', name:'LATAM · Sync · 2yr (sunset)',
    territories:['MX','BR'], rights:['SYNC'],
    feePct: 25, advance: 5000, advanceRecouped: 5000,
    termStart:'2020-01-01', termEnd:'2022-12-31', signedDate:'2019-12-01',
    works: 24, status:'sunset',
    sunsetClause:'6mo expired 2023-06-30', sunsetActive:false,
    audit:{ lastDate:'2023-02-04', findings:'Closed clean', dueDate:null },
  },
];

// ─────────── mirror SP_DEALS into the global AGREEMENTS array
// Sub-pub deals are agreements. Build a real agreement record for each one and
// merge it into window.AGREEMENTS so they show up in Catalog → Agreements and
// are reachable from agreement-detail. screens3.jsx loads first, so AGREEMENTS
// is guaranteed to exist by now (verify in index.html load order).
//
// Exposed on window so the New Sub-Pub wizard can re-run it after pushing a
// freshly-created deal — otherwise new deals stay invisible to Catalog until
// page refresh.
function mirrorSubPubDealsIntoAgreements() {
  // DISABLED — sub-pub seed deals are mock data and were polluting Catalog →
  // Agreements with fake AG-YYYY-XXXX rows. Real agreements now flow in only
  // from the Rocket-Science CSV import (rs-bridge.jsx). The function is kept
  // as a no-op so the New Sub-Pub wizard's call site doesn't crash.
  return;
  // eslint-disable-next-line no-unreachable
  const AGS = window.AGREEMENTS;
  if (!Array.isArray(AGS)) return; // bail silently if load order broke

  // Map US-style status to agreement status (active|expiring|sunset → active|expiring|expired)
  const statusMap = { active:'active', expiring:'expiring', sunset:'expired' };

  function valueLabel(d) {
    if (d.advance > 0) return '$' + (d.advance/1000).toFixed(0) + 'K adv. · ' + d.feePct + '%';
    return d.feePct + '% admin';
  }
  function shareLabel(d) {
    return d.feePct + '% admin';
  }
  function termLabel(d) {
    const yrs = (new Date(d.termEnd) - new Date(d.termStart)) / (365.25 * 86400000);
    return Math.round(yrs) + 'y';
  }
  function territoryLabel(d) {
    if (d.territories.length === 1) return d.territories[0];
    if (d.territories.length <= 3) return d.territories.join(' · ');
    return d.territories.length + ' territories';
  }

  SP_DEALS.forEach(d => {
    if (AGS.find(a => a.id === d.agreementId)) return; // already there
    const partner = SP_PARTNERS.find(p => p.id === d.partnerId);
    if (!partner) return;
    const advanceSchedule = d.advance > 0 ? [
      {
        n:1, label:'On signing', amount:Math.round(d.advance*0.6),
        due: d.termStart, paid: d.advanceRecouped > 0,
        paidOn: d.advanceRecouped > 0 ? d.termStart : null,
      },
      {
        n:2, label:'Year 2 milestone',
        amount:Math.round(d.advance*0.4),
        due: new Date(new Date(d.termStart).getFullYear()+1+'-'+d.termStart.slice(5)).toISOString().slice(0,10),
        paid: d.advanceRecouped >= d.advance*0.6,
        paidOn: d.advanceRecouped >= d.advance*0.6 ? new Date(new Date(d.termStart).getFullYear()+1+'-'+d.termStart.slice(5)).toISOString().slice(0,10) : null,
      },
    ] : [];

    AGS.push({
      id: d.agreementId,
      kind: 'Sub-publishing · Admin',
      a: 'Pluralis Music', // canonical originator
      b: partner.name,
      territory: territoryLabel(d),
      share: shareLabel(d),
      start: d.termStart, end: d.termEnd, term: termLabel(d),
      status: statusMap[d.status] || 'active',
      value: valueLabel(d),
      // Rich fields
      typeCwr: 'PG',
      refNumber: 'PM-' + partner.id.toUpperCase().replace('SP_','').slice(0,8) + '-SUB-' + d.signedDate.slice(0,4),
      societyAgreementNumber: partner.pro ? partner.pro.split(' · ')[0] + '-SUB-' + d.agreementId.slice(-4) : null,
      autoRenew: false, renewNoticeMonths: 6,
      retentionYears: parseInt(d.sunsetClause) || 1,
      retentionEndDate: (() => {
        const m = d.sunsetClause.match(/(\d+)\s*mo/i);
        const months = m ? parseInt(m[1]) : 12;
        const end = new Date(d.termEnd);
        end.setMonth(end.getMonth() + months);
        return end.toISOString().slice(0,10);
      })(),
      priorRoyaltyStatus: 'N',
      sharesCanChange: false,
      advanceGiven: d.advance > 0,
      salesOrManufacture: null,
      jurisdiction: partner.hqIso === 'JP' ? 'Japan · Tokyo'
                  : partner.hqIso === 'DE' ? 'Germany · Berlin'
                  : partner.hqIso === 'GB' ? 'England & Wales'
                  : partner.hqIso === 'KR' ? 'South Korea · Seoul'
                  : partner.hqCity + ', ' + partner.hqIso,
      disputeResolution: 'Local arbitration · ' + (partner.pro ? partner.pro.split(' · ')[0] : 'ICC'),
      parties: [
        { role:'Original Publisher', name:'Pluralis Music Ltd.', kind:'publisher', ipi:'00231887442', share:100, isControlled:true },
        { role:'Sub-Publisher',      name:partner.name,           kind:'publisher', ipi:partner.ipi, share:d.feePct, isControlled:false },
      ],
      territories: d.territories.map(iso => ({
        code: iso, label: nameFor(iso) || iso,
        rights: d.rights.includes('MECH') && d.rights.includes('PERF') ? 'Mech + Perf · all uses'
              : d.rights.join(' + '),
        collection: d.feePct, ownership: 0, excluded: [],
      })),
      // Empty works array — sub-pub deals are blanket; specific works are derived from cuesheets
      works: [],
      versions: [
        { n:1, date: d.signedDate, author:'Avery Cohen', note:'Final · counter-signed PDF', current:true },
      ],
      signatures: [
        { party:'Pluralis Music Ltd.', signer:'Avery Cohen', role:'Director', method:'DocuSign · email', at:d.signedDate+'T11:00:00Z', verified:true },
        { party:partner.name, signer:partner.acct, role:'Account Manager', method:'DocuSign · email', at:d.signedDate+'T15:00:00Z', verified:true },
      ],
      assets: [
        { kind:'PDF · Final', name:`${partner.id}-${d.signedDate.slice(0,4)}-SUB.pdf`, size:'940 KB', uploaded:d.signedDate, by:'Avery Cohen' },
        { kind:'Schedule A · Territories', name:'territory-schedule.xlsx', size:'48 KB', uploaded:d.signedDate, by:'Avery Cohen' },
      ],
      advanceSchedule,
      audit: [
        { at:d.signedDate+'T15:00:00Z', who:partner.acct, what:'Signed v1' },
        { at:d.signedDate+'T11:00:00Z', who:'Avery Cohen', what:'Counter-signed v1' },
        { at:d.signedDate+'T09:00:00Z', who:'Avery Cohen', what:'Draft sent to counter-party' },
        ...(d.audit?.lastDate ? [{ at:d.audit.lastDate+'T09:00:00Z', who:'Audit team', what:'Audit completed · '+(d.audit.findings || 'Clean') }] : []),
      ],
      // Operational link back to sub-pub dashboard fields
      subpub: {
        partnerId: d.partnerId,
        feePct: d.feePct,
        advance: d.advance,
        advanceRecouped: d.advanceRecouped,
        works: d.works,
        sunsetClause: d.sunsetClause,
        flags: d.flags || [],
      },
    });
  });
}
mirrorSubPubDealsIntoAgreements();
window.mirrorSubPubDealsIntoAgreements = mirrorSubPubDealsIntoAgreements;

// ─────────── statement history (last 4 quarters) per deal
function buildSpStatements() {
  // Deterministic — mulberry-style tiny rng
  const rng = (seed) => { let s=seed; return () => { s=(s*9301+49297)%233280; return s/233280; }; };
  const periods = ['2025Q4','2025Q3','2025Q2','2025Q1','2024Q4'];
  const out = [];
  SP_DEALS.forEach((d, di) => {
    if (d.status==='sunset') return;
    const r = rng(1000 + di*73);
    const baseGross = (d.works || 50) * (60 + r()*180); // USD
    periods.forEach((p, pi) => {
      const decay = 1 - pi*0.06;
      const gross = baseGross * decay * (0.85 + r()*0.3);
      const fee   = gross * d.feePct/100;
      const net   = gross - fee;
      // Advance recoup: subtract until balance hits 0
      const advRemaining = Math.max(0, d.advance - d.advanceRecouped - (pi===0?0:net*pi*0.4));
      const recouped = Math.min(net, advRemaining);
      const remitted = net - recouped;
      const dueDays = pi === 0 ? 60 : 0; // current quarter outstanding
      const status = pi === 0 ? (d.id==='spd_007' ? 'late' : 'pending')
                  : pi === 1 ? 'received'
                  : 'reconciled';
      out.push({
        id: `spst_${d.id}_${p}`, dealId: d.id, partnerId: d.partnerId,
        period: p,
        gross: Math.round(gross), fee: Math.round(fee), net: Math.round(net),
        recouped: Math.round(recouped), remitted: Math.round(remitted),
        currency: 'USD',
        receivedDate: pi === 0 ? null : `2025-${String(12-(pi-1)*3).padStart(2,'0')}-${15+pi}`,
        dueDate: pi === 0 ? '2026-05-15' : null,
        status,
        lines: Math.round((d.works||50) * (10+r()*40)),
        unmatched: Math.round((d.works||50) * (0.5 + r()*1.5)),
      });
    });
  });
  return out;
}
const SP_STATEMENTS = buildSpStatements();

// ─────────── derived: territory coverage map
function buildSpCoverage() {
  const map = new Map();
  SP_DEALS.filter(d => d.status==='active' || d.status==='expiring').forEach(d => {
    d.territories.forEach(iso => {
      if (!map.has(iso)) map.set(iso, []);
      map.get(iso).push(d);
    });
  });
  return map;
}

// Useful "key markets" we benchmark gap analysis against
const KEY_MARKETS = [
  { iso:'US', name:'United States', flag:'🇺🇸', tier:1 },
  { iso:'GB', name:'United Kingdom', flag:'🇬🇧', tier:1 },
  { iso:'DE', name:'Germany',        flag:'🇩🇪', tier:1 },
  { iso:'JP', name:'Japan',          flag:'🇯🇵', tier:1 },
  { iso:'FR', name:'France',         flag:'🇫🇷', tier:1 },
  { iso:'BR', name:'Brazil',         flag:'🇧🇷', tier:1 },
  { iso:'KR', name:'South Korea',    flag:'🇰🇷', tier:2 },
  { iso:'CA', name:'Canada',         flag:'🇨🇦', tier:1 },
  { iso:'AU', name:'Australia',      flag:'🇦🇺', tier:2 },
  { iso:'MX', name:'Mexico',         flag:'🇲🇽', tier:2 },
  { iso:'IT', name:'Italy',          flag:'🇮🇹', tier:2 },
  { iso:'ES', name:'Spain',          flag:'🇪🇸', tier:2 },
  { iso:'NL', name:'Netherlands',    flag:'🇳🇱', tier:2 },
  { iso:'SE', name:'Sweden',         flag:'🇸🇪', tier:2 },
  { iso:'NO', name:'Norway',         flag:'🇳🇴', tier:3 },
  { iso:'DK', name:'Denmark',        flag:'🇩🇰', tier:3 },
  { iso:'FI', name:'Finland',        flag:'🇫🇮', tier:3 },
  { iso:'AT', name:'Austria',        flag:'🇦🇹', tier:3 },
  { iso:'CH', name:'Switzerland',    flag:'🇨🇭', tier:2 },
  { iso:'IE', name:'Ireland',        flag:'🇮🇪', tier:3 },
  { iso:'AR', name:'Argentina',      flag:'🇦🇷', tier:3 },
  { iso:'CL', name:'Chile',          flag:'🇨🇱', tier:3 },
  { iso:'CO', name:'Colombia',       flag:'🇨🇴', tier:3 },
  { iso:'PE', name:'Peru',           flag:'🇵🇪', tier:3 },
  { iso:'IN', name:'India',          flag:'🇮🇳', tier:1 },
  { iso:'NZ', name:'New Zealand',    flag:'🇳🇿', tier:3 },
  { iso:'PL', name:'Poland',         flag:'🇵🇱', tier:3 },
  { iso:'ZA', name:'South Africa',   flag:'🇿🇦', tier:3 },
];

// Self-administered territories — handled directly via parent publisher / PRO
const SELF_ADMIN = ['US','CA','GB','FR','IT','ES','NL','PL','ZA'];

// Stash for cross-screen access
window.SP_PARTNERS   = SP_PARTNERS;
window.SP_DEALS      = SP_DEALS;
window.SP_STATEMENTS = SP_STATEMENTS;
// Pulse: count active partners + surface most-urgent flag (late stmts > overdue audits > expiring deals)
try {
  const activePartners = new Set(SP_DEALS.filter(d => d.status === 'active' || d.status === 'expiring').map(d => d.partnerId)).size;
  const expiring = SP_DEALS.filter(d => d.status === 'expiring').length;
  const lateStmts = SP_STATEMENTS.filter(s => s.status === 'late').length;
  const today = new Date('2026-04-30');
  const overdueAudits = SP_DEALS.filter(d => {
    if (d.status !== 'active' || !d.audit?.dueDate) return false;
    return new Date(d.audit.dueDate) < today;
  }).length;
  window.__SUBPUB_ACTIVE_PARTNERS = activePartners;
  window.__SUBPUB_EXPIRING        = expiring;
  window.__SUBPUB_LATE_STMTS      = lateStmts;
  window.__SUBPUB_OVERDUE_AUDITS  = overdueAudits;
  // Pick the highest-severity flag to surface on the Pulse tile
  if (lateStmts > 0)         window.__SUBPUB_URGENT = { kind:'late',     n:lateStmts,     label:`${lateStmts} late stmt${lateStmts===1?'':'s'}` };
  else if (overdueAudits > 0) window.__SUBPUB_URGENT = { kind:'audit',   n:overdueAudits, label:`${overdueAudits} audit${overdueAudits===1?'':'s'} overdue` };
  else if (expiring > 0)     window.__SUBPUB_URGENT = { kind:'expiring', n:expiring,      label:`${expiring} deal${expiring===1?'':'s'} expiring <90d` };
  else                       window.__SUBPUB_URGENT = null;
} catch {
  window.__SUBPUB_ACTIVE_PARTNERS = 0;
  window.__SUBPUB_EXPIRING = 0;
  window.__SUBPUB_LATE_STMTS = 0;
  window.__SUBPUB_OVERDUE_AUDITS = 0;
  window.__SUBPUB_URGENT = null;
}

// ─────────── helpers
function fmtUsd(n, opts) {
  if (n == null) return '—';
  const o = opts || {};
  if (o.compact && Math.abs(n) >= 1000) return '$' + (n/1000).toFixed(n>=10000?0:1) + 'k';
  return '$' + Number(n).toLocaleString('en-US');
}
function flagFor(iso) {
  const t = window.REF && window.REF.ready && window.REF.territoryByIso2 && window.REF.territoryByIso2.get(iso);
  if (t && t.emoji_flag) return t.emoji_flag;
  // KEY_MARKETS may be in TDZ if called from the agreements-mirror IIFE above; guard with typeof.
  const km = (typeof KEY_MARKETS !== 'undefined') ? KEY_MARKETS.find(k => k.iso === iso) : null;
  return km ? km.flag : '';
}
function nameFor(iso) {
  const t = window.REF && window.REF.ready && window.REF.territoryByIso2 && window.REF.territoryByIso2.get(iso);
  if (t && t.common_name) return t.common_name;
  const km = (typeof KEY_MARKETS !== 'undefined') ? KEY_MARKETS.find(k => k.iso === iso) : null;
  return km ? km.name : iso;
}
function partnerById(id) { return SP_PARTNERS.find(p => p.id === id); }
function dealById(id)    { return SP_DEALS.find(d => d.id === id); }

function scoreColor(s) {
  if (s >= 85) return '#3F8F6E';
  if (s >= 70) return 'var(--ink)';
  if (s >= 55) return '#c4831a';
  return '#b53a3a';
}

function StatusPill({ status, size }) {
  const map = {
    active:   { l:'ACTIVE',   bg:'var(--ink)',     fg:'var(--bg)' },
    expiring: { l:'EXPIRING', bg:'var(--accent)',  fg:'var(--accent-ink)' },
    sunset:   { l:'SUNSET',   bg:'var(--bg-2)',    fg:'var(--ink-3)' },
    pending:  { l:'PENDING',  bg:'var(--bg-2)',    fg:'var(--ink-2)' },
    received: { l:'RECEIVED', bg:'var(--bg-2)',    fg:'var(--ink-2)' },
    reconciled: { l:'RECONCILED', bg:'#3F8F6E', fg:'#fff' },
    late:     { l:'LATE',     bg:'#b53a3a',        fg:'#fff' },
  };
  const m = map[status] || map.active;
  return <span className="ff-mono upper" style={{
    fontSize: size === 'sm' ? 8 : 9, letterSpacing:'.1em',
    padding: size === 'sm' ? '2px 5px' : '3px 6px',
    background: m.bg, color: m.fg
  }}>{m.l}</span>;
}

// ─────────── KPI tile (matches agreement-detail style)
function SpKpi({ l, v, sub, accent }) {
  return (
    <div style={{ padding:'18px 22px', background: accent ? 'var(--accent)' : 'transparent', color: accent ? 'var(--accent-ink)' : 'var(--ink)' }}>
      <div className="ff-mono upper" style={{ fontSize:9, letterSpacing:'.12em', color: accent ? 'var(--accent-ink)' : 'var(--ink-3)', marginBottom:6, opacity: accent ? .85 : 1 }}>{l}</div>
      <div className="ff-display num" style={{ fontSize:26, fontWeight:600, letterSpacing:'-0.02em', lineHeight:1.05 }}>{v}</div>
      {sub && <div className="ff-mono" style={{ fontSize:10, color: accent ? 'var(--accent-ink)' : 'var(--ink-3)', marginTop:4, opacity: accent ? .75 : 1 }}>{sub}</div>}
    </div>
  );
}

// ─────────────────────────────────────────────── 01 PARTNERS TAB
function SpPartnersTab({ go, onOpenPartner }) {
  const [sort, setSort] = useSPS('score');
  const [kindFilter, setKindFilter] = useSPS('all');
  const partners = useSPM(() => {
    const enriched = SP_PARTNERS.map(p => {
      const deals = SP_DEALS.filter(d => d.partnerId === p.id && d.status !== 'sunset');
      const territories = new Set();
      deals.forEach(d => d.territories.forEach(t => territories.add(t)));
      const works = deals.reduce((s, d) => s + (d.works || 0), 0);
      // last 4 quarters net
      const net = SP_STATEMENTS
        .filter(s => s.partnerId === p.id && s.status !== 'pending')
        .reduce((sum, s) => sum + (s.remitted || 0), 0);
      const advanceOutstanding = deals.reduce((s, d) => s + Math.max(0, (d.advance||0) - (d.advanceRecouped||0)), 0);
      return { ...p, deals, territories: Array.from(territories), works, net, advanceOutstanding };
    });
    let filtered = enriched;
    if (kindFilter !== 'all') filtered = filtered.filter(p => p.kind === kindFilter);
    if (sort === 'score')      filtered.sort((a,b) => b.score - a.score);
    if (sort === 'net')        filtered.sort((a,b) => b.net - a.net);
    if (sort === 'works')      filtered.sort((a,b) => b.works - a.works);
    if (sort === 'territories')filtered.sort((a,b) => b.territories.length - a.territories.length);
    return filtered;
  }, [sort, kindFilter]);

  return (
    <div>
      {/* Toolbar */}
      <div style={{display:'flex',alignItems:'center',gap:14,padding:'14px 0',borderBottom:'1px solid var(--rule)'}}>
        <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.12em'}}>FILTER</div>
        {[
          {k:'all',l:'ALL · '+SP_PARTNERS.length},
          {k:'major',l:'MAJORS · '+SP_PARTNERS.filter(p=>p.kind==='major').length},
          {k:'indie',l:'INDIES · '+SP_PARTNERS.filter(p=>p.kind==='indie').length},
          {k:'admin',l:'ADMIN · '+SP_PARTNERS.filter(p=>p.kind==='admin').length},
        ].map(f => (
          <button key={f.k} onClick={()=>setKindFilter(f.k)} className="ff-mono upper" style={{
            fontSize:10, letterSpacing:'.1em', padding:'5px 10px',
            background: kindFilter===f.k?'var(--ink)':'transparent',
            color: kindFilter===f.k?'var(--bg)':'var(--ink-2)',
            border:'1px solid '+(kindFilter===f.k?'var(--ink)':'var(--rule)'), cursor:'pointer'
          }}>{f.l}</button>
        ))}
        <span style={{flex:1}}/>
        <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.12em'}}>SORT</div>
        <select value={sort} onChange={e=>setSort(e.target.value)} className="ff-mono"
          style={{padding:'5px 8px',fontSize:11,border:'1px solid var(--rule)',background:'var(--bg)',cursor:'pointer'}}>
          <option value="score">Score</option>
          <option value="net">Net remitted</option>
          <option value="works">Works under deal</option>
          <option value="territories">Territory count</option>
        </select>
      </div>

      {/* Partner cards */}
      <div style={{display:'grid',gridTemplateColumns:'1fr',gap:0}}>
        {partners.map(p => (
          <div key={p.id} onClick={()=>onOpenPartner(p.id)}
            style={{padding:'20px 4px',borderBottom:'1px solid var(--rule)',cursor:'pointer',display:'grid',
              gridTemplateColumns:'auto 1fr auto auto auto auto',gap:24,alignItems:'center'}}
            onMouseEnter={e=>e.currentTarget.style.background='var(--surface)'}
            onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
            {/* Flag + glyph */}
            <div style={{width:48,height:48,background:'var(--bg-2)',display:'flex',alignItems:'center',justifyContent:'center',fontSize:24,flexShrink:0}}>
              {flagFor(p.hqIso)}
            </div>
            {/* Identity */}
            <div style={{minWidth:0}}>
              <div style={{display:'flex',alignItems:'baseline',gap:8,marginBottom:3}}>
                <div className="ff-display" style={{fontSize:17,fontWeight:600,letterSpacing:'-0.01em'}}>{p.name}</div>
                <span className="ff-mono upper" style={{fontSize:8,letterSpacing:'.12em',padding:'2px 5px',
                  background:p.kind==='major'?'var(--ink)':p.kind==='indie'?'var(--bg-2)':'transparent',
                  color:p.kind==='major'?'var(--bg)':'var(--ink-2)',
                  border:p.kind==='admin'?'1px solid var(--rule)':'none'}}>
                  {p.kind === 'major' ? 'MAJOR' : p.kind === 'indie' ? 'INDIE' : 'ADMIN'}
                </span>
              </div>
              <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',letterSpacing:'.02em'}}>
                {p.hqCity} · {p.pro} · IPI {p.ipi} · since {p.since.slice(0,4)}
              </div>
            </div>
            {/* Territories */}
            <div style={{textAlign:'right',minWidth:140}}>
              <div className="ff-mono num" style={{fontSize:18,fontWeight:600,letterSpacing:'-0.01em'}}>{p.territories.length}</div>
              <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginTop:2}}>TERRITORIES</div>
              <div className="ff-mono" style={{fontSize:10,color:'var(--ink-2)',marginTop:4,letterSpacing:'.04em',maxWidth:140,textAlign:'right'}}>
                {p.territories.slice(0,4).map(t => flagFor(t)+' '+t).join(' · ')}
                {p.territories.length>4 ? ` · +${p.territories.length-4}` : ''}
              </div>
            </div>
            {/* Works */}
            <div style={{textAlign:'right',minWidth:90}}>
              <div className="ff-mono num" style={{fontSize:18,fontWeight:600}}>{p.works.toLocaleString()}</div>
              <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginTop:2}}>WORKS</div>
            </div>
            {/* Net 12mo */}
            <div style={{textAlign:'right',minWidth:120}}>
              <div className="ff-mono num" style={{fontSize:18,fontWeight:600}}>{fmtUsd(p.net,{compact:true})}</div>
              <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginTop:2}}>NET 12MO</div>
              {p.advanceOutstanding > 0 && (
                <div className="ff-mono" style={{fontSize:9,color:'#c4831a',marginTop:3,letterSpacing:'.04em'}}>
                  {fmtUsd(p.advanceOutstanding,{compact:true})} adv. unrec.
                </div>
              )}
            </div>
            {/* Score */}
            <div style={{textAlign:'right',minWidth:110}}>
              <div className="ff-mono num" style={{fontSize:22,fontWeight:700,letterSpacing:'-0.02em',color: scoreColor(p.score)}}>{p.score}</div>
              <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginTop:2}}>{p.healthLabel.toUpperCase()}</div>
              <div style={{height:3,background:'var(--bg-2)',marginTop:5,width:60,marginLeft:'auto'}}>
                <div style={{height:'100%',width:p.score+'%',background:scoreColor(p.score)}}/>
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────── 02 TERRITORY MAP TAB
function SpTerritoryMap({ go, onOpenPartner }) {
  const coverage = useSPM(() => buildSpCoverage(), []);
  const [hoverIso, setHoverIso] = useSPS(null);

  // Group by tier for the matrix
  const tier1 = KEY_MARKETS.filter(k => k.tier === 1);
  const tier2 = KEY_MARKETS.filter(k => k.tier === 2);
  const tier3 = KEY_MARKETS.filter(k => k.tier === 3);

  function statusForIso(iso) {
    if (SELF_ADMIN.includes(iso)) return 'self';
    if (coverage.has(iso)) return 'covered';
    return 'gap';
  }

  function TerritoryCell({ km }) {
    const status = statusForIso(km.iso);
    const deals = coverage.get(km.iso) || [];
    const partners = deals.map(d => partnerById(d.partnerId)).filter(Boolean);
    const isHover = hoverIso === km.iso;
    return (
      <div
        onMouseEnter={()=>setHoverIso(km.iso)}
        onMouseLeave={()=>setHoverIso(null)}
        onClick={()=>partners[0] && onOpenPartner(partners[0].id)}
        style={{
          padding:'14px 12px', borderRight:'1px solid var(--rule)', borderBottom:'1px solid var(--rule)',
          cursor: deals.length ? 'pointer' : 'default',
          background: status==='self' ? 'var(--bg-2)'
                    : status==='covered' ? (isHover ? 'var(--accent)' : 'var(--ink)')
                    : 'transparent',
          color: status==='covered' ? (isHover ? 'var(--accent-ink)' : 'var(--bg)') : 'var(--ink)',
          minHeight: 78, display:'flex', flexDirection:'column', justifyContent:'space-between',
          transition:'background .12s'
        }}>
        <div style={{display:'flex',alignItems:'center',gap:6}}>
          <span style={{fontSize:18}}>{km.flag}</span>
          <span className="ff-mono num" style={{fontSize:11,fontWeight:600,letterSpacing:'.04em'}}>{km.iso}</span>
        </div>
        <div className="ff-mono upper" style={{fontSize:8,letterSpacing:'.1em',
          opacity:status==='self'?.6:status==='gap'?.5:.85}}>
          {status==='self' ? 'SELF · DIRECT' : status==='covered' ? (partners[0]?.name.split(' ')[0]||'COVERED') : 'NO COVERAGE'}
        </div>
      </div>
    );
  }

  function MatrixRow({ label, list }) {
    return (
      <div style={{marginBottom: 28}}>
        <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:10,
          display:'flex',justifyContent:'space-between',alignItems:'baseline'}}>
          <span>{label}</span>
          <span>{list.filter(k => statusForIso(k.iso)==='covered').length}/{list.length} COVERED</span>
        </div>
        <div style={{
          display:'grid',
          gridTemplateColumns:'repeat(auto-fill, minmax(140px, 1fr))',
          border:'1px solid var(--rule)', borderRight:0, borderBottom:0
        }}>
          {list.map(km => <TerritoryCell key={km.iso} km={km}/>)}
        </div>
      </div>
    );
  }

  // Hovered detail panel
  const hoverDeals = hoverIso ? (coverage.get(hoverIso) || []) : [];
  const hoverPartners = hoverDeals.map(d => partnerById(d.partnerId)).filter(Boolean);

  return (
    <div>
      {/* Legend */}
      <div style={{display:'flex',gap:18,padding:'14px 0',borderBottom:'1px solid var(--rule)',marginBottom:24}}>
        <div style={{display:'flex',alignItems:'center',gap:8,fontSize:11}}>
          <span style={{width:14,height:14,background:'var(--ink)'}}/>
          <span className="ff-mono upper" style={{fontSize:10,letterSpacing:'.08em'}}>SUB-PUB COVERED</span>
        </div>
        <div style={{display:'flex',alignItems:'center',gap:8,fontSize:11}}>
          <span style={{width:14,height:14,background:'var(--bg-2)'}}/>
          <span className="ff-mono upper" style={{fontSize:10,letterSpacing:'.08em'}}>SELF-ADMIN · DIRECT</span>
        </div>
        <div style={{display:'flex',alignItems:'center',gap:8,fontSize:11}}>
          <span style={{width:14,height:14,background:'transparent',border:'1px solid var(--rule)'}}/>
          <span className="ff-mono upper" style={{fontSize:10,letterSpacing:'.08em'}}>UNCOVERED · LIKELY BLACK-BOX</span>
        </div>
        <span style={{flex:1}}/>
        <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.08em',color:'var(--ink-3)'}}>
          {KEY_MARKETS.filter(k => statusForIso(k.iso)==='covered').length} COVERED ·{' '}
          {SELF_ADMIN.length} SELF ·{' '}
          {KEY_MARKETS.filter(k => statusForIso(k.iso)==='gap').length} GAP
        </div>
      </div>

      <MatrixRow label="TIER 1 — TOP STREAMING MARKETS" list={tier1}/>
      <MatrixRow label="TIER 2 — STRATEGIC GROWTH" list={tier2}/>
      <MatrixRow label="TIER 3 — LONG TAIL" list={tier3}/>

      {/* Hover detail */}
      {hoverIso && hoverPartners.length > 0 && (
        <div style={{position:'fixed',right:24,bottom:80,width:340,background:'var(--bg)',border:'1px solid var(--ink)',
          padding:16,zIndex:50,boxShadow:'0 10px 40px rgba(0,0,0,.18)'}}>
          <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6}}>TERRITORY DETAIL</div>
          <div className="ff-display" style={{fontSize:18,fontWeight:600,marginBottom:10}}>
            {flagFor(hoverIso)} {nameFor(hoverIso)}
          </div>
          {hoverDeals.map(d => {
            const p = partnerById(d.partnerId);
            return (
              <div key={d.id} style={{padding:'10px 0',borderTop:'1px solid var(--rule-soft)'}}>
                <div style={{fontSize:12,fontWeight:500}}>{p?.name}</div>
                <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:3}}>
                  {d.rights.join(' · ')} · {d.feePct}% fee · ends {d.termEnd}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────── 03 CASH FLOW TAB
function SpCashFlowTab({ go, onOpenPartner }) {
  const [period, setPeriod] = useSPS('2025Q4');
  const periods = ['2025Q4','2025Q3','2025Q2','2025Q1','2024Q4'];

  const stmts = SP_STATEMENTS.filter(s => s.period === period);
  const totals = stmts.reduce((acc, s) => ({
    gross: acc.gross + s.gross,
    fee: acc.fee + s.fee,
    remitted: acc.remitted + s.remitted,
    recouped: acc.recouped + s.recouped,
    unmatched: acc.unmatched + s.unmatched,
  }), {gross:0,fee:0,remitted:0,recouped:0,unmatched:0});

  // Quarter-on-quarter trend (gross net of fee)
  const trend = periods.slice().reverse().map(p => {
    const v = SP_STATEMENTS.filter(s => s.period === p).reduce((sum, s) => sum + s.net, 0);
    return { p, v };
  });
  const trendMax = Math.max(...trend.map(t => t.v));

  return (
    <div>
      {/* KPI ribbon */}
      <div style={{display:'grid',gridTemplateColumns:'repeat(5, 1fr)',borderTop:'1px solid var(--rule)',borderBottom:'1px solid var(--rule)'}}>
        <SpKpi l="GROSS · QUARTER" v={fmtUsd(totals.gross,{compact:true})} sub={period}/>
        <div style={{borderLeft:'1px solid var(--rule)'}}><SpKpi l="SUB-PUB FEES" v={fmtUsd(totals.fee,{compact:true})} sub={`${(totals.fee/totals.gross*100).toFixed(1)}% effective`}/></div>
        <div style={{borderLeft:'1px solid var(--rule)'}}><SpKpi l="ADV. RECOUPED" v={fmtUsd(totals.recouped,{compact:true})} sub="from net before remit"/></div>
        <div style={{borderLeft:'1px solid var(--rule)'}}><SpKpi l="REMITTED" v={fmtUsd(totals.remitted,{compact:true})} sub={`${stmts.length} statements`} accent/></div>
        <div style={{borderLeft:'1px solid var(--rule)'}}><SpKpi l="UNMATCHED LINES" v={totals.unmatched.toLocaleString()} sub="across all partners"/></div>
      </div>

      {/* Period selector + trend */}
      <div style={{display:'flex',alignItems:'center',gap:18,padding:'18px 0',borderBottom:'1px solid var(--rule)'}}>
        <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.12em'}}>PERIOD</div>
        {periods.map(p => (
          <button key={p} onClick={()=>setPeriod(p)} className="ff-mono num" style={{
            fontSize:11,letterSpacing:'.04em',padding:'5px 10px',
            background: period===p?'var(--ink)':'transparent',
            color: period===p?'var(--bg)':'var(--ink-2)',
            border:'1px solid '+(period===p?'var(--ink)':'var(--rule)'), cursor:'pointer'
          }}>{p}</button>
        ))}
        <span style={{flex:1}}/>
        {/* Spark */}
        <div style={{display:'flex',alignItems:'flex-end',gap:3,height:30}}>
          {trend.map((t,i) => (
            <div key={t.p} title={`${t.p}: ${fmtUsd(t.v,{compact:true})}`}
              style={{width:20,height:Math.max(2, t.v/trendMax*30),background: t.p===period?'var(--ink)':'var(--ink-3)',cursor:'pointer'}}
              onClick={()=>setPeriod(t.p)}/>
          ))}
        </div>
        <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em'}}>5Q TREND</div>
      </div>

      {/* Statement table */}
      <div style={{padding:'14px 0',borderBottom:'1px solid var(--rule)'}}>
        <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:10}}>STATEMENTS · {period}</div>
        <table className="ff-mono num" style={{width:'100%',borderCollapse:'collapse',fontSize:11}}>
          <thead>
            <tr style={{borderBottom:'1px solid var(--rule)'}}>
              <th style={{textAlign:'left',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>PARTNER · DEAL</th>
              <th style={{textAlign:'right',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>GROSS</th>
              <th style={{textAlign:'right',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>FEE</th>
              <th style={{textAlign:'right',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>RECOUP</th>
              <th style={{textAlign:'right',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>NET REMIT</th>
              <th style={{textAlign:'right',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>LINES</th>
              <th style={{textAlign:'right',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>UNMATCHED</th>
              <th style={{textAlign:'left',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>STATUS</th>
            </tr>
          </thead>
          <tbody>
            {stmts.sort((a,b)=>b.gross-a.gross).map(s => {
              const p = partnerById(s.partnerId);
              const d = dealById(s.dealId);
              return (
                <tr key={s.id} onClick={()=>onOpenPartner(s.partnerId)} style={{borderBottom:'1px solid var(--rule-soft)',cursor:'pointer'}}
                  onMouseEnter={e=>e.currentTarget.style.background='var(--surface)'}
                  onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
                  <td style={{padding:'10px 6px'}}>
                    <div style={{display:'flex',alignItems:'center',gap:8}}>
                      <span style={{fontSize:14}}>{flagFor(p?.hqIso)}</span>
                      <div>
                        <div style={{fontSize:12,fontWeight:500,fontFamily:'var(--ff-sans)',letterSpacing:0}}>{p?.name}</div>
                        <div style={{fontSize:10,color:'var(--ink-3)',letterSpacing:'.02em'}}>{d?.name}</div>
                      </div>
                    </div>
                  </td>
                  <td style={{textAlign:'right',padding:'10px 6px'}}>{fmtUsd(s.gross)}</td>
                  <td style={{textAlign:'right',padding:'10px 6px',color:'var(--ink-3)'}}>−{fmtUsd(s.fee)}</td>
                  <td style={{textAlign:'right',padding:'10px 6px',color: s.recouped>0?'#c4831a':'var(--ink-3)'}}>{s.recouped>0?'−'+fmtUsd(s.recouped):'—'}</td>
                  <td style={{textAlign:'right',padding:'10px 6px',fontWeight:600}}>{fmtUsd(s.remitted)}</td>
                  <td style={{textAlign:'right',padding:'10px 6px'}}>{s.lines.toLocaleString()}</td>
                  <td style={{textAlign:'right',padding:'10px 6px',color: s.unmatched>50?'#b53a3a':'var(--ink-3)'}}>{s.unmatched}</td>
                  <td style={{padding:'10px 6px'}}><StatusPill status={s.status} size="sm"/></td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {/* Advance recoupment progress */}
      <div style={{padding:'18px 0'}}>
        <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:14}}>OUTSTANDING ADVANCES</div>
        {SP_DEALS.filter(d => d.advance > 0 && d.status !== 'sunset').map(d => {
          const p = partnerById(d.partnerId);
          const pct = (d.advanceRecouped / d.advance) * 100;
          return (
            <div key={d.id} style={{display:'grid',gridTemplateColumns:'1fr 280px 100px',gap:18,alignItems:'center',padding:'12px 0',borderBottom:'1px solid var(--rule-soft)'}}>
              <div>
                <div style={{fontSize:13,fontWeight:500}}>{p?.name}</div>
                <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',letterSpacing:'.02em',marginTop:2}}>
                  {d.name} · advance {fmtUsd(d.advance)}
                </div>
              </div>
              <div>
                <div style={{height:8,background:'var(--bg-2)',position:'relative'}}>
                  <div style={{height:'100%',width:pct+'%',background: pct>=100?'#3F8F6E':'var(--ink)'}}/>
                </div>
                <div className="ff-mono num" style={{fontSize:10,color:'var(--ink-3)',marginTop:4,letterSpacing:'.04em'}}>
                  {fmtUsd(d.advanceRecouped)} / {fmtUsd(d.advance)} ({pct.toFixed(0)}%)
                </div>
              </div>
              <div className="ff-mono num" style={{textAlign:'right',fontSize:13,fontWeight:600,
                color: pct>=100?'#3F8F6E':'#c4831a'}}>
                {pct >= 100 ? 'RECOUPED' : fmtUsd(d.advance - d.advanceRecouped) + ' left'}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────── 04 ISSUES TAB
function SpIssuesTab({ go, onOpenPartner }) {
  // Build issue list from deal flags + late statements + expiring deals + audit overdues
  const issues = useSPM(() => {
    const list = [];
    // Late statements
    SP_STATEMENTS.filter(s => s.status === 'late').forEach(s => {
      const p = partnerById(s.partnerId);
      const d = dealById(s.dealId);
      list.push({
        id:'iss_late_'+s.id, kind:'LATE STATEMENT', severity:'high',
        partnerId: s.partnerId, partner: p?.name,
        flag: p?.hqIso,
        title:`${s.period} statement overdue`,
        detail:`${d?.name} · expected ${s.dueDate} · ${fmtUsd(s.gross)} gross`,
        action:'Send reminder',
      });
    });
    // Expiring deals (status === 'expiring')
    SP_DEALS.filter(d => d.status === 'expiring').forEach(d => {
      const p = partnerById(d.partnerId);
      list.push({
        id:'iss_exp_'+d.id, kind:'EXPIRING DEAL', severity:'med',
        partnerId: d.partnerId, partner: p?.name,
        flag: p?.hqIso,
        title:`${d.name} ends ${d.termEnd}`,
        detail:`${d.territories.length} territories · ${d.works} works · review renewal terms`,
        action:'Open renewal',
      });
    });
    // Deal-level flags (e.g. cuesheet match rate)
    SP_DEALS.filter(d => d.flags && d.flags.length).forEach(d => {
      const p = partnerById(d.partnerId);
      d.flags.forEach((f, i) => {
        list.push({
          id:'iss_flag_'+d.id+'_'+i, kind:'PERFORMANCE FLAG', severity:'med',
          partnerId: d.partnerId, partner: p?.name,
          flag: p?.hqIso,
          title: f,
          detail:`${d.name}`,
          action:'Investigate',
        });
      });
    });
    // Audit overdue
    SP_DEALS.filter(d => d.status==='active' && d.audit?.dueDate).forEach(d => {
      const p = partnerById(d.partnerId);
      const dueDate = new Date(d.audit.dueDate);
      const today = new Date('2026-04-30');
      const daysToAudit = (dueDate - today) / 86400000;
      if (daysToAudit < 90 && daysToAudit > 0) {
        list.push({
          id:'iss_audit_'+d.id, kind:'AUDIT DUE', severity:'low',
          partnerId: d.partnerId, partner: p?.name,
          flag: p?.hqIso,
          title:`Audit window opens ${d.audit.dueDate}`,
          detail:`${d.name} · last audit ${d.audit.lastDate || 'never'}`,
          action:'Schedule',
        });
      }
    });
    // High-unmatched-line statements
    SP_STATEMENTS.filter(s => s.unmatched > 100).forEach(s => {
      const p = partnerById(s.partnerId);
      const d = dealById(s.dealId);
      list.push({
        id:'iss_unm_'+s.id, kind:'UNMATCHED LINES', severity:'med',
        partnerId: s.partnerId, partner: p?.name,
        flag: p?.hqIso,
        title:`${s.unmatched} unmatched lines on ${s.period}`,
        detail:`${d?.name} · ${(s.unmatched/s.lines*100).toFixed(0)}% of total`,
        action:'Reconcile',
      });
    });
    return list.sort((a,b) => {
      const sev = { high:0, med:1, low:2 };
      return sev[a.severity] - sev[b.severity];
    });
  }, []);

  const sevColor = { high:'#b53a3a', med:'#c4831a', low:'var(--ink-3)' };

  return (
    <div>
      {/* Counts */}
      <div style={{display:'grid',gridTemplateColumns:'repeat(4,1fr)',borderTop:'1px solid var(--rule)',borderBottom:'1px solid var(--rule)',marginBottom:0}}>
        <SpKpi l="OPEN ISSUES" v={issues.length} sub="across all partners"/>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="HIGH PRIORITY" v={issues.filter(i=>i.severity==='high').length} sub="late / blocking"/>
        </div>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="EXPIRING < 90D" v={SP_DEALS.filter(d=>d.status==='expiring').length} sub="needs renewal review"/>
        </div>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="UNMATCHED · TOTAL" v={SP_STATEMENTS.reduce((s,x)=>s+x.unmatched,0).toLocaleString()} sub="cuesheet / ID misses"/>
        </div>
      </div>

      <div style={{padding:'18px 0'}}>
        {issues.map(i => (
          <div key={i.id} onClick={()=>onOpenPartner(i.partnerId)}
            style={{display:'grid',gridTemplateColumns:'auto auto 1fr auto',gap:18,alignItems:'center',padding:'14px 4px',
              borderBottom:'1px solid var(--rule-soft)',cursor:'pointer'}}
            onMouseEnter={e=>e.currentTarget.style.background='var(--surface)'}
            onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
            <span style={{width:6,height:36,background:sevColor[i.severity]}}/>
            <div style={{display:'flex',alignItems:'center',gap:8,minWidth:200}}>
              <span style={{fontSize:18}}>{flagFor(i.flag)}</span>
              <div>
                <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:sevColor[i.severity],fontWeight:600}}>{i.kind}</div>
                <div style={{fontSize:11,color:'var(--ink-3)',marginTop:2}}>{i.partner}</div>
              </div>
            </div>
            <div>
              <div style={{fontSize:13,fontWeight:500,marginBottom:2}}>{i.title}</div>
              <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',letterSpacing:'.02em'}}>{i.detail}</div>
            </div>
            <button className="ff-mono upper" style={{fontSize:9,letterSpacing:'.1em',padding:'6px 12px',
              border:'1px solid var(--rule)',background:'transparent',cursor:'pointer'}}
              onClick={e=>{e.stopPropagation();window.dispatchEvent(new CustomEvent('astro-toast',{detail:i.action+' (demo)'}))}}>
              {i.action} →
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────── PARTNER DETAIL
function SpPartnerDetail({ partnerId, onBack, onOpenPartner }) {
  const p = partnerById(partnerId);
  const deals = SP_DEALS.filter(d => d.partnerId === partnerId);
  const stmts = SP_STATEMENTS.filter(s => s.partnerId === partnerId);
  const territories = Array.from(new Set(deals.flatMap(d => d.territories)));
  const works = deals.reduce((s, d) => s + (d.works || 0), 0);
  const totalAdvance = deals.reduce((s, d) => s + (d.advance||0), 0);
  const recoupedAdvance = deals.reduce((s, d) => s + (d.advanceRecouped||0), 0);
  const ytd = stmts.filter(s => s.period.startsWith('2025') && s.status !== 'pending')
    .reduce((sum, s) => sum + s.remitted, 0);

  if (!p) return null;

  return (
    <div>
      {/* Breadcrumb */}
      <div className="ff-mono upper" style={{fontSize:10,color:'var(--ink-3)',letterSpacing:'.12em',marginBottom:8,display:'flex',gap:10}}>
        <button onClick={onBack} style={{background:'transparent',border:0,padding:0,color:'inherit',cursor:'pointer',font:'inherit',letterSpacing:'inherit',textTransform:'inherit'}}>← SUB-PUBLISHERS</button>
        <span>/</span>
        <span>{p.hqIso}</span>
      </div>

      {/* Hero */}
      <div style={{padding:'18px 0 24px',borderBottom:'1px solid var(--rule)',marginBottom:24,
        display:'flex',alignItems:'flex-start',gap:24}}>
        <div style={{width:96,height:96,background:'var(--bg-2)',display:'flex',alignItems:'center',justifyContent:'center',fontSize:54,flexShrink:0}}>
          {flagFor(p.hqIso)}
        </div>
        <div style={{flex:1}}>
          <div style={{display:'flex',alignItems:'baseline',gap:10,marginBottom:6}}>
            <h1 className="ff-display heading-swap" style={{fontSize:38,fontWeight:700,letterSpacing:'-0.03em',lineHeight:1,margin:0}}>
              {p.name}
            </h1>
            <span className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',padding:'3px 7px',
              background:p.kind==='major'?'var(--ink)':'transparent',
              color:p.kind==='major'?'var(--bg)':'var(--ink-2)',
              border:p.kind==='major'?'none':'1px solid var(--rule)'}}>
              {p.kind === 'major' ? 'MAJOR' : p.kind === 'indie' ? 'INDIE' : 'ADMIN-ONLY'}
            </span>
          </div>
          <div className="ff-mono" style={{fontSize:12,color:'var(--ink-3)',letterSpacing:'.02em'}}>
            HQ {p.hqCity}, {p.hqIso} · {p.pro} · IPI {p.ipi} · partner since {p.since}
          </div>
          <div style={{display:'flex',gap:12,marginTop:14}}>
            <button className="ff-mono upper" style={{fontSize:10,letterSpacing:'.1em',padding:'7px 14px',background:'var(--ink)',color:'var(--bg)',border:0,cursor:'pointer'}}
              onClick={()=>window.dispatchEvent(new CustomEvent('astro-toast',{detail:'Email drafted to '+p.contact}))}>
              EMAIL ACCOUNT MANAGER
            </button>
            <button className="ff-mono upper" style={{fontSize:10,letterSpacing:'.1em',padding:'7px 14px',background:'transparent',color:'var(--ink)',border:'1px solid var(--rule)',cursor:'pointer'}}
              onClick={()=>window.dispatchEvent(new CustomEvent('astro-toast',{detail:'Statements page coming up'}))}>
              VIEW STATEMENTS
            </button>
          </div>
        </div>
        <div style={{textAlign:'right',minWidth:160}}>
          <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.12em',marginBottom:4}}>HEALTH SCORE</div>
          <div className="ff-display num" style={{fontSize:64,fontWeight:700,letterSpacing:'-0.04em',lineHeight:1,color:scoreColor(p.score)}}>{p.score}</div>
          <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.1em',color:'var(--ink-2)',marginTop:4}}>{p.healthLabel.toUpperCase()}</div>
          <div style={{height:4,background:'var(--bg-2)',marginTop:8}}>
            <div style={{height:'100%',width:p.score+'%',background:scoreColor(p.score)}}/>
          </div>
        </div>
      </div>

      {/* KPIs */}
      <div style={{display:'grid',gridTemplateColumns:'repeat(5,1fr)',borderTop:'1px solid var(--rule)',borderBottom:'1px solid var(--rule)',marginBottom:24}}>
        <SpKpi l="ACTIVE DEALS" v={deals.filter(d=>d.status!=='sunset').length} sub={`${deals.length} total`}/>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="TERRITORIES" v={territories.length} sub={territories.slice(0,5).join(' · ')}/>
        </div>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="WORKS UNDER ADMIN" v={works.toLocaleString()} sub="aggregated across deals"/>
        </div>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="REMITTED · YTD" v={fmtUsd(ytd,{compact:true})} sub="2025 statements"/>
        </div>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="ADV. OUTSTANDING" v={fmtUsd(totalAdvance-recoupedAdvance,{compact:true})} sub={`${(recoupedAdvance/totalAdvance*100||0).toFixed(0)}% recouped`}/>
        </div>
      </div>

      {/* Deals */}
      <div style={{marginBottom:32}}>
        <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:12}}>DEALS · {deals.length}</div>
        {deals.map(d => (
          <div key={d.id} style={{padding:'18px 4px',borderTop:'1px solid var(--rule)',display:'grid',
            gridTemplateColumns:'1fr auto auto auto',gap:24,alignItems:'flex-start'}}>
            <div>
              <div style={{display:'flex',alignItems:'baseline',gap:10,marginBottom:6}}>
                <div style={{fontSize:15,fontWeight:600}}>{d.name}</div>
                <StatusPill status={d.status}/>
              </div>
              <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',letterSpacing:'.02em',marginBottom:6,display:'flex',alignItems:'center',gap:10,flexWrap:'wrap'}}>
                <span>Deal ID {d.id} · signed {d.signedDate} · {d.termStart} → {d.termEnd}</span>
                {d.agreementId && (
                  <button onClick={(e)=>{e.stopPropagation();window.dispatchEvent(new CustomEvent('astro-open-agreement',{detail:{id:d.agreementId}}));}}
                    className="ff-mono upper"
                    style={{fontSize:9,letterSpacing:'.1em',padding:'2px 6px',background:'var(--bg-2)',border:'1px solid var(--rule-soft)',color:'var(--ink-2)',cursor:'pointer'}}>
                    {d.agreementId} →
                  </button>
                )}
              </div>
              <div style={{display:'flex',flexWrap:'wrap',gap:6,marginTop:8}}>
                {d.territories.map(t => (
                  <span key={t} className="ff-mono num" style={{fontSize:10,padding:'2px 6px',background:'var(--bg-2)',letterSpacing:'.02em'}}>
                    {flagFor(t)} {t}
                  </span>
                ))}
              </div>
              <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:8,letterSpacing:'.04em'}}>
                Rights: {d.rights.join(' · ')} · Sunset: {d.sunsetClause}
              </div>
              {d.flags && d.flags.length > 0 && (
                <div style={{marginTop:10,padding:'8px 10px',background:'#fdf3e0',borderLeft:'2px solid #c4831a'}}>
                  {d.flags.map((f,i) => (
                    <div key={i} className="ff-mono" style={{fontSize:11,color:'#9a6a18',letterSpacing:'.02em'}}>⚠ {f}</div>
                  ))}
                </div>
              )}
            </div>
            <div style={{textAlign:'right',minWidth:80}}>
              <div className="ff-mono num" style={{fontSize:20,fontWeight:600}}>{d.feePct}%</div>
              <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginTop:2}}>SUB-PUB FEE</div>
            </div>
            <div style={{textAlign:'right',minWidth:90}}>
              <div className="ff-mono num" style={{fontSize:20,fontWeight:600}}>{(d.works||0).toLocaleString()}</div>
              <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginTop:2}}>WORKS</div>
            </div>
            <div style={{minWidth:130}}>
              {d.advance > 0 ? (
                <>
                  <div className="ff-mono num" style={{fontSize:14,fontWeight:600,textAlign:'right'}}>
                    {fmtUsd(d.advanceRecouped,{compact:true})} / {fmtUsd(d.advance,{compact:true})}
                  </div>
                  <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',marginTop:2,textAlign:'right'}}>ADVANCE RECOUP</div>
                  <div style={{height:4,background:'var(--bg-2)',marginTop:6}}>
                    <div style={{height:'100%',width:(d.advanceRecouped/d.advance*100)+'%',background:'var(--ink)'}}/>
                  </div>
                </>
              ) : (
                <div className="ff-mono upper" style={{fontSize:9,color:'var(--ink-3)',letterSpacing:'.1em',textAlign:'right'}}>NO ADVANCE</div>
              )}
            </div>
          </div>
        ))}
      </div>

      {/* Statement history */}
      <div style={{marginBottom:32}}>
        <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:12}}>STATEMENT HISTORY</div>
        <table className="ff-mono num" style={{width:'100%',borderCollapse:'collapse',fontSize:11}}>
          <thead>
            <tr style={{borderBottom:'1px solid var(--rule)'}}>
              <th style={{textAlign:'left',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>PERIOD</th>
              <th style={{textAlign:'left',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>DEAL</th>
              <th style={{textAlign:'right',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>GROSS</th>
              <th style={{textAlign:'right',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>FEE</th>
              <th style={{textAlign:'right',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>NET</th>
              <th style={{textAlign:'right',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>RECEIVED</th>
              <th style={{textAlign:'left',padding:'8px 6px',fontWeight:600,letterSpacing:'.06em',fontSize:9,color:'var(--ink-3)',textTransform:'uppercase'}}>STATUS</th>
            </tr>
          </thead>
          <tbody>
            {stmts.map(s => {
              const d = dealById(s.dealId);
              return (
                <tr key={s.id} style={{borderBottom:'1px solid var(--rule-soft)'}}>
                  <td style={{padding:'10px 6px',fontWeight:600}}>{s.period}</td>
                  <td style={{padding:'10px 6px',fontFamily:'var(--ff-sans)',fontSize:12,letterSpacing:0,color:'var(--ink-2)'}}>{d?.name}</td>
                  <td style={{textAlign:'right',padding:'10px 6px'}}>{fmtUsd(s.gross)}</td>
                  <td style={{textAlign:'right',padding:'10px 6px',color:'var(--ink-3)'}}>−{fmtUsd(s.fee)}</td>
                  <td style={{textAlign:'right',padding:'10px 6px',fontWeight:600}}>{fmtUsd(s.remitted)}</td>
                  <td style={{textAlign:'right',padding:'10px 6px',color:'var(--ink-3)'}}>{s.receivedDate || '—'}</td>
                  <td style={{padding:'10px 6px'}}><StatusPill status={s.status} size="sm"/></td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {/* Audit log */}
      <div>
        <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:12}}>AUDIT TRAIL</div>
        {deals.map(d => (
          <div key={d.id} style={{padding:'12px 0',borderTop:'1px solid var(--rule-soft)',display:'grid',
            gridTemplateColumns:'1fr 200px 200px 200px',gap:18}}>
            <div>
              <div style={{fontSize:12,fontWeight:500}}>{d.name}</div>
            </div>
            <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',letterSpacing:'.02em'}}>
              <div className="upper" style={{fontSize:9,letterSpacing:'.1em',marginBottom:2}}>LAST AUDIT</div>
              {d.audit?.lastDate || '— never —'}
            </div>
            <div className="ff-mono" style={{fontSize:11,color:'var(--ink-3)',letterSpacing:'.02em'}}>
              <div className="upper" style={{fontSize:9,letterSpacing:'.1em',marginBottom:2}}>NEXT AUDIT WINDOW</div>
              {d.audit?.dueDate || '—'}
            </div>
            <div className="ff-mono" style={{fontSize:11,color:'var(--ink-2)',letterSpacing:'.02em'}}>
              <div className="upper" style={{fontSize:9,letterSpacing:'.1em',color:'var(--ink-3)',marginBottom:2}}>FINDINGS</div>
              {d.audit?.findings || '—'}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────── MAIN SCREEN
function ScreenSubpubs({ go, payload }) {
  const [tab, setTab] = useSPS('partners');
  const [partnerId, setPartnerId] = useSPS(payload?.partnerId || null);

  // External requests to open partner detail
  React.useEffect(() => {
    const onOpen = (e) => { if (e.detail?.partnerId) setPartnerId(e.detail.partnerId); };
    window.addEventListener('astro-open-subpub', onOpen);
    return () => window.removeEventListener('astro-open-subpub', onOpen);
  }, []);

  const openPartner = (id) => setPartnerId(id);

  if (partnerId) {
    return <SpPartnerDetail partnerId={partnerId} onBack={()=>setPartnerId(null)} onOpenPartner={openPartner}/>;
  }

  // KPI ribbon for the network
  const networkActive = SP_DEALS.filter(d => d.status === 'active' || d.status === 'expiring');
  const totalTerritories = new Set(networkActive.flatMap(d => d.territories));
  const totalWorks = networkActive.reduce((s, d) => s + (d.works||0), 0);
  const totalAdv = SP_DEALS.reduce((s, d) => s + Math.max(0, (d.advance||0)-(d.advanceRecouped||0)), 0);

  const TABS = [
    { k:'partners',  l:'Partners',         n: SP_PARTNERS.length },
    { k:'territory', l:'Territory map',    n: totalTerritories.size },
    { k:'cash',      l:'Cash flow',        n: SP_STATEMENTS.length },
    { k:'issues',    l:'Issues',           n: undefined }, // computed in tab
  ];

  return (
    <div>
      {/* Header — canonical <PageHeader> */}
      <PageHeader
        eyebrow="INTERNATIONAL ADMINISTRATION"
        title="Sub-publishers"
        highlight="Sub-publishers"
        sub="The local administrators who collect on our behalf in territories where we don't operate directly — their fees, their reporting, their performance. Every dollar that comes back through this network passes a sub-pub take, an advance recoupment, and a reconciliation step before it lands."
        actions={
          <button onClick={()=>window.dispatchEvent(new CustomEvent('astro-add-agreement',{detail:{kind:'SUB_PUBLISHING'}}))}
            className="ff-mono upper" style={{fontSize:10,letterSpacing:'.1em',padding:'10px 16px',
              background:'var(--ink)',color:'var(--bg)',border:0,cursor:'pointer',whiteSpace:'nowrap'}}>
            + NEW SUB-PUB AGREEMENT
          </button>
        }
      />

      {/* KPI ribbon */}
      <div style={{display:'grid',gridTemplateColumns:'repeat(5, 1fr)',borderTop:'1px solid var(--rule)',borderBottom:'1px solid var(--rule)',marginBottom:0}}>
        <SpKpi l="PARTNERS" v={SP_PARTNERS.length} sub={`${SP_PARTNERS.filter(p=>p.kind==='major').length} majors · ${SP_PARTNERS.filter(p=>p.kind==='indie').length} indies · ${SP_PARTNERS.filter(p=>p.kind==='admin').length} admin`}/>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="ACTIVE DEALS" v={networkActive.length} sub={`${SP_DEALS.filter(d=>d.status==='expiring').length} expiring < 90d`}/>
        </div>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="TERRITORIES COVERED" v={totalTerritories.size} sub={`${KEY_MARKETS.length-totalTerritories.size-SELF_ADMIN.length} key-market gaps`}/>
        </div>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="WORKS UNDER ADMIN" v={totalWorks.toLocaleString()} sub="aggregated · de-duped per work"/>
        </div>
        <div style={{borderLeft:'1px solid var(--rule)'}}>
          <SpKpi l="ADV. OUTSTANDING" v={fmtUsd(totalAdv,{compact:true})} sub="across active deals" accent/>
        </div>
      </div>

      {/* Tab strip */}
      <div style={{borderBottom:'1px solid var(--rule)',display:'flex',gap:0,marginTop:0,marginBottom:0}}>
        {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,
            display:'flex',alignItems:'baseline',gap:8
          }}>
            {t.l}
            {t.n != null && <span className="ff-mono num" style={{fontSize:10,color:'var(--ink-3)'}}>{t.n}</span>}
          </button>
        ))}
      </div>

      {/* Body */}
      <div style={{paddingTop:8}}>
        {tab === 'partners'  && <SpPartnersTab go={go} onOpenPartner={openPartner}/>}
        {tab === 'territory' && <SpTerritoryMap go={go} onOpenPartner={openPartner}/>}
        {tab === 'cash'      && <SpCashFlowTab go={go} onOpenPartner={openPartner}/>}
        {tab === 'issues'    && <SpIssuesTab go={go} onOpenPartner={openPartner}/>}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────── NEW SUB-PUB WIZARD
function NewSubpubWizard() {
  const [open, setOpen] = useSPS(false);
  const [step, setStep] = useSPS(0);
  const [form, setForm] = useSPS({
    partnerName:'', partnerKind:'indie', hqIso:'',
    contact:'', acct:'', pro:'', ipi:'',
    territories:[], rights:['MECH','PERF'],
    feePct:15, advance:0, termYears:3, sunsetMonths:12,
    signedDate:'', termStart:'',
  });

  React.useEffect(() => {
    const onOpen = () => { setOpen(true); setStep(0); };
    window.addEventListener('astro-open-subpub-wizard', onOpen);
    return () => window.removeEventListener('astro-open-subpub-wizard', onOpen);
  }, []);

  if (!open) return null;

  const steps = [
    { k:'partner',    l:'Partner' },
    { k:'territory',  l:'Territory & rights' },
    { k:'commercial', l:'Commercial' },
    { k:'review',     l:'Review' },
  ];

  const update = (patch) => setForm(f => ({ ...f, ...patch }));
  const toggle = (key, val) => setForm(f => {
    const arr = f[key] || [];
    return { ...f, [key]: arr.includes(val) ? arr.filter(x=>x!==val) : [...arr, val] };
  });

  const close = () => { setOpen(false); };

  const finish = () => {
    // Insert mock partner + deal in-memory
    const pid = 'sp_new_'+Math.random().toString(36).slice(2,8);
    const did = 'spd_new_'+Math.random().toString(36).slice(2,8);
    const startYear = parseInt((form.termStart || '2026-05-01').slice(0,4));
    const endYear = startYear + (form.termYears || 3);
    SP_PARTNERS.push({
      id: pid, name: form.partnerName, kind: form.partnerKind,
      hqIso: form.hqIso, hqCity:'—', contact: form.contact, acct: form.acct,
      since: form.signedDate || '2026-05-01',
      pro: form.pro, ipi: form.ipi,
      score: 70, healthLabel:'New',
    });
    SP_DEALS.push({
      id: did, partnerId: pid,
      name: `${form.territories.join('·')} · ${form.rights.join('+')} · ${form.termYears}yr`,
      territories: form.territories, rights: form.rights,
      feePct: form.feePct, advance: form.advance, advanceRecouped: 0,
      termStart: form.termStart || '2026-05-01',
      termEnd: `${endYear}-${(form.termStart||'2026-05-01').slice(5,10)}`,
      signedDate: form.signedDate || '2026-04-30',
      works: 0, status:'active',
      sunsetClause:`${form.sunsetMonths}mo post-term collection`,
      sunsetActive:false,
      audit:{ lastDate:null, findings:null, dueDate:`${startYear+1}-${(form.termStart||'2026-05-01').slice(5,10)}` },
    });
    // Mirror the new deal into window.AGREEMENTS so it shows up in Catalog → Agreements
    // and is reachable from agreement-detail without a page refresh.
    if (typeof window.mirrorSubPubDealsIntoAgreements === 'function') {
      window.mirrorSubPubDealsIntoAgreements();
    }
    // Refresh Pulse counters too
    try {
      const activePartners = new Set(SP_DEALS.filter(d => d.status === 'active' || d.status === 'expiring').map(d => d.partnerId)).size;
      window.__SUBPUB_ACTIVE_PARTNERS = activePartners;
      window.__SUBPUB_EXPIRING = SP_DEALS.filter(d => d.status === 'expiring').length;
    } catch {}
    window.dispatchEvent(new CustomEvent('astro-toast',{detail:`New sub-pub deal created — ${form.partnerName}`}));
    setOpen(false);
    // Force a re-render of any visible screen — fire a route event
    setTimeout(()=>{
      window.dispatchEvent(new CustomEvent('astro-route',{detail:{name:'subpubs'}}));
      if (window.__astroGo) window.__astroGo('subpubs');
    }, 50);
  };

  const can = (s) => {
    if (s === 0) return form.partnerName && form.hqIso && form.partnerKind;
    if (s === 1) return form.territories.length > 0 && form.rights.length > 0;
    if (s === 2) return form.feePct > 0 && form.feePct < 50 && form.termYears > 0;
    return true;
  };

  return (
    <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,.4)',display:'flex',alignItems:'center',justifyContent:'center',zIndex:200}}
      onClick={(e)=>{ if (e.target === e.currentTarget) close(); }}>
      <div style={{width:760,maxHeight:'90vh',background:'var(--bg)',border:'1px solid var(--ink)',display:'flex',flexDirection:'column',overflow:'hidden'}}>
        {/* Header */}
        <div style={{padding:'18px 24px',borderBottom:'1px solid var(--rule)',display:'flex',alignItems:'center',justifyContent:'space-between'}}>
          <div>
            <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:4}}>NEW SUB-PUB AGREEMENT</div>
            <div className="ff-display" style={{fontSize:22,fontWeight:600,letterSpacing:'-0.02em'}}>
              Step {step+1} of {steps.length} — {steps[step].l}
            </div>
          </div>
          <button onClick={close} style={{background:'transparent',border:0,fontSize:20,cursor:'pointer',padding:8,color:'var(--ink-3)'}}>×</button>
        </div>

        {/* Step indicator */}
        <div style={{display:'flex',padding:'12px 24px',gap:6,borderBottom:'1px solid var(--rule-soft)'}}>
          {steps.map((s,i) => (
            <div key={s.k} style={{flex:1,height:3,background: i<=step?'var(--ink)':'var(--bg-2)'}}/>
          ))}
        </div>

        {/* Body */}
        <div style={{padding:'24px',overflowY:'auto',flex:1}}>
          {step === 0 && (
            <div style={{display:'flex',flexDirection:'column',gap:18}}>
              <div>
                <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>PARTNER NAME</label>
                <input value={form.partnerName} onChange={e=>update({partnerName:e.target.value})} placeholder="e.g. Universal Music Publ. Japan"
                  style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:14,boxSizing:'border-box'}}/>
              </div>
              <div>
                <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>PARTNER TYPE</label>
                <div style={{display:'grid',gridTemplateColumns:'repeat(3,1fr)',gap:8}}>
                  {[
                    {k:'major',l:'MAJOR',sub:'Universal · Sony · WCM'},
                    {k:'indie',l:'INDIE',sub:'Mid-size territorial'},
                    {k:'admin',l:'ADMIN-ONLY',sub:'Service collection'},
                  ].map(o => (
                    <button key={o.k} onClick={()=>update({partnerKind:o.k})}
                      style={{padding:'12px 10px',textAlign:'left',cursor:'pointer',
                        background:form.partnerKind===o.k?'var(--ink)':'transparent',
                        color:form.partnerKind===o.k?'var(--bg)':'var(--ink)',
                        border:'1px solid '+(form.partnerKind===o.k?'var(--ink)':'var(--rule)')}}>
                      <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.1em',fontWeight:600}}>{o.l}</div>
                      <div className="ff-mono" style={{fontSize:10,opacity:.7,marginTop:2}}>{o.sub}</div>
                    </button>
                  ))}
                </div>
              </div>
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:14}}>
                <div>
                  <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>HQ TERRITORY</label>
                  <select value={form.hqIso} onChange={e=>update({hqIso:e.target.value})}
                    style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:14,boxSizing:'border-box'}}>
                    <option value="">— select —</option>
                    {KEY_MARKETS.map(km => <option key={km.iso} value={km.iso}>{km.flag} {km.name} ({km.iso})</option>)}
                  </select>
                </div>
                <div>
                  <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>IPI / CAE</label>
                  <input value={form.ipi} onChange={e=>update({ipi:e.target.value})} placeholder="00000000000" className="ff-mono num"
                    style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:13,boxSizing:'border-box'}}/>
                </div>
              </div>
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:14}}>
                <div>
                  <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>ACCOUNT MANAGER</label>
                  <input value={form.acct} onChange={e=>update({acct:e.target.value})} placeholder="Their contact at the partner"
                    style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:14,boxSizing:'border-box'}}/>
                </div>
                <div>
                  <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>EMAIL</label>
                  <input value={form.contact} onChange={e=>update({contact:e.target.value})} placeholder="contact@..."
                    style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:14,boxSizing:'border-box'}}/>
                </div>
              </div>
              <div>
                <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>SOCIETIES (FOR COLLECTION)</label>
                <input value={form.pro} onChange={e=>update({pro:e.target.value})} placeholder="e.g. JASRAC · NexTone · KOMCA"
                  style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:14,boxSizing:'border-box'}}/>
              </div>
            </div>
          )}

          {step === 1 && (
            <div style={{display:'flex',flexDirection:'column',gap:20}}>
              <div>
                <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:8,display:'block'}}>
                  TERRITORIES IN SCOPE · {form.territories.length} selected
                </label>
                <div style={{display:'grid',gridTemplateColumns:'repeat(6,1fr)',gap:6,maxHeight:300,overflowY:'auto',
                  padding:6,border:'1px solid var(--rule)'}}>
                  {KEY_MARKETS.map(km => {
                    const sel = form.territories.includes(km.iso);
                    const isSelf = SELF_ADMIN.includes(km.iso);
                    return (
                      <button key={km.iso} onClick={()=>!isSelf && toggle('territories', km.iso)}
                        disabled={isSelf}
                        style={{padding:'8px 6px',textAlign:'left',cursor:isSelf?'not-allowed':'pointer',
                          background:sel?'var(--ink)':isSelf?'var(--bg-2)':'transparent',
                          color:sel?'var(--bg)':isSelf?'var(--ink-3)':'var(--ink)',
                          border:'1px solid '+(sel?'var(--ink)':'var(--rule-soft)'),
                          opacity:isSelf?0.5:1}}>
                        <div style={{fontSize:14,marginBottom:2}}>{km.flag}</div>
                        <div className="ff-mono num" style={{fontSize:10,fontWeight:600,letterSpacing:'.04em'}}>{km.iso}</div>
                        {isSelf && <div className="ff-mono" style={{fontSize:8,opacity:.7,marginTop:1}}>self</div>}
                      </button>
                    );
                  })}
                </div>
                <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:6,letterSpacing:'.02em'}}>
                  Greyed-out territories are self-administered — we collect there directly.
                </div>
              </div>
              <div>
                <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:8,display:'block'}}>RIGHTS GRANTED</label>
                <div style={{display:'grid',gridTemplateColumns:'repeat(4,1fr)',gap:8}}>
                  {[
                    {k:'MECH',l:'MECHANICAL',sub:'Reproduction · streaming'},
                    {k:'PERF',l:'PERFORMANCE',sub:'Public · broadcast'},
                    {k:'SYNC',l:'SYNC',sub:'Film · TV · ad'},
                    {k:'PRINT',l:'PRINT',sub:'Sheet music · folio'},
                  ].map(o => {
                    const sel = form.rights.includes(o.k);
                    return (
                      <button key={o.k} onClick={()=>toggle('rights', o.k)}
                        style={{padding:'12px 10px',textAlign:'left',cursor:'pointer',
                          background:sel?'var(--ink)':'transparent',
                          color:sel?'var(--bg)':'var(--ink)',
                          border:'1px solid '+(sel?'var(--ink)':'var(--rule)')}}>
                        <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.1em',fontWeight:600}}>{o.l}</div>
                        <div className="ff-mono" style={{fontSize:9,opacity:.7,marginTop:2}}>{o.sub}</div>
                      </button>
                    );
                  })}
                </div>
              </div>
            </div>
          )}

          {step === 2 && (
            <div style={{display:'flex',flexDirection:'column',gap:20}}>
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:16}}>
                <div>
                  <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>SUB-PUB FEE %</label>
                  <input type="number" min="0" max="50" step="0.5" value={form.feePct} onChange={e=>update({feePct:parseFloat(e.target.value)||0})}
                    className="ff-mono num"
                    style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:14,boxSizing:'border-box'}}/>
                  <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:4,letterSpacing:'.02em'}}>
                    Industry typical: 10–15% (majors), 15–25% (indies/admins)
                  </div>
                </div>
                <div>
                  <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>ADVANCE (USD)</label>
                  <input type="number" min="0" step="1000" value={form.advance} onChange={e=>update({advance:parseInt(e.target.value)||0})}
                    className="ff-mono num"
                    style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:14,boxSizing:'border-box'}}/>
                  <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:4,letterSpacing:'.02em'}}>
                    Recoupable from net before remit
                  </div>
                </div>
              </div>
              <div style={{display:'grid',gridTemplateColumns:'1fr 1fr 1fr',gap:16}}>
                <div>
                  <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>TERM (YEARS)</label>
                  <input type="number" min="1" max="10" value={form.termYears} onChange={e=>update({termYears:parseInt(e.target.value)||3})}
                    className="ff-mono num"
                    style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:14,boxSizing:'border-box'}}/>
                </div>
                <div>
                  <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>SUNSET (MONTHS)</label>
                  <input type="number" min="0" max="36" value={form.sunsetMonths} onChange={e=>update({sunsetMonths:parseInt(e.target.value)||12})}
                    className="ff-mono num"
                    style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:14,boxSizing:'border-box'}}/>
                  <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',marginTop:4,letterSpacing:'.02em'}}>
                    Post-term collection window
                  </div>
                </div>
                <div>
                  <label className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:6,display:'block'}}>TERM START</label>
                  <input type="date" value={form.termStart} onChange={e=>update({termStart:e.target.value})}
                    className="ff-mono num"
                    style={{width:'100%',padding:'10px 12px',border:'1px solid var(--rule)',background:'var(--bg)',fontSize:13,boxSizing:'border-box'}}/>
                </div>
              </div>
              {/* Live commercial summary */}
              <div style={{marginTop:6,padding:'14px 16px',background:'var(--bg-2)',border:'1px solid var(--rule)'}}>
                <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:8}}>LIVE COMMERCIAL SUMMARY</div>
                <div style={{fontSize:13,lineHeight:1.6}}>
                  Partner takes <b>{form.feePct}%</b> of gross collected{form.advance>0?<>, against a recoupable advance of <b>{fmtUsd(form.advance)}</b></>:''}.
                  Term runs {form.termYears} year{form.termYears!==1?'s':''} from {form.termStart || '—'},
                  with {form.sunsetMonths}-month sunset for in-flight collections.
                </div>
              </div>
            </div>
          )}

          {step === 3 && (
            <div style={{display:'flex',flexDirection:'column',gap:0}}>
              <div className="ff-mono upper" style={{fontSize:9,letterSpacing:'.12em',color:'var(--ink-3)',marginBottom:14}}>REVIEW</div>
              {[
                ['Partner', `${form.partnerName} · ${form.partnerKind.toUpperCase()} · HQ ${form.hqIso || '—'}`],
                ['Contact', `${form.acct || '—'} · ${form.contact || '—'}`],
                ['Societies', form.pro || '—'],
                ['IPI', form.ipi || '—'],
                ['Territories', form.territories.length ? form.territories.map(t=>flagFor(t)+' '+t).join(' · ') : '—'],
                ['Rights', form.rights.join(' · ')],
                ['Fee', form.feePct + '%'],
                ['Advance', fmtUsd(form.advance)],
                ['Term', `${form.termYears} years from ${form.termStart||'—'}, ${form.sunsetMonths}mo sunset`],
              ].map(([k,v]) => (
                <div key={k} style={{display:'grid',gridTemplateColumns:'180px 1fr',gap:14,padding:'10px 0',borderBottom:'1px solid var(--rule-soft)'}}>
                  <div className="ff-mono upper" style={{fontSize:10,letterSpacing:'.1em',color:'var(--ink-3)'}}>{k}</div>
                  <div style={{fontSize:13}}>{v}</div>
                </div>
              ))}
            </div>
          )}
        </div>

        {/* Footer */}
        <div style={{padding:'14px 24px',borderTop:'1px solid var(--rule)',display:'flex',justifyContent:'space-between',alignItems:'center'}}>
          <button onClick={step===0 ? close : ()=>setStep(s=>s-1)} className="ff-mono upper"
            style={{fontSize:10,letterSpacing:'.1em',padding:'8px 14px',background:'transparent',color:'var(--ink-2)',border:'1px solid var(--rule)',cursor:'pointer'}}>
            {step===0 ? 'CANCEL' : '← BACK'}
          </button>
          <div className="ff-mono" style={{fontSize:10,color:'var(--ink-3)',letterSpacing:'.04em'}}>
            {step+1} / {steps.length}
          </div>
          {step < steps.length-1 ? (
            <button onClick={()=>can(step) && setStep(s=>s+1)} disabled={!can(step)} className="ff-mono upper"
              style={{fontSize:10,letterSpacing:'.1em',padding:'8px 14px',
                background:can(step)?'var(--ink)':'var(--bg-2)',
                color:can(step)?'var(--bg)':'var(--ink-3)',
                border:0,cursor:can(step)?'pointer':'not-allowed'}}>
              CONTINUE →
            </button>
          ) : (
            <button onClick={finish} className="ff-mono upper"
              style={{fontSize:10,letterSpacing:'.1em',padding:'8px 14px',background:'var(--accent)',color:'var(--accent-ink)',border:0,cursor:'pointer'}}>
              CREATE AGREEMENT ✓
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

// Expose
Object.assign(window, { ScreenSubpubs, NewSubpubWizard });
