// tab-pl.jsx — Project tracking. All-teams week vs target. Uses
// getPLTrackingData (different shape from getTeamData), so this tab
// manages its own fetch.
//
// Daily breakdown shows, per team per day:
//   Past / today: actual / expected · annotators committed.
//   Future:       expected · annotators committed.
// "Expected" is what the backend already computes — sum of each annotator's
// ramp-aware expected pace × their committed hours. Tenured annotators
// contribute at their cap (e.g. 144), ramping ones at their stage of the
// curve. PLs scan for cells where actual lags expected to find who needs a
// nudge.

const { useState: plUseState, useEffect: plUseEffect } = React;

function TabPL() {
  const [data, setData] = plUseState(null);
  const [loading, setLoading] = plUseState(true);
  const [error, setError] = plUseState(null);
  const [weekOffset, setWeekOffset] = plUseState(0);

  // Tracked separately from `loading` so the page doesn't blink to the
  // "Loading…" full-page state during a background refetch (e.g. after a
  // re-ingest). The first load still shows the loader; subsequent reloads
  // just update in place.
  const reload = () => {
    setError(null);
    window.YDApp.call('getPLTrackingData', { weekOffset })
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message || String(e)); setLoading(false); });
  };

  plUseEffect(() => {
    setLoading(true);
    reload();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [weekOffset]);

  if (loading) return <div className="page"><PageHeader title="Project tracking" subtitle="Loading…"/></div>;
  if (error) return (
    <div className="page">
      <PageHeader title="Project tracking" subtitle="Failed to load"/>
      <div className="card"><div className="card-bd" style={{ color: '#b91c1c' }}>{error}</div></div>
    </div>
  );

  const teams = data.teams || [];
  const allTeams = data.allTeams || { daily: [], weekTotal: { actual: 0, projected: 0, hours: 0, committedHours: 0 } };
  const todayIdx = data.todayIdx;
  const isPastWeek = data.isPastWeek;
  const DAY_LABELS = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];

  const totals = teams.reduce((t, r) => ({
    members:    t.members + (r.memberCount || 0),
    actual:     t.actual  + (r.weekTotal?.actual || 0),
    projected:  t.projected + (r.weekTotal?.projected || 0),
    atTarget:   t.atTarget + (r.weekTotal?.atTarget || 0),
    bonus:      t.bonus + (r.weekTotal?.bonus || 0),
    hours:      t.hours + (r.weekTotal?.hours || 0),
  }), { members: 0, actual: 0, projected: 0, atTarget: 0, bonus: 0, hours: 0 });

  // % of expected target actually hit by per-annotator at-target work.
  // This is the "true" production-vs-plan number — bonus from over-
  // performers can't mask under-performers.
  const atTargetPct = totals.projected > 0 ? Math.round((totals.atTarget / totals.projected) * 100) : 0;
  const targetGap = Math.max(0, totals.projected - totals.atTarget);

  return (
    <div className="page">
      <PageHeader
        eyebrow="Project tracking"
        title="All teams · week view"
        subtitle={`${teams.length} teams · ${totals.members} annotators · ${fmtK(totals.atTarget)} on target + ${fmtK(totals.bonus)} bonus / ${fmtK(totals.projected)} expected`}
        actions={
          <div className="flex g8 ac">
            <div className="segbar">
              <button className={weekOffset === 0 ? 'active' : ''} onClick={() => setWeekOffset(0)}>This week</button>
              <button className={weekOffset === -1 ? 'active' : ''} onClick={() => setWeekOffset(-1)}>Last week</button>
            </div>
            <button className="btn">Export</button>
          </div>
        }/>

      <div className="kpi-row" style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)' }}>
        <div className="kpi-tile">
          <div className="lbl">Week expected</div>
          <div className="val">{fmtK(totals.projected)}</div>
          <div className="sub">If everyone hits their pace · {teams.length} teams</div>
        </div>
        <div className="kpi-tile">
          <div className="lbl">Total production</div>
          <div className="val">{fmtK(totals.actual)}</div>
          <div className="sub">
            {fmtK(totals.atTarget)} on target + {fmtK(totals.bonus)} bonus
          </div>
        </div>
        <div className="kpi-tile">
          <div className="lbl">On target</div>
          <div className="val" style={{ color: atTargetPct >= 100 ? '#15803d' : atTargetPct >= 80 ? '#b45309' : '#b91c1c' }}>
            {fmtK(totals.atTarget)}
          </div>
          <div className="sub">{atTargetPct}% of expected · true production-vs-plan</div>
        </div>
        <div className="kpi-tile">
          <div className="lbl">Bonus (above target)</div>
          <div className="val" style={{ color: '#3730a3' }}>{fmtK(totals.bonus)}</div>
          <div className="sub">Over-performance — doesn't fill the gap</div>
        </div>
        <div className="kpi-tile">
          <div className="lbl">Target gap</div>
          <div className="val" style={{ color: targetGap > 0 ? '#b45309' : '#15803d' }}>
            {targetGap > 0 ? '−' : '+'}{fmtK(targetGap || (totals.atTarget - totals.projected))}
          </div>
          <div className="sub">
            {targetGap > 0
              ? 'Missing from expected even after bonus'
              : 'Hit plan'}
          </div>
        </div>
      </div>

      <div className="card">
        <div className="card-hd card-hd-bottomline">
          <h2>Daily breakdown</h2>
          <span className="meta">Each cell: actual / expected · annotators committed</span>
        </div>
        <div className="card-bd" style={{ padding: 0, overflowX: 'auto' }}>
          <table className="pl-table" style={{ minWidth: 880 }}>
            <thead>
              <tr>
                <th>Team</th>
                {DAY_LABELS.map((d, i) => (
                  <th key={d} className="num" style={{
                    color: i === todayIdx ? '#1d4ed8' : undefined,
                    fontWeight: i === todayIdx ? 800 : undefined,
                  }}>{d}{i === todayIdx ? ' · today' : ''}</th>
                ))}
                <th className="num">Week</th>
              </tr>
            </thead>
            <tbody>
              <tr style={{ background: '#fafafa', fontWeight: 700 }}>
                <td>All teams</td>
                {allTeams.daily.map((d, i) => (
                  <DailyCell key={i} d={d} idx={i} todayIdx={todayIdx} isPastWeek={isPastWeek}/>
                ))}
                <td className="num mono">
                  <div style={{ fontWeight: 700 }}>{fmtK(allTeams.weekTotal.actual)}</div>
                  <div style={{ fontSize: 10, color: '#6b7280' }}>/ {fmtK(allTeams.weekTotal.projected)}</div>
                </td>
              </tr>
              {teams.map((t) => (
                <tr key={t.name}>
                  <td style={{ fontWeight: 700, color: '#111' }}>{t.name}</td>
                  {(t.daily || []).map((d, i) => (
                    <DailyCell key={i} d={d} idx={i} todayIdx={todayIdx} isPastWeek={isPastWeek}/>
                  ))}
                  <td className="num mono">
                    <div style={{ fontWeight: 700 }}>{fmtK(t.weekTotal.actual)}</div>
                    <div style={{ fontSize: 10, color: '#6b7280' }}>/ {fmtK(t.weekTotal.projected)}</div>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      <div className="card">
        <div className="card-hd card-hd-bottomline"><h2>Team performance</h2></div>
        <div className="card-bd" style={{ padding: 0 }}>
          <table className="pl-table">
            <thead>
              <tr>
                <th>Team</th>
                <th className="num">Members</th>
                <th className="num">Expected</th>
                <th className="num">On target</th>
                <th className="num">Bonus</th>
                <th className="num">Actual</th>
                <th className="num">% on target</th>
                <th className="num">Hours</th>
                <th>Progress</th>
              </tr>
            </thead>
            <tbody>
              {teams.map(t => {
                const expected = t.weekTotal.projected || 0;
                const onTarget = t.weekTotal.atTarget || 0;
                const bonus = t.weekTotal.bonus || 0;
                const actual = t.weekTotal.actual || 0;
                const onTargetPct = expected > 0 ? Math.round((onTarget / expected) * 100) : 0;
                const totalPct = expected > 0 ? Math.round((actual / expected) * 100) : 0;
                const onColor = onTargetPct >= 100 ? '#15803d' : onTargetPct >= 80 ? '#b45309' : '#b91c1c';
                return (
                  <tr key={t.name}>
                    <td style={{ fontWeight: 700, color: '#111' }}>{t.name}</td>
                    <td className="num mono">{t.memberCount}</td>
                    <td className="num mono">{fmtK(expected)}</td>
                    <td className="num mono" style={{ fontWeight: 700, color: onColor }}>{fmtK(onTarget)}</td>
                    <td className="num mono" style={{ color: '#3730a3' }}>{bonus > 0 ? `+${fmtK(bonus)}` : '—'}</td>
                    <td className="num mono">{fmtK(actual)}</td>
                    <td className="num mono" style={{ color: onColor, fontWeight: 700 }}>{onTargetPct}%</td>
                    <td className="num mono">{Math.round(t.weekTotal.hours)}h</td>
                    <td style={{ width: 200 }}>
                      <div className="weekpct" title={`On target: ${onTargetPct}% · Total incl. bonus: ${totalPct}%`}>
                        <div className="weekpct-bar" style={{ position: 'relative' }}>
                          {/* Below: at-target portion (saturated) */}
                          <div className="weekpct-fill" style={{
                            width: Math.min(100, onTargetPct) + '%',
                            background: onColor,
                          }}/>
                          {/* Bonus portion stacked on top, more transparent */}
                          {bonus > 0 && totalPct > onTargetPct && (
                            <div style={{
                              position: 'absolute',
                              left: Math.min(100, onTargetPct) + '%',
                              top: 0, bottom: 0,
                              width: Math.min(100, totalPct - onTargetPct) + '%',
                              background: '#3730a3',
                              opacity: 0.6,
                            }}/>
                          )}
                        </div>
                      </div>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>

      <HourlyCapacity
        teams={teams}
        allTeams={allTeams}
        todayIdx={todayIdx}
        benchPace={data.benchPace || 0}
        isPastWeek={isPastWeek}/>

      {data.batches && data.batches.length > 0 && (
        <BatchVerification batches={data.batches} onReingestComplete={reload}/>
      )}
    </div>
  );
}

// Hour-of-day capacity heatmap. Rows = day-of-week, columns = hours 0..23,
// cells = how many annotators are scheduled to be online at that hour.
// Cells double as expected-resources/hr when toggled (count × benchPace).
// Today's row gets a coloured left border. Source: hourlyCapacity field
// added to getPLTrackingData (per-team and aggregated across all teams).
function HourlyCapacity({ teams, allTeams, todayIdx, benchPace, isPastWeek }) {
  const [view, setView] = plUseState('count'); // 'count' | 'expected'
  const [teamName, setTeamName] = plUseState('all');
  const DAYS = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];

  const grid = teamName === 'all'
    ? (allTeams.hourlyCapacity || [])
    : ((teams.find((t) => t.name === teamName) || {}).hourlyCapacity || []);

  // Find peak count for shading scale.
  let peak = 0;
  for (const row of grid) for (const v of row) if (v > peak) peak = v;
  if (peak === 0) peak = 1;

  // Cell value to display + cell colour ramp (light → dark green).
  const cellRender = (count) => {
    const value = view === 'expected' ? Math.round(count * benchPace) : count;
    const intensity = peak > 0 ? Math.min(1, count / peak) : 0;
    // 0 → off-white, max → solid green. Use HSL so the ramp is smooth.
    const lightness = 96 - Math.round(intensity * 50); // 96 → 46
    const bg = count === 0 ? '#fafafa' : `hsl(142, 60%, ${lightness}%)`;
    const color = intensity > 0.55 ? '#fff' : '#15803d';
    const display = count === 0 ? '·' : view === 'expected' ? fmtK(value) : String(value);
    return { bg, color, display };
  };

  const peakHour = (() => {
    let best = { day: -1, hour: -1, count: 0 };
    grid.forEach((row, d) => {
      row.forEach((v, h) => {
        if (v > best.count) best = { day: d, hour: h, count: v };
      });
    });
    return best;
  })();

  return (
    <div className="card">
      <div className="card-hd card-hd-bottomline">
        <h2>Hourly capacity</h2>
        <div className="flex g8 ac">
          <select
            value={teamName}
            onChange={(e) => setTeamName(e.target.value)}
            style={{ fontSize: 12, padding: '4px 8px', borderRadius: 6, border: '1px solid var(--line)' }}>
            <option value="all">All teams</option>
            {teams.map((t) => <option key={t.name} value={t.name}>{t.name}</option>)}
          </select>
          <div className="segbar">
            <button className={view === 'count' ? 'active' : ''} onClick={() => setView('count')}>Annotators</button>
            <button className={view === 'expected' ? 'active' : ''} onClick={() => setView('expected')}>Expected /hr</button>
          </div>
        </div>
      </div>
      <div className="card-bd">
        <div className="meta" style={{ marginBottom: 8 }}>
          {view === 'count'
            ? 'Annotators scheduled to be online at each hour-of-day'
            : `Expected resources/hr at ${benchPace || '—'}/hr per annotator`}
          {peakHour.count > 0 && (
            <> · peak <b>{peakHour.count} annotators</b> on {DAYS[peakHour.day]} at {String(peakHour.hour).padStart(2, '0')}:00</>
          )}
        </div>
        <div style={{ overflowX: 'auto' }}>
          <table style={{ borderCollapse: 'separate', borderSpacing: 2, fontSize: 10, fontFamily: 'Inconsolata, monospace' }}>
            <thead>
              <tr>
                <th style={{ width: 40 }}/>
                {Array.from({ length: 24 }, (_, h) => (
                  <th key={h} style={{
                    width: 32,
                    color: '#6b7280',
                    fontWeight: 500,
                    padding: '4px 0',
                    textAlign: 'center',
                  }}>
                    {String(h).padStart(2, '0')}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {DAYS.map((day, d) => {
                const isToday = !isPastWeek && d === todayIdx;
                return (
                  <tr key={day}>
                    <th style={{
                      textAlign: 'left',
                      padding: '0 6px',
                      fontSize: 11,
                      fontWeight: 600,
                      color: isToday ? '#1d4ed8' : '#111',
                      borderLeft: isToday ? '3px solid #1d4ed8' : '3px solid transparent',
                    }}>
                      {day}{isToday ? ' · today' : ''}
                    </th>
                    {(grid[d] || []).map((count, h) => {
                      const { bg, color, display } = cellRender(count);
                      return (
                        <td key={h}
                          title={count > 0
                            ? `${day} ${String(h).padStart(2,'0')}:00 — ${count} annotator${count === 1 ? '' : 's'}${benchPace ? ` · est ${(count * benchPace).toLocaleString()} resources/hr` : ''}`
                            : `${day} ${String(h).padStart(2,'0')}:00 — no coverage`}
                          style={{
                            background: bg,
                            color,
                            textAlign: 'center',
                            padding: '6px 0',
                            borderRadius: 3,
                            fontWeight: 700,
                            cursor: 'default',
                          }}>
                          {display}
                        </td>
                      );
                    })}
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>

        <BatchEtas grid={grid} benchPace={benchPace} todayIdx={todayIdx} isPastWeek={isPastWeek}/>
      </div>
    </div>
  );
}

// Walks each day's hourly throughput cumulatively and emits a milestone
// any time cumulative crosses a multiple of `batchSize`. The crossing
// hour is linearly interpolated within the hour (e.g. if cumulative goes
// from 48k at 11:00 to 53k at 12:00, the 50k crossing falls at 11:24).
// PLs use these times to give batch ETAs.
function batchMilestones(grid, benchPace, batchSize) {
  const out = [];
  for (let d = 0; d < 7; d++) {
    let cum = 0;
    let next = batchSize;
    const milestones = [];
    for (let h = 0; h < 24; h++) {
      const expected = ((grid[d] && grid[d][h]) || 0) * (benchPace || 0);
      const prev = cum;
      cum += expected;
      while (cum >= next) {
        // Linear interpolation within the hour
        const span = cum - prev;
        const fraction = span > 0 ? (next - prev) / span : 0;
        const hour = h + fraction;
        const hh = Math.floor(hour);
        const mm = Math.round((hour - hh) * 60);
        milestones.push({
          batchNumber: Math.round(next / batchSize),
          totalAt: next,
          time: `${String(hh).padStart(2, '0')}:${String(mm % 60).padStart(2, '0')}`,
        });
        next += batchSize;
      }
    }
    out.push({ milestones, dayTotal: Math.round(cum) });
  }
  return out;
}

function BatchEtas({ grid, benchPace, todayIdx, isPastWeek }) {
  const [batchSizeStr, setBatchSizeStr] = plUseState('50000');
  const batchSize = Math.max(1000, Number(batchSizeStr) || 50000);
  const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

  const perDay = batchMilestones(grid, benchPace, batchSize);

  if (!benchPace) {
    return (
      <div style={{ marginTop: 16, paddingTop: 12, borderTop: '1px solid var(--line-2)' }}>
        <div className="meta">No benchmark pace yet — set Pace targets in admin to enable batch ETAs.</div>
      </div>
    );
  }

  return (
    <div style={{ marginTop: 16, paddingTop: 12, borderTop: '1px solid var(--line-2)' }}>
      <div className="flex jb ac" style={{ marginBottom: 8, flexWrap: 'wrap', gap: 12 }}>
        <div>
          <div style={{ fontSize: 13, fontWeight: 700 }}>Batch ETAs</div>
          <div className="meta">When cumulative expected output crosses each batch threshold per day. Useful for giving clients a delivery window.</div>
        </div>
        <div className="flex ac g8">
          <span className="meta">Batch size</span>
          <input
            type="number"
            min="1000"
            step="5000"
            value={batchSizeStr}
            onChange={(e) => setBatchSizeStr(e.target.value)}
            style={{
              width: 110,
              padding: '4px 8px',
              fontSize: 12,
              fontFamily: 'Inconsolata, monospace',
              border: '1px solid var(--line)',
              borderRadius: 6,
            }}/>
          <span className="meta">resources</span>
        </div>
      </div>

      <div style={{ overflowX: 'auto' }}>
        <table style={{ borderCollapse: 'collapse', fontSize: 11, width: '100%' }}>
          <tbody>
            {DAYS.map((day, d) => {
              const isToday = !isPastWeek && d === todayIdx;
              const { milestones, dayTotal } = perDay[d];
              return (
                <tr key={day} style={{ borderTop: '1px solid var(--line-2)' }}>
                  <th style={{
                    width: 70,
                    textAlign: 'left',
                    padding: '8px 8px 8px 0',
                    fontSize: 11,
                    fontWeight: 700,
                    color: isToday ? '#1d4ed8' : '#111',
                    borderLeft: isToday ? '3px solid #1d4ed8' : '3px solid transparent',
                    paddingLeft: 8,
                    verticalAlign: 'top',
                  }}>
                    {day}{isToday ? ' · today' : ''}
                  </th>
                  <td style={{ padding: '8px 0', verticalAlign: 'top' }}>
                    {milestones.length === 0 ? (
                      <span className="meta">
                        Day total {fmtK(dayTotal)} — doesn't reach {fmtK(batchSize)}.
                      </span>
                    ) : (
                      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                        {milestones.map((m, i) => (
                          <span key={i} style={{
                            display: 'inline-flex', alignItems: 'center', gap: 6,
                            padding: '3px 8px',
                            background: '#f0fdf4',
                            color: '#15803d',
                            borderRadius: 4,
                            fontFamily: 'Inconsolata, monospace',
                            fontWeight: 700,
                            fontSize: 11,
                            border: '1px solid #bbf7d0',
                          }}>
                            <span>{fmtK(m.totalAt)}</span>
                            <span style={{ color: '#9ca3af' }}>by</span>
                            <span>{m.time}</span>
                          </span>
                        ))}
                        <span className="meta" style={{ alignSelf: 'center', marginLeft: 4 }}>
                          · day total {fmtK(dayTotal)}
                        </span>
                      </div>
                    )}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// Batch verification table with on-demand re-ingest. Each row shows the
// computed totals from kpiEntries; the Re-ingest button lists the Drive
// folder for files matching that jobId, downloads them, and re-runs the
// ingestion inline. Composite-key writes (`merge: false`) overwrite the
// stale rows, so on next refresh the corrected totals appear.
function BatchVerification({ batches, onReingestComplete }) {
  const [busyId, setBusyId] = plUseState(null);
  const [resultById, setResultById] = plUseState({}); // jobId -> { ok, msg }
  const [confirmId, setConfirmId] = plUseState(null);

  const runReingest = async (jobId) => {
    setBusyId(jobId);
    setResultById((prev) => Object.assign({}, prev, { [jobId]: null }));
    try {
      const resp = await window.YDApp.call('reingestKpiBatch', { jobId: String(jobId) });
      const msg = `Re-ingested ${resp.filesProcessed} file${resp.filesProcessed === 1 ? '' : 's'} · ${fmtK(resp.rowsWritten)} rows · ${fmtK(resp.totalResources)} resources. Refreshing totals…`;
      setResultById((prev) => Object.assign({}, prev, { [jobId]: { ok: true, msg } }));
      // Auto-refetch the page data so the row updates without a manual
      // refresh. Wrapped in setTimeout(0) so the success banner paints
      // first and the user gets visual confirmation.
      if (typeof onReingestComplete === 'function') {
        setTimeout(onReingestComplete, 0);
      }
    } catch (e) {
      setResultById((prev) => Object.assign({}, prev, { [jobId]: { ok: false, msg: e.message || String(e) } }));
    } finally {
      setBusyId(null);
      setConfirmId(null);
    }
  };

  return (
    <div className="card">
      <div className="card-hd card-hd-bottomline">
        <h2>Batch verification</h2>
        <span className="meta">
          Top {Math.min(20, batches.length)} jobs by resources · click Re-ingest to pull the latest Drive copy
        </span>
      </div>
      <div className="card-bd" style={{ padding: 0 }}>
        <table className="pl-table">
          <thead>
            <tr>
              <th>Job</th>
              <th>Project</th>
              <th className="num">Resources</th>
              <th className="num">Annotators</th>
              <th>Top 5</th>
              <th className="num">Hours</th>
              <th style={{ textAlign: 'right' }}>Action</th>
            </tr>
          </thead>
          <tbody>
            {batches.slice(0, 20).map((b) => {
              const result = resultById[b.jobId];
              const busy = busyId === b.jobId;
              return (
                <React.Fragment key={b.jobId}>
                  <tr>
                    <td className="mono">{b.jobId}</td>
                    <td>{b.project}</td>
                    <td className="num mono" style={{ fontWeight: 700 }}>{fmtK(b.resources)}</td>
                    <td className="num mono">{b.annotatorCount}</td>
                    <td>
                      <span style={{ fontSize: 11, color: '#6b7280' }}>
                        {b.top5.map((t) => `${t.name.split(' ')[0]} ${fmtK(t.resources)}`).join(' · ')}
                      </span>
                    </td>
                    <td className="num mono">{b.hours}h</td>
                    <td style={{ textAlign: 'right' }}>
                      {confirmId === b.jobId ? (
                        <div className="flex g6" style={{ justifyContent: 'flex-end' }}>
                          <button className="btn btn-sm" onClick={() => setConfirmId(null)} disabled={busy}>Cancel</button>
                          <button className="btn btn-sm btn-dark" onClick={() => runReingest(b.jobId)} disabled={busy}>
                            {busy ? 'Re-ingesting…' : 'Confirm re-ingest'}
                          </button>
                        </div>
                      ) : (
                        <button className="btn btn-sm" disabled={busy} onClick={() => setConfirmId(b.jobId)}>
                          {busy ? 'Re-ingesting…' : 'Re-ingest'}
                        </button>
                      )}
                    </td>
                  </tr>
                  {result && (
                    <tr>
                      <td colSpan={7} style={{
                        background: result.ok ? '#f0fdf4' : '#fef2f2',
                        color: result.ok ? '#15803d' : '#b91c1c',
                        fontSize: 12,
                        padding: '8px 16px',
                      }}>
                        {result.ok ? '✓ ' : '✗ '}{result.msg}
                      </td>
                    </tr>
                  )}
                </React.Fragment>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// One per-day cell. Past / today: bold actual on top, "/ expected · pct%"
// underneath in red/amber/green, and the annotator count at the very bottom.
// Future: expected (blue) on top, annotator count underneath. Off days
// (zero commitments) render as an em-dash so empty cells don't read as 0.
function DailyCell({ d, idx, todayIdx, isPastWeek }) {
  const expected = d.projected || 0;
  const actual = d.actual || 0;
  const annotators = d.availableCount || 0;
  const isFuture = !isPastWeek && idx > todayIdx;
  const isToday = !isPastWeek && idx === todayIdx;

  // Off day for everyone — no commitments and no resources logged.
  if (annotators === 0 && actual === 0 && expected === 0) {
    return <td className="num mono" style={{ color: '#d1d5db' }}>—</td>;
  }

  const pct = expected > 0 ? Math.round((actual / expected) * 100) : null;
  const pctColor = pct == null ? '#9ca3af'
    : pct >= 100 ? '#15803d'
    : pct >= 80 ? '#b45309'
    : '#b91c1c';

  return (
    <td className="num mono" style={{
      borderTop: isToday ? '2px solid #1d4ed8' : undefined,
      background: isToday ? '#eff6ff' : isFuture ? '#fafafa' : undefined,
    }}>
      {isFuture ? (
        <div style={{ fontWeight: 700, color: '#3730a3' }}>{fmtK(expected)}</div>
      ) : (
        <>
          <div style={{ fontWeight: 700 }}>{fmtK(actual)}</div>
          <div style={{ fontSize: 10, color: pctColor }}>
            / {fmtK(expected)}{pct != null ? ` · ${pct}%` : ''}
          </div>
        </>
      )}
      <div style={{ fontSize: 10, color: '#6b7280', marginTop: 2 }}>
        {annotators} {annotators === 1 ? 'annotator' : 'annotators'}
      </div>
    </td>
  );
}

Object.assign(window, { TabPL });
