/* global React, Icon, Btn, Avatar, VerifiedBadge, PlatformIcon, TopBar, Drawer, calcPlan, fmt, PLATFORMS, CREATORS_DB, LISTS */
// — Forecast/metric column definitions (data-driven so the column picker can toggle them) —
//   Engagement sub-metrics (likes/comments/saves/shares) are derived from total
//   engagements with typical platform-blend ratios so they always sum to engagements.
const ENGAGE_SPLIT = { likes: 0.70, comments: 0.12, saves: 0.10, shares: 0.08 };
const splitEng = (eng, key) => Math.round((eng || 0) * ENGAGE_SPLIT[key]);

const METRIC_COLS = [
  { key: 'adjustment', label: 'Adj.',     group: 'input',    width: 64,  defHidden: false },
  { key: 'views',       label: 'Views',    group: 'forecast', width: 74,  defHidden: false },
  { key: 'reach',       label: 'Reach',    group: 'forecast', width: 74,  defHidden: false },
  { key: 'engagements', label: 'Engmts',   group: 'forecast', width: 74,  defHidden: false },
  { key: 'likes',       label: 'Likes',    group: 'forecast', width: 74,  defHidden: true },
  { key: 'comments',    label: 'Comments', group: 'forecast', width: 84,  defHidden: true },
  { key: 'saves',       label: 'Saves',    group: 'forecast', width: 74,  defHidden: true },
  { key: 'shares',      label: 'Shares',   group: 'forecast', width: 74,  defHidden: true },
  { key: 'er',          label: 'ER',       group: 'forecast', width: 64,  defHidden: false },
];
const METRIC_COL_MAP = Object.fromEntries(METRIC_COLS.map((c) => [c.key, c]));

// — Max Payout column —
//   A spend ceiling derived from forecast impressions at a fixed CPM:
//   Max Payout = (Total Impressions / 1000) × MAX_PAYOUT_CPM.
//   Impressions are taken from forecast views (the plan's impression metric).
const MAX_PAYOUT_CPM = 25; // £ hardcoded rate per 1,000 impressions
const maxPayoutOf = (src) => (((src && src.views) || 0) / 1000) * MAX_PAYOUT_CPM;

// Value + zero-flag for a metric column given a totals-like source object.
function metricCellValue(key, src, opts = {}) {
  const eng = src.engagements || 0;
  switch (key) {
    case 'views':       return { v: fmt.num(src.views || 0),  zero: (src.views || 0) === 0 };
    case 'reach':       return { v: fmt.num(src.reach || 0),  zero: (src.reach || 0) === 0 };
    case 'engagements': return { v: fmt.num(eng),             zero: eng === 0 };
    case 'likes':       return { v: fmt.num(splitEng(eng, 'likes')),    zero: eng === 0 };
    case 'comments':    return { v: fmt.num(splitEng(eng, 'comments')), zero: eng === 0 };
    case 'saves':       return { v: fmt.num(splitEng(eng, 'saves')),    zero: eng === 0 };
    case 'shares':      return { v: fmt.num(splitEng(eng, 'shares')),   zero: eng === 0 };
    case 'er': {
      if (opts.twitch) return { v: '—', zero: false };
      const er = opts.perType ? src.erPerPost : src.er;
      return { v: er == null ? '—' : fmt.pct(er), zero: false };
    }
    default:            return { v: '', zero: false };
  }
}

// — Plan detail view — the heart of the prototype.
//   Spreadsheet-style plan editor with goal deltas, scenarios, versions.

// — CSV export: builds a full plan export (every metric) and downloads it client-side —
function exportPlanCsv(plan, calc, campaign) {
  const PLAT = (typeof PLATFORMS !== 'undefined' && PLATFORMS) || {};
  const esc = (v) => {
    if (v == null) return '';
    let s = String(v);
    if (/[",\n]/.test(s)) s = '"' + s.replace(/"/g, '""') + '"';
    return s;
  };
  const r2 = (n) => n == null ? '' : Math.round(n * 100) / 100;
  const r0 = (n) => n == null ? '' : Math.round(n);
  const pct = (n) => n == null ? '' : Math.round(n * 1000) / 10;

  const visible = calc.creators.filter((c) => !(c.inputs && c.inputs.hidden));

  const rows = [];
  rows.push(['Plan', plan.name]);
  rows.push(['Currency', plan.currency || 'GBP']);
  rows.push(['Creators', visible.length]);
  // Linked campaign block — only present once the plan has been sent to a campaign.
  if (campaign) {
    rows.push(['Campaign', campaign.name]);
    rows.push(['Campaign status', campaign.status || '']);
    rows.push(['Campaign start', campaign.start || '']);
    rows.push(['Campaign end', campaign.end || '']);
  }
  rows.push(['Target budget', plan.targetBudget, 'Target views', plan.targetViews, 'Target CPM', plan.targetCpm]);
  rows.push(['Misc additional fees', plan.paidMedia || 0]);
  rows.push([]);
  rows.push(['Row type', 'Creator', 'Platform', 'Post type', 'Posts', 'Views', 'Engagements', `Payout (${fmt.sym()})`, `Agency fee (${fmt.sym()})`, `CPM (${fmt.sym()})`, `Max Payout (${fmt.sym()})`, 'ER (%)', 'Comments']);

  visible.forEach((c) => {
    const t = c.totals;
    rows.push(['Creator', c.name, '', '', r0(t.posts), r0(t.views), r0(t.engagements), r2(t.payoutTotal), r2(t.agencyFeeTotal), r2(t.cpm), r2(maxPayoutOf(t)), pct(t.er), (c.inputs && c.inputs.note) || '']);
    c.handles.forEach((h) => {
      const platLabel = (PLAT[h.platform] && PLAT[h.platform].label) || h.platform;
      h.types.forEach((ty) => {
        const ti = ty._inputs || {};
        const payoutPost = (ti.payoutPerPost != null && ti.payoutPerPost !== '') ? ti.payoutPerPost * ty.postCount : null;
        const cpmPost = (ti.payoutPerPost && ty.views > 0) ? (ti.payoutPerPost * ty.postCount / ty.views) * 1000 : null;
        rows.push(['  Post type', c.name, platLabel + ' ' + h.handle, ty.label, r0(ty.postCount), r0(ty.views), r0(ty.engagements), payoutPost == null ? '' : r2(payoutPost), '', cpmPost == null ? '' : r2(cpmPost), r2(maxPayoutOf(ty)), pct(ty.erPerPost), (ti.note || '')]);
      });
    });
  });

  const T = calc.totals;
  rows.push([]);
  rows.push(['Plan total', plan.name, '', '', r0(T.posts), r0(T.views), r0(T.engagements), r2(T.creatorPayout), r2(T.agency), r2(T.totalCpm), r2(maxPayoutOf(T)), pct(T.er), '']);
  rows.push(['Misc additional fees', '', '', '', '', '', '', r2(T.paidMedia), '', '', '', '', '']);
  rows.push(['Total budget', '', '', '', '', '', '', r2(T.totalBudget), '', '', '', '', '']);

  const csv = rows.map((row) => row.map(esc).join(',')).join('\r\n');
  const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = (plan.name || 'plan').replace(/[^\w\- ]+/g, '').trim().replace(/\s+/g, '-').toLowerCase() + '.csv';
  document.body.appendChild(a);
  a.click();
  setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 1000);
}

const PlanView = ({ plan, planInputs, setPlanInputs, setPlanField, go, lists, plans, campaigns = [], onSendToCampaign, onAddCreators, onDuplicate, tweaks }) => {
  const [expanded, setExpanded] = React.useState(() => new Set(['aria', 'lina']));
  const [notesOpen, setNotesOpen] = React.useState(() => new Set());
  const linkedCampaign = campaigns.find((c) => c.id === plan.campaignId) || null;
  const [showHidden, setShowHidden] = React.useState(false);
  const [activeScen, setActiveScen] = React.useState(plan.versions[plan.versions.length - 1].label);
  const [showVersions, setShowVersions] = React.useState(false);
  const [showActions, setShowActions] = React.useState(false);
  const list = lists.find((l) => l.id === plan.listId);

  // Calculate live metrics
  const calc = React.useMemo(() => calcPlan(plan, planInputs), [plan, planInputs]);

  // — Which payout-section columns are active (driven by the plan settings) —
  const showPayout = plan.payoutEnabled !== false;
  const showAgency = !!(plan.agencyFee && plan.agencyFee.enabled);

  // — Efficiency metrics are chosen as GOALS during plan setup; each gates both its
  //   goal card and its matching table column. CPM additionally requires payout. —
  const showCpm = showPayout && (plan.targetCpm || 0) > 0;
  // Max Payout is always shown — a fixed-CPM spend ceiling off forecast impressions.
  const showMaxPayout = true;
  // Payout-section columns: Payout (showPayout) + Agency (showAgency) + CPM (showCpm) + Max Payout
  const payoutCols = (showPayout ? 1 : 0) + (showAgency ? 1 : 0) + (showCpm ? 1 : 0) + (showMaxPayout ? 1 : 0);

  // — Visible metric columns are DERIVED FROM GOALS, not a manual picker.
  //   Adjustment input + core forecast metrics (views / reach / engagements / ER) always
  //   show. —
  const ALWAYS_COLS = new Set(['views', 'engagements', 'er']);
  const visibleCols = METRIC_COLS.filter((c) => {
    if (ALWAYS_COLS.has(c.key)) return true;
    return false; // likes / comments / saves / shares are no longer surfaced
  });
  const inputCols = visibleCols.filter((c) => c.group === 'input');
  const forecastCols = visibleCols.filter((c) => c.group === 'forecast');
  // Spanning width for full-width body rows (add-row / handle filler): Posts + metric cols + payout cols + action
  const bodySpan = 1 + visibleCols.length + payoutCols + 1;

  // — Collapsible goal strip (persisted) —
  const [goalsCollapsed, setGoalsCollapsed] = React.useState(() => {
    try { return localStorage.getItem('cp_goalsCollapsed') === '1'; } catch (e) { return false; }
  });
  React.useEffect(() => {
    try { localStorage.setItem('cp_goalsCollapsed', goalsCollapsed ? '1' : '0'); } catch (e) {}
  }, [goalsCollapsed]);

  // — Efficiency goal card row (CPM). Renders greyed-out (inactive) when no goal is set
  //   but stays editable. —
  const efficiencyMetrics = [
    { key: 'cpm', label: 'CPM', goal: 75, actual: calc.totals.totalCpm, unit: '$', decimals: 2, prefix: fmt.sym(), locked: true, goalKey: 'targetCpm', betterIs: 'under', achievement: calc.deltas.cpmAchievement, inactive: false },
  ].filter(Boolean);

  const toggleExpand = (id) => {
    const next = new Set(expanded);
    if (next.has(id)) next.delete(id);else next.add(id);    setExpanded(next);
  };

  const toggleNotes = (id) => {
    setNotesOpen((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  };

  const updateInput = (creatorId, path, value) => {
    setPlanInputs((prev) => {
      const next = JSON.parse(JSON.stringify(prev));
      const c = next[creatorId] = next[creatorId] || { handles: {} };
      let cur = c;
      for (let i = 0; i < path.length - 1; i++) {
        cur[path[i]] = cur[path[i]] || {};
        cur = cur[path[i]];
      }
      cur[path[path.length - 1]] = value;
      return next;
    });
  };

  const toggleHidden = (creatorId) => {
    updateInput(creatorId, ['hidden'], !(planInputs[creatorId] || {}).hidden);
  };

  // — Add / remove platforms and post types within a creator —
  const [picker, setPicker] = React.useState(null); // {kind:'addPlatform'|'addType', creatorId, platformId?}

  const addPlatform = (creatorId, platformId) => {
    setPlanInputs((prev) => {
      const next = JSON.parse(JSON.stringify(prev));
      next[creatorId] = next[creatorId] || { agencyFeePct: 10, payoutTotal: 0, handles: {} };
      next[creatorId].handles = next[creatorId].handles || {};
      if (!next[creatorId].handles[platformId]) {
        const firstType = (PLATFORMS[platformId]?.types || [])[0];
        next[creatorId].handles[platformId] = firstType ?
        { [firstType.id]: { postCount: 1, payoutPerPost: null, adjustmentPct: 0 } } :
        {};
      }
      return next;
    });
    setPicker(null);
  };

  const removePlatform = (creatorId, platformId) => {
    setPlanInputs((prev) => {
      const next = JSON.parse(JSON.stringify(prev));
      if (next[creatorId]?.handles?.[platformId]) delete next[creatorId].handles[platformId];
      return next;
    });
  };

  const addType = (creatorId, platformId, typeId) => {
    setPlanInputs((prev) => {
      const next = JSON.parse(JSON.stringify(prev));
      next[creatorId] = next[creatorId] || { handles: {} };
      next[creatorId].handles[platformId] = next[creatorId].handles[platformId] || {};
      if (!next[creatorId].handles[platformId][typeId]) {
        next[creatorId].handles[platformId][typeId] = { postCount: 1, payoutPerPost: null, adjustmentPct: 0 };
      }
      return next;
    });
    setPicker(null);
  };

  const removeType = (creatorId, platformId, typeId) => {
    setPlanInputs((prev) => {
      const next = JSON.parse(JSON.stringify(prev));
      if (next[creatorId]?.handles?.[platformId]?.[typeId]) {
        delete next[creatorId].handles[platformId][typeId];
      }
      return next;
    });
  };

  return (
    <>
      <div className="body">

        {/* — Page header: breadcrumb + actions (moved off the top bar) — */}
        <div className="row gap-3" style={{ alignItems: 'flex-start', marginBottom: 20, flexWrap: 'wrap' }}>
          <div className="row gap-2" style={{ minWidth: 0, flex: 1 }}>
            <button className="btn-icon" onClick={() => go({ route: 'campaigns' })} title="Back"><Icon name="chevron-left" size={20} /></button>
            <div className="col" style={{ lineHeight: 1.2, minWidth: 0 }}>
              <div className="row gap-2 muted" style={{ fontSize: 12, fontWeight: 500 }}>
                <button className="crumb" onClick={() => go({ route: 'campaigns' })}>Campaigns</button><Icon name="chevron-right" size={10} /><button className="crumb" onClick={() => go({ route: 'campaigns', tab: 'plans' })}>Plans</button>
                {list && <><Icon name="chevron-right" size={10} /><button className="crumb" onClick={() => go({ route: 'list', id: list.id })}>From: {list.name}</button></>}
              </div>
              <h1 style={{ margin: '2px 0 0', fontSize: 26, fontWeight: 700, color: 'var(--ink)', letterSpacing: '-.01em' }}>{plan.name}</h1>
            </div>
          </div>

          <div className="row gap-3" style={{ flexWrap: 'wrap', justifyContent: 'flex-end' }}>
          <div style={{ position: 'relative' }}>
            <button className="filter-pill row gap-2" disabled onClick={() => setShowVersions((v) => !v)}>
              <Icon name="history" size={14} />
              <span style={{ fontWeight: 600, fontSize: 12 }}>{String(activeScen).replace(/^v/, '')}</span>
              <Icon name="chevron-down" size={12} />
            </button>
            {showVersions &&
            <div className="versions" onMouseLeave={() => setShowVersions(false)}>
                  <div className="muted" style={{ padding: '6px 10px', fontSize: 11, textTransform: 'uppercase', letterSpacing: '.06em', fontWeight: 700 }}>Versions</div>
                  {[...plan.versions].reverse().map((v, i) =>
              <div key={v.label} className={`ver-item ${v.label === activeScen ? 'cur' : ''}`} onClick={() => {setActiveScen(v.label);setShowVersions(false);}}>
                      <span className="ver-dot" style={{ background: v.label === activeScen ? 'var(--blue)' : 'var(--ink-4)' }} />
                      <div style={{ flex: 1 }}>
                        <div className="lbl row gap-2">{v.label}{i === 0 && <span className="pill pill-green" style={{ fontSize: 9 }}>current</span>}</div>
                        <div className="sub">{v.when} · {v.who} · {v.note}</div>
                      </div>
                    </div>
              )}
                  <hr style={{ border: 0, borderTop: '1px solid var(--border)', margin: '6px 4px' }} />
                  <div className="ver-item" style={{ color: 'var(--blue)' }}>
                    <Icon name="plus" size={13} />
                    <div style={{ flex: 1, fontWeight: 600, fontSize: 12 }}>Snapshot current as v{plan.versions.length + 1}</div>
                  </div>
                </div>
            }
          </div>
          <Btn variant="ghost" size="sm" icon="cloud-down" onClick={() => exportPlanCsv(plan, calc, linkedCampaign)}>Export CSV</Btn>
          <div style={{ position: 'relative' }}>
            <Btn variant="ghost" size="sm" icon="kebab" disabled onClick={() => setShowActions((v) => !v)} />
            {showActions &&
            <div style={{ position: 'absolute', right: 0, top: 38, background: '#fff', border: '1px solid var(--border)', borderRadius: 8, boxShadow: 'var(--shadow-md)', padding: 6, zIndex: 30, minWidth: 220 }} onMouseLeave={() => setShowActions(false)}>
                  {[
              { l: 'Save', icon: 'check' },
              { l: 'Save as new version', icon: 'history' },
              { l: 'Duplicate plan', icon: 'duplicate', cb: onDuplicate },
              { l: 'Rename', icon: 'edit' },
              { l: 'Delete plan', icon: 'trash', danger: true }].
              map((item) =>
              <button key={item.l} onClick={() => {setShowActions(false);item.cb && item.cb();}} style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 8, padding: '7px 10px', background: 'transparent', color: item.danger ? 'var(--red)' : 'var(--ink)', border: 0, borderRadius: 6, fontSize: 13, fontWeight: 500, cursor: 'pointer', textAlign: 'left' }}>
                      <Icon name={item.icon} size={14} /> {item.l}
                    </button>
              )}
                </div>
            }
          </div>
          </div>
        </div>

        {/* — Goal & forecast strip (collapsible) — */}
        <button className={`goals-collapse-btn ${goalsCollapsed ? 'collapsed' : ''}`} onClick={() => setGoalsCollapsed((v) => !v)}>
          <Icon name="chevron-down" size={13} />
          Plan goals
          {goalsCollapsed && <span className="summary">· {fmt.$(calc.totals.totalBudget)} budget · {fmt.compact(calc.totals.views)} views</span>}
        </button>
        {!goalsCollapsed &&
        <div className="row gap-3" style={{ marginBottom: 14, flexWrap: 'wrap', alignItems: 'stretch' }}>
          <TotalBudgetCard
            creator={calc.totals.creatorPayout}
            agency={calc.totals.agency}
            paidMedia={calc.totals.paidMedia}
            goal={plan.targetBudget}
            setPlanField={setPlanField}
            achievement={calc.deltas.budgetAchievement} />
          <GoalCard
            label="Plan views" goal={plan.targetViews} actual={calc.totals.views}
            unit="" goalKey="targetViews" setPlanField={setPlanField}
            betterIs="over" achievement={calc.deltas.viewsAchievement} />
          {efficiencyMetrics.length > 0 &&
          <MetricGroupCard title={'Plan ' + efficiencyMetrics.map((m) => m.label).join(' · ')} metrics={efficiencyMetrics} setPlanField={setPlanField} />}
        </div>}

        {/* — Payout & agency-fee plan settings (editable) — */}
        <PlanPayoutSettings plan={plan} setPlanField={setPlanField} />

        {/* — Scenarios rail (only when tweak enabled) — */}
        {tweaks.showScenarios &&
        <div className="card card-pad" style={{ marginBottom: 14, padding: '12px 16px' }}>
            <div className="row" style={{ marginBottom: 8 }}>
              <div style={{ fontWeight: 700, fontSize: 12, textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--ink-2)' }}>Scenarios</div>
              <span className="muted" style={{ fontSize: 11, marginLeft: 10 }}>Branching what-ifs from the current plan. Switching scenarios doesn't lose your edits.</span>
            </div>
            <div className="scen-rail no-scrollbar">
              {[
            { k: 'baseline', n: 'Baseline', sub: '8 creators · all platforms', cur: true },
            { k: 'tt-push', n: 'TikTok push', sub: 'cut IG · +50% TT' },
            { k: 'tail-bets', n: 'Long-tail bets', sub: '12 mid-tier creators' },
            { k: 'paid-heavy', n: 'Paid amp heavy', sub: '$12k → paid media' }].
            map((s) =>
            <div key={s.k} className={`scen-card ${s.cur ? 'active' : ''}`}>
                  <div className="lbl">{s.cur ? 'CURRENT' : 'WHAT-IF'}</div>
                  <div className="nm">{s.n}</div>
                  <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>{s.sub}</div>
                </div>
            )}
              <button className="scen-add" title="New scenario"><Icon name="plus" size={18} /></button>
            </div>
          </div>
        }

        {/* — Toolbar — */}
        <div className="row gap-3" style={{ marginBottom: 10, alignItems: 'center' }}>
          <span className="muted" style={{ fontSize: 12 }}>{calc.visibleCreators.length} of {calc.creators.length} creators · {calc.totals.posts} posts · {calc.totals.platformCount} platform{calc.totals.platformCount === 1 ? '' : 's'}</span>
        </div>

        {/* — Spreadsheet — */}
        <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 'var(--radius-lg)', overflow: 'hidden' }}>
          <div style={{ overflowX: 'auto' }}>
            <table className="ptbl">
              <colgroup>
                <col style={{ width: 28 }} />
                <col style={{ minWidth: 240 }} />
                <col style={{ width: 64 }} />
                {visibleCols.map((c) => <col key={c.key} style={{ width: c.width }} />)}
                {showPayout && <col style={{ width: 132 }} />}
                {showAgency && <col style={{ width: 74 }} />}
                {showCpm && <col style={{ width: 78 }} />}
                {showMaxPayout && <col style={{ width: 104 }} />}
                <col style={{ width: 30 }} />
              </colgroup>
              <thead>
                <tr>
                  <th colSpan={2}></th>
                  <th colSpan={1 + inputCols.length} className="group group-input">Inputs</th>
                  {forecastCols.length > 0 && <th colSpan={forecastCols.length} className="group group-forecast">Forecasted</th>}
                  {payoutCols > 0 && <th colSpan={payoutCols} className="group group-payout">Payout</th>}
                  <th></th>
                </tr>
                <tr>
                  <th></th>
                  <th className="left">Creator / handle / post type</th>
                  <th>Posts</th>
                  {visibleCols.map((c) => <th key={c.key}>{c.label}</th>)}
                  {showPayout && <th>Payout</th>}
                  {showAgency && <th>Agency</th>}
                  {showCpm && <th>CPM</th>}
                  {showMaxPayout && <th title={`(Total impressions ÷ 1,000) × ${fmt.sym()}${MAX_PAYOUT_CPM} CPM`}>Max Payout</th>}
                  <th></th>
                </tr>
              </thead>
              <tbody>
                {calc.creators.
                filter((c) => !c.inputs.hidden || showHidden).
                map((c) =>
                <React.Fragment key={c.id}>
                    <CreatorRow
                    c={c} expanded={expanded.has(c.id)}
                    targetCpm={plan.targetCpm}
                    showPayout={showPayout} showAgency={showAgency} showCpm={showCpm} showMaxPayout={showMaxPayout}
                    visibleCols={visibleCols}
                    payoutBasis={plan.payoutBasis || 'total'}
                    hasNote={!!(c.inputs.note && c.inputs.note.trim())}
                    noteOpen={notesOpen.has(c.id)}
                    onToggleNote={() => toggleNotes(c.id)}
                    onToggle={() => toggleExpand(c.id)}
                    onUpdate={(path, val) => updateInput(c.id, path, val)}
                    onHide={() => toggleHidden(c.id)} />

                    {notesOpen.has(c.id) &&
                  <NotesRow
                    span={bodySpan}
                    value={c.inputs.note || ''}
                    onChange={(val) => updateInput(c.id, ['note'], val)}
                    onClose={() => toggleNotes(c.id)} />
                  }
                  
                    {expanded.has(c.id) &&
                  <>
                        {c.handles.map((h) =>
                    <React.Fragment key={c.id + '-' + h.platform}>
                            <HandleRow handle={h} creatorName={c.name}
                      showPayout={showPayout} showAgency={showAgency} showCpm={showCpm} showMaxPayout={showMaxPayout}
                      visibleCols={visibleCols}
                      onRemove={() => removePlatform(c.id, h.platform)} />
                            {h.types.map((t) =>
                      <TypeRow key={c.id + '-' + h.platform + '-' + t.id} c={c} h={h} t={t}
                      targetCpm={plan.targetCpm}
                      showPayout={showPayout} showAgency={showAgency} showCpm={showCpm} showMaxPayout={showMaxPayout}
                      visibleCols={visibleCols}
                      onUpdate={(field, val) => updateInput(c.id, ['handles', h.platform, t.id, field], val)}
                      onRemove={() => removeType(c.id, h.platform, t.id)} />
                      )}
                            <AddTypeRow creatorId={c.id} platform={h.platform}
                      activeTypeIds={h.types.map((t) => t.id)}
                      span={bodySpan}
                      open={picker && picker.kind === 'addType' && picker.creatorId === c.id && picker.platformId === h.platform}
                      onToggle={(open) => setPicker(open ? { kind: 'addType', creatorId: c.id, platformId: h.platform } : null)}
                      onAdd={(typeId) => addType(c.id, h.platform, typeId)} />
                          </React.Fragment>
                    )}
                        <AddPlatformRow creatorId={c.id}
                    activePlatformIds={c.handles.map((h) => h.platform)}
                    span={bodySpan}
                    dbPlatformIds={(CREATORS_DB.find((x) => x.id === c.id)?.handles || []).map((h) => h.platform)}
                    open={picker && picker.kind === 'addPlatform' && picker.creatorId === c.id}
                    onToggle={(open) => setPicker(open ? { kind: 'addPlatform', creatorId: c.id } : null)}
                    onAdd={(platformId) => addPlatform(c.id, platformId)} />
                      </>
                  }
                  </React.Fragment>
                )}
                <PlanTotalsRow totals={calc.totals} targetCpm={plan.targetCpm} showPayout={showPayout} showAgency={showAgency} showCpm={showCpm} showMaxPayout={showMaxPayout} visibleCols={visibleCols} />
              </tbody>
            </table>
          </div>
        </div>

        {/* Hidden creators strip */}
        {calc.hiddenCreators.length > 0 && !showHidden &&
        <div className="hidden-strip">
            <Icon name="eye-off" size={14} />
            <span>{calc.hiddenCreators.length} creator{calc.hiddenCreators.length === 1 ? '' : 's'} removed from this plan: {calc.hiddenCreators.map((c) => c.name).join(', ')}.</span>
            <button className="btn-link" style={{ marginLeft: 'auto', padding: '2px 6px' }} onClick={() => setShowHidden(true)}>Show</button>
          </div>
        }
        {showHidden &&
        <div className="hidden-strip" style={{ borderStyle: 'solid', background: '#fbf7ff', borderColor: '#e8d5f8', color: 'var(--ink)' }}>
            <Icon name="eye" size={14} />
            <span>Showing {calc.hiddenCreators.length} removed creator{calc.hiddenCreators.length === 1 ? '' : 's'}.</span>
            <button className="btn-link" style={{ marginLeft: 'auto', padding: '2px 6px' }} onClick={() => setShowHidden(false)}>Hide them</button>
          </div>
        }

        <div className="row gap-3" style={{ marginTop: 18, color: 'var(--ink-3)', fontSize: 12, flexWrap: 'wrap' }}>
          <span><b style={{ color: 'var(--ink-2)' }}>Last saved</b> {plan.editedAt} by {plan.editedBy}</span>
          <span>·</span>
          <span>Forecasts use trailing 90-day post medians from the Creator Graph</span>
          <span>·</span>
          <span>Adjustments and inputs recalculate live</span>
        </div>
      </div>
    </>);

};

// — Plan-level segmented control —
const Seg = ({ value, onChange, options, size = 'md' }) => (
  <div className={`seg${size === 'sm' ? ' seg-sm' : ''}`}>
    {options.map(o => (
      <button key={o.value} type="button"
        className={value === o.value ? 'seg-on' : ''}
        onClick={() => onChange(o.value)}>{o.label}</button>
    ))}
  </div>
);

// — Payout & agency-fee plan settings (editable on the plan) —
const PlanPayoutSettings = ({ plan, setPlanField }) => {
  const payoutBasis = plan.payoutBasis || 'total';
  const payoutEnabled = plan.payoutEnabled !== false;
  const agency = plan.agencyFee || { enabled: false, basis: 'total', type: 'percentage', value: 10 };
  const setAgency = (patch) => setPlanField('agencyFee', { ...agency, ...patch });

  return (
    <div className="card card-pad" style={{ marginBottom: 14, padding: '12px 16px' }}>
      <div className="row gap-3" style={{ flexWrap: 'wrap', alignItems: 'center' }}>
        {/* Creator payout */}
        <div className="row gap-2" style={{ alignItems: 'center' }}>
          <label className="row gap-2" style={{ cursor: 'pointer', alignItems: 'center' }}>
            <input type="checkbox" checked={payoutEnabled} onChange={(e) => setPlanField('payoutEnabled', e.target.checked)} />
            <span style={{ fontWeight: 700, fontSize: 11, textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--ink-2)' }}>Creator payout</span>
          </label>
          {payoutEnabled ?
          <Seg size="sm" value={payoutBasis} onChange={(v) => setPlanField('payoutBasis', v)}
            options={[{ value: 'perPost', label: 'Per post' }, { value: 'total', label: 'Total' }]} /> :
          <span className="muted" style={{ fontSize: 11 }}>Not tracked in this plan</span>}
        </div>

        <span style={{ color: 'var(--border-strong)' }}>|</span>

        {/* Agency fee */}
        <div className="row gap-2" style={{ alignItems: 'center', flexWrap: 'wrap' }}>
          <label className="row gap-2" style={{ cursor: 'pointer', alignItems: 'center' }}>
            <input type="checkbox" checked={!!agency.enabled} onChange={(e) => setAgency({ enabled: e.target.checked })} />
            <span style={{ fontWeight: 700, fontSize: 11, textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--ink-2)' }}>Agency fee</span>
          </label>
          {agency.enabled && (
            <>
              <Seg size="sm" value={agency.basis} onChange={(v) => setAgency({ basis: v })}
                options={[{ value: 'perPost', label: 'Per post' }, { value: 'total', label: 'Total' }]} />
              <Seg size="sm" value={agency.type} onChange={(v) => setAgency({ type: v })}
                options={[{ value: 'flat', label: 'Flat' }, { value: 'percentage', label: '% of fee' }]} />
              <div style={{ position: 'relative', width: 92 }}>
                {agency.type !== 'percentage' && <span style={{ position: 'absolute', left: 9, top: '50%', transform: 'translateY(-50%)', color: 'var(--ink-3)', fontSize: 12, pointerEvents: 'none' }}>{fmt.sym()}</span>}
                <input type="number" min="0" value={agency.value ?? 0}
                  onChange={(e) => setAgency({ value: Math.max(0, Number(e.target.value)) })}
                  style={{ width: '100%', fontSize: 12, padding: '5px 8px', paddingLeft: agency.type === 'percentage' ? 8 : 20, paddingRight: agency.type === 'percentage' ? 22 : 8, border: '1px solid var(--border)', borderRadius: 6 }} />
                {agency.type === 'percentage' && <span style={{ position: 'absolute', right: 9, top: '50%', transform: 'translateY(-50%)', color: 'var(--ink-3)', fontSize: 12, pointerEvents: 'none' }}>%</span>}
              </div>
            </>
          )}
          {!agency.enabled && <span className="muted" style={{ fontSize: 11 }}>Using each creator's own fee</span>}
        </div>
      </div>
    </div>
  );
};

// — Total budget card (creator + agency + paid media breakdown, tracked against plan budget goal) —
const TotalBudgetCard = ({ creator, agency, paidMedia, goal, setPlanField, achievement }) => {
  const total = (creator || 0) + (agency || 0) + (paidMedia || 0);
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState(goal);
  React.useEffect(() => setDraft(goal), [goal]);

  const rows = [
    { label: 'Creator spend', value: creator },
    { label: 'Agency spend', value: agency },
    { label: 'Misc additional fees', value: paidMedia, field: 'paidMedia' },
  ];

  const hasGoal = (goal || 0) > 0;
  const delta = hasGoal ? total - goal : null;
  const state = delta == null ? 'ok' : delta <= 0 ? 'ok' : 'over';
  const pctNum = achievement != null ? achievement : hasGoal ? total / goal : 0;

  return (
    <div className={`goal goal-total ${hasGoal ? '' : 'goal-noplan'}`}>
      <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-start' }}>
        <span className="goal-eyebrow">Total budget</span>
        {editing ?
        <input
          type="number" value={draft || 0}
          onChange={(e) => setDraft(Number(e.target.value))}
          onBlur={() => { setPlanField('targetBudget', draft); setEditing(false); }}
          onKeyDown={(e) => { if (e.key === 'Enter') { setPlanField('targetBudget', draft); setEditing(false); } }}
          autoFocus style={{ width: 100, padding: '2px 6px', fontSize: 11, border: '1px solid var(--blue)', borderRadius: 4 }} /> :
        <button className={`goal-edit goal-edit-dark ${hasGoal ? '' : 'goal-edit-cta'}`} onClick={() => setEditing(true)}>{hasGoal ? `Goal: ${fmt.$(goal)}` : '+ Set goal'}</button>}
      </div>
      <div className="goal-row">
        <span className="goal-actual tabular">{fmt.$(total)}</span>
        {hasGoal && delta != null &&
        <span className={`goal-delta ${state}`}>
          <Icon name={delta <= 0 ? 'check' : 'arrow-up'} size={11} />
          {delta > 0 ? '+' : ''}{fmt.$(Math.abs(delta))}
        </span>}
      </div>
      <div className="budget-breakdown">
        {rows.map((r) => (
          <div key={r.label} className="budget-line">
            <span className="budget-line-label">{r.label}</span>
            {r.field ?
            <span className="budget-edit">
              <span className="budget-edit-dollar">{fmt.sym()}</span>
              <input className="budget-media-input tabular" type="number" min="0"
                value={r.value || 0}
                onChange={(e) => setPlanField(r.field, Math.max(0, Number(e.target.value)))} />
            </span> :
            <span className="budget-line-value tabular">{fmt.$(r.value || 0)}</span>}
          </div>
        ))}
      </div>
      {hasGoal &&
      <div className={`goal-bar ${state}`} style={{ marginTop: 4 }}>
        <span style={{ width: Math.min(100, Math.max(0, (pctNum || 0) * 100)) + '%' }} />
      </div>}
      {hasGoal &&
      <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>
        {pctNum != null ? (pctNum * 100).toFixed(0) : '—'}% of plan budget goal
      </div>}
    </div>
  );
};

// — Goal card —
const GoalCard = ({ label, goal, actual, unit = '', decimals = 0, betterIs, achievement, goalKey, setPlanField, note, editable }) => {
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState(goal);
  React.useEffect(() => setDraft(goal), [goal]);
  const fmtVal = (v) => v == null ? '—' : unit === '$' ? fmt.$(v, decimals) : fmt.num(v);

  const hasGoal = (goal || 0) > 0;
  const delta = hasGoal ? actual - goal : null;
  let state = 'ok';
  let pctNum = achievement != null ? achievement : hasGoal ? actual / goal : 0;
  if (hasGoal && betterIs === 'under') state = delta <= 0 ? 'ok' : 'over';
  else if (hasGoal && betterIs === 'over') state = delta >= 0 ? 'ok' : 'under';

  return (
    <div className={`goal ${hasGoal ? '' : 'goal-noplan'}`}>
      <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-start' }}>
        <span className="goal-eyebrow">{label}</span>
        {editing ?
        <input
          type="number" value={draft || 0}
          onChange={(e) => setDraft(Number(e.target.value))}
          onBlur={() => {setPlanField(goalKey, draft);setEditing(false);}}
          onKeyDown={(e) => {if (e.key === 'Enter') {setPlanField(goalKey, draft);setEditing(false);}}}
          autoFocus style={{ width: 90, padding: '2px 6px', fontSize: 11, border: '1px solid var(--blue)', borderRadius: 4 }} /> :

        hasGoal ?
        <button className="goal-edit" onClick={() => setEditing(true)}>Goal: {fmtVal(goal)}</button> :

        note ? <span className="goal-edit">{note}</span> :
        <button className="goal-edit goal-edit-cta" onClick={() => setEditing(true)}>+ Set goal</button>}
      </div>
      <div className="goal-row">
        <span className="goal-actual tabular">{fmtVal(actual)}</span>
        {hasGoal && delta != null &&
        <span className={`goal-delta ${state}`}>
            <Icon name={betterIs === 'under' ? delta <= 0 ? 'check' : 'arrow-up' : delta >= 0 ? 'check' : 'arrow-down'} size={11} />
            {delta > 0 ? '+' : ''}{fmtVal(Math.abs(delta))}
          </span>
        }
      </div>
      {hasGoal &&
      <div className={`goal-bar ${state}`}>
          <span style={{ width: Math.min(100, Math.max(0, (pctNum || 0) * 100)) + '%' }} />
        </div>
      }
      {hasGoal &&
      <div className="muted" style={{ fontSize: 11 }}>
          {pctNum != null ? (pctNum * 100).toFixed(0) : '—'}% of {label.toLowerCase()} goal
        </div>
      }
      {goal == null && editable &&
      <div className="row gap-2 muted" style={{ fontSize: 11, marginTop: 2 }}>
          <Icon name="plus" size={11} /> Optional pre-allocated paid spend
        </div>
      }
    </div>);

};

// — Combined metric card: stacks efficiency metric rows (each only when selected in setup) —
const MetricGroupCard = ({ title, metrics, setPlanField }) => {
  return (
    <div className="goal">
      <span className="goal-eyebrow">{title}</span>
      <div className="goal-metrics">
        {metrics.map((m) => <MetricRow key={m.key} m={m} setPlanField={setPlanField} />)}
      </div>
    </div>);

};

const MetricRow = ({ m, setPlanField }) => {
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState(m.goal);
  React.useEffect(() => setDraft(m.goal), [m.goal]);
  const fmtVal = (v) => {
    if (v == null) return '—';
    if (m.unit === '$') return (m.prefix || fmt.sym()) + fmt.num(v, m.decimals || 0);
    return fmt.num(v);
  };

  // A row is "active" once it has a goal; inactive rows are greyed but still editable.
  const hasActual = m.actual != null && isFinite(m.actual);
  const active = !m.inactive && (m.goal || 0) > 0;
  const delta = active && hasActual ? m.actual - m.goal : null;
  let state = 'ok';
  if (active && delta != null) {
    if (m.betterIs === 'under') state = delta <= 0 ? 'ok' : 'over';
    else if (m.betterIs === 'over') state = delta >= 0 ? 'ok' : 'under';
  }
  const pctNum = m.achievement != null ? m.achievement : (active && hasActual && m.goal ? m.actual / m.goal : 0);

  return (
    <div className={`goal-metric ${active ? '' : 'goal-metric-inactive'}`}>
      <div className="goal-metric-line">
        <span className="goal-metric-label">{m.label}</span>
        <span className="goal-metric-val tabular">{fmtVal(m.actual)}</span>
        {delta != null &&
        <span className={`goal-delta ${state}`}>
          <Icon name={m.betterIs === 'under' ? (delta <= 0 ? 'check' : 'arrow-up') : (delta >= 0 ? 'check' : 'arrow-down')} size={11} />
          {delta > 0 ? '+' : ''}{fmtVal(Math.abs(delta))}
        </span>}
        {m.locked ?
        <span className="goal-metric-goal" style={{ cursor: 'default' }}>{`Goal: ${fmtVal(m.goal)}`}</span> :
        editing ?
        <input
          type="number" value={draft || 0}
          onChange={(e) => setDraft(Number(e.target.value))}
          onBlur={() => { setPlanField(m.goalKey, draft); setEditing(false); }}
          onKeyDown={(e) => { if (e.key === 'Enter') { setPlanField(m.goalKey, draft); setEditing(false); } }}
          autoFocus style={{ width: 76, marginLeft: 'auto', padding: '2px 6px', fontSize: 11, border: '1px solid var(--blue)', borderRadius: 4 }} /> :
        <button className="goal-metric-goal" onClick={() => setEditing(true)}>{active ? `Goal: ${fmtVal(m.goal)}` : '+ Set goal'}</button>}
      </div>
      {active &&
      <div className={`goal-bar ${state}`}>
        <span style={{ width: Math.min(100, Math.max(0, (pctNum || 0) * 100)) + '%' }} />
      </div>}
    </div>);

};

// — Notes row (full-width, per creator) —
const NotesRow = ({ span, value, onChange, onClose }) => (
  <tr className="row-notes">
    <td></td>
    <td colSpan={span - 1}>
      <div className="notes-wrap">
        <div className="notes-head">
          <Icon name="comment" size={12} />
          <span>Note</span>
          <button className="notes-close" onClick={onClose} title="Close note"><Icon name="x" size={12} /></button>
        </div>
        <textarea
          className="notes-area"
          value={value}
          autoFocus
          placeholder="Add a note about this creator — rationale, talking points, deliverable specifics, contract terms…"
          onChange={(e) => onChange(e.target.value)} />
      </div>
    </td>
  </tr>
);

// — Creator row (collapsed summary) —
const CreatorRow = ({ c, expanded, targetCpm, showPayout, showAgency, showCpm, showMaxPayout, visibleCols, payoutBasis, hasNote, noteOpen, onToggleNote, onToggle, onUpdate, onHide }) => {
  const hidden = !!c.inputs.hidden;
  const t = c.totals;
  const perPostMode = payoutBasis === 'perPost';
  const overrideMode = perPostMode || t.payoutFromPerPost != null;
  const cpmOver = targetCpm != null && t.cpm != null && t.cpm > targetCpm;
  return (
    <tr className={`row-creator ${expanded ? 'expanded' : ''} ${hidden ? 'row-hidden' : ''}`}>
      <td className="left">
        <button className={`expand-toggle ${expanded ? 'open' : ''}`} onClick={onToggle}>
          <Icon name="chevron-right" size={14} />
        </button>
      </td>
      <td className="left">
        <div className="row gap-3">
          <Avatar name={c.name} color={c.avatar} size="sm" />
          <div>
            <div className="row gap-2" style={{ alignItems: 'center' }}>
              <span style={{ fontWeight: 600, fontSize: 13 }}>{c.name}</span>
              {hasNote && !noteOpen &&
              <button className="note-flag" onClick={onToggleNote} title="View note">
                <Icon name="comment" size={11} /> Note
              </button>}
            </div>
            <div className="row gap-2" style={{ marginTop: 2 }}>
              {c.handles.map((h) => <PlatformIcon key={h.platform} id={h.platform} size={14} />)}
              <span className="muted" style={{ fontSize: 11 }}>{c.handles.length} handle{c.handles.length === 1 ? '' : 's'}</span>
            </div>
          </div>
        </div>
      </td>
      <td className="tabular">{fmt.num(t.posts)}</td>
      {visibleCols.map((col) => {
        if (col.key === 'adjustment') return <td key={col.key} className="muted" style={{ fontSize: 11, textAlign: 'right' }}>—</td>;
        const { v } = metricCellValue(col.key, t);
        return <td key={col.key} className="tabular">{v}</td>;
      })}
      {showPayout &&
      <td>
        <input className={`cell-input ${overrideMode ? 'payout-overridden' : ''}`}
        value={overrideMode ? (perPostMode && t.payoutFromPerPost == null ? '0' : Math.round(t.payoutTotal).toLocaleString()) : c.inputs.payoutTotal ?? ''}
        disabled={overrideMode}
        placeholder=""
        title={perPostMode ? 'Aggregated from per-post payouts. Switch payout basis to “Total” to enter a creator-level amount.' : overrideMode ? 'Computed from per-post payouts. Clear per-post to edit creator-level total.' : 'Creator-level payout total'}
        onChange={(e) => {const v = e.target.value.replace(/[^\d.]/g, '');onUpdate(['payoutTotal'], v === '' ? null : Number(v));}} />
      </td>}
      {showAgency &&
      <td>
        <div className="row gap-2" style={{ justifyContent: 'flex-end' }}>
          <input className="cell-input" style={{ maxWidth: 46, color: '#a14e10' }}
          value={c.inputs.agencyFeePct ?? ''} placeholder="—"
          onChange={(e) => {const v = e.target.value.replace(/[^\d.]/g, '');onUpdate(['agencyFeePct'], v === '' ? null : Number(v));}} />
          <span className="muted" style={{ fontSize: 10 }}>%</span>
        </div>
        <div className="muted tabular" style={{ fontSize: 10, textAlign: 'right', marginTop: 1 }}>{fmt.$(t.agencyFeeTotal)}</div>
      </td>}
      {showCpm &&
      <td className={`tabular cpm-cell ${t.cpm == null ? '' : cpmOver ? 'cpm-over' : 'cpm-under'}`}>{t.cpm == null ? '—' : fmt.$2(t.cpm)}</td>}
      {showMaxPayout &&
      <td className="tabular" title="Forecast impressions × fixed CPM">{fmt.$(maxPayoutOf(t))}</td>}
      <td>
        <div className="row gap-1" style={{ justifyContent: 'flex-end' }}>
          <button className={`kebab ${noteOpen || hasNote ? 'note-btn-on' : ''}`} onClick={onToggleNote} title={hasNote ? 'Edit note' : 'Add a note'}>
            <Icon name="comment" size={14} />
          </button>
          <button className="kebab" onClick={onHide} title={hidden ? 'Re-add to plan' : 'Remove from plan'}>
            <Icon name={hidden ? 'eye' : 'eye-off'} size={14} />
          </button>
        </div>
      </td>
    </tr>);

};

// — Handle row (between creator and types) —
const HandleRow = ({ handle, creatorName, showPayout, showAgency, showCpm, showMaxPayout, visibleCols, onRemove }) => {
  const p = PLATFORMS[handle.platform];
  return (
    <tr className="row-handle">
      <td></td>
      <td className="left">
        <div className="row gap-2">
          <PlatformIcon id={handle.platform} size={14} />
          <span>{p ? p.label : handle.platform}</span>
          <span className="muted" style={{ fontSize: 11, fontWeight: 400 }}>{handle.handle}</span>
          {!handle._hasDbHandle &&
          <span className="pill pill-yellow" style={{ fontSize: 9, padding: '0 5px' }} title="No 90-day forecast data for this creator on this platform. Inputs will still calculate; forecasted metrics will be 0.">no forecast</span>
          }
        </div>
      </td>
      <td className="tabular muted">{fmt.num(handle.totals.posts)}</td>
      {visibleCols.map((col) => {
        if (col.key === 'adjustment' || col.key === 'er') return <td key={col.key}></td>;
        const { v } = metricCellValue(col.key, handle.totals);
        return <td key={col.key} className="tabular muted">{v}</td>;
      })}
      {((showPayout ? 1 : 0) + (showAgency ? 1 : 0) + (showCpm ? 1 : 0) + (showMaxPayout ? 1 : 0)) > 0 &&
      <td colSpan={(showPayout ? 1 : 0) + (showAgency ? 1 : 0) + (showCpm ? 1 : 0) + (showMaxPayout ? 1 : 0)}></td>}
      <td style={{ textAlign: 'right' }}>
        {onRemove &&
        <button className="kebab row-action" onClick={onRemove} title="Remove this platform from the plan">
            <Icon name="x" size={13} />
          </button>
        }
      </td>
    </tr>);

};

// — Post-type row (editable inputs) —
const TypeRow = ({ c, h, t, targetCpm, showPayout, showAgency, showCpm, showMaxPayout, visibleCols, onUpdate, onRemove }) => {
  const tInputs = t._inputs || {};
  const isTwitch = h.platform === 'twitch';
  const postCpm = (tInputs.payoutPerPost && t.views > 0) ? (tInputs.payoutPerPost * tInputs.postCount / t.views * 1000) : null;
  const postCpmOver = targetCpm != null && postCpm != null && postCpm > targetCpm;
  const adj = tInputs.adjustmentPct;
  const adjCls = adj == null ? 'muted' : adj > 0 ? 'adj' : adj < 0 ? 'adj-neg' : 'adj';
  return (
    <tr className="row-type">
      <td></td>
      <td className="left">
        <span className="muted" style={{ fontSize: 11, marginRight: 6 }}>·</span>
        {t.label}
      </td>
      <td>
        <input className="cell-input" value={tInputs.postCount ?? ''} placeholder="0"
        onChange={(e) => {const v = e.target.value.replace(/[^\d]/g, '');onUpdate('postCount', v === '' ? null : Number(v));}} />
      </td>
      {visibleCols.map((col) => {
        if (col.key === 'adjustment') {
          return (
            <td key={col.key}>
              <div className="row gap-2" style={{ justifyContent: 'flex-end' }}>
                <input className={`cell-input ${adjCls}`} style={{ maxWidth: 48 }}
                  value={tInputs.adjustmentPct ?? ''} placeholder="0"
                  onChange={(e) => {let v = e.target.value.replace(/[^\d\.\-]/g, '');v = v === '' ? null : Math.max(-100, Math.min(100, Number(v)));onUpdate('adjustmentPct', v);}} />
                <span className="muted" style={{ fontSize: 10 }}>%</span>
              </div>
            </td>
          );
        }
        const { v, zero } = metricCellValue(col.key, t, { perType: true, twitch: isTwitch });
        return <td key={col.key} className={`tabular cell-readonly ${zero ? 'zero' : ''}`}>{v}</td>;
      })}
      {showPayout &&
      <td>
        <div className="row gap-2" style={{ justifyContent: 'flex-end' }}>
          <span className="muted" style={{ fontSize: 11 }}>{fmt.sym()}</span>
          <input className="cell-input cell-input-payout" style={{ maxWidth: 96, fontSize: 13 }}
          value={tInputs.payoutPerPost ?? ''} placeholder="0"
          title="Payout per post overrides creator-level total"
          onChange={(e) => {const v = e.target.value.replace(/[^\d.]/g, '');onUpdate('payoutPerPost', v === '' ? null : Number(v));}} />
          <span className="muted" style={{ fontSize: 9 }}>/post</span>
        </div>
      </td>}
      {showAgency && <td></td>}
      {showCpm &&
      <td className={`tabular cell-readonly cpm-cell ${postCpm == null ? '' : postCpmOver ? 'cpm-over' : 'cpm-under'}`}>{postCpm == null ? '—' : fmt.$2(postCpm)}</td>}
      {showMaxPayout &&
      <td className="tabular cell-readonly">{fmt.$(maxPayoutOf(t))}</td>}
      <td style={{ textAlign: 'right' }}>
        {onRemove &&
        <button className="kebab row-action" onClick={onRemove} title="Remove this post type from the plan">
            <Icon name="x" size={12} />
          </button>
        }
      </td>
    </tr>);

};

// — Add post-type row —
const AddTypeRow = ({ platform, activeTypeIds, span = 10, open, onToggle, onAdd }) => {
  const validTypes = (PLATFORMS[platform]?.types || []).filter((t) => !activeTypeIds.includes(t.id));
  if (validTypes.length === 0) return null;
  return (
    <tr className="row-add row-type">
      <td></td>
      <td className="left" style={{ position: 'relative' }}>
        <button onClick={() => onToggle(!open)}
        className="row gap-2"
        style={{ padding: '2px 8px', marginLeft: -4, background: 'transparent', border: 0, borderRadius: 6, cursor: 'pointer', fontSize: 11.5, color: 'var(--blue)', fontWeight: 600, whiteSpace: 'nowrap' }}>
          <Icon name="plus" size={12} /> Add post type
        </button>
        {open &&
        <div style={{ position: 'absolute', top: 24, left: 60, background: '#fff', border: '1px solid var(--border)', borderRadius: 8, boxShadow: 'var(--shadow-md)', padding: 6, zIndex: 30, minWidth: 160 }} onMouseLeave={() => onToggle(false)}>
            <div className="muted" style={{ padding: '4px 10px', fontSize: 10, textTransform: 'uppercase', letterSpacing: '.06em', fontWeight: 700 }}>Valid for {PLATFORMS[platform]?.label}</div>
            {validTypes.map((typ) =>
          <button key={typ.id} onClick={() => onAdd(typ.id)}
          style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px', background: 'transparent', border: 0, borderRadius: 6, fontSize: 12.5, cursor: 'pointer', textAlign: 'left', fontWeight: 500 }}>
                {typ.label}
              </button>
          )}
          </div>
        }
      </td>
      <td colSpan={span}></td>
    </tr>);

};

// — Add platform row —
const AddPlatformRow = ({ creatorId, activePlatformIds, dbPlatformIds, span = 10, open, onToggle, onAdd }) => {
  const allPlatforms = Object.keys(PLATFORMS);
  const candidates = allPlatforms.filter((id) => !activePlatformIds.includes(id));
  return (
    <tr className="row-add row-handle">
      <td></td>
      <td className="left" style={{ position: 'relative', paddingLeft: 48 }}>
        <button onClick={() => onToggle(!open)}
        className="row gap-2"
        style={{ padding: '3px 10px', marginLeft: -4, background: 'transparent', border: '1px dashed var(--border-strong)', borderRadius: 999, cursor: 'pointer', fontSize: 11.5, color: 'var(--blue)', fontWeight: 600, whiteSpace: 'nowrap' }}>
          <Icon name="plus" size={12} /> Add platform
        </button>
        {open &&
        <div style={{ position: 'absolute', top: 30, left: 48, background: '#fff', border: '1px solid var(--border)', borderRadius: 8, boxShadow: 'var(--shadow-md)', padding: 6, zIndex: 30, minWidth: 220 }} onMouseLeave={() => onToggle(false)}>
            <div className="muted" style={{ padding: '4px 10px', fontSize: 10, textTransform: 'uppercase', letterSpacing: '.06em', fontWeight: 700 }}>Add a platform</div>
            {candidates.map((id) => {
            const hasData = dbPlatformIds.includes(id);
            const p = PLATFORMS[id];
            return (
              <button key={id} onClick={() => onAdd(id)}
              style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px', background: 'transparent', border: 0, borderRadius: 6, fontSize: 12.5, cursor: 'pointer', textAlign: 'left', fontWeight: 500 }}
              title={hasData ? '90-day forecast data available for this creator on this platform' : 'No forecast data — metrics start at 0; you can still set post counts &amp; payout.'}>
                  <PlatformIcon id={id} size={14} />
                  <span style={{ flex: 1 }}>{p.label}</span>
                  {hasData ?
                <span className="pill pill-green" style={{ fontSize: 9, padding: '0 5px' }}>data</span> :
                <span className="pill pill-grey" style={{ fontSize: 9, padding: '0 5px' }}>no data</span>}
                </button>);

          })}
          </div>
        }
      </td>
      <td colSpan={span}></td>
    </tr>);

};

const PlanTotalsRow = ({ totals, targetCpm, showPayout, showAgency, showCpm, showMaxPayout, visibleCols }) =>
<tr className="row-totals">
    <td className="left" colSpan={2}>
      <div className="row gap-2">
        <Icon name="sparkles" size={14} />
        <span style={{ fontWeight: 700 }}>Plan total · {totals.creatorCount} creators · {totals.platformCount} platforms</span>
      </div>
    </td>
    <td className="tabular">{fmt.num(totals.posts)}</td>
    {visibleCols.map((col) => {
      if (col.key === 'adjustment') return <td key={col.key}></td>;
      const { v } = metricCellValue(col.key, totals);
      return <td key={col.key} className="tabular">{v}</td>;
    })}
    {showPayout && <td className="tabular">{fmt.$(totals.creatorPayout)}</td>}
    {showAgency && <td className="tabular">{fmt.$(totals.agency)}</td>}
    {showCpm && <td className={`tabular cpm-cell ${totals.totalCpm == null ? '' : (targetCpm != null && totals.totalCpm > targetCpm) ? 'cpm-over' : 'cpm-under'}`}>{totals.totalCpm == null ? '—' : fmt.$2(totals.totalCpm)}</td>}
    {showMaxPayout && <td className="tabular">{fmt.$(maxPayoutOf(totals))}</td>}
    <td></td>
  </tr>;


Object.assign(window, { PlanView });