// tab-actionlog.jsx — Active monitoring + audit feed.
//
// Two views:
//   Active  — every action whose status is still 'open'. Monitored types
//             (Performance Notice / Verbal Warning / Pace Improvement Plan)
//             show their Target + Review-by countdown and Resolve / Escalate
//             buttons. Contact logs (Called / Messaged / Coaching note) just
//             get a single Dismiss button.
//   All     — full audit feed unchanged (every action ever logged).
//
// Mutations are optimistically hidden via a local "just-acted-on" set so
// the row disappears immediately; the actual data refresh happens on the
// next 10-min auto-tick or a manual sidebar refresh.

const { useState: alUseState, useMemo: alUseMemo } = React;

function fmtWhen(iso) {
  if (!iso) return '—';
  try {
    const d = new Date(iso);
    if (isNaN(d.getTime())) return iso;
    const now = new Date();
    const sameDay = d.toDateString() === now.toDateString();
    if (sameDay) return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
    return d.toLocaleDateString(undefined, { day: 'numeric', month: 'short' }) + ' ' + d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
  } catch { return iso; }
}

// Days between today and an ISO date. Negative = overdue.
function daysUntil(iso) {
  if (!iso) return null;
  const d = new Date(iso);
  if (isNaN(d.getTime())) return null;
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const target = new Date(d.getFullYear(), d.getMonth(), d.getDate());
  return Math.round((target - today) / 86_400_000);
}

function reviewLabel(days) {
  if (days == null) return '';
  if (days < 0) return Math.abs(days) + 'd overdue';
  if (days === 0) return 'due today';
  if (days === 1) return 'due tomorrow';
  return 'in ' + days + 'd';
}

function reviewTone(days) {
  if (days == null) return { color: '#6b7280', bg: '#f9fafb' };
  if (days < 0) return { color: '#b91c1c', bg: '#fef2f2' };
  if (days <= 2) return { color: '#b45309', bg: '#fffbeb' };
  return { color: '#15803d', bg: '#f0fdf4' };
}

function ActiveEntry({ entry, busy, onResolve, onEscalate, onDismiss }) {
  const monitored = entry.isMonitored;
  const days = monitored ? daysUntil(entry.reviewBy) : null;
  const tone = reviewTone(days);
  return (
    <div className="al-entry">
      <div className="al-entry-when">{fmtWhen(entry.when)}</div>
      <div className="al-entry-main">
        <div className="al-entry-hd">
          <span className="al-entry-action">{entry.action}</span>
          <span className="al-entry-arrow">·</span>
          <span className="al-entry-subject">{entry.subject}</span>
          {monitored && entry.reviewBy && (
            <span
              className="chip chip-mini"
              style={{ marginLeft: 8, color: tone.color, background: tone.bg, borderColor: tone.bg }}>
              Review {reviewLabel(days)}
            </span>
          )}
        </div>
        {monitored && entry.targetDescription && (
          <div className="al-entry-note"><b>Target:</b> {entry.targetDescription}</div>
        )}
        {entry.note && entry.note !== entry.action && (
          <div className="al-entry-note">{entry.note}</div>
        )}
        <div className="flex g6" style={{ marginTop: 8 }}>
          {monitored ? (
            <>
              <button className="btn btn-sm" disabled={busy} onClick={() => onResolve(entry)}>
                Mark met · resolve
              </button>
              <button className="btn btn-sm" disabled={busy} onClick={() => onEscalate(entry)}>
                Not met · escalate
              </button>
            </>
          ) : (
            <button className="btn btn-sm" disabled={busy} onClick={() => onDismiss(entry)}>
              Dismiss
            </button>
          )}
        </div>
      </div>
      <div className="al-entry-by">
        <Avatar name={entry.who} size={22}/>
        <span>{entry.who}</span>
      </div>
    </div>
  );
}

function HistoryEntry({ entry }) {
  const closed = entry.status && entry.status !== 'open';
  return (
    <div className="al-entry">
      <div className="al-entry-when">{fmtWhen(entry.when)}</div>
      <div className="al-entry-main">
        <div className="al-entry-hd">
          <span className="al-entry-action">{entry.action}</span>
          <span className="al-entry-arrow">·</span>
          <span className="al-entry-subject">{entry.subject}</span>
          {closed && (
            <span className="chip chip-mini" style={{
              marginLeft: 8,
              color: entry.status === 'escalated' ? '#b91c1c' : '#6b7280',
              background: entry.status === 'escalated' ? '#fef2f2' : '#f3f4f6',
              borderColor: 'transparent',
            }}>{entry.status}</span>
          )}
        </div>
        {entry.isMonitored && entry.targetDescription && (
          <div className="al-entry-note"><b>Target:</b> {entry.targetDescription}</div>
        )}
        {entry.note && entry.note !== entry.action && (
          <div className="al-entry-note">{entry.note}</div>
        )}
      </div>
      <div className="al-entry-by">
        <Avatar name={entry.who} size={22}/>
        <span>{entry.who}</span>
      </div>
    </div>
  );
}

// Filter chip for the Suggested actions card. `tone` picks a colour
// scheme matching the underlying signal (red for late/missed, amber for
// pace/incomplete/hours, neutral for All).
function SuggestionFilterChip({ active, onClick, label, count, tone }) {
  const palette = {
    all:           { active: { bg: '#111',    fg: '#fff' }, idle: { bg: '#fff', fg: '#374151', border: '#e5e7eb' } },
    late:          { active: { bg: '#b91c1c', fg: '#fff' }, idle: { bg: '#fef2f2', fg: '#b91c1c', border: '#fecaca' } },
    pace:          { active: { bg: '#b45309', fg: '#fff' }, idle: { bg: '#fffbeb', fg: '#b45309', border: '#fde68a' } },
    incomplete:    { active: { bg: '#b45309', fg: '#fff' }, idle: { bg: '#fffbeb', fg: '#b45309', border: '#fde68a' } },
    missed:        { active: { bg: '#b91c1c', fg: '#fff' }, idle: { bg: '#fef2f2', fg: '#b91c1c', border: '#fecaca' } },
    'reduced-hrs': { active: { bg: '#1d4ed8', fg: '#fff' }, idle: { bg: '#eff6ff', fg: '#1d4ed8', border: '#bfdbfe' } },
  }[tone] || { active: { bg: '#111', fg: '#fff' }, idle: { bg: '#fff', fg: '#374151', border: '#e5e7eb' } };
  const s = active ? palette.active : palette.idle;
  return (
    <button
      onClick={onClick}
      style={{
        display: 'inline-flex', alignItems: 'center', gap: 6,
        padding: '4px 10px',
        borderRadius: 999,
        background: s.bg,
        color: s.fg,
        border: `1px solid ${active ? s.bg : palette.idle.border}`,
        fontSize: 11,
        fontWeight: 600,
        cursor: 'pointer',
        fontFamily: 'inherit',
      }}>
      <span>{label}</span>
      <span style={{
        background: active ? 'rgba(255,255,255,0.18)' : 'rgba(0,0,0,0.06)',
        color: 'inherit',
        padding: '1px 6px',
        borderRadius: 999,
        fontSize: 10,
        fontFamily: 'Inconsolata, monospace',
        fontWeight: 700,
      }}>{count}</span>
    </button>
  );
}

// Compact suggestion row. Highlights chips matching the active filter so
// the lead's eye lands on the reason that brought them here. Reduced-hours
// rows get an extra "Approved (date)" / "Needs follow-up" chip drawn from
// users.lastShiftChangeReviewedAt so leads know whether a sub-25h schedule
// was sanctioned or someone fell off the roster.
function SuggestionRow({ s, highlightType, onLog }) {
  const hasReducedHrs = s.reasons.some((r) => r.type === 'reduced-hrs');
  const reviewedIso = s.member.lastShiftChangeReviewedAt;
  const reviewedAgoDays = reviewedIso ? Math.floor((Date.now() - new Date(reviewedIso).getTime()) / 86400000) : null;
  // Treat a review within the last 60 days as "approved reason for the
  // current schedule." Older reviews are stale enough that the lead should
  // re-confirm.
  const REVIEW_FRESH_DAYS = 60;
  const approvalChip = hasReducedHrs
    ? (reviewedAgoDays != null && reviewedAgoDays <= REVIEW_FRESH_DAYS)
      ? { color: '#15803d', bg: '#f0fdf4', text: `approved ${reviewedAgoDays === 0 ? 'today' : reviewedAgoDays + 'd ago'}` }
      : { color: '#b45309', bg: '#fffbeb', text: reviewedIso ? 'approval stale — confirm' : 'needs follow-up' }
    : null;
  return (
    <div className="al-entry" style={{ alignItems: 'center', gridTemplateColumns: '24px 1fr auto' }}>
      <div className="al-entry-when">
        <SevDot sev={s.member.severity} size={9}/>
      </div>
      <div className="al-entry-main">
        <div className="al-entry-hd" style={{ flexWrap: 'wrap', rowGap: 4 }}>
          <Avatar name={s.member.name} size={22}/>
          <span className="al-entry-action" style={{ fontSize: 13 }}>{s.member.name}</span>
          {s.reasons.map((r, i) => {
            const dim = highlightType && r.type !== highlightType;
            const palette = r.type === 'reduced-hrs'
              ? { color: '#1d4ed8', bg: '#eff6ff' }
              : r.tone === 'critical'
                ? { color: '#b91c1c', bg: '#fef2f2' }
                : { color: '#b45309', bg: '#fffbeb' };
            return (
              <span
                key={i}
                className="chip chip-mini"
                style={{
                  color: palette.color,
                  background: palette.bg,
                  borderColor: 'transparent',
                  opacity: dim ? 0.4 : 1,
                  fontWeight: highlightType && r.type === highlightType ? 700 : 500,
                }}>
                {r.text}
              </span>
            );
          })}
          {approvalChip && (
            <span className="chip chip-mini" style={{
              color: approvalChip.color,
              background: approvalChip.bg,
              borderColor: 'transparent',
              fontWeight: 600,
            }}>
              {approvalChip.text}
            </span>
          )}
        </div>
      </div>
      <div className="flex g6" style={{ alignItems: 'center' }}>
        <button
          className="btn btn-sm"
          onClick={() => onLog('Coaching note')}>
          Coach
        </button>
        {!hasReducedHrs && (
          <button
            className="btn btn-sm btn-dark"
            onClick={() => onLog(s.suggested)}>
            Log {s.suggested === 'Pace Improvement Plan' ? 'PIP' : 'VW'}
          </button>
        )}
      </div>
    </div>
  );
}

function TabActionLog({ refresh }) {
  const [view, setView] = alUseState('active'); // 'active' | 'all'
  const [filterAction, setFilterAction] = alUseState(null);
  const [suggestionFilter, setSuggestionFilter] = alUseState(null); // 'late' | 'pace' | 'incomplete' | 'missed' | 'reduced-hrs' | null
  const [showFullHistory, setShowFullHistory] = alUseState(false);
  const [logTarget, setLogTarget] = alUseState(null);
  const [toast, setToast] = alUseState(null);
  const [hidden, setHidden] = alUseState(() => new Set()); // optimistic IDs
  const [busyId, setBusyId] = alUseState(null);

  const log = window.YD.ACTION_LOG || [];

  const active = alUseMemo(
    () => log.filter((a) => a.status === 'open' && !hidden.has(a.id)),
    [log, hidden],
  );
  const monitored = alUseMemo(
    () => active.filter((a) => a.isMonitored).sort((a, b) => {
      const da = daysUntil(a.reviewBy); const db = daysUntil(b.reviewBy);
      if (da == null && db == null) return 0;
      if (da == null) return 1;
      if (db == null) return -1;
      return da - db; // overdue (negative) first
    }),
    [active],
  );
  const contactLogs = alUseMemo(() => active.filter((a) => !a.isMonitored), [active]);

  // History view defaults to "recent + open" so the audit feed doesn't
  // fill up with months-old resolved/dismissed/escalated entries that the
  // lead has long since processed. Toggle to surface everything when
  // they're actively investigating.
  const HISTORY_FRESH_DAYS = 30;
  const isRecentOrOpen = (a) => {
    if (!a.status || a.status === 'open') return true;
    if (!a.when) return true;
    const ms = new Date(a.when).getTime();
    if (isNaN(ms)) return true;
    return (Date.now() - ms) / 86400000 <= HISTORY_FRESH_DAYS;
  };
  const visibleLog = showFullHistory ? log : log.filter(isRecentOrOpen);
  const filtered = filterAction ? visibleLog.filter((a) => a.action === filterAction) : visibleLog;
  const hiddenCount = log.length - visibleLog.length;
  const byAction = visibleLog.reduce((acc, a) => { acc[a.action] = (acc[a.action] || 0) + 1; return acc; }, {});

  // Enforcement signals — annotators in critical/high severity who don't
  // already have an open monitored action against them. Surface these as
  // "you should probably log a PIP" suggestions so leads aren't manually
  // cross-referencing the triage banner against the action log. Built
  // entirely from data already on window.YD.
  // Each reason is tagged with a `type` so the filter chips above the
  // list can narrow the view: late / pace / incomplete / missed.
  const suggestions = alUseMemo(() => {
    const yd = window.YD || {};
    const bySev = yd.BY_SEVERITY || {};
    const allMembers = yd.MEMBERS || [];
    const monitoredEmails = new Set(
      log.filter((a) => a.status === 'open' && a.isMonitored && a.email).map((a) => a.email.toLowerCase()),
    );

    // Severity-based candidates feed the existing PIP/VW suggestion path.
    const severityCandidates = [...(bySev.critical || []), ...(bySev.high || [])]
      .filter((m) => m.email && !monitoredEmails.has(m.email.toLowerCase()));

    // Reduced-hours candidates are pulled from the full roster (not just
    // critical/high) — someone on a 15h schedule may still be hitting their
    // pro-rated target so they're severity=ok, but the lead still needs to
    // confirm the reduction was approved. Excludes leave so PTO doesn't
    // trip a follow-up.
    const FULL_TIME_HRS = 25;
    const reducedHoursCandidates = allMembers.filter((m) =>
      m.email && !m.isOnLeave && (m.weeklyHrs || 0) < FULL_TIME_HRS,
    );

    const byEmail = new Map();
    const ensure = (m) => {
      const key = m.email.toLowerCase();
      let entry = byEmail.get(key);
      if (!entry) {
        const isCritOrHigh = m.severity === 'critical' || m.severity === 'high';
        const suggested = m.severity === 'critical' ? 'Pace Improvement Plan' : 'Verbal Warning';
        entry = { member: m, reasons: [], suggested, severityFlagged: isCritOrHigh };
        byEmail.set(key, entry);
      }
      return entry;
    };

    for (const m of severityCandidates) {
      const entry = ensure(m);
      if (m.notClockedIn && (m.minutesLateClock || 0) > 120) {
        entry.reasons.push({ type: 'late', tone: 'critical', text: `late > ${Math.round(m.minutesLateClock / 60)}h today` });
      } else if (m.notClockedIn) {
        entry.reasons.push({ type: 'late', tone: 'high', text: 'late today' });
      }
      if ((m.missed || 0) >= 2) entry.reasons.push({ type: 'missed', tone: 'critical', text: `${m.missed} missed days this week` });
      else if ((m.missed || 0) === 1) entry.reasons.push({ type: 'missed', tone: 'high', text: '1 missed day' });
      if ((m.incomplete || 0) >= 2) entry.reasons.push({ type: 'incomplete', tone: 'high', text: `${m.incomplete} incomplete days` });
      const exp = m.expectedPace || 0;
      if (exp > 0 && m.pace > 0) {
        const pct = Math.round((m.pace / exp) * 100);
        if (pct < 50) entry.reasons.push({ type: 'pace', tone: 'critical', text: `pace ${pct}% of expected` });
        else if (pct < 70) entry.reasons.push({ type: 'pace', tone: 'high', text: `pace ${pct}% of expected` });
      }
    }

    for (const m of reducedHoursCandidates) {
      const entry = ensure(m);
      const hrs = m.weeklyHrs || 0;
      const text = hrs <= 0
        ? 'no shifts scheduled this week'
        : `${hrs}h committed (target ${FULL_TIME_HRS}h)`;
      entry.reasons.push({ type: 'reduced-hrs', tone: hrs <= 0 ? 'critical' : 'high', text });
    }

    return Array.from(byEmail.values())
      .filter((s) => s.reasons.length > 0)
      .sort((a, b) => {
        // critical-severity members first, then those with most reasons.
        const sa = a.member.severity === 'critical' ? 0 : 1;
        const sb = b.member.severity === 'critical' ? 0 : 1;
        if (sa !== sb) return sa - sb;
        return b.reasons.length - a.reasons.length;
      });
  }, [log]);

  // Counts per reason type — drives the filter chip badges. Computed
  // independently of the active filter so chip counts always reflect
  // the full suggestion pool.
  const suggestionCountsByType = alUseMemo(() => {
    const counts = { late: 0, pace: 0, incomplete: 0, missed: 0, 'reduced-hrs': 0 };
    for (const s of suggestions) {
      const seen = new Set();
      for (const r of s.reasons) {
        if (counts[r.type] != null && !seen.has(r.type)) {
          counts[r.type] += 1;
          seen.add(r.type);
        }
      }
    }
    return counts;
  }, [suggestions]);

  const filteredSuggestions = alUseMemo(() => {
    if (!suggestionFilter) return suggestions;
    return suggestions.filter((s) => s.reasons.some((r) => r.type === suggestionFilter));
  }, [suggestions, suggestionFilter]);

  // Make-up watch — annotators whose yesterday was incomplete (didn't hit
  // resource target OR clock the committed hours) and the deficit is
  // still owed today. Auto-clears when today's totals cover the combined
  // target. Computed entirely client-side from member data already on
  // window.YD; no backend round trip.
  const makeUps = alUseMemo(() => {
    const members = (window.YD && window.YD.MEMBERS) || [];
    return members
      .map((m) => ({
        member: m,
        makeUp: window.computeMakeUp({
          days: m.dailyShifts,
          kpi: m.dailyKpi,
          expectedPace: m.expectedPace,
        }),
      }))
      .filter((row) => row.makeUp != null)
      .sort((a, b) => {
        // Not-cleared first, then by total carry-over size
        const ac = a.makeUp.madeUp ? 1 : 0;
        const bc = b.makeUp.madeUp ? 1 : 0;
        if (ac !== bc) return ac - bc;
        const aw = a.makeUp.yesterday.resDeficit + a.makeUp.yesterday.hrDeficit * 100;
        const bw = b.makeUp.yesterday.resDeficit + b.makeUp.yesterday.hrDeficit * 100;
        return bw - aw;
      });
  }, []);

  const callMutate = async (entry, name, payload) => {
    setBusyId(entry.id);
    try {
      await window.YDApp.call(name, payload);
      setHidden((prev) => { const next = new Set(prev); next.add(entry.id); return next; });
      setToast(name === 'dismissActionItem' ? 'Dismissed' : `Marked ${payload.status || 'resolved'}`);
      setTimeout(() => setToast(null), 2200);
      // Refresh in the background so the next render reflects authoritative
      // state — keeps optimistic + server in sync without a UI flash.
      if (typeof refresh === 'function') refresh();
    } catch (e) {
      setToast(e.message || String(e));
      setTimeout(() => setToast(null), 3500);
    } finally {
      setBusyId(null);
    }
  };

  const onResolve  = (e) => callMutate(e, 'reviewActionItem', { id: e.id, status: 'resolved' });
  const onEscalate = (e) => callMutate(e, 'reviewActionItem', { id: e.id, status: 'escalated' });
  const onDismiss  = (e) => callMutate(e, 'dismissActionItem', { id: e.id });

  const onSaved = (count) => {
    setLogTarget(null);
    setToast(`Logged action for ${count} ${count === 1 ? 'person' : 'people'}`);
    setTimeout(() => setToast(null), 2400);
    if (typeof refresh === 'function') refresh();
  };

  return (
    <div className="page">
      <PageHeader
        eyebrow="Action log"
        title={view === 'active' ? `Active monitoring · ${active.length}` : `All actions · this week`}
        subtitle={view === 'active'
          ? `${monitored.length} on formal monitoring · ${contactLogs.length} contact log${contactLogs.length === 1 ? '' : 's'} pending dismiss`
          : `${log.length} recorded actions across ${new Set(log.map((a) => a.subject)).size} people`}
        actions={
          <div className="flex g8">
            <button className="btn">Export</button>
            <button className="btn btn-dark" onClick={() => setLogTarget('pick')}>+ Log action</button>
          </div>
        }/>

      <div className="row g16">
        <div className="f1">
          <div className="segbar" style={{ marginBottom: 16 }}>
            <button className={view === 'active' ? 'active' : ''} onClick={() => setView('active')}>
              Active <span style={{ marginLeft: 6, color: '#9ca3af' }}>{active.length}</span>
            </button>
            <button className={view === 'all' ? 'active' : ''} onClick={() => setView('all')}>
              All <span style={{ marginLeft: 6, color: '#9ca3af' }}>{log.length}</span>
            </button>
          </div>

          {view === 'active' ? (
            <div className="col g16">
              {suggestions.length > 0 && (
                <div className="card" style={{ borderColor: '#fde68a' }}>
                  <div className="card-hd card-hd-bottomline" style={{ background: '#fffbeb' }}>
                    <div>
                      <h3>Suggested actions · {filteredSuggestions.length}{suggestionFilter ? ` of ${suggestions.length}` : ''}</h3>
                      <span className="meta">People needing follow-up — performance signals or schedules below 25h/wk</span>
                    </div>
                  </div>
                  <div className="card-bd" style={{ padding: '10px 16px 0', borderBottom: '1px solid var(--line-2)' }}>
                    <div className="flex g6 ac" style={{ flexWrap: 'wrap' }}>
                      <SuggestionFilterChip
                        active={!suggestionFilter}
                        onClick={() => setSuggestionFilter(null)}
                        label="All"
                        count={suggestions.length}
                        tone="all"/>
                      <SuggestionFilterChip
                        active={suggestionFilter === 'late'}
                        onClick={() => setSuggestionFilter(suggestionFilter === 'late' ? null : 'late')}
                        label="Late today"
                        count={suggestionCountsByType.late}
                        tone="late"/>
                      <SuggestionFilterChip
                        active={suggestionFilter === 'pace'}
                        onClick={() => setSuggestionFilter(suggestionFilter === 'pace' ? null : 'pace')}
                        label="Below pace"
                        count={suggestionCountsByType.pace}
                        tone="pace"/>
                      <SuggestionFilterChip
                        active={suggestionFilter === 'incomplete'}
                        onClick={() => setSuggestionFilter(suggestionFilter === 'incomplete' ? null : 'incomplete')}
                        label="Incomplete shifts"
                        count={suggestionCountsByType.incomplete}
                        tone="incomplete"/>
                      <SuggestionFilterChip
                        active={suggestionFilter === 'missed'}
                        onClick={() => setSuggestionFilter(suggestionFilter === 'missed' ? null : 'missed')}
                        label="Missed shifts"
                        count={suggestionCountsByType.missed}
                        tone="missed"/>
                      <SuggestionFilterChip
                        active={suggestionFilter === 'reduced-hrs'}
                        onClick={() => setSuggestionFilter(suggestionFilter === 'reduced-hrs' ? null : 'reduced-hrs')}
                        label="Reduced hours"
                        count={suggestionCountsByType['reduced-hrs']}
                        tone="reduced-hrs"/>
                    </div>
                  </div>
                  <div className="card-bd" style={{ padding: 0 }}>
                    {filteredSuggestions.length === 0 ? (
                      <div style={{ padding: 24, textAlign: 'center', color: '#9ca3af', fontSize: 13 }}>
                        No suggestions match this filter.
                      </div>
                    ) : (
                      <div className="al-list">
                        {filteredSuggestions.map((s) => (
                          <SuggestionRow
                            key={s.member.email}
                            s={s}
                            highlightType={suggestionFilter}
                            onLog={(defaultType) => setLogTarget({ targets: [s.member], defaultType })}/>
                        ))}
                      </div>
                    )}
                  </div>
                </div>
              )}

              {makeUps.length > 0 && (
                <div className="card">
                  <div className="card-hd card-hd-bottomline">
                    <h3>Make-up watch · {makeUps.length}</h3>
                    <span className="meta">
                      Annotators with an incomplete shift yesterday — green = cleared today, amber = still owed
                    </span>
                  </div>
                  <div className="card-bd" style={{ padding: 0 }}>
                    <div className="al-list">
                      {makeUps.map(({ member, makeUp }) => {
                        const tone = makeUp.madeUp
                          ? { color: '#15803d', bg: '#f0fdf4', label: 'cleared' }
                          : { color: '#b45309', bg: '#fffbeb', label: 'still owed' };
                        const owedParts = [];
                        if (makeUp.yesterday.resDeficit > 0) owedParts.push(`${fmtK(makeUp.yesterday.resDeficit)} res`);
                        if (makeUp.yesterday.hrDeficit > 0) owedParts.push(`${makeUp.yesterday.hrDeficit.toFixed(1)}h`);
                        const stillParts = [];
                        if (makeUp.today.resStillNeeded > 0) stillParts.push(`${fmtK(makeUp.today.resStillNeeded)} res`);
                        if (makeUp.today.hrStillNeeded > 0) stillParts.push(`${makeUp.today.hrStillNeeded.toFixed(1)}h`);
                        return (
                          <div key={member.email} className="al-entry">
                            <div className="al-entry-when">
                              <span className="chip chip-mini" style={{
                                color: tone.color, background: tone.bg, borderColor: 'transparent',
                              }}>{tone.label}</span>
                            </div>
                            <div className="al-entry-main">
                              <div className="al-entry-hd">
                                <span className="al-entry-action">{member.name}</span>
                                <span className="al-entry-arrow">·</span>
                                <span className="al-entry-subject" style={{ fontSize: 11, color: '#6b7280' }}>
                                  {makeUp.yesterday.day} owed {owedParts.join(' + ')}
                                </span>
                              </div>
                              <div className="al-entry-note">
                                Today: <b>{fmtK(makeUp.today.actualRes)}</b> done of <b>{fmtK(makeUp.today.effectiveTargetRes)}</b> target
                                {' '}(base {fmtK(makeUp.today.baseTargetRes)} + {fmtK(makeUp.yesterday.resDeficit)} carry).
                                {!makeUp.madeUp && stillParts.length > 0 && (
                                  <> Still need <b>{stillParts.join(' + ')}</b> to clear.</>
                                )}
                              </div>
                              <div className="flex g6" style={{ marginTop: 8 }}>
                                <button
                                  className="btn btn-sm"
                                  onClick={() => setLogTarget({ targets: [member], defaultType: 'Coaching note' })}>
                                  Log coaching note
                                </button>
                                {!makeUp.madeUp && (
                                  <button
                                    className="btn btn-sm"
                                    onClick={() => setLogTarget({ targets: [member], defaultType: 'Performance Notice' })}>
                                    Log Performance Notice
                                  </button>
                                )}
                              </div>
                            </div>
                            <div className="al-entry-by">
                              <Avatar name={member.name} size={22}/>
                            </div>
                          </div>
                        );
                      })}
                    </div>
                  </div>
                </div>
              )}

              <div className="card">
                <div className="card-hd card-hd-bottomline">
                  <h3>On monitoring · {monitored.length}</h3>
                  <span className="meta">Resolve when target is met, escalate if not</span>
                </div>
                <div className="card-bd" style={{ padding: 0 }}>
                  <div className="al-list">
                    {monitored.length === 0 ? (
                      <div style={{ padding: 24, textAlign: 'center', color: '#9ca3af', fontSize: 13 }}>
                        Nobody on formal monitoring right now.
                      </div>
                    ) : monitored.map((a) => (
                      <ActiveEntry
                        key={a.id}
                        entry={a}
                        busy={busyId === a.id}
                        onResolve={onResolve}
                        onEscalate={onEscalate}
                        onDismiss={onDismiss}/>
                    ))}
                  </div>
                </div>
              </div>

              <div className="card">
                <div className="card-hd card-hd-bottomline">
                  <h3>Contact log · {contactLogs.length}</h3>
                  <span className="meta">Dismiss to clear · stays on the person's history</span>
                </div>
                <div className="card-bd" style={{ padding: 0 }}>
                  <div className="al-list">
                    {contactLogs.length === 0 ? (
                      <div style={{ padding: 24, textAlign: 'center', color: '#9ca3af', fontSize: 13 }}>
                        No open contact logs.
                      </div>
                    ) : contactLogs.map((a) => (
                      <ActiveEntry
                        key={a.id}
                        entry={a}
                        busy={busyId === a.id}
                        onResolve={onResolve}
                        onEscalate={onEscalate}
                        onDismiss={onDismiss}/>
                    ))}
                  </div>
                </div>
              </div>
            </div>
          ) : (
            <>
              <div className="flex g8 jb ac" style={{ marginBottom: 16, flexWrap: 'wrap' }}>
                <div className="segbar">
                  <button className={!filterAction ? 'active' : ''} onClick={() => setFilterAction(null)}>
                    All ({visibleLog.length})
                  </button>
                  {Object.entries(byAction).map(([action, count]) => (
                    <button key={action} className={filterAction === action ? 'active' : ''} onClick={() => setFilterAction(action)}>
                      {action} <span style={{ marginLeft: 6, color: '#9ca3af' }}>{count}</span>
                    </button>
                  ))}
                </div>
                {(hiddenCount > 0 || showFullHistory) && (
                  <button
                    className="btn btn-sm"
                    onClick={() => setShowFullHistory(!showFullHistory)}
                    title={`Closed actions older than ${HISTORY_FRESH_DAYS} days are hidden by default`}>
                    {showFullHistory
                      ? `Hide closed > ${HISTORY_FRESH_DAYS}d`
                      : `Show ${hiddenCount} older closed`}
                  </button>
                )}
              </div>

              <div className="card">
                <div className="card-bd" style={{ padding: 0 }}>
                  <div className="al-list">
                    {filtered.length === 0 ? (
                      <div style={{ padding: 24, textAlign: 'center', color: '#9ca3af', fontSize: 13 }}>
                        No actions recorded {filterAction ? `for ${filterAction}` : 'yet'}.
                        {!showFullHistory && hiddenCount > 0 && (
                          <div style={{ marginTop: 8, fontSize: 12 }}>
                            {hiddenCount} closed action{hiddenCount === 1 ? '' : 's'} older than {HISTORY_FRESH_DAYS} days hidden.
                          </div>
                        )}
                      </div>
                    ) : filtered.map((a, i) => <HistoryEntry key={a.id || i} entry={a}/>)}
                  </div>
                </div>
              </div>
            </>
          )}
        </div>

        <div style={{ width: 300 }}>
          <div className="card">
            <div className="card-hd card-hd-bottomline"><h3>Action summary</h3></div>
            <div className="card-bd">
              <div className="col g8">
                {Object.keys(byAction).length === 0 ? (
                  <div className="meta">No data.</div>
                ) : Object.entries(byAction).map(([action, count]) => (
                  <div key={action} className="flex jb ac">
                    <span style={{ fontSize: 12 }}>{action}</span>
                    <span className="mono" style={{ fontWeight: 700 }}>{count}</span>
                  </div>
                ))}
              </div>
            </div>
          </div>
        </div>
      </div>

      {toast && <div className="toast">{toast}</div>}
      {logTarget === 'pick' && (
        <PickTargetModal onClose={() => setLogTarget(null)} onPicked={(m) => setLogTarget({ targets: [m] })}/>
      )}
      {logTarget && typeof logTarget === 'object' && (
        <ActionModal
          targets={logTarget.targets}
          defaultType={logTarget.defaultType}
          onClose={() => setLogTarget(null)}
          onSaved={onSaved}/>
      )}
    </div>
  );
}

// Two-step "+ Log action" flow on this page only: first pick a person from
// the team roster (the other tabs already have a selected member in
// context), then the parent swaps to ActionModal with that person.
function PickTargetModal({ onClose, onPicked }) {
  const [q, setQ] = alUseState('');
  const members = (window.YD.MEMBERS || []).slice().sort((a, b) => a.name.localeCompare(b.name));
  const ql = q.trim().toLowerCase();
  const matches = ql
    ? members.filter((m) => m.name.toLowerCase().includes(ql) || (m.email || '').toLowerCase().includes(ql))
    : members;
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-card" onClick={(e) => e.stopPropagation()}>
        <div className="modal-hd">
          <h3>Log action · pick person</h3>
          <button className="modal-x" onClick={onClose} aria-label="Close">×</button>
        </div>
        <div className="modal-bd">
          <input
            className="modal-input"
            autoFocus
            placeholder="Search by name or email…"
            value={q}
            onChange={(e) => setQ(e.target.value)}/>
          <div style={{ maxHeight: 320, overflowY: 'auto', marginTop: 10, border: '1px solid var(--line)', borderRadius: 8 }}>
            {matches.length === 0 ? (
              <div style={{ padding: 16, color: '#9ca3af', fontSize: 13, textAlign: 'center' }}>No matches.</div>
            ) : matches.slice(0, 50).map((m) => (
              <button
                key={m.email}
                onClick={() => onPicked(m)}
                style={{
                  display: 'flex', alignItems: 'center', gap: 10, width: '100%',
                  padding: '8px 12px', border: 0, borderBottom: '1px solid var(--line-2)',
                  background: '#fff', cursor: 'pointer', textAlign: 'left',
                }}>
                <Avatar name={m.name} size={26}/>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 600 }}>{m.name}</div>
                  <div className="meta">{m.email}</div>
                </div>
              </button>
            ))}
          </div>
        </div>
        <div className="modal-actions">
          <button className="btn" onClick={onClose}>Cancel</button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { TabActionLog });
