/* =============================================================================
   strategy/Strategy.jsx — standalone School Strategy planning board.
   Plan the list: status, scope (in scope / independent), fit scores, real stats,
   auto deadlines, reach/target/safety balance, notes, drag-to-rank, client summary.
   Syncs with the rest of the platform via KHSync (store.strategy on the demo client).
   ============================================================================= */

const C = (window.UI && UI.C) || {};
function DEMO() { return (window.KHSync && KHSync.DEMO_ID) || 'client-portal'; }

/* ---- data adapters -------------------------------------------------------- */
const STATS = (window.PORTAL_CONTENT && PORTAL_CONTENT.schoolStats) || [];
function slugFor(name) {
  // Prefer the shared resolver (matches against SCHOOL_CONTENT by name, slug,
  // parenthetical, short name, or a loose contains) so every school that has
  // real module content — not just the original seven — resolves correctly.
  // Without this, a school added after this list was last updated (e.g. NYU
  // Stern) would fall through to `null` here and its deadline would never
  // populate anywhere this file is used, even though SCHOOL_CONTENT has it.
  if (window.khSchoolSlugFromName) { const s = window.khSchoolSlugFromName(name); if (s) return s; }
  const n = (name || '').toLowerCase();
  if (n.includes('harvard') || n === 'hbs') return 'hbs';
  if (n.includes('stanford')) return 'stanford';
  if (n.includes('wharton')) return 'wharton';
  if (n.includes('columbia')) return 'cbs';
  if (n.includes('mit') || n.includes('sloan')) return 'mit';
  if (n.includes('booth')) return 'booth';
  if (n.includes('kellogg')) return 'kellogg';
  if (n.includes('stern') || n.includes('nyu')) return 'stern';
  return null;
}
const CATALOG = (() => {
  const base = STATS.map(s => ({ name: s.school, short: s.short, accept: s.accept, gmat: s.gmatFocus, size: s.size, slug: slugFor(s.school) }));
  const names = new Set(base.map(b => b.name));
  // Additional top US + European programs (approximate stats) so any can be added.
  const EXTRA = [
    { name: 'Michigan Ross', short: 'Ross', accept: '29%', gmat: '710', size: 350 },
    { name: 'Virginia Darden', short: 'Darden', accept: '38%', gmat: '710', size: 350 },
    { name: 'Duke Fuqua', short: 'Fuqua', accept: '22%', gmat: '710', size: 450 },
    { name: 'Cornell Johnson', short: 'Johnson', accept: '30%', gmat: '710', size: 280 },
    { name: 'UCLA Anderson', short: 'Anderson', accept: '30%', gmat: '710', size: 360 },
    { name: 'CMU Tepper', short: 'Tepper', accept: '30%', gmat: '700', size: 210 },
    { name: 'UNC Kenan-Flagler', short: 'UNC', accept: '38%', gmat: '700', size: 280 },
    { name: 'USC Marshall', short: 'USC', accept: '30%', gmat: '710', size: 220 },
    { name: 'Georgetown McDonough', short: 'GTown', accept: '45%', gmat: '690', size: 280 },
    { name: 'Texas McCombs', short: 'McCombs', accept: '40%', gmat: '700', size: 280 },
    { name: 'INSEAD', short: 'INSEAD', accept: '31%', gmat: '710', size: 1000 },
    { name: 'London Business School', short: 'LBS', accept: '25%', gmat: '700', size: 480 },
    { name: 'IESE Business School', short: 'IESE', accept: '30%', gmat: '680', size: 350 },
    { name: 'HEC Paris', short: 'HEC', accept: '20%', gmat: '690', size: 300 },
    { name: 'IMD', short: 'IMD', accept: '30%', gmat: '680', size: 90 },
  ];
  EXTRA.forEach(e => { if (!names.has(e.name)) base.push({ ...e, slug: slugFor(e.name) }); });
  base.sort((a, b) => a.name.localeCompare(b.name));
  return base;
})();
function catEntry(key, name) { return CATALOG.find(c => (key && c.slug === key) || c.name === name) || null; }
function acceptNum(a) { const m = (a || '').match(/(\d+)/); return m ? Number(m[1]) : null; }
function tierOf(accept) { const n = acceptNum(accept); if (n == null) return null; return n < 12 ? 'reach' : n <= 25 ? 'target' : 'safety'; }
const TIER = { reach: { n: '1', label: 'Dream', c: 'var(--sch-denied-pre)' }, target: { n: '2', label: 'Reach', c: 'var(--sch-inprogress)' }, safety: { n: '3', label: 'Target', c: 'var(--sch-completed)' } };

const RM_ROUND_LABEL = { R1: 'Round 1', R2: 'Round 2', R3: 'Round 3' };
function roadmapRound(cid) { try { const s = KHSync.read(cid || DEMO()); if (s && s.roadmap && RM_ROUND_LABEL[s.roadmap.round]) return s.roadmap.round; } catch (e) {} return 'R1'; }
const SB_ORDINAL_WORDS = { one: 1, two: 2, three: 3, four: 4, five: 5 };
function sbRoundOrdinal(label) {
  const s = String(label || '').toLowerCase();
  const m = s.match(/(\d+)/);
  if (m) return parseInt(m[1], 10);
  // Some schools spell rounds out ("Round One") instead of using digits — a
  // plain digit regex misses those entirely (e.g. Chicago Booth), so words matter too.
  for (const w in SB_ORDINAL_WORDS) { if (s.includes(w)) return SB_ORDINAL_WORDS[w]; }
  return null;
}
// Rows saved before a school existed in SCHOOL_CONTENT (or before slugFor could
// resolve it) persist their raw name as `key` instead of a real slug — that key
// never self-heals on its own, so any lookup that trusts it directly (deadline,
// logo) silently comes up empty forever, even after the school is added to
// SCHOOL_CONTENT later. Re-resolve through the name whenever the stored key
// doesn't match, the same way Roadmap.jsx always does.
function contentSlugFor(key, name) {
  if (key && window.SCHOOL_CONTENT && SCHOOL_CONTENT[key]) return key;
  return slugFor(name) || key;
}
function deadlineFor(slug, roundLabel) {
  const c = slug && window.SCHOOL_CONTENT && SCHOOL_CONTENT[slug];
  if (!c || !roundLabel) return null;
  let items = null;
  if (c.deadlines) items = c.deadlines;
  else if (c.deadlineGroups) { const g = c.deadlineGroups.find(x => /august/i.test(x.label)) || c.deadlineGroups[0]; items = g && g.items; }
  if (!items) return null;
  let it = items.find(d => d.round === roundLabel);
  // Fall back to matching by round number/ordinal so schools that label their
  // rounds differently (e.g. Chicago Booth's "Round One", NYU Stern's "1st
  // Deadline") still resolve against the standard "Round 1"/"Round 2" labels.
  if (!it) { const ord = sbRoundOrdinal(roundLabel); if (ord != null) it = items.find(d => sbRoundOrdinal(d.round) === ord); }
  return it ? it.date : null;
}

/* ---- strategy status vocabulary (from Karen's sheet) ---------------------- */
const STRAT_STATUS = {
  'considering':   { label: 'Considering applying', c: 'oklch(0.62 0.012 250)' },
  'confirmed':     { label: 'Confirmed applying',   c: 'oklch(0.66 0.11 55)' },
  'in-progress':   { label: 'Application in progress', c: 'oklch(0.70 0.095 80)' },
  'submitted':     { label: 'Application submitted', c: 'oklch(0.62 0.085 150)' },
  'interview':     { label: 'Interview invite',     c: 'oklch(0.60 0.09 250)' },
  'admitted':      { label: 'Admitted',             c: 'oklch(0.52 0.095 150)', solid: true },
  'waitlisted':    { label: 'Waitlisted',           c: 'oklch(0.56 0.10 300)' },
  'not-admitted':  { label: 'Not admitted',         c: 'oklch(0.60 0.105 25)' },
};
const STRAT_ORDER = ['considering', 'confirmed', 'in-progress', 'submitted', 'interview', 'admitted', 'waitlisted', 'not-admitted'];
function statusStyle(k) {
  const s = STRAT_STATUS[k] || STRAT_STATUS.considering;
  if (s.solid) return { color: '#fff', background: s.c, border: `1px solid ${s.c}` };
  return { color: `color-mix(in oklab, ${s.c}, #1D1D1D 26%)`, background: `color-mix(in oklab, ${s.c} 14%, #fff)`, border: `1px solid color-mix(in oklab, ${s.c} 36%, #fff)` };
}

// The eight criteria from the School Strategy module — checked per school.
const CRITERIA = [
  ['culturalFit', 'Cultural fit'], ['career', 'Career alignment'], ['location', 'Location'], ['brand', 'Brand'],
  ['cost', 'Cost & ROI'], ['alumni', 'Alumni network'], ['curriculum', 'Curriculum'], ['gut', 'Gut feeling'],
];
function fitCount(f) { if (!f) return 0; return CRITERIA.reduce((n, [k]) => n + (f[k] ? 1 : 0), 0); }

/* ---- persistence ---------------------------------------------------------- */
function seedRows(cid, seedSchools) {
  const store = window.KHSync && KHSync.read(cid || DEMO());
  if (store && store.strategy && store.strategy.rows && store.strategy.rows.length) return store.strategy.rows;
  const base = (store && store.schools && store.schools.length) ? store.schools.map(s => ({ key: s.key, name: s.name, short: s.short }))
    : (seedSchools && seedSchools.length) ? seedSchools.map(s => ({ key: s.key, name: s.name, short: s.short })) :
    ((window.PORTAL_CONTENT && PORTAL_CONTENT.client.targets) || []).map(n => ({ key: slugFor(n), name: n, short: n.split(' ')[0] }));
  return base.map(b => ({ key: b.key || slugFor(b.name) || b.name, name: b.name, short: b.short, status: 'confirmed', structure: 'in-scope', fit: { career: 0, culture: 0, location: 0, brand: 0 }, notes: '' }));
}

/* dual-path Codata persistence: the client's own session (CodataAPI, portal)
   vs. the admin viewing another client (CodataAdmin). Mirrors wsCodataSave so
   the School Tracker is durable in Codata, not just this browser's KHSync. */
async function sbSaveStrategy(cid, strategy) {
  try {
    if (window.CodataAPI && CodataAPI.getCurrentClientId && CodataAPI.getCurrentClientId() === cid) {
      await CodataAPI.saveClientMeta({ strategy });
    } else if (window.CodataAdmin && CodataAdmin.saveClientMetaById) {
      await CodataAdmin.saveClientMetaById(cid, { strategy });
    }
  } catch (e) { console.error('Codata strategy save failed:', e); }
}
async function sbLoadStrategy(cid) {
  try {
    if (window.CodataAPI && CodataAPI.getCurrentClientId && CodataAPI.getCurrentClientId() === cid) {
      const { meta } = await CodataAPI.getClientMeta(); return meta.strategy || null;
    } else if (window.CodataAdmin && CodataAdmin.getClientMetaRaw) {
      const meta = await CodataAdmin.getClientMetaRaw(cid); return meta.strategy || null;
    }
  } catch (e) {}
  return null;
}
async function sbSaveSchoolStatus(cid, key, status) {
  try {
    if (window.CodataAPI && CodataAPI.setTargetSchoolStatus && CodataAPI.getCurrentClientId && CodataAPI.getCurrentClientId() === cid) {
      await CodataAPI.setTargetSchoolStatus(cid, key, status);
    } else if (window.CodataAdmin && CodataAdmin.setSchoolStatusByKey) {
      await CodataAdmin.setSchoolStatusByKey(cid, key, status);
    }
  } catch (e) { console.error('Codata school status save failed:', e); }
}

/* ========================================================================== */
function StrategyApp({ clientId, clientName, seedSchools } = {}) {
  const cid = clientId || DEMO();
  const [rows, setRows] = React.useState(() => seedRows(cid, seedSchools));
  const [expanded, setExpanded] = React.useState(null);
  const [toast, setToast] = React.useState('');
  const dragIdx = React.useRef(null);
  const round = roadmapRound(cid);
  const roundLabel = RM_ROUND_LABEL[round];
  const client = clientName ? { name: clientName, cycle: '' } : ((window.PORTAL_CONTENT && PORTAL_CONTENT.client) || { name: 'Client', cycle: '' });

  // persist (local KHSync + durable Codata)
  const didMount = React.useRef(false);
  const skipSave = React.useRef(false);
  React.useEffect(() => {
    if (!didMount.current) { didMount.current = true; return; }
    if (skipSave.current) { skipSave.current = false; return; }
    const strategy = { rows, updated: Date.now() };
    if (window.KHSync) KHSync.patch(cid, { strategy });
    sbSaveStrategy(cid, strategy);
  }, [rows]);
  // Hydrate from Codata (source of truth across devices + the admin console) on
  // mount; skip the immediate save the resulting setRows would otherwise trigger.
  React.useEffect(() => {
    let cancelled = false;
    sbLoadStrategy(cid).then(remote => {
      if (cancelled || !remote || !Array.isArray(remote.rows) || !remote.rows.length) return;
      skipSave.current = true;
      if (window.KHSync) KHSync.patch(cid, { strategy: remote });
      setRows(remote.rows);
    });
    return () => { cancelled = true; };
  }, [cid]);

  const update = (key, patch) => setRows(rs => rs.map(r => r.key === key ? { ...r, ...patch } : r));
  const remove = (key) => setRows(rs => rs.filter(r => r.key !== key));
  const [appStatus, setAppStatus] = React.useState(() => { const m = {}; try { const s = window.KHSync && KHSync.read(cid); (s && s.schools || []).forEach(x => m[x.key] = x.status); } catch (e) {} return m; });
  const setSchoolStatus = (key, name, short, status) => {
    setAppStatus(p => ({ ...p, [key]: status }));
    if (window.KHSync) { const s = KHSync.read(cid) || {}; const schools = (s.schools || []).slice(); const idx = schools.findIndex(x => x.key === key); if (idx >= 0) schools[idx] = { ...schools[idx], status }; else schools.push({ key, name, short, status, access: true }); KHSync.patch(cid, { schools }); }
    sbSaveSchoolStatus(cid, key, status);
  };
  const addSchool = (name) => {
    const slug = slugFor(name) || name;
    if (rows.some(r => r.key === slug)) return;
    const cat = catEntry(slug, name);
    setRows(rs => [...rs, { key: slug, name, short: cat ? cat.short : name.split(' ')[0], status: 'considering', structure: 'in-scope', fit: { career: 0, culture: 0, location: 0, brand: 0 }, notes: '' }]);
    show('Added ' + name);
  };
  const show = (m) => setToast(m);
  React.useEffect(() => { if (!toast) return; const t = setTimeout(() => setToast(''), 2200); return () => clearTimeout(t); }, [toast]);

  const onDrop = (i) => { const from = dragIdx.current; if (from == null || from === i) return; setRows(rs => { const a = [...rs]; const [m] = a.splice(from, 1); a.splice(i, 0, m); return a; }); dragIdx.current = null; };

  const inScope = rows.filter(r => r.structure === 'in-scope');
  const tierCounts = { reach: 0, target: 0, safety: 0 };
  inScope.forEach(r => { const t = r.tier; if (t) tierCounts[t]++; });
  const balanceHint = (() => {
    if (!inScope.length) return null;
    const set = tierCounts.reach + tierCounts.target + tierCounts.safety;
    if (set === 0) return 'Set a tier on each school to gauge the balance.';
    if (tierCounts.safety === 0) return 'No Target schools — consider adding one to balance the list.';
    if (tierCounts.target === 0) return 'No Reach schools yet.';
    if (tierCounts.reach === 0) return 'No Dream schools.';
    return 'Balanced list — a healthy spread across the three tiers.';
  })();

  const [view, setView] = React.useState('list');
  const catalogRemaining = CATALOG.filter(c => !rows.some(r => r.key === c.slug || r.name === c.name));

  return (
    <div style={{ maxWidth: 1280, margin: '0 auto', padding: '28px 28px 80px' }}>
      <Header client={client} roundLabel={roundLabel} inScope={inScope.length} total={rows.length} />
      <BalanceBar counts={tierCounts} hint={balanceHint} total={inScope.length} />

      <div style={{ background: '#fff', border: `1px solid ${C.hair}`, marginTop: 18, overflowX: 'auto' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', minWidth: 1040 }}>
          <thead>
            <tr style={{ borderBottom: `1px solid var(--border-rule)` }}>
              {['', 'School', 'Status', 'Scope', 'Tier', 'Acceptance rate', 'GMAT Focus', 'Class size', roundLabel ? roundLabel + ' deadline' : 'Deadline', 'Notes', ''].map((h, i) => (
                <th key={i} style={{ textAlign: 'left', padding: '11px 14px', fontFamily: C.sans, fontSize: 9.5, fontWeight: 600, letterSpacing: '0.1em', textTransform: 'uppercase', color: C.carbon50, whiteSpace: 'nowrap' }}>{h}</th>
              ))}
            </tr>
          </thead>
          <tbody>
            {rows.map((r, i) => (
              <StrategyRow key={r.key} r={r} i={i} round={round} roundLabel={roundLabel} expanded={expanded === r.key}
                appStatus={appStatus[r.key]} onSetStatus={(v) => setSchoolStatus(r.key, r.name, r.short, v)}
                onExpand={() => setExpanded(expanded === r.key ? null : r.key)}
                onUpdate={update} onRemove={() => remove(r.key)}
                onDragStart={() => { dragIdx.current = i; }} onDropRow={() => onDrop(i)} />
            ))}
            {!rows.length && <tr><td colSpan={11} style={{ padding: 36, textAlign: 'center', fontFamily: C.serif, fontSize: 15, color: C.carbon50 }}>No schools yet — add one below.</td></tr>}
          </tbody>
        </table>
      </div>

      {catalogRemaining.length > 0 && (
        <div style={{ marginTop: 14 }}>
          <AddSchool options={catalogRemaining} onAdd={addSchool} />
        </div>
      )}

      {toast && <div style={{ position: 'fixed', bottom: 24, left: '50%', transform: 'translateX(-50%)', background: C.carbon, color: '#fff', padding: '12px 20px', fontFamily: C.sans, fontSize: 12, letterSpacing: '0.04em', display: 'flex', alignItems: 'center', gap: 9, zIndex: 200, boxShadow: 'var(--shadow-3)', borderRadius: 2 }}><Icon name="check" size={15} color="#fff" />{toast}</div>}
    </div>
  );
}

/* ---- header --------------------------------------------------------------- */
function Header({ client, roundLabel, inScope, total }) {
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 24, flexWrap: 'wrap' }}>
      <div>
        <div style={{ fontFamily: C.sans, fontSize: 10, fontWeight: 600, letterSpacing: '0.2em', textTransform: 'uppercase', color: C.carbon50, marginBottom: 10 }}>School Selection</div>
        <h1 style={{ fontFamily: 'var(--font-display)', fontWeight: 400, textTransform: 'uppercase', letterSpacing: '-0.005em', fontSize: 'clamp(30px,3.4vw,46px)', lineHeight: 1, margin: 0, color: C.carbon }}>{client.name}</h1>
        <p style={{ fontFamily: C.serif, fontSize: 15, lineHeight: 1.55, color: C.carbon70, marginTop: 10, maxWidth: 620 }}>
          We build and balance the list together. It may evolve throughout our process as we continue to explore your needs and your fit for various programs.
        </p>
      </div>
    </div>
  );
}

/* ---- balance bar ---------------------------------------------------------- */
function BalanceBar({ counts, hint, total }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 18, flexWrap: 'wrap', marginTop: 20, background: '#fff', border: `1px solid ${C.hair}`, padding: '14px 18px' }}>
      <span style={{ fontFamily: C.sans, fontSize: 10, fontWeight: 600, letterSpacing: '0.12em', textTransform: 'uppercase', color: C.carbon50 }}>List balance</span>
      {['reach', 'target', 'safety'].map(t => (
        <span key={t} style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontFamily: C.sans, fontSize: 12.5, color: C.carbon }}>
          <span style={{ width: 26, height: 26, borderRadius: 999, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700, fontSize: 12, color: '#fff', background: TIER[t].c }} className="st-num">{counts[t]}</span>
          {TIER[t].n} · {TIER[t].label}
        </span>
      ))}
      <span style={{ flex: 1 }} />
      <span style={{ fontFamily: C.sans, fontSize: 12.5, color: C.carbon }}><strong className="st-num" style={{ fontWeight: 700 }}>{total}</strong> {total === 1 ? 'school' : 'schools'} in scope</span>
      {hint && <span style={{ fontFamily: C.serif, fontStyle: 'italic', fontSize: 13.5, color: counts.safety === 0 || counts.target === 0 ? 'color-mix(in oklab, var(--sch-denied-pre), #1D1D1D 20%)' : C.carbon70 }}>{hint}</span>}
    </div>
  );
}

/* ---- row ------------------------------------------------------------------ */
function StrategyRow({ r, i, round, roundLabel, expanded, appStatus, onSetStatus, onExpand, onUpdate, onRemove, onDragStart, onDropRow }) {
  const [h, setH] = React.useState(false);
  const cat = catEntry(r.key, r.name);
  const tier = r.tier || null;
  const slug = contentSlugFor(r.key, r.name);
  const dl = deadlineFor(slug, roundLabel);
  const fc = fitCount(r.fit);
  const td = { padding: '11px 14px', borderBottom: `1px solid ${C.hair}`, verticalAlign: 'middle' };
  const independent = r.structure === 'independent';
  return (
    <>
      <tr draggable onDragStart={onDragStart} onDragOver={e => e.preventDefault()} onDrop={onDropRow}
        onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
        style={{ background: h ? 'rgba(29,29,29,0.02)' : 'transparent', opacity: independent ? 0.72 : 1 }}>
        <td style={{ ...td, cursor: 'grab', color: C.carbon30, width: 26, textAlign: 'center' }} title="Drag to rank"><Icon name="grip" size={15} color="var(--kh-carbon-30)" /></td>
        <td style={td}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
            <UI.SchoolMark slug={slug} short={r.short} size={32} variant="solid" />
            <div style={{ minWidth: 0 }}>
              <div style={{ fontFamily: C.sans, fontWeight: 600, fontSize: 13.5, color: C.carbon, whiteSpace: 'nowrap' }}>{r.name}</div>
              {cat && <div style={{ fontFamily: C.sans, fontSize: 10.5, color: C.carbon50 }}>{r.short}</div>}
            </div>
          </div>
        </td>
        <td style={td}><StatusDropdown value={r.status} onChange={(v) => onUpdate(r.key, { status: v })} /></td>
        <td style={td}>
          <div style={{ display: 'inline-flex', border: `1px solid var(--border-rule)`, borderRadius: 2, overflow: 'hidden' }}>
            {[['in-scope', 'In scope'], ['independent', 'Independent']].map(([v, l], k) => (
              <button key={v} onClick={() => onUpdate(r.key, { structure: v })} style={{ fontFamily: C.sans, fontSize: 9.5, fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', padding: '6px 9px', cursor: 'pointer', border: 'none', borderLeft: k ? '1px solid var(--border-rule)' : 'none', background: r.structure === v ? C.carbon : 'transparent', color: r.structure === v ? '#fff' : C.carbon50, whiteSpace: 'nowrap' }}>{l}</button>
            ))}
          </div>
        </td>
        <td style={td}><TierDropdown value={r.tier} onChange={(v) => onUpdate(r.key, { tier: v })} /></td>
        <td style={{ ...td, whiteSpace: 'nowrap' }}><span className="st-num" style={{ fontFamily: C.sans, fontSize: 12.5, color: cat ? C.carbon70 : C.carbon30 }}>{cat ? (cat.accept || '—') : '—'}</span></td>
        <td style={{ ...td, whiteSpace: 'nowrap' }}><span className="st-num" style={{ fontFamily: C.sans, fontSize: 12.5, color: cat ? C.carbon70 : C.carbon30 }}>{cat ? (cat.gmat || '—') : '—'}</span></td>
        <td style={{ ...td, whiteSpace: 'nowrap' }}><span className="st-num" style={{ fontFamily: C.sans, fontSize: 12.5, color: cat ? C.carbon70 : C.carbon30 }}>{cat ? (cat.size || '—') : '—'}</span></td>
        <td style={{ ...td, whiteSpace: 'nowrap' }}>
          {dl ? <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontFamily: C.sans, fontSize: 12.5, color: C.carbon }}><Icon name="calendar" size={13} color="var(--kh-clay-deep)" />{dl}</span>
            : <span style={{ fontFamily: C.sans, fontSize: 11, color: C.carbon30 }}>{roundLabel ? 'Not released' : 'Set round'}</span>}
        </td>
        <td style={td}>
          <button onClick={onExpand} title="Notes" style={{ display: 'inline-flex', alignItems: 'center', gap: 7, background: r.notes ? 'var(--kh-clay-soft)' : 'transparent', border: `1px solid ${r.notes ? 'var(--kh-clay-deep)' : C.hair}`, borderRadius: 999, padding: '5px 11px', cursor: 'pointer' }}>
            <Icon name="edit" size={13} color="var(--kh-carbon)" />
            <Icon name={expanded ? 'chevronDown' : 'chevronRight'} size={12} color="var(--kh-carbon-50)" />
          </button>
        </td>
        <td style={{ ...td, textAlign: 'right' }}>
          <button onClick={onRemove} title="Remove" style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 4, opacity: h ? 0.6 : 0 }}><Icon name="x" size={14} color="var(--kh-carbon-50)" /></button>
        </td>
      </tr>
      <tr>
        <td colSpan={11} style={{ borderBottom: `1px solid ${C.hair}`, padding: '0 14px 12px 56px', background: h ? 'rgba(29,29,29,0.02)' : 'transparent' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 7, flexWrap: 'wrap' }}>
            <span style={{ fontFamily: C.sans, fontSize: 9, fontWeight: 600, letterSpacing: '0.1em', textTransform: 'uppercase', color: C.carbon50, marginRight: 2 }}>Criteria</span>
            {CRITERIA.map(([k, label]) => {
              const on = !!(r.fit || {})[k];
              return (
                <button key={k} onClick={() => onUpdate(r.key, { fit: { ...(r.fit || {}), [k]: !on } })} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontFamily: C.sans, fontSize: 11, fontWeight: 500, padding: '4px 10px', borderRadius: 999, cursor: 'pointer', border: `1px solid ${on ? C.carbon : 'var(--border-rule)'}`, background: on ? C.carbon : 'transparent', color: on ? '#fff' : C.carbon70 }}>
                  {on && <Icon name="check" size={11} color="#fff" />}{label}
                </button>
              );
            })}
          </div>
        </td>
      </tr>
      {expanded && (
        <tr>
          <td colSpan={11} style={{ borderBottom: `1px solid ${C.hair}`, background: C.porcelain, padding: '14px 22px 16px 56px' }}>
            <div style={{ fontFamily: C.sans, fontSize: 10, fontWeight: 600, letterSpacing: '0.14em', textTransform: 'uppercase', color: C.carbon50, marginBottom: 9 }}>Notes &amp; rationale</div>
            <textarea value={r.notes} onChange={e => onUpdate(r.key, { notes: e.target.value })} rows={3} placeholder="Why this school, fit notes, scope rationale (e.g. 'may deprioritize', 'decide after R1')…"
              style={{ width: '100%', maxWidth: 680, fontFamily: C.serif, fontSize: 14, lineHeight: 1.5, color: C.carbon, border: `1px solid var(--border-rule)`, borderRadius: 2, padding: '11px 13px', outline: 'none', resize: 'vertical', background: '#fff' }} />
          </td>
        </tr>
      )}
    </>
  );
}

function Dots({ value, onChange }) {
  return (
    <div style={{ display: 'inline-flex', gap: 5 }}>
      {[1, 2, 3, 4, 5].map(n => (
        <button key={n} onClick={() => onChange(n === value ? 0 : n)} title={n + ' / 5'} style={{ width: 18, height: 18, borderRadius: 999, border: `1.5px solid ${n <= value ? C.carbon : C.carbon30}`, background: n <= value ? C.carbon : 'transparent', cursor: 'pointer', padding: 0 }} />
      ))}
    </div>
  );
}

function CriteriaMatrix({ rows, onUpdate }) {
  return (
    <div style={{ background: '#fff', border: `1px solid ${C.hair}`, marginTop: 12, overflowX: 'auto' }}>
      <table style={{ borderCollapse: 'collapse', width: '100%', minWidth: 820 }}>
        <thead>
          <tr style={{ borderBottom: `1px solid var(--border-rule)` }}>
            <th style={{ position: 'sticky', left: 0, zIndex: 2, background: '#fbfaf7', textAlign: 'left', padding: '11px 16px', fontFamily: C.sans, fontSize: 9.5, fontWeight: 600, letterSpacing: '0.1em', textTransform: 'uppercase', color: C.carbon50, minWidth: 210, borderRight: `1px solid ${C.hair}` }}>School</th>
            {CRITERIA.map(([k, label]) => (
              <th key={k} style={{ padding: '11px 8px', fontFamily: C.sans, fontSize: 9, fontWeight: 600, letterSpacing: '0.03em', textTransform: 'uppercase', color: C.carbon50, textAlign: 'center', minWidth: 72 }}>{label}</th>
            ))}
            <th style={{ padding: '11px 12px', fontFamily: C.sans, fontSize: 9, fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', color: C.carbon50, textAlign: 'center' }}>Fit</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(r => {
            const f = r.fit || {};
            const count = CRITERIA.reduce((n, [k]) => n + (f[k] ? 1 : 0), 0);
            return (
              <tr key={r.key} style={{ borderBottom: `1px solid ${C.hair}`, opacity: r.structure === 'independent' ? 0.7 : 1 }}>
                <td style={{ position: 'sticky', left: 0, zIndex: 1, background: '#fff', padding: '10px 16px', borderRight: `1px solid ${C.hair}` }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                    <UI.SchoolMark slug={contentSlugFor(r.key, r.name)} short={r.short} size={26} variant="solid" />
                    <span style={{ fontFamily: C.sans, fontWeight: 600, fontSize: 13, color: C.carbon, whiteSpace: 'nowrap' }}>{r.name}</span>
                  </div>
                </td>
                {CRITERIA.map(([k]) => {
                  const on = !!f[k];
                  return (
                    <td key={k} style={{ textAlign: 'center', padding: '8px' }}>
                      <button onClick={() => onUpdate(r.key, { fit: { ...f, [k]: !on } })} style={{ width: 20, height: 20, borderRadius: 4, border: `1.5px solid ${on ? C.carbon : C.carbon30}`, background: on ? C.carbon : 'transparent', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>{on && <Icon name="check" size={12} color="#fff" />}</button>
                    </td>
                  );
                })}
                <td className="st-num" style={{ textAlign: 'center', padding: '8px', fontFamily: C.sans, fontWeight: 700, fontSize: 12, color: C.carbon }}>{count}/8</td>
              </tr>
            );
          })}
          {!rows.length && <tr><td colSpan={10} style={{ padding: 36, textAlign: 'center', fontFamily: C.serif, fontSize: 15, color: C.carbon50 }}>No schools yet — add one below.</td></tr>}
        </tbody>
      </table>
    </div>
  );
}

function TierDropdown({ value, onChange }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => { const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h); }, []);
  return (
    <div ref={ref} style={{ position: 'relative', display: 'inline-block' }}>
      <button onClick={() => setOpen(o => !o)} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, cursor: 'pointer', border: 'none', background: 'none', padding: 0 }}>
        {value ? <span style={{ fontFamily: C.sans, fontSize: 9.5, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase', padding: '4px 9px', borderRadius: 999, color: '#fff', background: TIER[value].c }}>{TIER[value].n} · {TIER[value].label}</span> : <span style={{ fontFamily: C.sans, fontSize: 10, letterSpacing: '0.06em', textTransform: 'uppercase', color: C.carbon50, border: `1px dashed ${C.carbon30}`, padding: '4px 9px', borderRadius: 999 }}>Set tier</span>}
        <Icon name="chevronDown" size={12} color="var(--kh-carbon-50)" />
      </button>
      {open && (
        <div style={{ position: 'absolute', top: '120%', left: 0, zIndex: 40, background: '#fff', border: `1px solid var(--border-rule)`, minWidth: 150, boxShadow: 'var(--shadow-2)', padding: 4 }}>
          {['reach', 'target', 'safety'].map(k => (
            <button key={k} onClick={() => { onChange(k); setOpen(false); }} style={{ display: 'flex', alignItems: 'center', gap: 9, width: '100%', textAlign: 'left', background: value === k ? C.porcelain : 'none', border: 'none', cursor: 'pointer', padding: '7px 10px', fontFamily: C.sans, fontSize: 12.5, color: C.carbon }}>
              <span style={{ width: 8, height: 8, borderRadius: 999, background: TIER[k].c }} />{TIER[k].n} · {TIER[k].label}
            </button>
          ))}
          {value && <button onClick={() => { onChange(null); setOpen(false); }} style={{ display: 'block', width: '100%', textAlign: 'left', background: 'none', border: 'none', cursor: 'pointer', padding: '7px 10px', fontFamily: C.sans, fontSize: 12, color: C.carbon50, borderTop: `1px solid ${C.hair}` }}>Clear</button>}
        </div>
      )}
    </div>
  );
}

function StatusDropdown({ value, onChange }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => { const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h); }, []);
  const s = STRAT_STATUS[value] || STRAT_STATUS.considering;
  return (
    <div ref={ref} style={{ position: 'relative', display: 'inline-block' }}>
      <button onClick={() => setOpen(o => !o)} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, cursor: 'pointer', border: 'none', background: 'none', padding: 0 }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontFamily: C.sans, fontSize: 10, fontWeight: 600, letterSpacing: '0.05em', textTransform: 'uppercase', whiteSpace: 'nowrap', padding: '4px 9px', borderRadius: 999, ...statusStyle(value) }}>{s.label}</span>
        <Icon name="chevronDown" size={12} color="var(--kh-carbon-50)" />
      </button>
      {open && (
        <div style={{ position: 'absolute', top: '120%', left: 0, zIndex: 40, background: '#fff', border: `1px solid var(--border-rule)`, minWidth: 190, boxShadow: 'var(--shadow-2)', padding: 4 }}>
          {STRAT_ORDER.map(k => (
            <button key={k} onClick={() => { onChange(k); setOpen(false); }} style={{ display: 'flex', alignItems: 'center', gap: 9, width: '100%', textAlign: 'left', background: k === value ? C.porcelain : 'none', border: 'none', cursor: 'pointer', padding: '7px 10px', fontFamily: C.sans, fontSize: 12.5, color: C.carbon }}>
              <span style={{ width: 8, height: 8, borderRadius: 999, background: STRAT_STATUS[k].c, flexShrink: 0 }} />{STRAT_STATUS[k].label}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function AddSchool({ options, onAdd }) {
  const [open, setOpen] = React.useState(false);
  const [customMode, setCustomMode] = React.useState(false);
  const [customName, setCustomName] = React.useState('');
  const ref = React.useRef(null);
  React.useEffect(() => { const h = (e) => { if (ref.current && !ref.current.contains(e.target)) { setOpen(false); setCustomMode(false); setCustomName(''); } }; document.addEventListener('mousedown', h); return () => document.removeEventListener('mousedown', h); }, []);
  const submitCustom = () => { const n = customName.trim(); if (!n) return; onAdd(n); setCustomName(''); setCustomMode(false); setOpen(false); };
  return (
    <div ref={ref} style={{ position: 'relative', display: 'inline-block' }}>
      <button onClick={() => setOpen(o => !o)} style={{ display: 'inline-flex', alignItems: 'center', gap: 8, background: '#fff', border: `1px dashed ${C.carbon30}`, padding: '9px 14px', cursor: 'pointer', fontFamily: C.sans, fontSize: 11.5, fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase', color: C.carbon70, borderRadius: 2 }}>
        <Icon name="plus" size={14} /> Add a school
      </button>
      {open && (
        <div style={{ position: 'absolute', bottom: '120%', left: 0, zIndex: 40, background: '#fff', border: `1px solid var(--border-rule)`, minWidth: 260, boxShadow: 'var(--shadow-2)', padding: 4, maxHeight: 360, overflowY: 'auto' }}>
          {!customMode && options.map(o => (
            <button key={o.name} onClick={() => { onAdd(o.name); setOpen(false); }} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, width: '100%', textAlign: 'left', background: 'none', border: 'none', cursor: 'pointer', padding: '8px 11px', fontFamily: C.sans, fontSize: 12.5, color: C.carbon }}
              onMouseEnter={e => e.currentTarget.style.background = C.porcelain} onMouseLeave={e => e.currentTarget.style.background = 'none'}>
              <span>{o.name}</span>
              <span className="st-num" style={{ fontFamily: C.sans, fontSize: 10.5, color: C.carbon50 }}>{o.accept || ''}</span>
            </button>
          ))}
          {!customMode && (
            <button onClick={() => setCustomMode(true)} style={{ display: 'flex', alignItems: 'center', gap: 8, width: '100%', textAlign: 'left', background: 'none', border: 'none', borderTop: options.length ? `1px solid ${C.hair}` : 'none', cursor: 'pointer', padding: '9px 11px', fontFamily: C.sans, fontSize: 12.5, fontWeight: 600, color: C.carbon }}>
              <Icon name="plus" size={13} /> Enter a school manually
            </button>
          )}
          {customMode && (
            <div style={{ padding: 8, display: 'flex', flexDirection: 'column', gap: 8 }}>
              <input autoFocus value={customName} onChange={e => setCustomName(e.target.value)} onKeyDown={e => e.key === 'Enter' && submitCustom()} placeholder="School / program name"
                style={{ fontFamily: C.sans, fontSize: 13, color: C.carbon, border: `1px solid var(--border-rule)`, borderRadius: 2, padding: '9px 10px', outline: 'none' }} />
              <div style={{ display: 'flex', gap: 8 }}>
                <button onClick={submitCustom} disabled={!customName.trim()} style={{ flex: 1, fontFamily: C.sans, fontSize: 11.5, fontWeight: 600, letterSpacing: '0.04em', textTransform: 'uppercase', padding: '8px 10px', border: 'none', borderRadius: 2, cursor: customName.trim() ? 'pointer' : 'default', background: customName.trim() ? C.carbon : C.carbon30, color: '#fff' }}>Add</button>
                <button onClick={() => { setCustomMode(false); setCustomName(''); }} style={{ fontFamily: C.sans, fontSize: 11.5, padding: '8px 10px', border: `1px solid ${C.hair}`, borderRadius: 2, cursor: 'pointer', background: 'none', color: C.carbon70 }}>Cancel</button>
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

/* ---- client summary (the agreed list) ------------------------------------- */
function ClientSummary({ rows, round, roundLabel }) {
  const list = rows.slice();
  return (
    <div style={{ marginTop: 40 }}>
      <div style={{ fontFamily: C.sans, fontSize: 11, fontWeight: 600, letterSpacing: '0.14em', textTransform: 'uppercase', color: C.carbon, marginBottom: 14 }}>The agreed list <span style={{ color: C.carbon50, fontWeight: 400 }}>· what the client sees</span></div>
      {!list.length && <div style={{ fontFamily: C.serif, fontSize: 14, color: C.carbon50 }}>No schools in scope yet.</div>}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(230px, 1fr))', gap: 12 }}>
        {list.map(r => {
          const cat = catEntry(r.key, r.name); const tier = cat ? tierOf(cat.accept) : null;
          const slug = contentSlugFor(r.key, r.name);
          const dl = deadlineFor(slug, roundLabel);
          return (
            <div key={r.key} style={{ background: '#fff', border: `1px solid ${C.hair}`, padding: 16 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
                <UI.SchoolMark slug={slug} short={r.short} size={30} variant="solid" />
                <div style={{ fontFamily: C.sans, fontWeight: 600, fontSize: 13.5, color: C.carbon }}>{r.name}</div>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 7, flexWrap: 'wrap' }}>
                <span style={{ fontFamily: C.sans, fontSize: 9, fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', padding: '3px 8px', borderRadius: 999, ...statusStyle(r.status) }}>{(STRAT_STATUS[r.status] || {}).label}</span>
                {tier && <span style={{ fontFamily: C.sans, fontSize: 9, fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', padding: '3px 8px', borderRadius: 999, color: '#fff', background: TIER[tier].c }}>{TIER[tier].label}</span>}
              </div>
              {dl && <div style={{ fontFamily: C.serif, fontSize: 12.5, color: C.carbon70, marginTop: 9 }}>{roundLabel}: {dl}</div>}
            </div>
          );
        })}
      </div>
    </div>
  );
}

window.StrategyApp = StrategyApp;
window.StrategyBoard = StrategyApp;
