/* ============================================================
   page-employees.jsx — directory + drawer
   ============================================================ */

/* Re-bind parseDetail from primitives.jsx */
const parseDetail = window.parseDetail || ((body, status) => {
  const d = body?.detail;
  if (typeof d === 'string') return d;
  if (Array.isArray(d)) return d.map(x => x?.msg || JSON.stringify(x)).join('; ');
  if (d && typeof d === 'object') return d.message || d.error || JSON.stringify(d);
  const m = body?.message;
  if (typeof m === 'string') return m;
  if (Array.isArray(m)) return m.map(x => (typeof x === 'string' ? x : (x?.message || JSON.stringify(x)))).join('; ');
  if (m && typeof m === 'object') return m.message || m.error || JSON.stringify(m);
  return `HTTP ${status}`;
});

/* Format a duration in minutes as hours+minutes (e.g. 196 -> "3h 16m", 45 -> "45m"). */
function fmtMins(m) {
  m = Math.round(m || 0);
  if (m < 60) return `${m}m`;
  const h = Math.floor(m / 60), mm = m % 60;
  return mm ? `${h}h ${mm}m` : `${h}h`;
}

/* TODO Bug 6: role shows 3x — could not reproduce in JSX (only 1 render at line 1269).
   If still happening, check if person.role value itself contains repeated text (DB issue)
   or if there is a CSS ::before/::after pseudo-element in employee-card repeating content. */


/* ──────────────────────────────────────────────────────────────────────────
   StrikeBadge (issue #4) — full treatment
   --------------------------------------
   Renders the user's current strike count with:
   (a) inline tier description: 1/3 forgiveness · 2/3 last warning · 3/3 admin reported
   (c) color-coded background: 0=green, 1=yellow, 2=orange, 3+=red
   (b) onClick opens StrikeHistoryModal showing every format_strike /
       late_strike / admin_manual_strike audit-log row + a "validate"
       line confirming the count matches the audit history.

   The bot's 3-strike rule (per CLAUDE.md):
     strike 1 → forgiveness DM ("✅ One-time forgiveness…")
     strike 2 → "LAST WARNING" DM
     strike 3 → admin report posted to STANDUP_ROOM + manager DM
   ────────────────────────────────────────────────────────────────────────── */

const _STRIKE_TIERS = {
  0: { label: (window.appT||(s=>s))('clear'),           bg: 'var(--ok)',   fg: '#fff' },
  1: { label: (window.appT||(s=>s))('1/3 forgiveness'), bg: '#f1c40f',     fg: '#1a1a1a' },
  2: { label: (window.appT||(s=>s))('2/3 last warning'),bg: '#e67e22',     fg: '#fff' },
  3: { label: (window.appT||(s=>s))('3/3 admin reported'), bg: 'var(--err)', fg: '#fff' },
};

function StrikeBadge({ count, userId, displayName }) {
  const [open, setOpen] = useState(false);
  const tier = _STRIKE_TIERS[Math.min(3, Math.max(0, count|0))];
  return (
    <>
      <button
        type="button"
        onClick={(e) => { e.stopPropagation(); setOpen(true); }}
        title={(window.appT||(s=>s))("Click to see every strike event for this user + validate count against the audit log")}
        className="btn"
        style={{
          background: tier.bg, color: tier.fg,
          padding: '3px 9px', fontSize: 11.5, fontWeight: 600,
          border: 'none', borderRadius: 'var(--r-sm)', cursor: 'pointer',
          display: 'inline-flex', alignItems: 'center', gap: 6,
        }}
      >
        <span className="mono" style={{ fontSize: 13 }}>{count|0}</span>
        <span>· {tier.label}</span>
      </button>
      {open && (
        <StrikeHistoryModal
          userId={userId}
          displayName={displayName}
          currentCount={count|0}
          onClose={() => setOpen(false)}
        />
      )}
    </>
  );
}


function StrikeHistoryModal({ userId, displayName, currentCount, onClose }) {
  const [loading, setLoading] = useState(true);
  const [events, setEvents] = useState([]);
  const [error, setError] = useState(null);
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        // The /api/audit endpoint already supports user_id + action_type filter.
        // Pull every strike-related action across all time for this user.
        const types = ['format_strike','late_strike','admin_manual_strike',
                       'admin_force_strike','admin_manual_violation',
                       'format_violation','force_strike','strike_warned',
                       'strike_escalated','force_decrement_strike',
                       'force_clear_strikes'];
        const all = [];
        for (const t of types) {
          try {
            const r = await fetch(`/api/audit?user_id=${userId}&action_type=${t}&limit=200`,
              { credentials: 'include' });
            if (r.ok) {
              const rows = await r.json();
              all.push(...rows);
            }
          } catch(_) {}
        }
        if (cancelled) return;
        // Sort newest first
        all.sort((a, b) => (b.occurred_at || '').localeCompare(a.occurred_at || ''));
        setEvents(all);
      } catch (e) {
        if (!cancelled) setError(e.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [userId]);

  // Validate: increments vs decrements should reconcile to `currentCount`.
  // - format_strike / late_strike / admin_manual_strike / format_violation /
  //   admin_manual_violation / force_strike / admin_force_strike -> +1
  // - force_decrement_strike -> -1
  // - force_clear_strikes -> sets to 0 (we ignore prior events after this)
  const validation = useMemo(() => {
    const ordered = events.slice().reverse();  // oldest first
    let count = 0;
    let cleared_at = null;
    for (const ev of ordered) {
      const t = ev.action_type || '';
      if (t === 'force_clear_strikes') {
        count = 0; cleared_at = ev.occurred_at;
        continue;
      }
      if (t === 'force_decrement_strike') {
        count = Math.max(0, count - 1);
        continue;
      }
      if (t.endsWith('_strike') || t.endsWith('_violation') || t === 'force_strike') {
        count += 1;
      }
    }
    // Reconcile the UNCAPPED lifetime count against the stored (also uncapped,
    // pay-linked) warning_count. Capping the computed value at 3 here made every
    // employee with >3 lifetime strikes show a permanent false "Mismatch",
    // because the stored count they were compared against is never capped.
    // The 0-3 escalation tier is a separate, derived value shown alongside.
    const computed = count;
    const tier = Math.min(3, count);
    return {
      computed,
      tier,
      expected: currentCount,
      matches: computed === currentCount,
      cleared_at,
      total_events: events.length,
    };
  }, [events, currentCount]);

  return (
    <div
      className="modal-overlay"
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0,
        background: 'rgba(0,0,0,0.55)',
        zIndex: 1000,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}
    >
      <div
        onClick={e => e.stopPropagation()}
        style={{
          background: 'var(--bg-elev-1)',
          color: 'var(--text)',
          width: 'min(640px, 92vw)',
          maxHeight: '80vh',
          overflow: 'hidden',
          display: 'flex', flexDirection: 'column',
          borderRadius: 'var(--r-lg)',
          border: '1px solid var(--line)',
          boxShadow: 'var(--shadow)',
        }}
      >
        <div style={{
          padding: '14px 18px',
          borderBottom: '1px solid var(--line)',
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 600 }}>{(window.appT||(s=>s))("Strike history")} — {displayName}</div>
            <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>
              {(window.appT||(s=>s))("Current:")} <strong>{currentCount|0}</strong> · {(window.appT||(s=>s))("3-strike rule: 1 = forgiveness DM · 2 = last warning · 3 = admin report")}
            </div>
          </div>
          <button className="btn btn-ghost btn-icon" onClick={onClose} title={(window.appT||(s=>s))("Close (Esc)")}>
            <Icon name="x" size={14}/>
          </button>
        </div>

        {/* Validation row — confirms count matches audit history */}
        {!loading && (
          <div
            style={{
              padding: '10px 18px',
              background: validation.matches
                ? 'color-mix(in oklch, var(--ok) 8%, transparent)'
                : 'color-mix(in oklch, var(--warn) 14%, transparent)',
              borderBottom: '1px solid var(--line)',
              fontSize: 12,
            }}
          >
            {validation.matches ? '✅' : '⚠️'} <strong>{(window.appT||(s=>s))("Validated:")}</strong>{' '}
            {(window.appLang&&window.appLang()==='ar')
              ? `يُظهر سجل التدقيق ${validation.total_events} حدثًا متعلقًا بالمخالفات؛ العدد المحسوب = `
              : `audit log shows ${validation.total_events} strike-related event${validation.total_events === 1 ? '' : 's'}; computed lifetime count = `}<strong>{validation.computed}</strong>{(window.appLang&&window.appLang()==='ar') ? `، العدد المُخزَّن = ` : `, stored lifetime count = `}<strong>{validation.expected}</strong>{(window.appLang&&window.appLang()==='ar') ? `، مستوى التصعيد (0-3) = ` : `, escalation tier (0-3) = `}<strong>{validation.tier}</strong>.
            {validation.matches
              ? (window.appT||(s=>s))(' Counts match — bot state machine is consistent with audit history.')
              : (window.appT||(s=>s))(' ⚠️ Mismatch — likely an old `force_clear_strikes` event before the audit log was added, or a bot restart lost a transient counter. Use the "Forgive all" or "Downgrade" buttons on the employee card to reconcile.')}
            {validation.cleared_at && (
              <div className="muted" style={{ marginTop: 4 }}>
                {(window.appLang&&window.appLang()==='ar')
                  ? `آخر مسح في ${validation.cleared_at.replace('T',' ').slice(0,19)} (تُحتسب الأحداث بعد تلك النقطة).`
                  : `Last cleared at ${validation.cleared_at.replace('T',' ').slice(0,19)} (events after that point counted).`}
              </div>
            )}
          </div>
        )}

        <div style={{ overflowY: 'auto', flex: 1, padding: '4px 0' }}>
          {loading && <Loading label={(window.appT||(s=>s))("Loading…")} rows={3} />}
          {!loading && events.length === 0 && (
            <div className="muted" style={{ padding: 18, fontSize: 13 }}>
              {(window.appT||(s=>s))("No strike events on record.")} {currentCount > 0
                ? ((window.appLang&&window.appLang()==='ar')
                    ? `(العدد المُخزَّن هو ${currentCount} — على الأرجح مخالفة سابقة لسجل التدقيق؛ فكِّر في "العفو عن الكل" لإعادة التعيين.)`
                    : `(Stored count is ${currentCount} — likely a pre-audit-log strike; consider Forgive all to reset.)`)
                : ''}
            </div>
          )}
          {!loading && events.length > 0 && (
            <table className="t" style={{ width: '100%', fontSize: 12 }}>
              <thead>
                <tr>
                  <th style={{ textAlign: 'left', padding: '6px 14px', width: 140 }}>{(window.appT||(s=>s))("When")}</th>
                  <th style={{ textAlign: 'left', padding: '6px 14px' }}>{(window.appT||(s=>s))("Action")}</th>
                  <th style={{ textAlign: 'left', padding: '6px 14px' }}>{(window.appT||(s=>s))("Detail")}</th>
                </tr>
              </thead>
              <tbody>
                {events.map(ev => (
                  <tr key={ev.id}>
                    <td className="mono" style={{ padding: '6px 14px', color: 'var(--text-3)' }}>
                      {(ev.occurred_at || '').replace('T',' ').slice(0,19)}
                    </td>
                    <td style={{ padding: '6px 14px' }}>
                      <div style={{ display:'flex', flexDirection:'column', gap: 2 }}>
                        <Badge kind="warn" title={ev.action_type || ''}>
                          {ev.action_label || (window.humanizeActionType ? window.humanizeActionType(ev.action_type) : ev.action_type)}
                        </Badge>
                        {/* Show WHO did this action — API's `actor` field carries
                            the admin name, bot label, or generic "Admin" for
                            admin-GUI actions. */}
                        {ev.actor && (
                          <span style={{ fontSize: 10.5, color: 'var(--text-3)' }}>{(window.appT||(s=>s))("by")} {ev.actor}</span>
                        )}
                      </div>
                    </td>
                    <td style={{ padding: '6px 14px', color: 'var(--text-2)', fontSize: 11.5 }}>
                      {(ev.body || ev.trigger || '').slice(0, 140)}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
}


/* ──────────────────────────────────────────────────────────────────────────
   Q8 — Progress timeline + admin-pickable metrics grid (issue #24)
   ───────────────────────────────────────────────────────────────────────── */

/* (d) Inline-SVG line chart of per-day progress score with PTO bands,
   blocker dots, and strike triangles overlaid. Pure SVG — no chart lib
   needed (the codebase intentionally avoids one for bundle size). */
function ProgressTimelineCard({ byDay, ptoHistory, violations, rangeStart, rangeEnd }) {
  // Build a date-keyed map of {progress, has_blocker} from by_day
  const byDate = useMemo(() => {
    const m = {};
    (byDay || []).forEach(d => {
      m[d.date] = {
        score: typeof d.progress_score === 'number' ? d.progress_score : null,
        has_blocker: !!(d.blocker && String(d.blocker).trim()),
        today: (d.today || '').slice(0, 80),
      };
    });
    return m;
  }, [byDay]);

  // Compose an ordered date array across the full range so gaps render as gaps
  const dates = useMemo(() => {
    if (!rangeStart || !rangeEnd) return [];
    const start = new Date(rangeStart + 'T00:00:00');
    const end = new Date(rangeEnd + 'T00:00:00');
    const out = [];
    for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
      out.push(d.toISOString().slice(0, 10));
    }
    return out;
  }, [rangeStart, rangeEnd]);

  // Pre-compute PTO bands as [{start_idx, end_idx, kind}]
  const ptoBands = useMemo(() => {
    if (!ptoHistory || !dates.length) return [];
    const idxOf = d => dates.indexOf(d);
    const bands = [];
    ptoHistory.forEach(p => {
      const sIdx = idxOf(p.start_date);
      const eIdx = idxOf(p.end_date);
      if (sIdx === -1 && eIdx === -1) return;
      bands.push({
        start: Math.max(0, sIdx === -1 ? 0 : sIdx),
        end: Math.min(dates.length - 1, eIdx === -1 ? dates.length - 1 : eIdx),
        kind: p.kind || 'pto',
      });
    });
    return bands;
  }, [ptoHistory, dates]);

  // Strike marker dates (just keep the calendar date of each event)
  const strikeDates = useMemo(() => {
    const out = new Set();
    (violations || []).forEach(v => {
      if (v.occurred_at) out.add(v.occurred_at.slice(0, 10));
    });
    return out;
  }, [violations]);

  const W = 720, H = 200, M = { top: 14, right: 14, bottom: 22, left: 28 };
  const innerW = W - M.left - M.right;
  const innerH = H - M.top - M.bottom;
  const n = dates.length;
  const xFor = i => M.left + (n <= 1 ? innerW / 2 : (i * innerW) / (n - 1));
  const yFor = score => M.top + innerH - (score / 2) * innerH;  // score 0..2

  // Build line path skipping null days (no score that day)
  const linePath = useMemo(() => {
    if (!dates.length) return '';
    const segs = [];
    let started = false;
    dates.forEach((d, i) => {
      const rec = byDate[d];
      if (rec && rec.score != null) {
        segs.push((started ? 'L' : 'M') + xFor(i).toFixed(1) + ',' + yFor(rec.score).toFixed(1));
        started = true;
      } else {
        started = false;  // gap
      }
    });
    return segs.join(' ');
  }, [dates, byDate, n]);

  const _ptoColor = (k) => ({
    sick: '#ef4444',
    vacation: '#3b82f6',
    pto: '#3b82f6',
    half_day: '#a855f7',
    holiday: '#9ca3af',
  })[k] || '#3b82f6';

  return (
    <>
      <SectionHeader
        title={(window.appT||(s=>s))("Progress timeline")}
        tip={(window.appT||(s=>s))("Per-day progress score (0–2) across the selected period. Light vertical bands = approved PTO (color-coded by kind). Orange dots on the score line = days with a reported blocker. Red ▲ triangles at the top = strike events. Hover any day for details.")}
        sub={(window.appLang&&window.appLang()==='ar')
          ? `${dates.length} يوم · ${ptoBands.length} فترة إجازة · ${strikeDates.size} مخالفة`
          : `${dates.length} day${dates.length === 1 ? '' : 's'} · ${ptoBands.length} PTO block${ptoBands.length === 1 ? '' : 's'} · ${strikeDates.size} strike${strikeDates.size === 1 ? '' : 's'}`}
      />
      <div style={{ overflowX: 'auto' }}>
        <svg
          viewBox={`0 0 ${W} ${H}`}
          width="100%"
          style={{ minWidth: 400, maxHeight: 220, fontFamily: 'var(--font-sans)', fontSize: 10 }}
        >
          {/* Y-axis grid lines + labels for score 0, 1, 2 */}
          {[0, 1, 2].map(s => (
            <g key={s}>
              <line
                x1={M.left} x2={W - M.right}
                y1={yFor(s)} y2={yFor(s)}
                stroke="var(--line)" strokeDasharray={s === 0 ? '' : '2 3'}
              />
              <text x={M.left - 4} y={yFor(s) + 3} textAnchor="end" fill="var(--text-3)">{s}</text>
            </g>
          ))}

          {/* PTO bands (under the line) */}
          {ptoBands.map((b, i) => {
            const x1 = xFor(b.start) - 4;
            const x2 = xFor(b.end) + 4;
            return (
              <rect key={`pto-${i}`}
                x={x1} y={M.top} width={Math.max(2, x2 - x1)} height={innerH}
                fill={_ptoColor(b.kind)} opacity="0.12"
              >
                <title>{`${b.kind.toUpperCase()} ${dates[b.start]} → ${dates[b.end]}`}</title>
              </rect>
            );
          })}

          {/* Progress score line */}
          {linePath && (
            <path d={linePath} fill="none" stroke="var(--brand)" strokeWidth="2" />
          )}

          {/* Per-day score dot + blocker indicator */}
          {dates.map((d, i) => {
            const rec = byDate[d];
            if (!rec || rec.score == null) return null;
            const cx = xFor(i), cy = yFor(rec.score);
            return (
              <g key={`p-${i}`}>
                <circle
                  cx={cx} cy={cy} r={rec.has_blocker ? 4.5 : 3}
                  fill={rec.has_blocker ? '#f59e0b' : 'var(--brand)'}
                  stroke="var(--bg-elev-1)" strokeWidth="1.5"
                >
                  <title>{(window.appLang&&window.appLang()==='ar') ? `${d} · الدرجة ${rec.score}${rec.has_blocker ? ' · عائق' : ''}${rec.today ? '\n' + rec.today : ''}` : `${d} · score ${rec.score}${rec.has_blocker ? ' · blocker' : ''}${rec.today ? '\n' + rec.today : ''}`}</title>
                </circle>
              </g>
            );
          })}

          {/* Strike triangles at top of chart */}
          {Array.from(strikeDates).map((d, i) => {
            const idx = dates.indexOf(d);
            if (idx === -1) return null;
            const x = xFor(idx);
            return (
              <polygon
                key={`s-${i}`}
                points={`${x},${M.top - 2} ${x - 4},${M.top + 5} ${x + 4},${M.top + 5}`}
                fill="var(--err)"
              >
                <title>{(window.appLang&&window.appLang()==='ar') ? `مخالفة في ${d}` : `Strike on ${d}`}</title>
              </polygon>
            );
          })}

          {/* X-axis ticks (every 5 days) */}
          {dates.map((d, i) => (
            i % 5 === 0 || i === dates.length - 1 ? (
              <text key={`x-${i}`}
                x={xFor(i)} y={H - 6} textAnchor="middle"
                fill="var(--text-3)" fontSize="9"
              >{d.slice(5)}</text>
            ) : null
          ))}
        </svg>
      </div>
      <div className="muted" style={{ fontSize: 11, marginTop: 8, display: 'flex', gap: 14, flexWrap: 'wrap' }}>
        <span><span style={{ color: 'var(--brand)' }}>●</span> {(window.appT||(s=>s))("daily score")}</span>
        <span><span style={{ color: '#f59e0b' }}>●</span> {(window.appT||(s=>s))("blocker day")}</span>
        <span><span style={{ color: 'var(--err)' }}>▲</span> {(window.appT||(s=>s))("strike")}</span>
        <span><span style={{ background: '#3b82f644', padding: '0 6px' }}>&nbsp;&nbsp;&nbsp;</span> {(window.appT||(s=>s))("PTO")}</span>
        <span><span style={{ background: '#ef444444', padding: '0 6px' }}>&nbsp;&nbsp;&nbsp;</span> {(window.appT||(s=>s))("Sick")}</span>
      </div>
    </>
  );
}


/* (c) Admin-pickable period metrics grid.
   Reads window.STATUS_DATA.ME.user_id to scope the picker preference
   per admin (localStorage key includes the admin's user_id, so different
   admins can have different default views without stomping each other). */

const _ALL_METRICS = [
  { key: 'avg_signin_local',          label: (window.appT||(s=>s))('Avg sign-in'),          unit: '',
    tip: (window.appT||(s=>s))('Average wall-clock sign-in time in the user\'s local timezone.') },
  { key: 'avg_signoff_local',         label: (window.appT||(s=>s))('Avg sign-off'),         unit: '',
    tip: (window.appT||(s=>s))('Average sign-off time, user\'s local TZ.') },
  { key: 'signin_consistency_minutes', label: (window.appT||(s=>s))('σ sign-in'),           unit: (window.appT||(s=>s))('min'),
    tip: (window.appT||(s=>s))('Standard deviation of sign-in minute over the period — lower = more consistent.') },
  { key: 'longest_on_time_streak',    label: (window.appT||(s=>s))('Longest streak'),       unit: (window.appT||(s=>s))('days'),
    tip: (window.appT||(s=>s))('Max consecutive working days signed in on time.') },
  { key: 'delivery_pct',              label: (window.appT||(s=>s))('Delivery %'),           unit: '%',
    tip: (window.appT||(s=>s))('Fraction of today_text items containing a ship-verb (shipped/done/completed/merged/deployed/finished/closed/released).') },
  { key: 'delivered_items',           label: (window.appT||(s=>s))('Delivered items'),      unit: '',
    tip: (window.appT||(s=>s))('Count of today_text lines with a ship-verb across the period.') },
  { key: 'total_today_items',         label: (window.appT||(s=>s))('Total tasks'),          unit: '',
    tip: (window.appT||(s=>s))('Total today_text items across the period.') },
  { key: 'rolling_7d_progress_score', label: (window.appT||(s=>s))('Rolling 7-d score'),    unit: '/2',
    tip: (window.appT||(s=>s))('Avg progress score across the last 7 working days — recent trajectory.') },
  { key: 'stale_open_tasks',          label: (window.appT||(s=>s))('Stale tasks'),          unit: '',
    tip: (window.appT||(s=>s))('Open tasks >= 2 days old without a finished_at — candidates for nudge.') },
  { key: 'repeated_accomplishments',  label: (window.appT||(s=>s))('Repeated wins'),        unit: '',
    tip: (window.appT||(s=>s))('Same accomplishment listed in yesterday_text 3+ days in a row — potential padding.') },
  { key: 'vs_prior_attendance_pct',   label: (window.appT||(s=>s))('Δ attendance% vs prior'), unit: '',
    tip: (window.appT||(s=>s))('Change in attendance % vs the equivalent prior window.') },
  { key: 'vs_prior_hours',            label: (window.appT||(s=>s))('Δ hours vs prior'),     unit: 'h',
    tip: (window.appT||(s=>s))('Change in total hours vs the equivalent prior window.') },
];

const _DEFAULT_VISIBLE_METRICS = [
  'avg_signin_local',
  'longest_on_time_streak',
  'delivery_pct',
  'rolling_7d_progress_score',
  'stale_open_tasks',
  'vs_prior_attendance_pct',
];

function _formatMetric(m, metrics) {
  let raw;
  if (m.key === 'vs_prior_attendance_pct') {
    const v = (metrics.vs_prior_period && metrics.vs_prior_period.delta_attendance_pct);
    if (v == null) return { display: '—', color: 'var(--text-3)' };
    const sign = v > 0 ? '+' : '';
    return { display: `${sign}${v} ${(window.appT||(s=>s))('pts')}`, color: v >= 0 ? 'var(--ok)' : 'var(--err)' };
  }
  if (m.key === 'vs_prior_hours') {
    const v = (metrics.vs_prior_period && metrics.vs_prior_period.delta_hours);
    if (v == null) return { display: '—', color: 'var(--text-3)' };
    const sign = v > 0 ? '+' : '';
    return { display: `${sign}${v}h`, color: v >= 0 ? 'var(--ok)' : 'var(--err)' };
  }
  raw = metrics[m.key];
  if (raw == null) return { display: '—', color: 'var(--text-3)' };
  if (m.unit === '%') return { display: `${raw}%`, color: 'var(--text)' };
  if (m.unit === 'h') return { display: `${Number(raw).toFixed(1)}h`, color: 'var(--text)' };
  if (m.unit === '/2') return { display: `${Number(raw).toFixed(2)} / 2`, color: 'var(--text)' };
  if (m.unit) return { display: `${raw} ${m.unit}`, color: 'var(--text)' };
  return { display: String(raw), color: 'var(--text)' };
}

// C3: HR documents vault — doctor notes / contracts / IDs / other.
// ── Goal & benchmark (what the Efficiency% is measured against) ────────────
// The score alone never said 68% of WHAT. This shows the admin-set target for
// the employee's team (Bot Behavior ▸ Goals, per-team overridable) and the
// team's own median over recent weeks as a like-for-like comparison
// (GET /api/goals/{id}). A window that produced no score reads "no score",
// never 0% — somebody on leave all week hasn't missed anything.
function GoalCard({ userId, forcePeriod }) {
  const [period, setPeriod] = useState(forcePeriod || 'month');
  useEffect(() => { if (forcePeriod && forcePeriod !== period) setPeriod(forcePeriod); }, [forcePeriod]);
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const T = (window.appT || (s => s));

  useEffect(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      try {
        const r = await fetch(`/api/goals/${userId}?period=${period}`, { credentials: 'include' });
        const d = r.ok ? await r.json() : null;
        if (!cancelled) setData(d);
      } catch (_) { if (!cancelled) setData(null); }
      finally { if (!cancelled) setLoading(false); }
    })();
    return () => { cancelled = true; };
  }, [userId, period]);

  const BANDS = {
    behind:   { l: T('Behind target'), c: '#b26a00', k: 'warn' },
    on_track: { l: T('On track'),      c: '#2e6f40', k: 'ok' },
    ahead:    { l: T('Ahead'),         c: '#1f6feb', k: 'info' },
    no_data:  { l: T('No score'),      c: 'var(--text-3)', k: 'mute' },
  };
  const g = (data && data.goal) || null;
  const band = g ? (BANDS[g.band] || BANDS.no_data) : null;
  const bench = (data && data.benchmark) || {};
  const vs = data ? data.vs_benchmark : null;
  const pctOfTarget = g && g.attainment_pct !== null && g.attainment_pct !== undefined
    ? Math.max(0, Math.min(150, g.attainment_pct)) : null;

  return (
    <>
      <SectionHeader
        title={T('Goal & benchmark')}
        tip={T("The efficiency target for this employee's team and how they sit against it, plus the team's own median over recent weeks so the number is read against colleagues on the same rubric rather than an absolute ideal. Targets are set in Bot Behavior ▸ Goals and can be overridden per team; changing them never changes anyone's score.")}
        right={
          <select value={period} onChange={e => setPeriod(e.target.value)}
                  style={{ fontSize: '12px', padding: '2px 4px' }}>
            <option value="week">{T('Week')}</option>
            <option value="month">{T('Month')}</option>
            <option value="quarter">{T('Quarter')}</option>
          </select>
        }
      />
      {loading && <Loading label={T('Loading...')} rows={2} />}
      {!loading && !data && <div className="muted small">{T('No goal available.')}</div>}
      {!loading && data && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: '10px', flexWrap: 'wrap' }}>
            <span className="mono" style={{ fontSize: '22px', fontWeight: 700, color: band.c }}>
              {g.actual === null || g.actual === undefined ? '—' : `${g.actual}%`}
            </span>
            <span className="small muted">{T('target')} {g.target}%</span>
            <Badge kind={band.k} dot>{band.l}</Badge>
            {g.gap !== null && g.gap !== undefined && (
              <span className="small" style={{ color: g.gap < 0 ? '#b26a00' : '#2e6f40' }}>
                {g.gap > 0 ? `+${g.gap}` : g.gap} {T('pts vs target')}
              </span>
            )}
          </div>

          {pctOfTarget !== null && (
            <div title={`${pctOfTarget}% ${T('of target')}`}
                 style={{ height: '6px', background: 'var(--line, #e5e5e5)',
                          borderRadius: '3px', overflow: 'hidden' }}>
              <div style={{ width: `${Math.min(100, pctOfTarget / 1.5)}%`, height: '100%',
                            background: band.c }} />
            </div>
          )}

          <div style={{ display: 'flex', gap: '14px', flexWrap: 'wrap',
                        borderTop: '1px solid var(--line, #e5e5e5)', paddingTop: '8px' }}>
            <div style={{ minWidth: '110px' }}>
              <div className="mono" style={{ fontSize: '15px', fontWeight: 600 }}>
                {bench.median === null || bench.median === undefined ? T('unknown') : `${bench.median}%`}
              </div>
              <div className="small muted">
                {T('team median')}{bench.employees ? ` · ${bench.employees} ${T('colleagues')}` : ''}
              </div>
            </div>
            <div style={{ minWidth: '110px' }}>
              <div className="mono" style={{ fontSize: '15px', fontWeight: 600,
                     color: vs === null || vs === undefined ? 'var(--text-3)'
                            : (vs < 0 ? '#b26a00' : '#2e6f40') }}>
                {vs === null || vs === undefined ? '—' : (vs > 0 ? `+${vs}` : vs)}
              </div>
              <div className="small muted">{T('vs team')}</div>
            </div>
            <div style={{ minWidth: '110px' }}>
              <div className="mono" style={{ fontSize: '15px', fontWeight: 600 }}>{g.stretch}%</div>
              <div className="small muted">{T('stretch')}</div>
            </div>
          </div>
          {bench.median === null && (
            <div className="small muted">
              {T('No comparable team history yet — the benchmark fills in as weekly scores are materialised.')}
            </div>
          )}
        </div>
      )}
    </>
  );
}

// ── Work health (overwork risk, active-vs-idle, meeting load) ──────────────
// Wellbeing signals, deliberately NOT part of Efficiency%: someone working
// themselves into the ground would otherwise score better for it. Every number
// shows the evidence that produced it (GET /api/workhealth/{id}). Thresholds are
// admin-tunable per team — Bot Behavior ▸ Work health.
// ── Day timeline (the evidence view for attendance disputes) ────────────────
// Everything the bot recorded for one day, in the employee's own timezone
// (GET /api/timeline/{id}). Message activity is shown as half-hourly COUNTS —
// never message text — and the network row only appears for callers allowed to
// see it, exactly like the IP endpoints.
function DayTimelineCard({ userId }) {
  const [day, setDay] = useState(() => new Date().toISOString().slice(0, 10));
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [open, setOpen] = useState(false);
  const T = (window.appT || (s => s));

  useEffect(() => {
    if (!open) return;
    let cancelled = false;
    (async () => {
      setLoading(true);
      try {
        const r = await fetch(`/api/timeline/${userId}?day=${day}`, { credentials: 'include' });
        const d = r.ok ? await r.json() : null;
        if (!cancelled) setData(d);
      } catch (_) { if (!cancelled) setData(null); }
      finally { if (!cancelled) setLoading(false); }
    })();
    return () => { cancelled = true; };
  }, [userId, day, open]);

  const ICON = {
    signin: '▶', signoff: '■', messages: '💬', presence: '◍',
    call: '📞', network: '🌐', bot: '⚙',
  };
  const s = (data && data.summary) || {};
  const shift = n => {
    const d = new Date(day + 'T12:00:00');
    d.setDate(d.getDate() + n);
    setDay(d.toISOString().slice(0, 10));
  };

  return (
    <>
      <SectionHeader
        title={T('Day timeline')}
        tip={T("Everything the bot recorded on one day, in order and in this employee's timezone — sign-in, message activity, presence changes, calls, sign-off and anything the bot enforced. Message counts only, never message text. Use it to settle a disagreement about a day instead of arguing over one total.")}
        right={
          <button className="btn btn-ghost small" onClick={() => setOpen(o => !o)}>
            {open ? T('Hide') : T('Show')}
          </button>
        }
      />
      {open && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
          <div style={{ display: 'flex', gap: '6px', alignItems: 'center', flexWrap: 'wrap' }}>
            <button className="btn btn-ghost small" onClick={() => shift(-1)}>←</button>
            <input type="date" value={day} onChange={e => setDay(e.target.value)}
                   style={{ fontSize: '12px', padding: '2px 4px' }} />
            <button className="btn btn-ghost small" onClick={() => shift(1)}>→</button>
            {data && (
              <span className="small muted">
                {data.timezone}
                {s.excused ? ` · ${T('excused')}${s.excuse_reason ? ` (${s.excuse_reason})` : ''}` : ''}
                {!s.is_working_day ? ` · ${T('not a working day')}` : ''}
                {s.was_late ? ` · ${T('late')}` : ''}
                {` · ${Number(s.hours || 0).toFixed(1)}h`}
              </span>
            )}
          </div>
          {loading && <Loading label={T('Loading...')} rows={3} />}
          {!loading && data && data.events.length === 0 && (
            <div className="muted small">{T('Nothing recorded on this day.')}</div>
          )}
          {!loading && data && data.events.length > 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
              {data.events.map((e, i) => (
                <div key={i} style={{ display: 'flex', gap: '8px', alignItems: 'baseline' }}>
                  <span className="mono small" style={{ minWidth: '42px', color: 'var(--text-3)' }}>
                    {e.time}
                  </span>
                  <span style={{ width: '18px', textAlign: 'center' }}>{ICON[e.kind] || '·'}</span>
                  <span className="small" style={{ fontWeight: 600 }}>{e.label}</span>
                  {e.detail && <span className="small muted">· {e.detail}</span>}
                </div>
              ))}
            </div>
          )}
          {!loading && data && !data.network_visible && (
            <div className="small muted">
              {T('Network addresses are hidden for your role.')}
            </div>
          )}
        </div>
      )}
    </>
  );
}

function WorkHealthCard({ userId }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [days, setDays] = useState(30);
  const T = (window.appT || (s => s));

  useEffect(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      try {
        const r = await fetch(`/api/workhealth/${userId}?days=${days}`, { credentials: 'include' });
        const d = r.ok ? await r.json() : null;
        if (!cancelled) setData(d);
      } catch (_) { if (!cancelled) setData(null); }
      finally { if (!cancelled) setLoading(false); }
    })();
    return () => { cancelled = true; };
  }, [userId, days]);

  const LEVELS = {
    ok:    { l: T('Healthy'),   c: '#2e6f40' },
    watch: { l: T('Watch'),     c: '#b26a00' },
    high:  { l: T('High risk'), c: '#b3261e' },
  };
  const b = (data && data.burnout) || null;
  const lvl = b ? (LEVELS[b.level] || { l: b.level, c: 'var(--text-2)' }) : null;
  const w = (data && data.workload) || {};
  const p = (data && data.presence) || {};
  const m = (data && data.meetings) || {};
  const fmtH = v => (v === null || v === undefined) ? '—' : `${Number(v).toFixed(1)}h`;

  const Stat = ({ label, value, hint }) => (
    <div style={{ minWidth: '92px' }} title={hint || ''}>
      <div className="mono" style={{ fontSize: '15px', fontWeight: 600 }}>{value}</div>
      <div className="small muted" style={{ lineHeight: 1.2 }}>{label}</div>
    </div>
  );

  return (
    <>
      <SectionHeader
        title={T('Work health')}
        tip={T('Overwork and workload signals — long days, days off worked, late finishes, runs without a day off. Leave actually taken lowers the risk. These are wellbeing signals for a manager and never affect the efficiency score.')}
        right={
          <select value={days} onChange={e => setDays(Number(e.target.value))}
                  style={{ fontSize: '12px', padding: '2px 4px' }}>
            <option value={30}>{T('30 days')}</option>
            <option value={60}>{T('60 days')}</option>
            <option value={90}>{T('90 days')}</option>
          </select>
        }
      />
      {loading && <Loading label={T('Loading...')} rows={2} />}
      {!loading && !data && <div className="muted small">{T('No work-health data available.')}</div>}
      {!loading && data && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: '10px', flexWrap: 'wrap' }}>
            <span className="mono" style={{ fontSize: '22px', fontWeight: 700, color: lvl.c }}>
              {b.score}
            </span>
            <Badge kind={b.level === 'high' ? 'err' : (b.level === 'watch' ? 'warn' : 'ok')} dot>
              {lvl.l}
            </Badge>
            {!b.confident && (
              <span className="small muted">
                {T('Not enough worked days in this window to be confident.')}
              </span>
            )}
          </div>

          {b.factors.length === 0 && (
            <div className="small" style={{ color: 'var(--text-2)' }}>
              {T('No overwork signals in this window.')}
            </div>
          )}
          {b.factors.length > 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
              {b.factors.map(f => (
                <div key={f.key} style={{ display: 'flex', gap: '8px', alignItems: 'baseline' }}>
                  <span className="mono small" style={{
                    minWidth: '34px', textAlign: 'right', fontWeight: 600,
                    color: f.points < 0 ? '#2e6f40' : 'var(--text-1)',
                  }}>
                    {f.points > 0 ? `+${f.points}` : f.points}
                  </span>
                  <span className="small" style={{ fontWeight: 600 }}>{f.label}</span>
                  <span className="small muted">· {f.detail}</span>
                </div>
              ))}
            </div>
          )}

          <div style={{ display: 'flex', gap: '14px', flexWrap: 'wrap', paddingTop: '4px' }}>
            <Stat label={T('avg / day')} value={fmtH(w.avg_daily_hours)}
                  hint={T('Credited hours per worked day against the scheduled ') + fmtH(w.expected_daily_hours)} />
            <Stat label={T('long days')} value={w.long_days} />
            <Stat label={T('days off worked')} value={w.days_off_worked} />
            <Stat label={T('late finishes')} value={w.late_finishes} />
            <Stat label={T('longest run')} value={`${w.longest_streak}d`}
                  hint={T('Longest unbroken run of worked days')} />
            <Stat label={T('leave taken')} value={`${w.leave_days}d`} />
          </div>

          <div style={{ display: 'flex', gap: '14px', flexWrap: 'wrap',
                        borderTop: '1px solid var(--line, #e5e5e5)', paddingTop: '8px' }}>
            <Stat label={T('active')} value={fmtH(p.active_hours)}
                  hint={T('Presence samples where the client reported real activity')} />
            <Stat label={T('idle')} value={fmtH(p.idle_hours)}
                  hint={T('Online but nobody at the keyboard')} />
            <Stat label={T('active share')}
                  value={p.active_ratio === null || p.active_ratio === undefined
                    ? T('unknown') : `${Math.round(p.active_ratio * 100)}%`}
                  hint={p.samples ? `${p.samples} ${T('samples')}` : T('No presence samples in this window')} />
            <Stat label={T('meetings')} value={m.meetings} />
            <Stat label={T('in calls')} value={fmtH(m.hours)}
                  hint={m.unfinished ? `${m.unfinished} ${T('call(s) never seen to end — not counted')}` : ''} />
            <Stat label={T('longest call')} value={`${m.longest_minutes}m`} />
          </div>
        </div>
      )}
    </>
  );
}

function EfficiencyCard({ userId, forcePeriod, showSelect = true }) {
  const [data, setData]       = useState(null);
  const [loading, setLoading] = useState(false);
  // When `forcePeriod` is supplied the card is controlled by the parent's
  // period selector (PersonDrawer maps day/week→week, month→month,
  // 6_months/annual→quarter). Otherwise it owns its own week/month/quarter.
  const [period, setPeriod]   = useState(forcePeriod || 'month');
  useEffect(() => { if (forcePeriod && forcePeriod !== period) setPeriod(forcePeriod); }, [forcePeriod]);
  const [roleAi, setRoleAi]   = useState(false);
  const [aiLoading, setAiLoading] = useState(false);

  const fetchEff = async (periodVal, withAi, cancelled) => {
    const url = `/api/employees/${userId}/efficiency?period=${periodVal}${withAi ? '&ai=1' : ''}`;
    const r = await fetch(url, { credentials: 'include' });
    if (r.ok && !cancelled) return r.json();
    return null;
  };

  useEffect(() => {
    let cancelled = false;
    (async () => {
      setLoading(true);
      setRoleAi(false);
      try {
        const d = await fetchEff(period, false, cancelled);
        if (!cancelled) setData(d);
      } catch (_) { if (!cancelled) setData(null); }
      finally { if (!cancelled) setLoading(false); }
    })();
    return () => { cancelled = true; };
  }, [userId, period]);

  const requestAiInsight = async () => {
    setAiLoading(true);
    try {
      const d = await fetchEff(period, true, false);
      if (d) { setData(d); setRoleAi(true); }
    } catch (_) {}
    finally { setAiLoading(false); }
  };

  const T = (window.appT || (s => s));
  const BANDS = {
    exit_risk: { l: T('Exit risk'),     c: '#b3261e' },
    warn:      { l: T('Warning'),       c: '#b26a00' },
    standard:  { l: T('Standard'),      c: '#2e6f40' },
    top:       { l: T('Top performer'), c: '#1f6feb' },
  };
  const PILLAR_LABELS = {
    progress:       T('Progress'),
    presence:       T('Presence & hours'),
    reliability:    T('Reliability'),
    integrity:      T('Active presence'),
    responsibility: T('Responsibility'),
    manager_feedback: T('Manager feedback'),
  };
  const band = data ? (BANDS[data.band] || { l: data.band, c: 'var(--ink, #333)' }) : null;
  const pillars = (data && data.pillars) || {};
  const PATTERNS = {
    precise:           { e: '🟢', l: T('Precise & punctual') },
    inconsistent:      { e: '🟡', l: T('Inconsistent') },
    questionable:      { e: '🟠', l: T('Questionable') },
    red_flag:          { e: '🔴', l: T('Red flag') },
    insufficient_data: { e: '⚪', l: T('Insufficient data') },
  };
  const pat = (data && data.pattern)
    ? (PATTERNS[data.pattern.label] || { e: '', l: data.pattern.label })
    : null;

  return (
    <>
      <SectionHeader
        title={T('Efficiency score')}
        tip={T('Overall weighted indicator of this employee value: progress (output) leads, then presence/hours, reliability, active-presence and responsibility. Excused days (PTO/sick/holiday) and non-working days are never counted. Drives the quarterly KPI multiplier.')}
        right={
          showSelect ? (
            <select value={period} onChange={e => setPeriod(e.target.value)}
                    style={{ fontSize: '12px', padding: '2px 4px' }}>
              <option value="week">{T('Week')}</option>
              <option value="month">{T('Month')}</option>
              <option value="quarter">{T('Quarter')}</option>
            </select>
          ) : (
            <span className="mono small" style={{ color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '.06em' }}>
              {({ week: T('Week'), month: T('Month'), quarter: T('Quarter') })[period] || period}
            </span>
          )
        }
      />
      {loading && <Loading label={T('Loading...')} rows={2} />}
      {!loading && !data && <div className="muted small">{T('No score available.')}</div>}
      {!loading && data && (
        <div className="eff-card">
          <div style={{ display: 'flex', alignItems: 'baseline', gap: '12px', flexWrap: 'wrap' }}>
            <div style={{ fontSize: '34px', fontWeight: 700, lineHeight: 1, color: band.c }}>
              {Math.round(data.efficiency)}%
            </div>
            <div>
              <div style={{ fontWeight: 600, color: band.c }}>{band.l}</div>
              <div className="muted small">{T('KPI')} {Math.round(data.kpi_pct)}%</div>
            </div>
            {data.confident === false &&
              <span className="chip warn"
                    title={T('Not enough data this period for a confident score.')}>
                {T('low confidence')}
              </span>}
            {pat &&
              <span className="chip"
                    title={(data.pattern.reasons || []).join('; ')}>
                {pat.e} {pat.l}
              </span>}
          </div>
          <div style={{ marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '6px' }}>
            {Object.keys(PILLAR_LABELS).map(k => {
              const p = pillars[k];
              const dropped = !p;
              const score = dropped ? 0 : Math.round(p.score);
              return (
                <div key={k} style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                  <div className="small" style={{ width: '120px' }}>{PILLAR_LABELS[k]}</div>
                  <div style={{ flex: 1, height: '8px', background: 'var(--rule, #e7e2d8)',
                                borderRadius: '4px', overflow: 'hidden' }}>
                    {!dropped &&
                      <div style={{ width: `${score}%`, height: '100%', background: band.c }} />}
                  </div>
                  <div className="small mono" style={{ width: '52px', textAlign: 'right' }}>
                    {dropped ? <span className="muted">{T('n/a')}</span> : `${score}%`}
                  </div>
                </div>
              );
            })}
          </div>
          <div className="muted small" style={{ marginTop: '8px' }}>
            {T('Strikes')} -{Math.round(data.strike_penalty || 0)}
            {' · '}{T('Day-off bonus')} +{Math.round(data.dayoff_bonus || 0)}
            {(data.overtime_bonus || 0) > 0 ? ` · ${T('Overtime bonus')} +${Math.round(data.overtime_bonus)}` : ''}
            {data.hard_cap_applied ? ` · ${T('capped (integrity)')} ${data.hard_cap_applied}%` : ''}
          </div>
          {data.signals && ((data.signals.stalled_tasks || 0) > 0 || (data.signals.completed_tasks || 0) > 0) &&
            <div className="muted small" style={{ marginTop: '4px' }}>
              {T('Tasks')}: {data.signals.completed_tasks || 0} {T('completed')}
              {' · '}{data.signals.stalled_tasks || 0} {T('stalled')}
            </div>}
          {data.location_mix &&
            <div className="muted small" style={{ marginTop: '4px' }}>
              {T('Location')}: {data.location_mix.office || 0} {T('office')}
              {' · '}{data.location_mix.home || 0} {T('home')}
              {' · '}{data.location_mix.mobile || 0} {T('mobile')}
              {data.wfh && data.wfh.eligible !== null && data.wfh.eligible !== undefined &&
                <span> · {T('WFH')}: {data.wfh.eligible
                  ? <span style={{ color: '#2e6f40' }}>{T('eligible')}</span>
                  : <span style={{ color: '#b26a00' }}>{T('review')}</span>}
                  {' '}({data.wfh.delta > 0 ? '+' : ''}{data.wfh.delta})</span>}
            </div>}

          {/* ── Role Alignment ── */}
          {(() => {
            const ra = data.role_alignment;
            const ALIGN_COLORS = {
              aligned:   '#2e6f40',
              partial:   '#b26a00',
              deviation: '#b3261e',
              unknown:   'var(--text-3, #999)',
            };
            const noAlignment = !ra || ra.alignment == null || ra.label === 'unknown';
            return (
              <div style={{ marginTop: 12, borderTop: '1px solid var(--line, #e7e2d8)', paddingTop: 10 }}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, marginBottom: 6 }}>
                  <span style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase', letterSpacing: .4 }}>
                    {T('Role alignment')}
                  </span>
                  {!roleAi && !noAlignment && (
                    <button
                      className="btn btn-ghost"
                      style={{ fontSize: 11, padding: '2px 8px' }}
                      onClick={requestAiInsight}
                      disabled={aiLoading}
                      title={T('Request AI-generated narrative for this alignment')}
                    >
                      {aiLoading ? T('Thinking…') : <><Icon name="sparkle" size={11}/> {T('AI insight')}</>}
                    </button>
                  )}
                </div>
                {noAlignment ? (
                  <div className="muted small">{T('Set a job function to enable role alignment.')}</div>
                ) : (
                  <>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
                      <span className="small">{T('Alignment')}</span>
                      <span style={{ fontWeight: 700, color: ALIGN_COLORS[ra.label] || 'var(--ink)' }}>
                        {ra.alignment}%
                      </span>
                      <span className="chip" style={{ background: ALIGN_COLORS[ra.label] || 'var(--bg)', color: '#fff', fontSize: 10.5, padding: '1px 7px' }}>
                        {ra.label}
                      </span>
                    </div>
                    {ra.innovation != null && (
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
                        <span className="small">{T('Innovation')}</span>
                        <span style={{ fontWeight: 600 }}>{ra.innovation}%</span>
                        <span className="muted" style={{ fontSize: 10.5 }}>{T('(novel/stretch work beyond routine)')}</span>
                      </div>
                    )}
                    {(ra.matched_functions || []).length > 0 && (
                      <div style={{ marginBottom: 4, display: 'flex', flexWrap: 'wrap', gap: 4, alignItems: 'center' }}>
                        <span className="muted small">{T('Evidenced:')}</span>
                        {ra.matched_functions.map((fn, i) => (
                          <span key={i} className="chip" style={{ fontSize: 10.5, background: 'var(--bg)', color: 'var(--text-2)' }}>{fn}</span>
                        ))}
                      </div>
                    )}
                    {(ra.off_scope_count || 0) > 0 && (
                      <details style={{ marginBottom: 4 }}>
                        <summary className="muted small" style={{ cursor: 'pointer', userSelect: 'none' }}>
                          {T('Off-scope items:')} {ra.off_scope_count}
                        </summary>
                        <ul style={{ margin: '4px 0 0 16px', padding: 0 }}>
                          {(ra.off_scope || []).map((s, i) => (
                            <li key={i} className="muted small">{s}</li>
                          ))}
                        </ul>
                      </details>
                    )}
                    {(ra.reasons || []).length > 0 && (
                      <ul style={{ margin: '4px 0 4px 14px', padding: 0 }}>
                        {ra.reasons.map((r, i) => (
                          <li key={i} className="muted small">{r}</li>
                        ))}
                      </ul>
                    )}
                    {ra.note && (
                      <div style={{ marginTop: 6, padding: '6px 10px', background: 'var(--bg)', borderRadius: 'var(--r-md)', borderLeft: '3px solid var(--acc, #4f7fe4)' }}>
                        <span className="chip" style={{ fontSize: 10, marginRight: 6, background: 'var(--acc, #4f7fe4)', color: '#fff', padding: '1px 5px' }}>AI</span>
                        <em className="muted small">{ra.note}</em>
                      </div>
                    )}
                  </>
                )}
              </div>
            );
          })()}

          <ManagerRatingControl userId={userId} onSaved={() => setPeriod(p => p)} />
        </div>
      )}
    </>
  );
}


/* Manager-feedback control (#9): admins/managers set a 0-100 rating that feeds
   the ~5% manager-feedback efficiency pillar. Everyone else sees the latest
   rating read-only. Appends history server-side; scoring uses the latest. */
function ManagerRatingControl({ userId, onSaved }) {
  const T = (window.appT || (s => s));
  const meRole = String((typeof D !== 'undefined' && D?.ME?.org_role) || '').toLowerCase();
  // Manager-tier = the same set the rest of this page treats as able to manage
  // (admin / superadmin / moderator / manager). The backend require_manager
  // enforces this too. `D.ME` exposes `id` (not `user_id`) — use it to block
  // self-rating.
  const _meId = Number((typeof D !== 'undefined' && D?.ME && (D.ME.id ?? D.ME.user_id)) ?? -1);
  const canRate = ['admin', 'superadmin', 'moderator', 'manager'].includes(meRole)
    && Number(userId) !== _meId;
  const [cur, setCur]     = useState(undefined); // undefined=loading, null=none
  const [score, setScore] = useState(80);
  const [note, setNote]   = useState('');
  const [saving, setSaving] = useState(false);
  const toast = useToast();

  const load = React.useCallback(() => {
    fetch(`/api/employees/${userId}/manager-rating`, { credentials: 'include' })
      .then(r => r.ok ? r.json() : null)
      .then(j => {
        setCur(j || null);
        if (j && j.score != null) { setScore(j.score); setNote(j.note || ''); }
      })
      .catch(() => setCur(null));
  }, [userId]);
  useEffect(() => { load(); }, [load]);

  const save = async () => {
    setSaving(true);
    try {
      const r = await fetch(`/api/employees/${userId}/manager-rating`, {
        method: 'POST', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ score: Number(score), note: note.trim() || null }),
      });
      if (r.ok) { toast && toast.push && toast.push({ msg: T('Manager rating saved'), kind: 'ok' }); load(); onSaved && onSaved(); }
      else { const e = await r.json().catch(() => ({})); toast && toast.push && toast.push({ msg: e.detail || T('Save failed'), kind: 'err' }); }
    } catch (e) { toast && toast.push && toast.push({ msg: String(e), kind: 'err' }); }
    setSaving(false);
  };

  if (cur === undefined) return null; // still loading
  if (!canRate && (!cur || cur.score == null)) return null; // nothing to show non-managers

  return (
    <div style={{ marginTop: 12, borderTop: '1px solid var(--line, #e7e2d8)', paddingTop: 10 }}>
      <div style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--text-2)', textTransform: 'uppercase', letterSpacing: .4, marginBottom: 6 }}>
        {T('Manager feedback')} <span className="muted" style={{ textTransform: 'none', fontWeight: 400 }}>({T('~5% of score')})</span>
      </div>
      {cur && cur.score != null && (
        <div className="muted small" style={{ marginBottom: canRate ? 8 : 0 }}>
          {T('Current:')} <strong>{cur.score}%</strong>
          {cur.rated_by ? <> · {T('by')} {cur.rated_by}</> : null}
          {cur.rated_at ? <> · {new Date(cur.rated_at).toLocaleDateString()}</> : null}
          {cur.note ? <div style={{ marginTop: 2 }}><em>{cur.note}</em></div> : null}
        </div>
      )}
      {canRate && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <input type="range" min="0" max="100" value={score}
                   onChange={e => setScore(Number(e.target.value))} style={{ flex: 1 }} />
            <span className="mono" style={{ width: 42, textAlign: 'right', fontWeight: 700 }}>{score}%</span>
          </div>
          <input className="inp" placeholder={T('Optional note (rationale)')} value={note}
                 onChange={e => setNote(e.target.value)} style={{ fontSize: 12 }} />
          <div>
            <button className="btn btn-pri" onClick={save} disabled={saving} style={{ fontSize: 12 }}>
              {saving ? T('Saving…') : (cur && cur.score != null ? T('Update rating') : T('Save rating'))}
            </button>
          </div>
        </div>
      )}
    </div>
  );
}


function HrDocumentsCard({ userId }) {
  const [docs, setDocs]       = useState([]);
  const [loading, setLoading] = useState(true);  // init true so the list shows a loader, not "No documents", on first paint
  const [uploading, setUploading] = useState(false);
  const [kind, setKind]       = useState('doctor_note');
  const [file, setFile]       = useState(null);
  const [notes, setNotes]     = useState('');
  const toast = useToast ? useToast() : { push: () => {} };

  const KINDS = [
    { v: 'doctor_note', l: (window.appT||(s=>s))('Doctor note') },
    { v: 'contract',    l: (window.appT||(s=>s))('Contract') },
    { v: 'id',          l: (window.appT||(s=>s))('ID document') },
    { v: 'tax',         l: (window.appT||(s=>s))('Tax form') },
    { v: 'review',      l: (window.appT||(s=>s))('Performance review') },
    { v: 'other',       l: (window.appT||(s=>s))('Other') },
  ];

  const load = async () => {
    setLoading(true);
    try {
      const r = await fetch(`/api/employees/${userId}/documents`, { credentials: 'include' });
      if (r.ok) setDocs(await r.json());
    } catch (_) {}
    finally { setLoading(false); }
  };

  useEffect(() => { load(); }, [userId]);

  const upload = async () => {
    if (!file) {
      toast.push({ msg: (window.appT||(s=>s))('Pick a file first'), kind: 'warn' });
      return;
    }
    setUploading(true);
    const fd = new FormData();
    fd.append('file', file);
    fd.append('kind', kind);
    if (notes) fd.append('notes', notes);
    try {
      const r = await fetch(`/api/employees/${userId}/documents`, {
        method: 'POST', credentials: 'include', body: fd,
      });
      if (!r.ok) throw new Error(`${r.status}`);
      toast.push({ msg: (window.appT||(s=>s))('Uploaded'), kind: 'ok' });
      setFile(null); setNotes('');
      await load();
    } catch (e) {
      toast.push({ msg: (window.appT||(s=>s))('Upload failed: ') + e.message, kind: 'err' });
    } finally {
      setUploading(false);
    }
  };

  return (
    <>
      <SectionHeader
        title={(window.appT||(s=>s))("HR documents")}
        tip={(window.appT||(s=>s))("Doctor notes, contracts, ID copies, tax forms, performance reviews. Visible to admins/managers/HR and the employee themselves. Stored encrypted-at-rest.")}
        sub={(window.appLang&&window.appLang()==='ar')
          ? `${docs.length} مستند`
          : `${docs.length} document${docs.length === 1 ? '' : 's'}`}
        right={<Refresh onClick={load} loading={loading} />}
      />
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 12, flexWrap: 'wrap' }}>
        <select className="sel" value={kind} onChange={e => setKind(e.target.value)}
          style={{ fontSize: 12, minWidth: 140 }}>
          {KINDS.map(k => <option key={k.v} value={k.v}>{k.l}</option>)}
        </select>
        <input type="file" onChange={e => setFile(e.target.files[0])}
          style={{ fontSize: 12, flex: 1, minWidth: 200 }} />
        <input type="text" placeholder={(window.appT||(s=>s))("Optional notes")} value={notes}
          onChange={e => setNotes(e.target.value)}
          style={{ fontSize: 12, padding: '4px 8px', minWidth: 160 }} />
        <button className="btn btn-pri" onClick={upload} disabled={uploading || !file}>
          {uploading ? (window.appT||(s=>s))('Uploading…') : (window.appT||(s=>s))('Upload')}
        </button>
      </div>
      <table className="t" style={{ fontSize: 12 }}>
        <thead><tr><th>{(window.appT||(s=>s))("Kind")}</th><th>{(window.appT||(s=>s))("Name")}</th><th>{(window.appT||(s=>s))("Notes")}</th><th>{(window.appT||(s=>s))("Uploaded")}</th><th>{(window.appT||(s=>s))("Size")}</th><th></th></tr></thead>
        <tbody>
          {loading ? (
            <LoadingRow colSpan={6} label={(window.appT||(s=>s))("Loading documents…")} />
          ) : docs.length === 0 ? (
            <tr><td colSpan={6} className="muted" style={{ textAlign: 'center', padding: 16 }}>{(window.appT||(s=>s))("No documents.")}</td></tr>
          ) : docs.map(d => (
            <tr key={d.id}>
              <td><span className="badge mute">{d.kind}</span></td>
              <td>{d.filename}</td>
              <td className="muted">{d.notes || ''}</td>
              <td className="mono" style={{ fontSize: 11 }}>{d.uploaded_at ? new Date(d.uploaded_at).toLocaleDateString() : ''}</td>
              <td className="mono" style={{ fontSize: 11 }}>{Math.round((d.size_bytes || 0) / 1024)}k</td>
              <td>
                <a href={`/api/employees/${userId}/documents/${d.id}`}
                  className="btn btn-ghost"
                  style={{ padding: '2px 8px', fontSize: 11 }}
                  target="_blank" rel="noopener">{(window.appT||(s=>s))("Download")}</a>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </>
  );
}

// C2: sign-in time consistency heatmap (3/6/12 month).
function SigninHeatmapCard({ userId }) {
  const [months, setMonths] = useState(3);
  const [data, setData]     = useState(null);
  const [loading, setLoading] = useState(false);

  const load = async () => {
    setLoading(true);
    try {
      const r = await fetch(`/api/employees/${userId}/signin-heatmap?months=${months}`, { credentials: 'include' });
      if (r.ok) setData(await r.json());
    } catch (_) {}
    finally { setLoading(false); }
  };

  useEffect(() => { load(); }, [userId, months]);

  const DAYS = [
    (window.appT||(s=>s))('Mon'),(window.appT||(s=>s))('Tue'),(window.appT||(s=>s))('Wed'),
    (window.appT||(s=>s))('Thu'),(window.appT||(s=>s))('Fri'),(window.appT||(s=>s))('Sat'),(window.appT||(s=>s))('Sun')];
  const HOURS = Array.from({ length: 24 }, (_, i) => i);

  const max = data && data.grid ? Math.max(1, ...data.grid.flat()) : 1;
  const cellSize = 14;

  return (
    <>
      <SectionHeader
        title={(window.appT||(s=>s))("Sign-in time heatmap")}
        tip={(window.appLang&&window.appLang()==='ar')
          ? `يُصنِّف أحداث تسجيل الدخول حسب يوم الأسبوع × الساعة خلال آخر ${months} أشهر. التركيز الأعلى في خلايا قليلة = توقيت ثابت. تلخِّص درجة الثبات (0..1) مدى انتظام نمط تسجيل دخولك.`
          : `Bins sign-in events by day-of-week × hour over the last ${months} months. Higher concentration in a few cells = consistent timing. The consistency score (0..1) summarizes how tight your sign-in pattern is.`}
        sub={data ? ((window.appLang&&window.appLang()==='ar')
          ? `${data.total} تسجيل دخول · الثبات ${(data.consistency_score * 100).toFixed(0)}%`
          : `${data.total} sign-ins · consistency ${(data.consistency_score * 100).toFixed(0)}%`) : (window.appT||(s=>s))('Loading…')}
        right={
          <div style={{ display: 'flex', gap: 4 }}>
            {[3, 6, 12].map(m => (
              <button key={m}
                className={`btn ${months === m ? 'btn-pri' : 'btn-ghost'}`}
                style={{ padding: '3px 10px', fontSize: 11 }}
                onClick={() => setMonths(m)}>{m}m</button>
            ))}
          </div>
        }
      />
      {loading && <Loading label={(window.appT||(s=>s))("Loading…")} rows={3} />}
      {data && data.grid && (
        <div style={{ overflowX: 'auto', padding: 8 }}>
          <table style={{ borderCollapse: 'separate', borderSpacing: 2 }}>
            <thead>
              <tr>
                <th style={{ width: 36 }}></th>
                {HOURS.map(h => (
                  <th key={h} style={{ fontSize: 9, color: 'var(--text-3)', minWidth: cellSize, textAlign: 'center' }}>
                    {h % 3 === 0 ? h : ''}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {DAYS.map((d, di) => (
                <tr key={d}>
                  <td style={{ fontSize: 10, color: 'var(--text-3)', paddingRight: 6 }}>{d}</td>
                  {HOURS.map(h => {
                    const v = data.grid[di][h];
                    const alpha = v / max;
                    return (
                      <td key={h} title={(window.appLang&&window.appLang()==='ar') ? `${d} ${h}:00 — ${v} تسجيل دخول` : `${d} ${h}:00 — ${v} sign-ins`}
                        style={{
                          width: cellSize, height: cellSize,
                          background: alpha > 0
                            ? `color-mix(in oklch, var(--brand) ${Math.round(alpha * 100)}%, transparent)`
                            : 'var(--bg-elev-2)',
                          borderRadius: 2,
                        }} />
                    );
                  })}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </>
  );
}

function MetricsPickerCard({ metrics }) {
  const D = window.STATUS_DATA || {};
  const myUid = D?.ME?.user_id || 'anon';
  const lsKey = `emp_metrics_visible:${myUid}`;
  const [pickerOpen, setPickerOpen] = useState(false);
  const [visible, setVisible] = useState(() => {
    try {
      const stored = JSON.parse(localStorage.getItem(lsKey) || 'null');
      if (Array.isArray(stored) && stored.length > 0) return stored;
    } catch (_) {}
    return _DEFAULT_VISIBLE_METRICS.slice();
  });
  const toggle = (k) => {
    setVisible(cur => {
      const next = cur.includes(k) ? cur.filter(x => x !== k) : cur.concat(k);
      try { localStorage.setItem(lsKey, JSON.stringify(next)); } catch (_) {}
      return next;
    });
  };
  const shown = _ALL_METRICS.filter(m => visible.includes(m.key));
  return (
    <>
      <SectionHeader
        title={(window.appT||(s=>s))("Period metrics")}
        tip={(window.appT||(s=>s))("Computed from the same period as the rest of the drawer. Click the gear to pick which metrics to show — your selection is remembered per admin.")}
        sub={(window.appLang&&window.appLang()==='ar')
          ? `${shown.length} من ${_ALL_METRICS.length} معروض`
          : `${shown.length} of ${_ALL_METRICS.length} shown`}
        right={
          <button
            className="btn btn-ghost btn-icon"
            onClick={() => setPickerOpen(o => !o)}
            title={(window.appT||(s=>s))("Choose which metrics show on this card")}
          >
            <Icon name="cog" size={12}/> {pickerOpen ? (window.appT||(s=>s))('Done') : (window.appT||(s=>s))('Pick')}
          </button>
        }
      />
      {pickerOpen && (
        <div style={{
          padding: 10, background: 'var(--bg-elev-2)',
          border: '1px solid var(--line)', borderRadius: 'var(--r-md)',
          marginBottom: 10, display: 'grid',
          gridTemplateColumns: 'repeat(2, 1fr)', gap: 6,
        }}>
          {_ALL_METRICS.map(m => (
            <label key={m.key} style={{ display: 'flex', gap: 6, fontSize: 12, cursor: 'pointer' }}
                   title={m.tip}>
              <input
                type="checkbox"
                checked={visible.includes(m.key)}
                onChange={() => toggle(m.key)}
              />
              <span>{m.label}</span>
            </label>
          ))}
          <div style={{ gridColumn: '1 / -1', fontSize: 11, color: 'var(--text-3)', marginTop: 6 }}>
            {(window.appT||(s=>s))("Saved to localStorage per admin — your peers see their own set.")}
          </div>
        </div>
      )}
      {shown.length === 0 ? (
        <div className="muted" style={{ padding: 12, fontSize: 13 }}>
          {(window.appT||(s=>s))("No metrics selected — click")} <strong>{(window.appT||(s=>s))("Pick")}</strong> {(window.appT||(s=>s))("above to choose what to show.")}
        </div>
      ) : (
        <div style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))',
          gap: 10,
        }}>
          {shown.map(m => {
            const f = _formatMetric(m, metrics || {});
            return (
              <div key={m.key} title={m.tip}
                style={{
                  padding: '10px 12px',
                  background: 'var(--bg-elev-2)',
                  borderRadius: 'var(--r-md)',
                  border: '1px solid var(--line)',
                }}>
                <div style={{ fontSize: 11, color: 'var(--text-3)', marginBottom: 4 }}>{m.label}</div>
                <div className="mono" style={{ fontSize: 17, fontWeight: 600, color: f.color }}>{f.display}</div>
              </div>
            );
          })}
        </div>
      )}
      {/* Top tasks rendered separately since it's an array */}
      {Array.isArray(metrics?.top_tasks) && metrics.top_tasks.length > 0 && (
        <div style={{ marginTop: 14, paddingTop: 10, borderTop: '1px solid var(--line)' }}>
          <div style={{ fontSize: 11, color: 'var(--text-3)', marginBottom: 6 }}>
            {(window.appT||(s=>s))("Top 5 tasks (by today_text frequency)")}
          </div>
          {metrics.top_tasks.slice(0, 5).map((t, i) => (
            <div key={i} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, padding: '2px 0' }}>
              <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.task}</span>
              <span className="mono muted">{t.days}× {(window.appLang&&window.appLang()==='ar') ? 'يوم' : `day${t.days === 1 ? '' : 's'}`}</span>
            </div>
          ))}
        </div>
      )}
    </>
  );
}


function EmployeesPage({ live }) {
  const employees = live.employees;
  const [q, setQ] = useState('');
  const [team, setTeam] = useState('all');
  const [statusFilter, setStatusFilter] = useState('all');
  // Test accounts always hidden — the only test row is the demo account,
  // which stays visible by design. The opt-in toggle was removed.
  const showTestAccounts = false;
  // (#9) Filter by IP classification — backend enriches each employee with ip_class (home/office/null).
  const [ipFilter, setIpFilter] = useState('all');
  // Time filter for the per-card scoreboard (attendance pie + hours). 'week'
  // keeps the original behaviour; other periods use a fixed window so the grid
  // stays populated at the Sunday→Sunday week rollover (a fresh week reads 0%
  // until the first sign-in, which is why the '30-day' pie looked empty).
  const [scorePeriod, setScorePeriod] = useState('week');
  const SCORE_PERIODS = [
    ['day', 'Today'], ['week', 'This week'], ['month', '30 days'],
    ['6_months', '6 months'], ['annual', 'Year'],
  ];
  // (#6) Export menu (CSV / PDF / XLSX) — replaces the single Export button.
  const [exportMenuOpen, setExportMenuOpen] = useState(false);
  const [view, setView] = useState('grid');
  const [selectedId, setSelectedId] = useState(null);
  const [refreshing, setRefreshing] = useState(false);
  const [scoreboard, setScoreboard] = useState({});
  const D = window.STATUS_DATA;
  const meRole = String(D?.ME?.org_role || '').toLowerCase();
  const canManage = meRole === 'admin' || meRole === 'manager';
  const isAdmin = meRole === 'admin' || meRole === 'superadmin';
  // Global admin Edit toggle (same source page-monthly / page-ops consume).
  // When ON, card fields backed by an existing endpoint become click-to-edit.
  const editMode = (typeof window.useEditMode === 'function') ? window.useEditMode() : false;
  // A single inline-edit modal for the card grid. Clicking an editable field
  // on a card opens EditProfileModal (server-backed: profile + contact PUT)
  // for that person. Strikes have their own dedicated affordance below.
  const [cardEditPerson, setCardEditPerson] = useState(null);
  const toast = useToast();
  const [requestingAll, setRequestingAll] = useState(false);

  // Ask EVERY active, non-test employee to self-report their contact details
  // via the bot. Admin-only (matches the backend require_admin gate).
  const requestContactsAll = async () => {
    const ok = window.confirm((window.appLang&&window.appLang()==='ar')
      ? 'سيقوم البوت بمراسلة جميع الموظفين النشطين لطلب تفاصيل الاتصال. هل تريد المتابعة؟'
      : 'The bot will DM every active employee asking for their contact details. Continue?');
    if (!ok) return;
    setRequestingAll(true);
    try {
      const r = await fetch('/api/admin/contact-requests', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ all: true }),
      });
      if (!r.ok) {
        const b = await r.json().catch(() => null);
        throw new Error(parseDetail(b, r.status));
      }
      const data = await r.json().catch(() => ({}));
      toast.push({ msg: (window.appLang&&window.appLang()==='ar')
        ? `طُلبت تفاصيل الاتصال من ${data.requested || 0} موظفًا`
        : `Requested contact details from ${data.requested || 0} employee(s)`, kind: 'ok' });
    } catch (e) {
      toast.push({ msg: (window.appT||(s=>s))('Request failed: ') + e.message, kind: 'err' });
    } finally {
      setRequestingAll(false);
    }
  };

  // #35: a click on any PersonCell elsewhere in the app jumps to this
  // page and dispatches `focus-user-now`. Open the requested drawer
  // by matching either user_id or matrix_id (PersonCells in old
  // surfaces sometimes only have one or the other).
  useEffect(() => {
    const resolveAndOpen = (detail) => {
      if (!detail) return false;
      if (!employees.length) {
        // C8: employees not loaded yet — keep the hint around until they are.
        window.__focusUser = { ...detail, ts: detail.ts || Date.now() };
        return false;
      }
      let target = null;
      if (detail.user_id != null) {
        target = employees.find(e => e.id === detail.user_id);
      }
      if (!target && detail.matrix_id) {
        target = employees.find(e => e.matrix_id === detail.matrix_id);
      }
      if (target) {
        setSelectedId(target.id);
        return true;
      }
      return false;
    };
    // Handle a focus request that may have been stashed before this
    // component mounted (the App listener sets window.__focusUser).
    // Stash window extended 5s → 30s because cold-start /api/employees
    // can be slow on real iPhones and the user wouldn't see their tap
    // produce a drawer.
    if (window.__focusUser && (Date.now() - (window.__focusUser.ts || 0)) < 30000) {
      if (resolveAndOpen(window.__focusUser)) {
        window.__focusUser = null;
      }
    }
    const onFocus = (e) => {
      if (resolveAndOpen(e?.detail)) {
        window.__focusUser = null;
      }
    };
    window.addEventListener('focus-user-now', onFocus);
    return () => window.removeEventListener('focus-user-now', onFocus);
  }, [employees]);

  // Fetch per-user weekly metrics so each row can show an attendance pie.
  useEffect(() => {
    let cancelled = false;
    const load = async () => {
      try {
        const r = await fetch(`/api/today/scoreboard?period=${scorePeriod}`, {credentials:'include'});
        if (!r.ok) return;
        const arr = await r.json();
        if (cancelled) return;
        const map = {};
        (Array.isArray(arr) ? arr : []).forEach(s => { map[s.user_id] = s; });
        setScoreboard(map);
      } catch(_) {}
    };
    load();
    const id = setInterval(() => { if (!document.hidden) load(); }, 60000);
    // Bug #1: strike/violation counts on these cards come from the scoreboard;
    // refresh immediately after a strike mutation or attendance update instead
    // of waiting for the 60s poll (so the count stops looking "stuck" while the
    // period charts have already moved).
    const onMutate = () => load();
    window.addEventListener('admin-override-applied', onMutate);
    window.addEventListener('attendance-updated', onMutate);
    return () => {
      cancelled = true;
      clearInterval(id);
      window.removeEventListener('admin-override-applied', onMutate);
      window.removeEventListener('attendance-updated', onMutate);
    };
  }, [employees.length, scorePeriod]);

  // Per-user live K9 presence + today's online/away/offline minute split.
  // ONE batched request: /api/presence/live?breakdown=daily returns the
  // Synapse dot AND the DB minute-buckets for every user (previously this
  // fired a separate /presence-breakdown fetch per card — 26 requests every
  // 15s — which kept the page repainting and contributed to scroll flicker).
  const [presenceMap, setPresenceMap] = useState({});
  useEffect(() => {
    if (!employees.length) return;
    let cancelled = false;
    const load = async () => {
      try {
        const live = await fetch('/api/presence/live?breakdown=daily', {credentials:'include'})
          .then(r => r.ok ? r.json() : {users: []})
          .catch(() => ({users: []}));
        const map = {};
        (live.users || []).forEach(u => {
          map[u.user_id] = {
            presence: u.presence,
            last_active_ago_ms: u.last_active_ago_ms,
            breakdown: u.breakdown || null,
          };
        });
        if (!cancelled) setPresenceMap(map);
      } catch(_) {}
    };
    load();
    // Skip ticks while the tab is hidden — resync happens on the next tick
    // after the user comes back (matches the 45s bootstrap's hidden gate).
    const id = setInterval(() => { if (!document.hidden) load(); }, 15000);
    const onAttendance = () => load();
    window.addEventListener('attendance-updated', onAttendance);
    return () => { cancelled = true; clearInterval(id); window.removeEventListener('attendance-updated', onAttendance); };
  }, [employees.length]);

  // Shared status precedence: absent → signed-off → late → present.
  // 'late-off' = was late AND has now signed off (still counts under both
  // the 'late' and 'signed-off' filters for compliance purposes).
  const computeTodayStatus = (e) => {
    if (!e.signed_in) return 'absent';
    if (e.signed_off) return e.was_late ? 'late-off' : 'signed-off';
    return e.was_late ? 'late' : 'present';
  };

  const rows = useMemo(() => {
    return employees.filter(e => {
      if (!showTestAccounts && e.is_test_account) return false;
      if (ipFilter !== 'all' && (e.ip_class || 'unknown') !== ipFilter) return false;
      if (q && !((e.display_name || '').toLowerCase().includes(q.toLowerCase()) || (e.matrix_id || '').toLowerCase().includes(q.toLowerCase()))) return false;
      if (team !== 'all' && e.team !== team) return false;
      const status = computeTodayStatus(e);
      if (statusFilter === 'all') return true;
      if (statusFilter === 'late')        return status === 'late' || status === 'late-off';
      if (statusFilter === 'signed-off')  return status === 'signed-off' || status === 'late-off';
      return statusFilter === status;
    });
  }, [employees, q, team, statusFilter, showTestAccounts, ipFilter]);

  const enriched = rows.map(e => ({
    ...e,
    status: computeTodayStatus(e),
  }));
  const { sorted, sort, click } = useSortable(enriched, { key:'display_name', dir:'asc' });

  const selected = employees.find(e => e.id === selectedId);

  const [wizardOpen, setWizardOpen] = useState(false);
  const [bulkDmOpen, setBulkDmOpen] = useState(false);

  const handleInvite = () => setWizardOpen(true);

  const handleRefresh = async () => {
    setRefreshing(true);
    try {
      window.dispatchEvent(new CustomEvent('status-data-refresh-requested'));
      toast.push({ msg: (window.appT||(s=>s))('Refreshed'), kind: 'ok' });
    } catch(e) {
      toast.push({ msg: (window.appT||(s=>s))('Refresh failed: ') + e.message, kind: 'err' });
    } finally {
      setRefreshing(false);
    }
  };

  // (#6) Format-aware export. We MUST fetch with credentials so the session
  // cookie is sent (window.open() in WKWebView opens an unauthenticated
  // context and the server 401s). Then trigger the download from an <a> that's
  // attached to the DOM before .click() (some WebViews ignore clicks on
  // detached elements).
  const handleExport = async (fmt) => {
    setExportMenuOpen(false);
    try {
      const params = new URLSearchParams();
      if (q) params.set('search', q);
      if (team !== 'all') params.set('team', team);
      if (statusFilter !== 'all') params.set('status', statusFilter);
      let url, filename;
      if (fmt === 'csv') {
        params.set('range', 'today');
        url = `/api/reports.csv?${params.toString()}`;
        filename = 'employees.csv';
      } else {
        params.set('format', fmt);
        params.set('period', 'day');
        url = `/api/summary/export?${params.toString()}`;
        filename = `employees.${fmt}`;
      }
      const r = await fetch(url, { credentials: 'include' });
      if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
      const blob = await r.blob();
      // Use the cross-platform helper so iOS gets the Share Sheet (Save to
      // Files), desktop browsers get the silent <a download>.
      const result = await window.saveDownload(blob, filename);
      // Toast shows which strategy actually fired — useful when debugging
      // real-device vs simulator. Strategies in order: blob-window (iOS
      // native preview + Share btn), share-sheet (Web Share API),
      // a-download (desktop), or failed.
      if (!result || !result.ok) {
        throw new Error('Download failed via ' + (result && result.via || 'unknown'));
      }
      if (result.via === 'share-cancelled') {
        // user dismissed the Share Sheet — no toast
      } else if (result.via === 'blob-window') {
        toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `${filename} فُتح — اضغط مشاركة ← حفظ في الملفات` : `${filename} opened — tap Share → Save to Files`, kind: 'ok' });
      } else if (result.via === 'share-sheet') {
        toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `${filename} — اختر "حفظ في الملفات" من قائمة المشاركة` : `${filename} — pick "Save to Files" in Share Sheet`, kind: 'ok' });
      } else {
        toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم تنزيل ${filename} (عبر ${result.via})` : `Downloaded ${filename} (via ${result.via})`, kind: 'ok' });
      }
    } catch (e) {
      toast.push({ msg: (window.appT||(s=>s))('Export failed: ') + (e.message || e), kind: 'err' });
    }
  };

  return (
    <div className="page">
      <PageHeader
        title={(window.appT||(s=>s))("Employees")}
        meta={(window.appLang&&window.appLang()==='ar')
          ? `${employees.length} متابَع عبر كل الغرف · ${employees.filter(e=>e.signed_in).length} سجّلوا الدخول اليوم`
          : `${employees.length} tracked across all rooms · ${employees.filter(e=>e.signed_in).length} signed in today`}
        right={
          <div className="emp-header-actions" style={{ display:'flex', gap: 8, flexWrap: 'wrap' }}>
            <Refresh onClick={handleRefresh} loading={refreshing} />
            {canManage && <button className="btn btn-ghost emp-action-bulkdm" onClick={() => setBulkDmOpen(true)} title={(window.appT||(s=>s))("Bulk DM")}><Icon name="send" size={14}/> <span className="emp-action-label">{(window.appT||(s=>s))("Bulk DM")}</span></button>}
            {/* Native <select> for Export — iOS WKWebView uses the system picker.
                Replaces the custom button dropdown that kept misfiring (PDF tap
                downloading XLSX on touch devices). With a native select, there
                is no closure/event-target ambiguity: e.target.value IS the
                option the user picked. */}
            <select
              className="btn btn-ghost"
              value=""
              onChange={(e) => {
                const fmt = e.target.value;
                if (fmt) handleExport(fmt);
                e.target.value = '';  // reset so the same choice can be picked again
              }}
              style={{
                padding: '6px 10px', fontSize: 14, cursor: 'pointer',
                appearance: 'none', WebkitAppearance: 'none',
                paddingRight: 26,
                backgroundImage: 'linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%)',
                backgroundPosition: 'calc(100% - 12px) 50%, calc(100% - 7px) 50%',
                backgroundSize: '5px 5px, 5px 5px',
                backgroundRepeat: 'no-repeat',
              }}
              title={(window.appT||(s=>s))("Choose an export format")}
            >
              <option value="" disabled>{(window.appT||(s=>s))("⬇ Export…")}</option>
              <option value="csv">{(window.appT||(s=>s))("Download CSV")}</option>
              <option value="pdf">{(window.appT||(s=>s))("Download PDF")}</option>
              <option value="xlsx">{(window.appT||(s=>s))("Download XLSX")}</option>
            </select>
            {canManage && <button className="btn btn-pri emp-action-invite" onClick={handleInvite} title={(window.appT||(s=>s))("Invite a new employee")}><Icon name="plus" size={14}/> <span className="emp-action-label">{(window.appT||(s=>s))("Invite")}</span></button>}
          </div>
        }
      />

      <div className="card">
        <div className="toolbar">
          <SearchInput
            value={q}
            onChange={setQ}
            placeholder={(window.appT||(s=>s))("Search by name or K9 ID…")}
          />
          <div style={{ display:'flex', gap: 8, marginLeft:'auto', flexWrap:'wrap' }}>
            <select className="sel" value={scorePeriod} onChange={e=>setScorePeriod(e.target.value)}
              title={(window.appT||(s=>s))("Time window for the attendance pie + hours on each card")}
              style={{ fontSize: 12, padding: '4px 8px' }}>
              {SCORE_PERIODS.map(([v, lbl]) => (
                <option key={v} value={v}>{(window.appT||(s=>s))(lbl)}</option>
              ))}
            </select>
            <select className="sel" value={team} onChange={e=>setTeam(e.target.value)}>
              <option value="all">{(window.appT||(s=>s))("All teams")}</option>
              {D.TEAM_NAMES.map(t => <option key={t} value={t}>{t}</option>)}
            </select>
            <Segmented
              value={statusFilter} onChange={setStatusFilter}
              options={[
                { value:'all',        label:(window.appT||(s=>s))('All') },
                { value:'present',    label:(window.appT||(s=>s))('Present') },
                { value:'late',       label:(window.appT||(s=>s))('Late') },
                { value:'signed-off', label:(window.appT||(s=>s))('Signed off') },
                { value:'absent',     label:(window.appT||(s=>s))('Absent') },
              ]}
            />
            <select className="sel" value={ipFilter} onChange={e => setIpFilter(e.target.value)}
              title={(window.appT||(s=>s))("Filter by where they're signing in from (latest IP classification)")}
              style={{ fontSize: 12, padding: '4px 8px' }}>
              <option value="all">{(window.appT||(s=>s))("All locations")}</option>
              <option value="office">{(window.appT||(s=>s))("Office")}</option>
              <option value="home">{(window.appT||(s=>s))("Home")}</option>
              {/* "Other" / "Unknown" removed — every IP classifies as one of
                  the two; anything previously unknown reads as the next
                  classification cycle after the first sign-in. */}
            </select>
            {/* Show-test-accounts toggle removed — see note at the
                showTestAccounts declaration above. */}
            {isAdmin && (
              <button
                className="btn btn-ghost"
                onClick={requestContactsAll}
                disabled={requestingAll}
                title={(window.appT||(s=>s))("DM every active employee asking them to fill in their contact details")}
                style={{ fontSize: 12, padding:'4px 10px' }}
              >
                {requestingAll
                  ? (window.appT||(s=>s))('Requesting…')
                  : <><Icon name="msg" size={12}/> {(window.appT||(s=>s))("Request contact details (all)")}</>}
              </button>
            )}
          </div>
        </div>

        <div className="grid-cards employee-card-grid">
            {sorted.map(e => {
              const sb = scoreboard[e.id];
              return (
              <div key={e.id} className="person-card employee-square-card" onClick={() => setSelectedId(e.id)}
                aria-label={(window.appLang&&window.appLang()==='ar') ? `انقر لفتح لوحة نتائج ${e.display_name}` : `Click to open ${e.display_name}'s scoreboard`}>
                <div className="employee-card-head">
                  <Avatar person={e} status={e.status} size={44} />
                  <div style={{ flex:1, minWidth: 0 }}>
                    <div className="employee-card-name">{e.display_name}</div>
                    <div className="muted mono employee-card-id">{e.matrix_id}</div>
                  </div>
                  <StatusBadge status={e.status} />
                </div>

                {/* Two pies side-by-side: 30d attendance + today live-presence */}
                <div className="employee-card-pies">
                  <div className="employee-card-pie">
                    {sb ? (
                      <>
                        <UserPie online={sb.on_time_days} away={sb.late_days} offline={sb.absent_days} size={84}
                          title={(window.appT||(s=>s))("Slice breakdown: green = on-time, orange = late, grey = absent (last 30 working days)")} />
                        <div className="employee-card-pct"
                          title={(window.appT||(s=>s))("(on_time_days + 0.5 × half_days) / working_days × 100. Green ≥ 90%, orange 70-89%, red < 70%.")}
                          style={{ color: sb.attendance_pct>=90?'var(--ok)':sb.attendance_pct>=70?'var(--warn)':'var(--err)' }}>
                          {sb.attendance_pct}%
                        </div>
                        <div className="employee-card-pie-label">{(window.appT||(s=>s))((SCORE_PERIODS.find(([v]) => v === scorePeriod) || [null,'This week'])[1]) + ' ' + (window.appT||(s=>s))("attendance")}</div>
                      </>
                    ) : (
                      <div className="muted" style={{padding: 6, fontSize: 11}}>—</div>
                    )}
                  </div>
                  <div className="employee-card-pie">
                    {(() => {
                      const pres = presenceMap[e.id];
                      const buckets = pres?.breakdown?.buckets || {};
                      const on = buckets.online ?? 0;
                      const aw = buckets.away ?? 0;
                      const off = buckets.offline ?? 0;
                      const total = on + aw + off;
                      // Attendance is the authoritative "is this person working now"
                      // signal — Matrix presence is unreliable for web clients
                      // (often reports offline/unavailable even while active). So a
                      // user who is signed in and not yet signed off shows ONLINE
                      // regardless of Matrix presence. Once signed off, fall back to
                      // Matrix presence / today's minute buckets.
                      const working = e.signed_in && !e.signed_off;
                      const status = working
                        ? 'online'
                        : (pres?.presence || (on > 0 ? 'online' : aw > 0 ? 'away' : 'offline'));
                      const dotColor = status === 'online' ? 'var(--ok)' : status === 'away' ? 'var(--warn)' : 'var(--text-3)';
                      return (
                        <>
                          <UserPie online={on} away={aw} offline={off} size={84} />
                          <div className="employee-card-pct" style={{ color: dotColor, fontSize: 14, lineHeight: 1, textTransform:'capitalize' }}>
                            <span style={{display:'inline-block', width:8, height:8, borderRadius:'50%', background: dotColor, marginRight: 6}}></span>
                            {(window.appT||(s=>s))(status)}
                          </div>
                          <div className="employee-card-pie-label">{(window.appLang&&window.appLang()==='ar') ? `الحضور المباشر (${total} د)` : `live presence (${total}m)`}</div>
                        </>
                      );
                    })()}
                  </div>
                </div>

                <div className="employee-card-stats">
                  <div title={(window.appT||(s=>s))("Days the employee signed in before (expected_start + late_buffer). Default 9:00 + 30 min.")}>
                    <span className="dot ok"></span>{sb ? sb.on_time_days : 0} {(window.appT||(s=>s))("on-time")}
                  </div>
                  <div title={(window.appT||(s=>s))("Days the employee signed in but past the late buffer. A 3rd late strike escalates to admin.")}>
                    <span className="dot warn"></span>{sb ? sb.late_days : 0} {(window.appT||(s=>s))("late")}
                  </div>
                  <div title={(window.appT||(s=>s))("Working days with no sign-in and no approved PTO. Counts against attendance %.")}>
                    <span className="dot mute"></span>{sb ? sb.absent_days : 0} {(window.appT||(s=>s))("absent")}
                  </div>
                </div>

                {/* Mini-table of key stats — replaces the wide-table view.
                    When admin Edit mode is ON, fields backed by an existing
                    endpoint show a dashed "click to edit" affordance (mirrors
                    page-monthly's editMode cells). Team / Sign-in / Hours open
                    the profile+contact editor (PUT /profile + /contact);
                    Strikes uses its own StrikeBadge modal. */}
                {(() => {
                  const cardEditable = editMode && canManage;
                  const editTip = (window.appLang&&window.appLang()==='ar') ? 'انقر للتعديل' : 'click to edit';
                  // Dashed-outline affordance reused across editable card cells.
                  const editCellProps = cardEditable ? {
                    onClick: (ev) => { ev.stopPropagation(); setCardEditPerson(e); },
                    title: editTip,
                    style: {
                      cursor: 'pointer',
                      outline: '1px dashed var(--brand)',
                      outlineOffset: -1,
                      borderRadius: 3,
                    },
                  } : {};
                  return (
                <table className="employee-card-table">
                  <tbody>
                    <tr>
                      <td className="lbl">{(window.appT||(s=>s))("Team")}</td>
                      <td {...editCellProps}>{e.team || '—'}{cardEditable && <span style={{ marginInlineStart: 6, fontSize: 10, color: 'var(--brand)' }}>✎</span>}</td>
                    </tr>
                    <tr>
                      <td className="lbl">{(window.appT||(s=>s))("Sign-in")}</td>
                      <td className="mono" {...(cardEditable ? { ...editCellProps, style: { ...editCellProps.style } } : {})}>{e.sign_in_at || '—'}</td>
                    </tr>
                    <tr>
                      <td className="lbl">{(window.appT||(s=>s))("Hours today")}</td>
                      <td className="mono" {...(cardEditable ? { ...editCellProps, style: { ...editCellProps.style } } : {})}>{e.hours_worked ? `${e.hours_worked.toFixed(1)}h` : '—'}</td>
                    </tr>
                    <tr>
                      <td className="lbl">{(window.appT||(s=>s))("Strikes")}</td>
                      <td onClick={ev => ev.stopPropagation()}
                          {...(cardEditable ? {
                            title: editTip,
                            style: { outline: '1px dashed var(--brand)', outlineOffset: -1, borderRadius: 3 },
                          } : {})}>
                        <StrikeBadge count={sb?.strike_count ?? 0} userId={e.id} displayName={e.display_name} />
                      </td>
                    </tr>
                    {sb && (
                      <tr>
                        <td className="lbl">{(window.appT||(s=>s))("Week hours")}</td>
                        <td className="mono">{(sb.week_hours ?? 0).toFixed(1)}h</td>
                      </tr>
                    )}
                  </tbody>
                </table>
                  );
                })()}

                <TimeBar inAt={e.sign_in_at} outAt={e.sign_off_at} late={e.was_late} />
                <WeekHoursBar hours={sb?.week_hours ?? 0} target={sb?.week_hours_target ?? 0} />

                <div className="employee-card-actions" onClick={ev => ev.stopPropagation()}>
                  <EmployeeRowActions person={e} compact currentStrikes={sb?.strike_count ?? 0} />
                </div>
              </div>
              );
            })}
        </div>

        {sorted.length === 0 && (
          <Empty icon={<Icon name="user" size={24}/>} title={(window.appT||(s=>s))("No people match")} subtitle={(window.appT||(s=>s))("Try a different filter or search term.")} />
        )}
      </div>

      {selected && <PersonDrawer person={selected} onClose={() => setSelectedId(null)} />}
      {wizardOpen && (
        <ModalErrorBoundary onClose={() => setWizardOpen(false)}>
          <AddEmployeeWizard onClose={() => setWizardOpen(false)} />
        </ModalErrorBoundary>
      )}
      {bulkDmOpen && <BulkDmModal employees={employees} teams={D.TEAMS || []} onClose={() => setBulkDmOpen(false)} />}
      {/* Inline card edit (Edit-mode click on a card field) → existing
          profile + contact editor. Reuses PUT /api/employees/{id}/profile
          and PUT /api/employees/{id}/contact — no new endpoints. */}
      {cardEditPerson && (
        <ModalErrorBoundary onClose={() => setCardEditPerson(null)}>
          <EditProfileModal person={cardEditPerson} onClose={() => setCardEditPerson(null)} />
        </ModalErrorBoundary>
      )}
    </div>
  );
}

function employeeApiErrorText(detail, fallback = 'Request failed') {
  if (!detail) return fallback;
  if (typeof detail === 'string') return detail;
  if (Array.isArray(detail)) {
    return detail.map(item => employeeApiErrorText(item, '')).filter(Boolean).join(', ') || fallback;
  }
  if (typeof detail === 'object') {
    if (typeof detail.msg === 'string') return detail.msg;
    if (typeof detail.message === 'string') return detail.message;
    if (detail.loc && detail.type) {
      const loc = Array.isArray(detail.loc) ? detail.loc.join('.') : detail.loc;
      return `${loc}: ${detail.type}`;
    }
    try { return JSON.stringify(detail); } catch (_) { return fallback; }
  }
  return String(detail);
}

/* ── Per-row admin action icons (8 actions, vertical or horizontal) ──
   currentStrikes is forwarded by the card-row that already has scoreboard
   data in scope; without it the downgrade/add handlers throw "sb is not
   defined" (fix #19). Default 0 so a missing prop just disables both
   actions instead of crashing. */
function EmployeeRowActions({ person, compact, vertical = false, currentStrikes = 0 }) {
  const D = window.STATUS_DATA || {};
  const meRole = String(D?.ME?.org_role || '').toLowerCase();
  // Admin-tier (admin / moderator / superadmin) + managers can run row
  // actions on anyone (DM, give strike, forgive, assign PTO, send
  // report, etc.). That stays broad — moderators still need to drive
  // the team day-to-day.
  const isSelf = D?.ME?.id === person.id || D?.ME?.matrix_id === person.matrix_id;
  const isModerator = meRole === 'moderator';
  const isAdmin = meRole === 'admin' || meRole === 'superadmin';
  const canManage = isAdmin || isModerator || meRole === 'manager';
  // The Edit-profile pencil is the ONE action restricted further:
  // moderators can only edit their own row, even though they can
  // manage everyone else's strikes / PTO / DMs.
  const canEditProfile = isAdmin || meRole === 'manager' || (isModerator && isSelf);
  const [dmOpen, setDmOpen] = useState(false);
  const [profileOpen, setProfileOpen] = useState(false);
  const [ptoOpen, setPtoOpen] = useState(false);
  const [violationOpen, setViolationOpen] = useState(false);
  const [scoreboardOpen, setScoreboardOpen] = useState(false);
  const [busy, setBusy] = useState(null); // 'forgive' | 'downgrade' | 'send_report'
  const [_sendReportPickerOpen, _setSendReportPickerOpen] = useState(false);
  const toast = useToast();

  const btnStyle = { padding:'3px 6px', fontSize: 11 };

  // Phase 2 — strike PATCH (forgive / downgrade / add) via the unified
  // PATCH /api/users/{id}/strikes endpoint. Emits an undo token (90s) so a
  // misclick can be reverted. Falls back to the legacy POST endpoints only
  // if the new PATCH route 404s (running against an older backend).
  const _patchStrikes = async (newCount, intentLabel) => {
    const r = await fetch(`/api/users/${person.id}/strikes`, {
      method: 'PATCH', credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ count: newCount, reason: `${intentLabel} via dashboard` }),
    });
    if (!r.ok) {
      const body = await r.json().catch(() => null);
      throw new Error(parseDetail(body, r.status));
    }
    const data = await r.json().catch(() => ({}));
    if (data.undo_token && typeof window.showUndoToast === 'function') {
      window.showUndoToast({
        token: data.undo_token,
        label: `${person.display_name}: strikes ${data.old_count} → ${data.new_count}`,
        // Re-fetch the person row by triggering the page-level live refresh.
        onUndone: () => window.dispatchEvent(new CustomEvent('admin-override-applied')),
      });
    }
    return data;
  };

  const forgiveStrikes = async () => {
    if (!confirm((window.appLang&&window.appLang()==='ar') ? `العفو عن كل المخالفات لـ ${person.display_name}؟` : `Forgive ALL strikes for ${person.display_name}?`)) return;
    setBusy('forgive');
    try {
      await _patchStrikes(0, 'Forgive all');
      toast.push({ kind:'ok', msg: (window.appLang&&window.appLang()==='ar') ? `تم العفو عن كل المخالفات لـ ${person.display_name}` : `All strikes forgiven for ${person.display_name}` });
    } catch(e) {
      toast.push({ kind:'err', msg: (window.appT||(s=>s))('Forgive failed: ') + e.message });
    } finally { setBusy(null); }
  };

  const downgradeStrikes = async () => {
    setBusy('downgrade');
    try {
      const current = currentStrikes ?? 0;
      if (current <= 0) {
        toast.push({ kind:'warn', msg: (window.appLang&&window.appLang()==='ar') ? `${person.display_name} لديه بالفعل 0 مخالفة` : `${person.display_name} already has 0 strikes` });
        return;
      }
      const data = await _patchStrikes(current - 1, 'Downgrade');
      toast.push({ kind:'ok', msg: (window.appLang&&window.appLang()==='ar') ? `تم تخفيض المخالفة — ${person.display_name} لديه الآن ${data.new_count ?? '?'} مخالفة` : `Strike downgraded — ${person.display_name} now has ${data.new_count ?? '?'} strike(s)` });
    } catch(e) {
      toast.push({ kind:'err', msg: (window.appT||(s=>s))('Downgrade failed: ') + e.message });
    } finally { setBusy(null); }
  };

  // Phase 2 — admin can manually ADD a strike (e.g. after a verbal warning
  // outside the bot's auto-detection). Caps at 3 (the auto-escalation limit).
  const addStrike = async () => {
    setBusy('add_strike');
    try {
      const current = currentStrikes ?? 0;
      if (current >= 3) {
        toast.push({ kind:'warn', msg: (window.appLang&&window.appLang()==='ar') ? `${person.display_name} وصل بالفعل إلى الحد الأقصى 3 مخالفات` : `${person.display_name} is already at the 3-strike cap` });
        return;
      }
      const data = await _patchStrikes(current + 1, 'Manual strike');
      toast.push({ kind:'warn', msg: (window.appLang&&window.appLang()==='ar') ? `تم تسجيل مخالفة — ${person.display_name} لديه الآن ${data.new_count ?? '?'} مخالفة` : `Strike recorded — ${person.display_name} now has ${data.new_count ?? '?'} strike(s)` });
    } catch(e) {
      toast.push({ kind:'err', msg: (window.appT||(s=>s))('Add strike failed: ') + e.message });
    } finally { setBusy(null); }
  };

  const quickSendReport = async (period = 'week') => {
    const labels = { day:(window.appT||(s=>s))('Daily'), week:(window.appT||(s=>s))('Weekly'), month:(window.appT||(s=>s))('Monthly'), quarter:(window.appT||(s=>s))('Quarterly'), '6_months':(window.appT||(s=>s))('Semi-annual'), year:(window.appT||(s=>s))('Annual'), annual:(window.appT||(s=>s))('Annual') };
    if (!confirm((window.appLang&&window.appLang()==='ar') ? `إرسال ملخص الحضور ${labels[period] || period} إلى ${person.display_name} عبر رسالة خاصة؟` : `DM the ${labels[period] || period} attendance summary to ${person.display_name}?`)) return;
    setBusy('send_report');
    _setSendReportPickerOpen(false);
    try {
      const r = await fetch(`/api/employees/${person.id}/report/send?period=${period}`, {
        method: 'POST', credentials: 'include',
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      toast.push({ kind:'ok', msg: (window.appLang&&window.appLang()==='ar') ? `تم إرسال تقرير ${labels[period] || period} إلى ${person.display_name}` : `${labels[period] || period} report DM'd to ${person.display_name}` });
    } catch(e) {
      toast.push({ kind:'err', msg: (window.appT||(s=>s))('Send failed: ') + e.message });
    } finally { setBusy(null); }
  };

  const containerStyle = vertical
    ? { display: 'flex', flexDirection: 'column', gap: 4 }
    : { display: 'flex', gap: 4, flexWrap: 'wrap' };

  return (
    <>
      {!canManage ? (
        <div style={containerStyle} onClick={ev => ev.stopPropagation()}>
          <button className="btn btn-ghost" style={btnStyle} title={(window.appT||(s=>s))("Open scoreboard (view only)")}
            onClick={e => { e.stopPropagation(); setScoreboardOpen(true); }}>
            <Icon name="report" size={12}/>
          </button>
        </div>
      ) : (
      <div style={containerStyle} onClick={ev => ev.stopPropagation()}>
        <button className="btn btn-ghost" style={btnStyle} title={(window.appT||(s=>s))("Assign PTO (auto-DMs the user)")}
          onClick={e => { e.stopPropagation(); setPtoOpen(true); }}>
          <Icon name="calendar" size={12}/>
        </button>
        <button className="btn btn-ghost" style={btnStyle} title={(window.appT||(s=>s))("Send format/attendance violation")}
          onClick={e => { e.stopPropagation(); setViolationOpen(true); }}>
          <Icon name="alert" size={12}/>
        </button>
        <button className="btn btn-ghost" style={btnStyle} title={(window.appT||(s=>s))("Forgive ALL strikes")}
          onClick={e => { e.stopPropagation(); forgiveStrikes(); }} disabled={busy === 'forgive'}>
          {busy === 'forgive' ? '…' : <Icon name="check" size={12}/>}
        </button>
        <button className="btn btn-ghost" style={btnStyle} title={(window.appT||(s=>s))("Downgrade strikes by 1")}
          onClick={e => { e.stopPropagation(); downgradeStrikes(); }} disabled={busy === 'downgrade'}>
          {busy === 'downgrade' ? '…' : <Icon name="caret" size={12}/>}
        </button>
        <button className="btn btn-ghost" style={{ ...btnStyle, color:'var(--warn)' }} title={(window.appT||(s=>s))("Manually add a strike (90s undo)")}
          onClick={e => { e.stopPropagation(); addStrike(); }} disabled={busy === 'add_strike'}>
          {busy === 'add_strike' ? '…' : <Icon name="alert" size={12}/>}
        </button>
        <button className="btn btn-ghost" style={btnStyle} title={(window.appT||(s=>s))("DM this user")}
          onClick={e => { e.stopPropagation(); setDmOpen(true); }}>
          <Icon name="msg" size={12}/>
        </button>
        <div style={{ position:'relative', display:'inline-block' }}>
          <button className="btn btn-ghost" style={btnStyle} title={(window.appT||(s=>s))("Send attendance report via DM (pick period)")}
            onClick={e => { e.stopPropagation(); _setSendReportPickerOpen(v => !v); }} disabled={busy === 'send_report'}>
            {busy === 'send_report' ? '…' : <Icon name="send" size={12}/>}
          </button>
          {_sendReportPickerOpen && (
            <div
              onClick={e => e.stopPropagation()}
              style={{
                // A15: raise z-index above the next row + ensure container
                // doesn't clip. Use bottom-positioned anchor when near
                // bottom of card. minWidth wider + position fixed-anchor.
                position:'absolute', top:'100%', right: 0, marginTop: 4, zIndex: 9999,
                background:'var(--bg-elev-2)', border:'1px solid var(--line)',
                borderRadius:'var(--r-md)', padding: 6, minWidth: 160,
                boxShadow: '0 12px 32px -4px rgba(0,0,0,.55)',
                pointerEvents: 'auto',
              }}>
              <div style={{ fontSize: 10, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'.06em', padding:'2px 8px 4px' }}>{(window.appT||(s=>s))("Send report")}</div>
              {[['day',(window.appT||(s=>s))('Daily')], ['week',(window.appT||(s=>s))('Weekly')], ['month',(window.appT||(s=>s))('Monthly')], ['quarter',(window.appT||(s=>s))('Quarterly')], ['6_months',(window.appT||(s=>s))('Semi-annual')], ['annual',(window.appT||(s=>s))('Annual')]].map(([p, label]) => (
                <button key={p} className="btn btn-ghost"
                  style={{ width:'100%', justifyContent:'flex-start', padding:'6px 8px', fontSize:12 }}
                  onClick={e => { e.stopPropagation(); quickSendReport(p); }}>
                  {label}
                </button>
              ))}
            </div>
          )}
        </div>
        <button className="btn btn-ghost" style={btnStyle} title={(window.appT||(s=>s))("Open scoreboard (pies + tables + AI summary + send-to-user)")}
          onClick={e => { e.stopPropagation(); setScoreboardOpen(true); }}>
          <Icon name="report" size={12}/>
        </button>
        <button
          className="btn btn-ghost"
          style={{ ...btnStyle, opacity: canEditProfile ? 1 : 0.4 }}
          title={canEditProfile ? (window.appT||(s=>s))('Edit profile') : (isModerator ? (window.appT||(s=>s))('Moderators can only edit their own profile') : (window.appT||(s=>s))('Manager or admin required'))}
          disabled={!canEditProfile}
          onClick={e => { e.stopPropagation(); if (canEditProfile) setProfileOpen(true); }}
        >
          <Icon name="edit" size={12}/>
        </button>
      </div>
      )}
      {dmOpen && <DmModal person={person} onClose={() => setDmOpen(false)} />}
      {profileOpen && (
        <ModalErrorBoundary onClose={() => setProfileOpen(false)}>
          <EditProfileModal person={person} onClose={() => setProfileOpen(false)} />
        </ModalErrorBoundary>
      )}
      {scoreboardOpen && <EmployeeScoreboardModal person={person} onClose={() => setScoreboardOpen(false)} />}
      {ptoOpen && <AssignPtoModal person={person} onClose={() => setPtoOpen(false)} />}
      {violationOpen && <SendViolationModal person={person} onClose={() => setViolationOpen(false)} />}
    </>
  );
}

/* ── Assign PTO modal ── */
// A9 fix: use local-date format ('en-CA' = YYYY-MM-DD in local TZ) so that
// a user in a negative-UTC offset doesn't see yesterday's date stored for
// "today". toISOString() returns UTC midnight which rolls back for users
// west of UTC after their local 5pm.
function _localISODate(d = new Date()) {
  try { return d.toLocaleDateString('en-CA'); }
  catch { return d.toISOString().slice(0, 10); }
}
function AssignPtoModal({ person, onClose }) {
  const today = _localISODate();
  const [fromDate, setFromDate] = useState(today);
  const [toDate, setToDate]     = useState(today);
  const [kind, setKind]         = useState('vacation');
  const [reason, setReason]     = useState('');
  const [busy, setBusy]         = useState(false);
  const [err, setErr]           = useState('');
  const toast = useToast();

  const submit = async () => {
    setErr(''); setBusy(true);
    try {
      const r = await fetch(`/api/employees/${person.id}/pto/assign`, {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ from_date: fromDate, to_date: toDate, kind, reason: reason || null }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم تعيين ${kind} ${fromDate} → ${toDate} لـ ${person.display_name}` : `Assigned ${kind} ${fromDate} → ${toDate} for ${person.display_name}`, kind: 'ok' });
      onClose();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <strong>{(window.appT||(s=>s))("Assign PTO")} — {person.display_name}</strong>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        <div className="modal-body" style={{ display:'flex', flexDirection:'column', gap: 10 }}>
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 10 }}>
            <label style={{ fontSize: 12, color:'var(--text-2)' }}>{(window.appT||(s=>s))("From")}
              <input className="inp" type="date" value={fromDate} onChange={e=>setFromDate(e.target.value)} />
            </label>
            <label style={{ fontSize: 12, color:'var(--text-2)' }}>{(window.appT||(s=>s))("To")}
              <input className="inp" type="date" value={toDate} onChange={e=>setToDate(e.target.value)} />
            </label>
          </div>
          <label style={{ fontSize: 12, color:'var(--text-2)' }}>{(window.appT||(s=>s))("Kind")}
            <select className="sel" value={kind} onChange={e=>setKind(e.target.value)}>
              <option value="vacation">{(window.appT||(s=>s))("Vacation")}</option>
              <option value="pto">{(window.appT||(s=>s))("PTO")}</option>
              <option value="sick">{(window.appT||(s=>s))("Sick")}</option>
              <option value="personal">{(window.appT||(s=>s))("Personal")}</option>
              <option value="half_day">{(window.appT||(s=>s))("Half-day")}</option>
              <option value="holiday">{(window.appT||(s=>s))("Holiday")}</option>
            </select>
          </label>
          <label style={{ fontSize: 12, color:'var(--text-2)' }}>{(window.appT||(s=>s))("Reason (optional)")}
            <textarea className="inp" rows={2} value={reason} onChange={e=>setReason(e.target.value)} placeholder={(window.appT||(s=>s))("e.g. team retreat")} />
          </label>
          {err && <div style={{ color:'var(--err)', fontSize: 12 }}>{err}</div>}
        </div>
        <div className="modal-foot">
          <button className="btn btn-ghost" onClick={onClose} disabled={busy}>{(window.appT||(s=>s))("Cancel")}</button>
          <button className="btn btn-pri" onClick={submit} disabled={busy}>{busy ? (window.appT||(s=>s))('Assigning…') : (window.appT||(s=>s))('Assign')}</button>
        </div>
      </div>
    </div>
  );
}

/* ── Send violation modal ── */
function SendViolationModal({ person, onClose }) {
  const [reason, setReason]   = useState('');
  const [notify, setNotify]   = useState(true);
  const [busy, setBusy]       = useState(false);
  const [err, setErr]         = useState('');
  const toast = useToast();

  const submit = async () => {
    if (!reason.trim()) { setErr((window.appT||(s=>s))('Reason required')); return; }
    setErr(''); setBusy(true);
    try {
      const r = await fetch(`/api/employees/${person.id}/violation`, {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ reason: reason.trim(), notify_user: notify }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      const data = await r.json();
      toast.push({
        msg: (window.appLang&&window.appLang()==='ar')
          ? `تم تسجيل المخالفة ${data.new_count}/3 لـ ${person.display_name}${data.escalated ? ' (تم إبلاغ المشرف)' : ''}`
          : `Strike ${data.new_count}/3 recorded for ${person.display_name}${data.escalated ? ' (admin notified)' : ''}`,
        kind: data.escalated ? 'warn' : 'ok'
      });
      onClose();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <strong>{(window.appT||(s=>s))("Send violation")} — {person.display_name}</strong>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        <div className="modal-body" style={{ display:'flex', flexDirection:'column', gap: 10 }}>
          <label style={{ fontSize: 12, color:'var(--text-2)' }}>{(window.appT||(s=>s))("Reason")}
            <textarea className="inp" rows={3} value={reason} onChange={e=>setReason(e.target.value)} placeholder={(window.appT||(s=>s))("e.g. Missed sign-in 3 days in a row")} autoFocus />
          </label>
          <label style={{ fontSize: 12, display:'flex', alignItems:'center', gap: 6 }}>
            <input type="checkbox" checked={notify} onChange={e=>setNotify(e.target.checked)} />
            {(window.appT||(s=>s))("Send DM to")} {person.display_name}
          </label>
          {err && <div style={{ color:'var(--err)', fontSize: 12 }}>{err}</div>}
        </div>
        <div className="modal-foot">
          <button className="btn btn-ghost" onClick={onClose} disabled={busy}>{(window.appT||(s=>s))("Cancel")}</button>
          <button className="btn btn-pri" onClick={submit} disabled={busy}>{busy ? (window.appT||(s=>s))('Recording…') : (window.appT||(s=>s))('Record strike')}</button>
        </div>
      </div>
    </div>
  );
}

/* ── DM Templates Manager Modal ── */
function DmTemplatesManagerModal({ initialTemplates, onClose, onSaved }) {
  // Each row: { name, body, _key: unique string for React key }
  const [rows, setRows] = useState(() =>
    (initialTemplates || []).map((t, i) => ({ name: t.name || '', body: t.body || '', _key: String(i) }))
  );
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const toast = useToast();
  const nextKey = useRef(initialTemplates ? initialTemplates.length : 0);

  const addRow = () => {
    nextKey.current += 1;
    setRows(r => [...r, { name: '', body: '', _key: String(nextKey.current) }]);
  };

  const removeRow = (key) => setRows(r => r.filter(x => x._key !== key));

  const updateRow = (key, field, val) =>
    setRows(r => r.map(x => x._key === key ? { ...x, [field]: val } : x));

  const save = async () => {
    setBusy(true); setErr('');
    try {
      const templates = rows
        .filter(r => r.name.trim() || r.body.trim())
        .map(r => ({ name: r.name.trim(), body: r.body.trim() }));
      const resp = await fetch('/api/dm/templates', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ templates }),
      });
      if (!resp.ok) {
        const data = await resp.json().catch(() => null);
        throw new Error((data && (data.detail || data.message)) || `${resp.status}`);
      }
      const data = await resp.json();
      toast.push({ msg: (window.appT||(s=>s))('Templates saved'), kind: 'ok' });
      if (onSaved) onSaved(data.templates || templates);
      onClose();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  return (
    <div className="drawer-overlay" onClick={onClose} style={{ zIndex: 400 }}>
      <div onClick={e => e.stopPropagation()} style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
        background: 'var(--bg-elev-1)', border: '1px solid var(--line-hi)',
        borderRadius: 'var(--r-lg)', padding: 24, width: 520, maxWidth: '95vw',
        maxHeight: '85vh', display: 'flex', flexDirection: 'column',
        boxShadow: '0 24px 64px -16px #00000080', zIndex: 401,
      }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 14 }}>
          <div style={{ fontWeight: 600, fontSize: 14 }}>{(window.appT||(s=>s))("Manage DM Templates")}</div>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        <div style={{ fontSize: 11, color: 'var(--text-2)', marginBottom: 10 }}>
          {(window.appT||(s=>s))("Use {name} to insert the recipient's display name.")}
        </div>
        {err && (
          <div className="badge badge-err" style={{ marginBottom: 10, padding:'7px 12px', fontSize: 12 }}>
            <Icon name="alert" size={13}/> {err}
          </div>
        )}
        <div style={{ overflowY: 'auto', flex: 1, display: 'flex', flexDirection: 'column', gap: 10 }}>
          {rows.map((row, idx) => (
            <div key={row._key} style={{
              background: 'var(--bg-elev-2)', border: '1px solid var(--line)',
              borderRadius: 'var(--r-md)', padding: '10px 12px',
              display: 'flex', flexDirection: 'column', gap: 6,
            }}>
              <div style={{ display:'flex', gap: 6, alignItems: 'center' }}>
                <span style={{ fontSize: 10.5, color: 'var(--text-3)', minWidth: 16 }}>{idx + 1}</span>
                <input
                  className="inp"
                  style={{ flex: 1, fontSize: 12 }}
                  placeholder={(window.appT||(s=>s))("Template name (e.g. Gentle reminder)")}
                  value={row.name}
                  maxLength={60}
                  onChange={e => updateRow(row._key, 'name', e.target.value)}
                />
                <button className="btn btn-ghost btn-icon" onClick={() => removeRow(row._key)} title={(window.appT||(s=>s))("Remove")}>
                  <Icon name="x" size={12}/>
                </button>
              </div>
              <textarea
                className="inp"
                rows={3}
                style={{ fontSize: 12, resize: 'vertical', width: '100%', boxSizing: 'border-box' }}
                placeholder={(window.appT||(s=>s))("Message body… use {name} for recipient's name")}
                value={row.body}
                maxLength={2000}
                onChange={e => updateRow(row._key, 'body', e.target.value)}
              />
            </div>
          ))}
          {rows.length < 30 && (
            <button className="btn btn-ghost" style={{ fontSize: 12, alignSelf: 'flex-start' }} onClick={addRow}>
              + {(window.appT||(s=>s))("Add template")}
            </button>
          )}
        </div>
        <div style={{ display:'flex', gap: 8, marginTop: 14, justifyContent:'flex-end' }}>
          <button className="btn btn-ghost" onClick={onClose} disabled={busy}>{(window.appT||(s=>s))("Cancel")}</button>
          <button className="btn btn-pri" onClick={save} disabled={busy}>
            {busy ? (window.appT||(s=>s))('Saving…') : (window.appT||(s=>s))('Save templates')}
          </button>
        </div>
      </div>
    </div>
  );
}


/* ── DM Modal ── */
function DmModal({ person, onClose }) {
  const [msg, setMsg] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const [sent, setSent] = useState(false);
  const [templates, setTemplates] = useState([]);
  const [selectedTpl, setSelectedTpl] = useState('');
  const [selectedTplName, setSelectedTplName] = useState('');
  const [aiBusy, setAiBusy] = useState(false);
  const [showManage, setShowManage] = useState(false);
  // Scheduling controls — when: 'now' | 'later' | 'recurring'
  const [when, setWhen] = useState('now');
  const [sendAt, setSendAt] = useState('');          // datetime-local value
  const [repeat, setRepeat] = useState('weekly');    // weekly | monthly | quarterly
  const [dayOfWeek, setDayOfWeek] = useState(1);     // 0=Mon … 6=Sun
  const [dayOfMonth, setDayOfMonth] = useState(1);   // 1-28
  const [timeOfDay, setTimeOfDay] = useState('09:00');
  const [schedules, setSchedules] = useState([]);
  const [showSchedules, setShowSchedules] = useState(false);
  const toast = useToast();
  const taRef = useRef(null);
  const T = window.appT || (s => s);

  useEffect(() => { taRef.current?.focus(); }, []);

  const loadSchedules = () => {
    fetch('/api/dm/schedules', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : [])
      .then(rows => setSchedules(Array.isArray(rows) ? rows : []))
      .catch(() => {});
  };

  const loadTemplates = (attempt = 0) => {
    // A fetch during a bot restart (deploy window) used to leave the picker
    // permanently empty for the open drawer — retry, and never clobber a
    // previously-loaded list with an error result.
    fetch('/api/dm/templates', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(new Error(String(r.status))))
      .then(rows => { if (Array.isArray(rows)) setTemplates(rows); })
      .catch(() => { if (attempt < 3) setTimeout(() => loadTemplates(attempt + 1), 3000); });
  };

  // Load canned DM templates from /api/dm/templates
  useEffect(() => { loadTemplates(); }, []);

  const applyTemplate = (id) => {
    setSelectedTpl(id);
    if (!id) { setSelectedTplName(''); return; }
    const t = templates.find(x => String(x.id) === String(id));
    if (t) {
      setSelectedTplName(t.name || '');
      if (t.body) {
        // Substitute {name} and {display_name} placeholders
        setMsg(t.body
          .replace(/\{name\}/g, person.display_name || '')
          .replace(/\{display_name\}/g, person.display_name || ''));
      }
    }
  };

  const aiGenerate = async () => {
    setAiBusy(true); setErr('');
    try {
      const r = await fetch('/api/messages/templates/generate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({
          prompt: (msg.trim() || 'a friendly check-in DM') + ' to ' + (person.display_name || 'an employee'),
          tone: 'friendly',
        }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      const data = await r.json();
      const out = data.body || data.text || data.message || (typeof data === 'string' ? data : ''); if (out) setMsg(out);
    } catch(e) {
      setErr((window.appT||(s=>s))('AI generate failed: ') + e.message);
    } finally {
      setAiBusy(false);
    }
  };

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape' && !showManage) onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose, showManage]);

  const send = async () => {
    if (!msg.trim() || busy) return;
    setBusy(true); setErr('');
    const ctrl = new AbortController();
    const tid = setTimeout(() => ctrl.abort(), 30000);
    try {
      const r = await fetch('/api/dm/send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        signal: ctrl.signal,
        body: JSON.stringify({ user_id: person.id, message: msg.trim() }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(employeeApiErrorText(body?.detail, `${r.status} ${r.statusText}`));
      }
      setSent(true);
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم إرسال الرسالة إلى ${person.display_name}` : `DM sent to ${person.display_name}`, kind: 'ok' });
      setTimeout(onClose, 1200);
    } catch (e) {
      if (e.name === 'AbortError') {
        setErr((window.appT||(s=>s))('Send timed out — check audit log for delivery status.'));
      } else {
        setErr(e.message);
      }
    } finally {
      clearTimeout(tid);
      setBusy(false);
    }
  };

  const schedule = async () => {
    if (!msg.trim() || busy) return;
    const payload = {
      user_ids: [person.id],
      body: msg.trim(),
      template_name: selectedTplName || null,
    };
    if (when === 'later') {
      if (!sendAt) { setErr(T('Pick a date and time.')); return; }
      payload.repeat = 'once';
      // datetime-local has no timezone; send as local ISO so the server
      // interprets it consistently (it normalizes to UTC).
      payload.send_at = new Date(sendAt).toISOString();
    } else { // recurring
      payload.repeat = repeat;
      payload.time_of_day = timeOfDay;
      if (repeat === 'weekly') payload.day_of_week = Number(dayOfWeek);
      else payload.day_of_month = Number(dayOfMonth);
    }
    setBusy(true); setErr('');
    try {
      const r = await fetch('/api/dm/schedule', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify(payload),
      });
      const body = await r.json().catch(() => null);
      if (!r.ok) throw new Error(employeeApiErrorText(body?.detail, `${r.status} ${r.statusText}`));
      if (!body || !body.created) throw new Error(T('Nothing scheduled — recipient may have no Matrix ID.'));
      setSent(true);
      toast.push({ msg: when === 'later' ? T('DM scheduled') : T('Recurring DM created'), kind: 'ok' });
      setTimeout(onClose, 1200);
    } catch (e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  const cancelSchedule = async (id) => {
    try {
      const r = await fetch('/api/dm/schedules/' + id, {
        method: 'DELETE', credentials: 'same-origin',
      });
      if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
      setSchedules(s => s.filter(x => x.id !== id));
      toast.push({ msg: T('Scheduled DM cancelled'), kind: 'ok' });
    } catch (e) {
      toast.push({ msg: T('Cancel failed: ') + e.message, kind: 'err' });
    }
  };

  const submit = () => { (when === 'now') ? send() : schedule(); };

  return (
    <>
      {showManage && (
        <DmTemplatesManagerModal
          initialTemplates={templates.filter(t => (t.source || 'canned') === 'canned')}
          onClose={() => setShowManage(false)}
          onSaved={() => { loadTemplates(); setSelectedTpl(''); }}
        />
      )}
      <div className="drawer-overlay" onClick={onClose} style={{ zIndex: 300 }}>
        <div
          onClick={e => e.stopPropagation()}
          style={{
            position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
            background: 'var(--bg-elev-1)', border: '1px solid var(--line-hi)',
            borderRadius: 'var(--r-lg)', padding: 24, width: 400, maxWidth: '90vw',
            boxShadow: '0 24px 64px -16px #00000080', zIndex: 301,
          }}
        >
          <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 16 }}>
            <div style={{ fontWeight: 600, fontSize: 14 }}>{(window.appT||(s=>s))("DM")} {person.display_name}</div>
            <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
          </div>

          {sent ? (
            <div className="badge badge-ok" style={{ padding:'10px 12px', fontSize: 13 }}>
              <Icon name="check" size={13}/> {(window.appT||(s=>s))("Message sent!")}
            </div>
          ) : (
            <>
              {err && (
                <div className="badge badge-err" style={{ marginBottom: 10, padding:'7px 12px', fontSize: 12 }}>
                  <Icon name="alert" size={13}/> {err}
                </div>
              )}
              <div style={{ display:'flex', gap: 6, marginBottom: 8, alignItems: 'center' }}>
                <select className="inp" value={selectedTpl} onChange={e => applyTemplate(e.target.value)}
                  style={{ flex: 1, fontSize: 12 }}>
                  <option value="">{(window.appT||(s=>s))("Pick a template…")}</option>
                  {['canned','library','auto'].map(src => {
                    const group = templates.filter(t => (t.source || 'canned') === src);
                    if (!group.length) return null;
                    const label = src === 'canned' ? (window.appT||(s=>s))('Canned')
                      : src === 'library' ? (window.appT||(s=>s))('Library')
                      : (window.appT||(s=>s))('Automatic DMs');
                    return (
                      <optgroup key={src} label={label}>
                        {group.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
                      </optgroup>
                    );
                  })}
                </select>
                <button className="btn btn-ghost" onClick={aiGenerate} disabled={aiBusy} title={(window.appT||(s=>s))("AI-generate a draft based on the textarea contents")}>
                  {aiBusy ? '…' : <>✨ {(window.appT||(s=>s))("AI")}</>}
                </button>
                <button className="btn btn-ghost" style={{ fontSize: 11, whiteSpace: 'nowrap' }}
                  onClick={() => setShowManage(true)}
                  title={(window.appT||(s=>s))("Manage templates")}>
                  {(window.appT||(s=>s))("Manage")}
                </button>
              </div>
              <textarea
                ref={taRef}
                value={msg}
                onChange={e => setMsg(e.target.value)}
                onKeyDown={e => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey) && when === 'now') send(); }}
                placeholder={(window.appLang&&window.appLang()==='ar') ? `رسالة إلى ${person.display_name}…` : `Message to ${person.display_name}…`}
                rows={4}
                style={{
                  width: '100%', boxSizing: 'border-box',
                  background: 'var(--bg-elev-2)', border: '1px solid var(--line)',
                  borderRadius: 'var(--r-md)', padding: '10px 12px',
                  color: 'var(--text)', fontSize: 13, resize: 'vertical',
                  fontFamily: 'var(--font-sans)',
                }}
              />

              {/* When control: Send now / Send later / Recurring */}
              <div style={{ marginTop: 10 }}>
                <div style={{ fontSize: 11, color:'var(--text-3)', marginBottom: 5 }}>{T("When")}</div>
                <div style={{ display:'flex', gap: 6 }}>
                  {[['now', T('Send now')], ['later', T('Send later')], ['recurring', T('Recurring')]].map(([v, label]) => (
                    <button key={v}
                      className={'btn ' + (when === v ? 'btn-pri' : 'btn-ghost')}
                      style={{ flex: 1, fontSize: 12 }}
                      onClick={() => setWhen(v)}>{label}</button>
                  ))}
                </div>

                {when === 'later' && (
                  <div style={{ marginTop: 8 }}>
                    <input type="datetime-local" className="inp" value={sendAt}
                      onChange={e => setSendAt(e.target.value)}
                      style={{ width: '100%', boxSizing: 'border-box', fontSize: 12 }} />
                  </div>
                )}

                {when === 'recurring' && (
                  <div style={{ marginTop: 8, display:'flex', flexDirection:'column', gap: 8 }}>
                    <div style={{ display:'flex', gap: 6 }}>
                      {[['weekly', T('Weekly')], ['monthly', T('Monthly')], ['quarterly', T('Quarterly')]].map(([v, label]) => (
                        <button key={v}
                          className={'btn ' + (repeat === v ? 'btn-pri' : 'btn-ghost')}
                          style={{ flex: 1, fontSize: 12 }}
                          onClick={() => setRepeat(v)}>{label}</button>
                      ))}
                    </div>
                    <div style={{ display:'flex', gap: 6, alignItems:'center' }}>
                      {repeat === 'weekly' ? (
                        <select className="inp" value={dayOfWeek}
                          onChange={e => setDayOfWeek(e.target.value)}
                          style={{ flex: 1, fontSize: 12 }}>
                          {[T('Monday'), T('Tuesday'), T('Wednesday'), T('Thursday'), T('Friday'), T('Saturday'), T('Sunday')].map((d, i) => (
                            <option key={i} value={i}>{d}</option>
                          ))}
                        </select>
                      ) : (
                        <select className="inp" value={dayOfMonth}
                          onChange={e => setDayOfMonth(e.target.value)}
                          style={{ flex: 1, fontSize: 12 }}
                          title={T('Day of month (1–28)')}>
                          {Array.from({ length: 28 }, (_, i) => i + 1).map(d => (
                            <option key={d} value={d}>{T('Day')} {d}</option>
                          ))}
                        </select>
                      )}
                      <input type="time" className="inp" value={timeOfDay}
                        onChange={e => setTimeOfDay(e.target.value)}
                        style={{ width: 110, fontSize: 12 }} />
                    </div>
                  </div>
                )}
              </div>

              <div style={{ display:'flex', gap: 8, marginTop: 12, justifyContent:'space-between', alignItems:'center' }}>
                <button className="btn btn-ghost" style={{ fontSize: 11 }}
                  onClick={() => { const nx = !showSchedules; setShowSchedules(nx); if (nx) loadSchedules(); }}>
                  {showSchedules ? T('Hide scheduled') : T('Scheduled DMs')}
                </button>
                <div style={{ display:'flex', gap: 8 }}>
                  <button className="btn btn-ghost" onClick={onClose}>{T("Cancel")}</button>
                  <button
                    className="btn btn-pri"
                    onClick={submit}
                    disabled={busy || !msg.trim() || (when === 'later' && !sendAt)}
                  >
                    {busy
                      ? (when === 'now' ? T('Sending…') : T('Scheduling…'))
                      : (when === 'now'
                          ? <><Icon name="send" size={13}/> {T("Send DM")}</>
                          : (when === 'later' ? T('Schedule') : T('Create reminder')))}
                  </button>
                </div>
              </div>

              {showSchedules && (
                <div style={{ marginTop: 12, borderTop: '1px solid var(--line)', paddingTop: 10 }}>
                  <div style={{ fontSize: 11, color:'var(--text-3)', marginBottom: 6 }}>{T('Active scheduled & recurring DMs')}</div>
                  {schedules.length === 0 ? (
                    <div style={{ fontSize: 12, color:'var(--text-3)' }}>{T('None scheduled.')}</div>
                  ) : (
                    <div style={{ display:'flex', flexDirection:'column', gap: 6, maxHeight: 180, overflowY: 'auto' }}>
                      {schedules.map(s => (
                        <div key={s.id} style={{ display:'flex', justifyContent:'space-between', alignItems:'center', gap: 8,
                          background:'var(--bg-elev-2)', border:'1px solid var(--line)', borderRadius:'var(--r-md)', padding:'6px 10px' }}>
                          <div style={{ minWidth: 0 }}>
                            <div style={{ fontSize: 12, fontWeight: 600 }}>
                              {s.display_name} · <span style={{ color:'var(--text-3)', fontWeight: 400 }}>{s.repeat}</span>
                            </div>
                            <div style={{ fontSize: 11, color:'var(--text-3)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>
                              {s.next_send_at ? new Date(s.next_send_at + 'Z').toLocaleString() : ''} — {s.body_preview}
                            </div>
                          </div>
                          <button className="btn btn-ghost btn-icon" title={T('Cancel')}
                            onClick={() => cancelSchedule(s.id)}><Icon name="x" size={12}/></button>
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              )}

              <div style={{ fontSize: 11, color:'var(--text-3)', marginTop: 8, textAlign:'right' }}>
                {when === 'now' ? T("⌘↵ to send") : ''}
              </div>
            </>
          )}
        </div>
      </div>
    </>
  );
}


function BulkDmModal({ employees, teams, onClose }) {
  const [mode, setMode] = useState('team');     // 'team' | 'manual' | 'all'
  const [team, setTeam] = useState('all');
  const [selectedIds, setSelectedIds] = useState([]);
  const [manualMxids, setManualMxids] = useState('');
  const [msg, setMsg] = useState('');
  const [templates, setTemplates] = useState([]);
  const [selectedTpl, setSelectedTpl] = useState('');
  const [busy, setBusy] = useState(false);
  const [progress, setProgress] = useState({ done: 0, total: 0, fails: [] });
  const [aiBusy, setAiBusy] = useState(false);
  const toast = useToast();

  useEffect(() => {
    const load = (attempt = 0) => {
      fetch('/api/dm/templates', { credentials: 'same-origin' })
        .then(r => r.ok ? r.json() : Promise.reject(new Error(String(r.status))))
        .then(rows => { if (Array.isArray(rows)) setTemplates(rows); })
        .catch(() => { if (attempt < 3) setTimeout(() => load(attempt + 1), 3000); });
    };
    load();
  }, []);

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape' && !busy) onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose, busy]);

  // Compute the actual list of recipients based on mode
  const recipients = (() => {
    if (mode === 'all') return employees;
    if (mode === 'team') {
      if (team === 'all') return employees;
      return employees.filter(e => (e.team || '') === team);
    }
    if (mode === 'select') return employees.filter(e => selectedIds.includes(e.id));
    if (mode === 'manual') {
      const mxids = manualMxids.split(/[\n,;\s]+/).map(s => s.trim()).filter(Boolean);
      return employees.filter(e => mxids.includes(e.matrix_id));
    }
    return [];
  })();

  const aiGenerate = async () => {
    setAiBusy(true);
    try {
      const r = await fetch('/api/messages/templates/generate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({
          prompt: (() => {
            const sel = employees.filter(e => selectedIds.includes(e.id));
            const names = sel.map(e => e.display_name).filter(Boolean);
            let who;
            if (sel.length === 0)       who = 'the team (use {display_name} placeholder)';
            else if (sel.length === 1)  who = names[0];
            else if (sel.length <= 3)   who = names.join(' and ');
            else                        who = sel.length + ' team members (use {display_name} placeholder)';
            return msg.trim() ? msg.trim() + ' — recipient: ' + who : 'a friendly DM to ' + who;
          })(),
          tone: 'friendly',
        }),
      });
      if (r.ok) {
        const data = await r.json();
        const out = data.body || data.text || data.message || (typeof data === 'string' ? data : ''); if (out) setMsg(out);
      }
    } catch(_) {}
    setAiBusy(false);
  };

  const send = async () => {
    if (!msg.trim() || recipients.length === 0 || busy) return;
    if (!confirm((window.appLang&&window.appLang()==='ar') ? `إرسال رسالة خاصة إلى ${recipients.length} موظف؟` : `Send DM to ${recipients.length} employee(s)?`)) return;
    setBusy(true);
    setProgress({ done: 0, total: recipients.length, fails: [] });
    const fails = [];
    for (let i = 0; i < recipients.length; i++) {
      const r = recipients[i];
      const personalized = msg.trim().replace(/\{display_name\}/g, r.display_name || '');
      try {
        const resp = await fetch('/api/dm/send', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          credentials: 'same-origin',
          body: JSON.stringify({ user_id: r.id, message: personalized }),
        });
        if (!resp.ok) fails.push(r.display_name);
      } catch(_) { fails.push(r.display_name); }
      setProgress({ done: i + 1, total: recipients.length, fails: fails.slice() });
    }
    setBusy(false);
    if (fails.length === 0) {
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `✅ تم إرسال الرسالة إلى جميع المستلمين الـ ${recipients.length}` : `✅ DM sent to all ${recipients.length} recipients`, kind: 'ok' });
      setTimeout(onClose, 1200);
    } else {
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم الإرسال ${recipients.length - fails.length}/${recipients.length} (${fails.length} فشل)` : `Sent ${recipients.length - fails.length}/${recipients.length} (${fails.length} failed)`, kind: 'warn' });
    }
  };

  return (
    <div className="drawer-overlay" onClick={() => !busy && onClose()} style={{ zIndex: 300 }}>
      <div onClick={e => e.stopPropagation()}
        style={{
          position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
          background: 'var(--bg-elev-1)', border: '1px solid var(--line-hi)',
          borderRadius: 'var(--r-lg)', padding: 24, width: 560, maxWidth: '95vw',
          boxShadow: '0 24px 64px -16px #00000080', zIndex: 301,
          maxHeight: '90vh', overflowY: 'auto',
        }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 14 }}>
          <div style={{ fontWeight: 600, fontSize: 15 }}>📤 {(window.appT||(s=>s))("Bulk DM")}</div>
          <button className="btn btn-ghost btn-icon" onClick={() => !busy && onClose()} disabled={busy}><Icon name="x" size={14}/></button>
        </div>

        <div style={{ marginBottom: 12 }}>
          <div style={{ fontSize: 11, color:'var(--text-2)', marginBottom: 6, textTransform:'uppercase', letterSpacing:'.06em' }}>{(window.appT||(s=>s))("Recipients")}</div>
          <div style={{ display:'flex', gap: 4, marginBottom: 10 }}>
            {[['all',(window.appT||(s=>s))('Everyone')], ['team',(window.appT||(s=>s))('By team')], ['select',(window.appT||(s=>s))('Pick employees')], ['manual',(window.appT||(s=>s))('Manual K9 IDs')]].map(([k, label]) => (
              <button key={k}
                className={'btn ' + (mode === k ? 'btn-pri' : 'btn-ghost')}
                style={{ fontSize: 11.5, padding:'5px 10px' }}
                onClick={() => setMode(k)}>
                {label}
              </button>
            ))}
          </div>
          {mode === 'team' && (
            <select className="inp" value={team} onChange={e => setTeam(e.target.value)}
              style={{ width:'100%', fontSize: 12 }}>
              <option value="all">{(window.appT||(s=>s))("All teams")}</option>
              {teams.map(t => <option key={t.team_name || t.id} value={t.team_name}>{t.team_name}</option>)}
            </select>
          )}
          {mode === 'select' && (
            <div style={{ maxHeight: 180, overflowY:'auto', border:'1px solid var(--line)', borderRadius:'var(--r-md)', padding: 8 }}>
              {employees.map(e => (
                <label key={e.id} style={{ display:'flex', gap: 8, alignItems:'center', padding:'4px 0', fontSize: 12, cursor:'pointer' }}>
                  <input type="checkbox" checked={selectedIds.includes(e.id)}
                    onChange={() => setSelectedIds(s => s.includes(e.id) ? s.filter(x => x !== e.id) : [...s, e.id])} />
                  <span>{e.display_name}</span>
                  {e.team && <span className="muted" style={{ fontSize: 10 }}>{e.team}</span>}
                </label>
              ))}
            </div>
          )}
          {mode === 'manual' && (
            <textarea className="inp" value={manualMxids} onChange={e => setManualMxids(e.target.value)}
              placeholder="@user1:k9.ms&#10;@user2:k9.ms"
              rows={4}
              style={{ width:'100%', boxSizing:'border-box', fontFamily:'var(--font-mono)', fontSize: 12 }} />
          )}
          <div style={{ marginTop: 6, fontSize: 11.5, color:'var(--text-2)' }}>
            <strong>{recipients.length}</strong> {(window.appLang&&window.appLang()==='ar') ? 'مستلم محدَّد' : `recipient${recipients.length === 1 ? '' : 's'} selected`}
          </div>
        </div>

        <div style={{ marginBottom: 8 }}>
          <div style={{ fontSize: 11, color:'var(--text-2)', marginBottom: 6, textTransform:'uppercase', letterSpacing:'.06em' }}>{(window.appT||(s=>s))("Message")}</div>
          <div style={{ display:'flex', gap: 6, marginBottom: 6 }}>
            <select className="inp" value={selectedTpl}
              onChange={e => {
                setSelectedTpl(e.target.value);
                const t = templates.find(x => String(x.id) === String(e.target.value));
                if (t && t.body) setMsg(t.body);
              }}
              style={{ flex: 1, fontSize: 12 }}>
              <option value="">{(window.appT||(s=>s))("Pick a template…")}</option>
              {['canned','library','auto'].map(src => {
                const group = templates.filter(t => (t.source || 'canned') === src);
                if (!group.length) return null;
                const label = src === 'canned' ? (window.appT||(s=>s))('Canned')
                  : src === 'library' ? (window.appT||(s=>s))('Library')
                  : (window.appT||(s=>s))('Automatic DMs');
                return (
                  <optgroup key={src} label={label}>
                    {group.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
                  </optgroup>
                );
              })}
            </select>
            <button className="btn btn-ghost" onClick={aiGenerate} disabled={aiBusy}
              title={(window.appT||(s=>s))("AI-generate a draft based on the textarea contents")}>
              {aiBusy ? '…' : <>✨ {(window.appT||(s=>s))("AI")}</>}
            </button>
          </div>
          <textarea className="inp" value={msg} onChange={e => setMsg(e.target.value)}
            placeholder={(window.appT||(s=>s))("Use {display_name} to personalize per recipient…")}
            rows={6}
            style={{ width:'100%', boxSizing:'border-box', resize:'vertical', fontSize: 12.5 }} />
          <div style={{ fontSize: 10.5, color:'var(--text-3)', marginTop: 4 }}>
            <code>{'{display_name}'}</code> {(window.appT||(s=>s))("will be replaced per recipient.")}
          </div>
        </div>

        {busy && (
          <div style={{ marginBottom: 10, padding:'8px 10px', background:'var(--bg-elev-2)', borderRadius:'var(--r-md)', fontSize: 12 }}>
            {(window.appT||(s=>s))("Sending…")} <strong>{progress.done}</strong> / {progress.total}
            {progress.fails.length > 0 && <span style={{ color:'var(--err)' }}> · {(window.appLang&&window.appLang()==='ar') ? `${progress.fails.length} فشل` : `${progress.fails.length} failed`}</span>}
          </div>
        )}

        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end' }}>
          <button className="btn btn-ghost" onClick={onClose} disabled={busy}>{(window.appT||(s=>s))("Cancel")}</button>
          <button className="btn btn-pri" onClick={send}
            disabled={busy || !msg.trim() || recipients.length === 0}>
            {busy ? ((window.appLang&&window.appLang()==='ar') ? `جارٍ الإرسال ${progress.done}/${progress.total}…` : `Sending ${progress.done}/${progress.total}…`) : <><Icon name="send" size={13}/> {(window.appT||(s=>s))("Send to")} {recipients.length}</>}
          </button>
        </div>
      </div>
    </div>
  );
}

/* ── Edit Role Modal ── */
function EditRoleModal({ person, onClose, onSaved }) {
  const [role, setRole] = useState(person.org_role || person.role || 'employee');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const [saved, setSaved] = useState(false);
  const toast = useToast();

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const saveRole = async (nextRole) => {
    setBusy(true); setErr('');
    setSaved(false);
    try {
      const r = await fetch(`/api/users/${encodeURIComponent(person.matrix_id)}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ org_role: nextRole, role: nextRole }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم تحديث الدور إلى ${nextRole}` : `Role updated to ${nextRole}`, kind: 'ok' });
      onSaved && onSaved(nextRole);
      setSaved(true);
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  const onRoleChange = async (e) => {
    const nextRole = e.target.value;
    if (nextRole === role || busy) return;
    setRole(nextRole);
    await saveRole(nextRole);
  };

  return (
    <div className="drawer-overlay" onClick={onClose} style={{ zIndex: 300 }}>
      <div
        onClick={e => e.stopPropagation()}
        style={{
          position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
          background: 'var(--bg-elev-1)', border: '1px solid var(--line-hi)',
          borderRadius: 'var(--r-lg)', padding: 24, width: 340, maxWidth: '90vw',
          boxShadow: '0 24px 64px -16px #00000080', zIndex: 301,
        }}
      >
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 16 }}>
          <div style={{ fontWeight: 600, fontSize: 14 }}>{(window.appT||(s=>s))("Edit role")} — {person.display_name}</div>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        {err && (
          <div className="badge badge-err" style={{ marginBottom: 10, padding:'7px 12px', fontSize: 12 }}>
            <Icon name="alert" size={13}/> {err}
          </div>
        )}
        <label className="login-label">{(window.appT||(s=>s))("Dashboard role")}</label>
        <select className="sel" value={role} onChange={onRoleChange} disabled={busy} style={{ width:'100%', marginBottom: 10 }}>
          <option value="employee">{(window.appT||(s=>s))("Employee")}</option>
          <option value="manager">{(window.appT||(s=>s))("Manager")}</option>
          <option value="admin">{(window.appT||(s=>s))("Admin")}</option>
        </select>
        <div style={{ minHeight: 18, marginBottom: 10, fontSize: 12, color: saved ? 'var(--ok)' : 'var(--text-3)' }}>
          {busy ? (window.appT||(s=>s))('Saving role…') : saved ? (window.appT||(s=>s))('Saved automatically.') : (window.appT||(s=>s))('Changes save automatically when switched.')}
        </div>
        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end' }}>
          <button className="btn btn-pri" onClick={onClose}><Icon name="check" size={13}/> {(window.appT||(s=>s))("Done")}</button>
        </div>
      </div>
    </div>
  );
}

/* ── Profile field — module-scope to avoid closure-remount bug ── */
// Defaults by region:
//  - Cairo / Riyadh / Dubai / Kuwait / Qatar / Bahrain / Muscat:
//    SUNDAY-THURSDAY workweek (Friday + Saturday are weekend).
//    Returns "mon,tue,wed,thu,sun" in stable Mon..Sun storage order.
//  - Americas / Europe / UTC: Mon-Fri (Sat + Sun off).
// Used both by Edit Profile and the Add Employee wizard.
function defaultWorkingDaysForTz(tz) {
  if (!tz) return 'mon,tue,wed,thu,fri';
  if (tz === 'Africa/Cairo' || tz.startsWith('Africa/') ||
      tz === 'Asia/Riyadh' || tz === 'Asia/Dubai' ||
      tz === 'Asia/Kuwait' || tz === 'Asia/Qatar' ||
      tz === 'Asia/Bahrain' || tz === 'Asia/Muscat') {
    return 'mon,tue,wed,thu,sun';
  }
  return 'mon,tue,wed,thu,fri';
}
window.defaultWorkingDaysForTz = defaultWorkingDaysForTz;

function WorkingDaysChips({ value, onChange }) {
  // B4: clickable day-of-week chips. value is a comma-separated string like
  // "mon,tue,wed,thu,fri". Emits the same string format so existing
  // PATCH/PUT endpoints don't need to change.
  const DAYS = [
    { k: 'mon', l: (window.appT||(s=>s))('Mon') }, { k: 'tue', l: (window.appT||(s=>s))('Tue') }, { k: 'wed', l: (window.appT||(s=>s))('Wed') },
    { k: 'thu', l: (window.appT||(s=>s))('Thu') }, { k: 'fri', l: (window.appT||(s=>s))('Fri') }, { k: 'sat', l: (window.appT||(s=>s))('Sat') },
    { k: 'sun', l: (window.appT||(s=>s))('Sun') },
  ];
  const selected = new Set(
    String(value || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean)
  );
  const toggle = (k) => {
    const next = new Set(selected);
    if (next.has(k)) next.delete(k); else next.add(k);
    const order = DAYS.map(d => d.k).filter(d => next.has(d));
    onChange(order.join(','));
  };
  return (
    <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
      {DAYS.map(d => (
        <button type="button" key={d.k}
          onClick={() => toggle(d.k)}
          className={`btn ${selected.has(d.k) ? 'btn-pri' : 'btn-ghost'}`}
          style={{ fontSize: 11.5, padding: '4px 10px', minWidth: 44 }}
          title={(window.appLang&&window.appLang()==='ar') ? `تبديل ${d.l}` : `Toggle ${d.l}`}>
          {d.l}
        </button>
      ))}
      {/* Three quick presets so admin doesn't have to click each chip. */}
      <button type="button"
        onClick={() => onChange('mon,tue,wed,thu,fri')}
        className="btn btn-ghost"
        style={{ fontSize: 11, padding: '4px 8px', marginLeft: 6, color: 'var(--text-3)' }}
        title={(window.appT||(s=>s))("Mon-Fri workweek (Americas / Europe / UTC default; Sat+Sun off)")}>{(window.appT||(s=>s))("Mon–Fri")}</button>
      <button type="button"
        onClick={() => onChange('mon,tue,wed,thu,sun')}
        className="btn btn-ghost"
        style={{ fontSize: 11, padding: '4px 8px', color: 'var(--text-3)' }}
        title={(window.appT||(s=>s))("Sun–Thu (Cairo / Middle East workweek; Fri+Sat off)")}>{(window.appT||(s=>s))("Cairo")}</button>
      <button type="button"
        onClick={() => onChange('mon,tue,wed,thu,fri,sat,sun')}
        className="btn btn-ghost"
        style={{ fontSize: 11, padding: '4px 8px', color: 'var(--text-3)' }}
        title={(window.appT||(s=>s))("All 7 days — flexible / always-on employees with no fixed day off (the bot won't send any off-day DMs)")}>{(window.appT||(s=>s))("All 7")}</button>
    </div>
  );
}

function ProfileField({ label, value, onChange, type = 'text', placeholder }) {
  return (
    <label style={{ display:'flex', flexDirection:'column', gap: 4 }}>
      <span style={{ fontSize: 11.5, color:'var(--text-2)' }}>{label}</span>
      <input
        className="inp"
        type={type}
        value={value}
        onChange={onChange}
        placeholder={placeholder}
      />
    </label>
  );
}

/* Curated fallback list (used if Intl.supportedValuesOf is unavailable). */
const _TZ_FALLBACK = [
  'UTC',
  'America/New_York','America/Chicago','America/Denver','America/Los_Angeles',
  'America/Anchorage','America/Phoenix','America/Toronto','America/Vancouver',
  'America/Mexico_City','America/Sao_Paulo','America/Bogota','America/Buenos_Aires',
  'Europe/London','Europe/Dublin','Europe/Lisbon','Europe/Paris','Europe/Berlin',
  'Europe/Amsterdam','Europe/Madrid','Europe/Rome','Europe/Warsaw','Europe/Stockholm',
  'Europe/Zurich','Europe/Athens','Europe/Moscow','Europe/Istanbul',
  'Africa/Cairo','Africa/Johannesburg','Africa/Lagos','Africa/Nairobi',
  'Asia/Jerusalem','Asia/Beirut','Asia/Riyadh','Asia/Dubai','Asia/Karachi',
  'Asia/Kolkata','Asia/Dhaka','Asia/Bangkok','Asia/Singapore','Asia/Hong_Kong',
  'Asia/Shanghai','Asia/Taipei','Asia/Tokyo','Asia/Seoul',
  'Australia/Perth','Australia/Sydney','Australia/Melbourne','Australia/Brisbane',
  'Pacific/Auckland','Pacific/Honolulu','Pacific/Fiji',
];

function TimezoneField({ label, value, onChange, placeholder }) {
  const tzList = useMemo(() => {
    try {
      if (typeof Intl !== 'undefined' && typeof Intl.supportedValuesOf === 'function') {
        const list = Intl.supportedValuesOf('timeZone');
        if (Array.isArray(list) && list.length) return list;
      }
    } catch (_) { /* ignore */ }
    return _TZ_FALLBACK;
  }, []);
  // Make sure the current value is selectable even if it's not in the list.
  const options = useMemo(() => {
    const v = (value || '').trim();
    if (v && !tzList.includes(v)) return [v, ...tzList];
    return tzList;
  }, [tzList, value]);
  const handle = (e) => {
    // ProfileField passes a synthetic event; here we forward a string for
    // consistency with the existing set('timezone') handler shape.
    if (typeof onChange === 'function') {
      // The set() helper expects an event with target.value, so synthesize one.
      onChange({ target: { value: e.target.value } });
    }
  };
  return (
    <label style={{ display:'flex', flexDirection:'column', gap: 4 }}>
      <span style={{ fontSize: 11.5, color:'var(--text-2)' }}>{label}</span>
      <select
        className="inp"
        value={value || ''}
        onChange={handle}
      >
        {!value && <option value="" disabled>{placeholder || (window.appT||(s=>s))('Select a timezone…')}</option>}
        {options.map(tz => <option key={tz} value={tz}>{tz}</option>)}
      </select>
    </label>
  );
}

/* React error boundary so any render crash inside the modal only takes
 * down the modal — never the whole app (the bug that produced a totally
 * blank screen when chip click threw TypeError). User sees a friendly
 * fallback + Close button instead. */
// Expose to window so page-ops, page-misc, page-bots can wrap their
// own dialogs without redefining the boundary class.
window.ModalErrorBoundary = window.ModalErrorBoundary || class ModalErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { error: null }; }
  static getDerivedStateFromError(error) { return { error }; }
  componentDidCatch(error, info) {
    try { console.error('Edit Profile modal crashed:', error, info); } catch (_) {}
  }
  render() {
    if (this.state.error) {
      return (
        <div className="drawer-overlay" onClick={this.props.onClose} style={{ zIndex: 300 }}>
          <div onClick={(e) => e.stopPropagation()} style={{
            background: 'var(--bg-elev-1)', border: '1px solid var(--line-hi)',
            borderRadius: 'var(--r-lg)', width: 420, maxWidth: '92vw', padding: 22,
            position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
            boxShadow: '0 24px 80px -20px #000000c0',
          }}>
            <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 8 }}>
              {(window.appT||(s=>s))("Something went wrong loading this profile editor")}
            </div>
            <div className="muted" style={{ fontSize: 12.5, marginBottom: 16, lineHeight: 1.5 }}>
              {(window.appT||(s=>s))("The error didn't affect anything else — close this dialog and try again. If it keeps happening, take a screenshot and report the bug.")}
            </div>
            <pre style={{
              fontSize: 11, background: 'var(--bg)', padding: 8, borderRadius: 6,
              border: '1px solid var(--line)', color: 'var(--text-3)',
              maxHeight: 120, overflow: 'auto', marginBottom: 12,
            }}>{String(this.state.error?.message || this.state.error)}</pre>
            <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
              <button className="btn btn-pri" onClick={this.props.onClose}>{(window.appT||(s=>s))("Close")}</button>
            </div>
          </div>
        </div>
      );
    }
    return this.props.children;
  }
};
// Alias so existing references inside this file (wrapping EditProfile +
// AddEmployeeWizard) keep working without a rename.
const ModalErrorBoundary = window.ModalErrorBoundary;

/* ── Edit Profile Modal ── */
function EditProfileModal({ person, onClose }) {
  const toast = useToast();
  const [busy, setBusy] = useState(false);
  const [loading, setLoading] = useState(true);
  const [err, setErr] = useState('');
  const [form, setForm] = useState({
    display_name: person.display_name || '',
    role: person.role || '',
    team: person.team || '',
    timezone: person.timezone || 'UTC',
    working_days: '',
    week_start_day: null,
    weekly_hours: '',
    team_weekly_hours: null,
    pto_quota: '',
    is_flexible: false,
    org_role: person.org_role || 'employee',
    active: person.active !== false,
    phone: '',
    alt_phone: '',
    private_email: '',
    work_email: '',
    personal_email: '',
    whatsapp: '',
    contact_note: '',
    job_title: '',
    job_function: '',
    regular_tasks: '',
  });
  const [allTeams, setAllTeams] = useState([]);
  const [roleCatalog, setRoleCatalog] = useState([]);
  const [requesting, setRequesting] = useState(false);

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  useEffect(() => {
    let cancelled = false;
    const load = async () => {
      setLoading(true);
      try {
        const r = await fetch(`/api/employees/${person.id}/profile`, {
          credentials: 'same-origin',
        });
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        const data = await r.json();
        if (cancelled) return;
        setForm(f => ({
          ...f,
          display_name: data.display_name || person.display_name || '',
          role: data.role || person.role || '',
          team: data.team || person.team || '',
          timezone: data.timezone || person.timezone || 'UTC',
          working_days: data.working_days || '',
          week_start_day: data.week_start_day != null ? data.week_start_day : null,
          weekly_hours: (data.profile && data.profile.weekly_hours != null) ? data.profile.weekly_hours
            : (data.weekly_hours != null ? data.weekly_hours : ''),
          team_weekly_hours: (data.team_weekly_hours != null) ? data.team_weekly_hours : null,
          pto_quota: (data.profile && data.profile.pto_quota != null) ? data.profile.pto_quota
            : (data.pto_quota != null ? data.pto_quota : ''),
          is_flexible: !!data.is_flexible,
          org_role: data.org_role || person.org_role || 'employee',
          active: data.active !== false,
          job_title: (data.profile && data.profile.job_title) || data.job_title || '',
          job_function: (data.profile && data.profile.job_function) || data.job_function || '',
          regular_tasks: (data.profile && data.profile.regular_tasks) || data.regular_tasks || '',
        }));
      } catch (_) {
        if (!cancelled) {
          setForm((f) => ({
            ...f,
            display_name: person.display_name || '',
            role: person.role || '',
            team: person.team || '',
            timezone: person.timezone || 'UTC',
          }));
        }
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    load();

    // Load contact fields + team list (admin-only endpoints).
    fetch(`/api/employees/${person.id}/contact`, { credentials:'same-origin' })
      .then(r => r.ok ? r.json() : null)
      .then(c => { if (c && !cancelled) setForm(f => ({ ...f,
        phone: c.phone || '',
        alt_phone: c.alt_phone || '',
        private_email: c.private_email || '',
        work_email: c.work_email || '',
        personal_email: c.personal_email || '',
        whatsapp: c.whatsapp || '',
        contact_note: c.contact_note || '',
      })); })
      .catch(() => {});
    fetch('/api/teams', { credentials:'same-origin' })
      .then(r => r.ok ? r.json() : [])
      .then(ts => { if (!cancelled) setAllTeams(Array.isArray(ts) ? ts : []); })
      .catch(() => {});
    fetch('/api/roles/catalog', { credentials:'same-origin' })
      .then(r => r.ok ? r.json() : { roles: [] })
      .then(d => { if (!cancelled) setRoleCatalog(Array.isArray(d.roles) ? d.roles : []); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [person.id]);

  const set = (key) => (e) => setForm(f => ({ ...f, [key]: e.target.value }));

  const save = async () => {
    setBusy(true); setErr('');
    try {
      const payload = {
        display_name: form.display_name,
        role: form.role,
        team: form.team,
        timezone: form.timezone,
        working_days: String(form.working_days || '').trim() || null,
        week_start_day: form.week_start_day != null ? parseInt(form.week_start_day, 10) : null,
        weekly_hours: String(form.weekly_hours || '').trim() === '' ? 0 : Number(form.weekly_hours),
        pto_quota: String(form.pto_quota || '').trim() === '' ? 0 : Number(form.pto_quota),
        is_flexible: !!form.is_flexible,
        org_role: form.org_role || 'employee',
        active: !!form.active,
        job_title: String(form.job_title || '').trim() || null,
        job_function: String(form.job_function || '').trim() || null,
        regular_tasks: String(form.regular_tasks || '').trim() || null,
      };
      const r = await fetch(`/api/employees/${person.id}/profile`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify(payload),
      });
      // Contact details — admin-only endpoint, ignore failures gracefully.
      try {
        await fetch(`/api/employees/${person.id}/contact`, {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          credentials: 'same-origin',
          body: JSON.stringify({
            phone: form.phone || '',
            alt_phone: form.alt_phone || '',
            private_email: form.private_email || '',
            work_email: form.work_email || '',
            personal_email: form.personal_email || '',
            whatsapp: form.whatsapp || '',
            contact_note: form.contact_note || '',
          }),
        });
      } catch(_) {}
      if (!r.ok) {
        if (r.status === 404) {
          toast.push({ msg: (window.appT||(s=>s))('Coming soon — endpoint not yet available'), kind: 'warn' });
          onClose();
          return;
        }
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      person.display_name = form.display_name;
      person.role = form.role;
      person.team = form.team;
      person.timezone = form.timezone;
      person.working_days = form.working_days;
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم تحديث الملف الشخصي لـ ${person.display_name}` : `Profile updated for ${person.display_name}`, kind: 'ok' });
      try {
        window.dispatchEvent(new CustomEvent('attendance-updated'));
        window.dispatchEvent(new CustomEvent('status-data-refresh-requested'));
      } catch (_) {}
      onClose();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  const requestFromEmployee = async () => {
    setRequesting(true);
    try {
      const r = await fetch('/api/admin/contact-requests', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ user_ids: [person.id] }),
      });
      if (!r.ok) {
        const b = await r.json().catch(() => null);
        throw new Error(parseDetail(b, r.status));
      }
      const data = await r.json().catch(() => ({}));
      if ((data.requested || 0) > 0) {
        toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `طلبت من ${person.display_name} تفاصيل الاتصال عبر البوت` : `Asked ${person.display_name} for contact details via the bot`, kind: 'ok' });
      } else {
        toast.push({ msg: (window.appT||(s=>s))('No active employee to ask (test/inactive accounts are skipped)'), kind: 'warn' });
      }
    } catch (e) {
      toast.push({ msg: (window.appT||(s=>s))('Request failed: ') + e.message, kind: 'err' });
    } finally {
      setRequesting(false);
    }
  };

  // Portal to <body>: this modal is rendered from INSIDE an employee card,
  // and any transformed/contained ancestor would become the containing block
  // for its position:fixed overlay (modal clipped to the card box / jumping
  // while scrolling as hover toggled the card's transform). Rendering into
  // document.body makes it immune to every ancestor trap.
  return ReactDOM.createPortal(
    <div className="drawer-overlay" onClick={onClose} style={{ zIndex: 300 }}>
      <div
        onClick={e => e.stopPropagation()}
        style={{
          position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
          background: 'var(--bg-elev-1)', border: '1px solid var(--line-hi)',
          borderRadius: 'var(--r-lg)', padding: 24, width: 460, maxWidth: '95vw',
          boxShadow: '0 24px 64px -16px #00000080', zIndex: 301,
          maxHeight: '90vh', overflowY: 'auto',
        }}
      >
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 16 }}>
          <div style={{ fontWeight: 600, fontSize: 14 }}>{(window.appT||(s=>s))("Edit profile")} — {person.display_name}</div>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        {err && (
          <div className="badge badge-err" style={{ marginBottom: 12, padding:'7px 12px', fontSize: 12 }}>
            <Icon name="alert" size={13}/> {err}
          </div>
        )}
        {loading ? (
          <Loading label={(window.appT||(s=>s))("Loading profile…")} rows={3} />
        ) : (
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 12, marginBottom: 16, alignItems:'start' }}>
          {/* ── Identity ── */}
          <ProfileField label={(window.appT||(s=>s))("Display name")} value={form.display_name} onChange={set('display_name')} placeholder="Jane Smith" />
          <ProfileField label={(window.appT||(s=>s))("Role / title")} value={form.role} onChange={set('role')} placeholder={(window.appT||(s=>s))("Engineer")} />

          {/* ── Team + Timezone (two short selects, paired) ── */}
          <label style={{ display:'flex', flexDirection:'column', gap: 4 }}>
            <span style={{ fontSize: 11.5, color:'var(--text-2)' }}>{(window.appT||(s=>s))("Team")}</span>
            <select className="sel" value={form.team} onChange={set('team')} style={{ width:'100%' }}>
              <option value="">—</option>
              {(allTeams || []).filter(t => t && (t.team_name || t.name)).map(team => {
                const label = team.team_name || team.name;
                return <option key={label} value={label}>{label}</option>;
              })}
              {form.team && !((allTeams || []).some(t => (t && (t.team_name || t.name)) === form.team)) && (
                <option value={form.team}>{form.team} {(window.appT||(s=>s))("(current)")}</option>
              )}
            </select>
          </label>
          <TimezoneField
            label={(window.appT||(s=>s))("Timezone")}
            value={form.timezone}
            // Auto-seed working_days when admin picks a new TZ (Cairo / ME
            // → all 7 days, Americas/Europe → Mon-Fri) but only if the
            // field is still empty — never overwrite an admin's choice.
            onChange={(e) => {
              const tz = e.target.value;
              setForm((f) => {
                const next = { ...f, timezone: tz };
                if (!String(f.working_days || '').trim()) {
                  next.working_days = window.defaultWorkingDaysForTz
                    ? window.defaultWorkingDaysForTz(tz)
                    : 'mon,tue,wed,thu,fri';
                }
                return next;
              });
            }}
            placeholder="America/New_York"
          />

          {/* ── Org role (FULL WIDTH — long option labels + helper text
              don't fit a half column, which caused the lopsided gap) ── */}
          <label style={{ gridColumn:'1 / -1', display:'flex', flexDirection:'column', gap: 4 }}>
            <span style={{ fontSize: 11.5, color:'var(--text-2)' }}>{(window.appT||(s=>s))("Org role")}</span>
            <select
              className="sel"
              value={form.org_role || 'employee'}
              onChange={(e) => setForm(f => ({ ...f, org_role: e.target.value }))}
              title={(window.appT||(s=>s))("employee/team_lead/manager → tracked normally. moderator/admin → flexible (no nagging). superadmin → bot-god (not tracked at all).")}
              style={{ width:'100%' }}
            >
              <option value="employee">{(window.appT||(s=>s))("employee")}</option>
              <option value="team_lead">{(window.appT||(s=>s))("team_lead")}</option>
              <option value="manager">{(window.appT||(s=>s))("manager")}</option>
              <option value="moderator">{(window.appT||(s=>s))("moderator — flexible, manages others")}</option>
              <option value="admin">{(window.appT||(s=>s))("admin — flexible")}</option>
              <option value="superadmin">{(window.appT||(s=>s))("superadmin — bot-god, never tracked")}</option>
            </select>
            <div className="muted" style={{ fontSize: 10.5, marginTop: 2, lineHeight: 1.4 }}>
              {form.org_role === 'superadmin'
                ? (window.appT||(s=>s))('Bot ignores this user completely — no DMs, no attendance counting, no reports.')
                : (form.org_role === 'moderator' || form.org_role === 'admin')
                  ? (window.appT||(s=>s))('Flexible: no nag DMs, no strikes, still counted on dashboard.')
                  : (window.appT||(s=>s))('Tracked normally: off-day DMs, strikes, and reminders apply.')}
            </div>
          </label>

          {/* ── Working days (FULL WIDTH so the 7 chips lay out in one row
              instead of wrapping 2-per-line in a cramped half column) ── */}
          <div style={{ gridColumn:'1 / -1', display: 'flex', flexDirection: 'column', gap: 6 }}>
            <label className="muted" style={{ fontSize: 11.5, fontWeight: 500 }}>{(window.appT||(s=>s))("Working days")}</label>
            <WorkingDaysChips
              value={form.working_days}
              // WorkingDaysChips emits the new comma-string directly; set()
              // returns an event-handler that reads e.target.value, so
              // passing it raw crashes with TypeError (.target undefined)
              // and blanks the modal. Use a direct value-setter.
              onChange={(v) => setForm(f => ({ ...f, working_days: v }))}
            />
            <div className="muted" style={{ fontSize: 10.5, marginTop: 2, lineHeight: 1.45 }}>
              <strong style={{ color: 'var(--text-2)' }}>{(window.appT||(s=>s))("Each employee can have a different schedule")}</strong>{' '}
              {(window.appT||(s=>s))("— even in the same country. Pick exactly the days this person works. Empty means treat every day as working (no off-day DMs). Presets: Mon-Fri (Americas), Cairo (Sun-Thu), All 7 (flexible / always-on).")}
            </div>
          </div>

          {/* ── Week start override ── */}
          <div style={{ gridColumn:'1 / -1', display: 'flex', flexDirection: 'column', gap: 4 }}>
            <label className="muted" style={{ fontSize: 11.5, fontWeight: 500 }}>{(window.appT||(s=>s))("Week starts on (override)")}</label>
            <select
              value={form.week_start_day != null ? String(form.week_start_day) : ''}
              onChange={e => setForm(f => ({ ...f, week_start_day: e.target.value === '' ? null : parseInt(e.target.value, 10) }))}
              style={{ fontSize: 13, padding: '4px 8px', maxWidth: 200 }}
            >
              <option value="">{(window.appT||(s=>s))("Inherit from team (default)")}</option>
              <option value="0">{(window.appT||(s=>s))("Monday")}</option>
              <option value="1">{(window.appT||(s=>s))("Tuesday")}</option>
              <option value="2">{(window.appT||(s=>s))("Wednesday")}</option>
              <option value="3">{(window.appT||(s=>s))("Thursday")}</option>
              <option value="4">{(window.appT||(s=>s))("Friday")}</option>
              <option value="5">{(window.appT||(s=>s))("Saturday")}</option>
              <option value="6">{(window.appT||(s=>s))("Sunday")}</option>
            </select>
            <div className="muted" style={{ fontSize: 10.5, lineHeight: 1.45 }}>
              {(window.appT||(s=>s))("Controls which day this employee's 'this week' window starts on. Leave as Inherit to use the team default (usually Sunday).")}
            </div>
          </div>

          {/* ── Expected hours / week override ── */}
          <div style={{ gridColumn:'1 / -1', display: 'flex', flexDirection: 'column', gap: 4 }}>
            <label className="muted" style={{ fontSize: 11.5, fontWeight: 500 }}>{(window.appT||(s=>s))("Expected hours / week (overrides team)")}</label>
            <input
              className="inp"
              type="number"
              min="0"
              max="168"
              step="0.5"
              value={form.weekly_hours}
              onChange={e => setForm(f => ({ ...f, weekly_hours: e.target.value }))}
              placeholder={form.team_weekly_hours != null
                ? `${(window.appT||(s=>s))("Team default")}: ${form.team_weekly_hours}h`
                : (window.appT||(s=>s))("Team default")}
              style={{ maxWidth: 200 }}
            />
            <div className="muted" style={{ fontSize: 10.5, lineHeight: 1.45 }}>
              {String(form.weekly_hours || '').trim() !== ''
                ? `${(window.appT||(s=>s))("This employee is expected to work")} ${form.weekly_hours}h/week${form.team_weekly_hours != null ? ` (${(window.appT||(s=>s))("team default")}: ${form.team_weekly_hours}h)` : ''}.`
                : (form.team_weekly_hours != null
                    ? `${(window.appT||(s=>s))("Blank = use the team default of")} ${form.team_weekly_hours}h/week.`
                    : (window.appT||(s=>s))("Blank = use the team default."))}
              {' '}
              {(window.appT||(s=>s))("Governs scoring, PTO proration, the short-day nudge, overtime, shift-end timing and hour targets.")}
            </div>
          </div>

          {/* ── PTO / flex-day allowance override (seniors get more) ── */}
          <div style={{ gridColumn:'1 / -1', display: 'flex', flexDirection: 'column', gap: 4 }}>
            <label className="muted" style={{ fontSize: 11.5, fontWeight: 500 }}>{(window.appT||(s=>s))("PTO allowance (days / year)")}</label>
            <input
              className="inp"
              type="number"
              min="0"
              max="366"
              step="1"
              value={form.pto_quota}
              onChange={e => setForm(f => ({ ...f, pto_quota: e.target.value }))}
              placeholder={(window.appT||(s=>s))("Org default")}
              style={{ maxWidth: 200 }}
            />
            <div className="muted" style={{ fontSize: 10.5, lineHeight: 1.45 }}>
              {(window.appT||(s=>s))("Blank = use the org default. Set a higher number for senior staff — drives this person's leave balance and remaining days.")}
            </div>
          </div>

          {/* ── Flexible toggle (FULL WIDTH — long description) ── */}
          <div style={{
            gridColumn:'1 / -1',
            display: 'flex', alignItems: 'flex-start', gap: 10,
            padding: 10, borderRadius: 'var(--r-md)',
            background: 'var(--bg)', border: '1px solid var(--line)',
          }}>
            <input
              type="checkbox"
              id="profile-is-flexible"
              checked={!!form.is_flexible}
              onChange={(e) => setForm(f => ({ ...f, is_flexible: e.target.checked }))}
              style={{ marginTop: 3, cursor: 'pointer' }}
            />
            <label htmlFor="profile-is-flexible" style={{ flex: 1, cursor: 'pointer' }}>
              <div style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--text-1)' }}>
                {(window.appT||(s=>s))("Flexible / superadmin (no bot nagging)")}
              </div>
              <div className="muted" style={{ fontSize: 10.5, marginTop: 3, lineHeight: 1.45 }}>
                {(window.appT||(s=>s))("Skip every nag DM for this person: no off-day messages, no format strikes, no no-progress nudges, no missed-signoff reminders. Use for founders, on-call leads, 24/7 admins, anyone who shouldn't be bugged about missed goals or attendance rules. They still get OTP login codes, welcome DMs, and admin-initiated messages.")}
              </div>
            </label>
          </div>

          {/* ── Active toggle (FULL WIDTH) — (re)activate or deactivate
              a user without a DB script. Inactive users drop off the
              roster + attendance counts but keep all history. ── */}
          <div style={{
            gridColumn:'1 / -1',
            display: 'flex', alignItems: 'center', gap: 10,
            padding: 10, borderRadius: 'var(--r-md)',
            background: form.active ? 'var(--bg)' : 'rgba(220,80,80,.08)',
            border: '1px solid ' + (form.active ? 'var(--line)' : 'var(--err)'),
          }}>
            <input
              type="checkbox"
              id="profile-active"
              checked={!!form.active}
              onChange={(e) => setForm(f => ({ ...f, active: e.target.checked }))}
              style={{ cursor: 'pointer' }}
            />
            <label htmlFor="profile-active" style={{ flex: 1, cursor: 'pointer' }}>
              <div style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--text-1)' }}>
                {(window.appT||(s=>s))("Active employee")} {form.active ? '' : (window.appT||(s=>s))('— currently INACTIVE')}
              </div>
              <div className="muted" style={{ fontSize: 10.5, marginTop: 3, lineHeight: 1.45 }}>
                {(window.appT||(s=>s))("When on, this person is tracked: sign-ins/offs are acked, they show on the roster, Today, and reports. Uncheck to deactivate (hides from counts but keeps all history) — or check to (re)activate someone who was added to a room but never turned on.")}
              </div>
            </label>
          </div>

          {/* ── Role & Functions ── */}
          <div style={{ gridColumn:'1 / -1', marginTop: 4 }}>
            <span style={{ fontSize: 11.5, fontWeight: 600, color:'var(--text-2)', textTransform:'uppercase', letterSpacing: .4 }}>{(window.appT||(s=>s))("Role & Functions")}</span>
          </div>
          <ProfileField
            label={(window.appT||(s=>s))("Job title")}
            value={form.job_title}
            onChange={set('job_title')}
            placeholder={(window.appT||(s=>s))("e.g. Senior Engineer")}
          />
          <label style={{ display:'flex', flexDirection:'column', gap: 4 }}>
            <span style={{ fontSize: 11.5, color:'var(--text-2)' }}>{(window.appT||(s=>s))("Job function")}</span>
            <select
              className="sel"
              value={form.job_function || ''}
              onChange={e => setForm(f => ({ ...f, job_function: e.target.value }))}
              style={{ width:'100%' }}
            >
              <option value="">— {(window.appT||(s=>s))("None")} —</option>
              {roleCatalog.map(r => (
                <option key={r.key} value={r.key}>{r.title}</option>
              ))}
            </select>
            {form.job_function && roleCatalog.length > 0 && (() => {
              const picked = roleCatalog.find(r => r.key === form.job_function);
              return picked && picked.summary ? (
                <div className="muted" style={{ fontSize: 10.5, lineHeight: 1.4, marginTop: 2 }}>{picked.summary}</div>
              ) : null;
            })()}
          </label>
          <div style={{ gridColumn:'1 / -1', display:'flex', flexDirection:'column', gap: 4 }}>
            <label className="muted" style={{ fontSize: 11.5, fontWeight: 500 }}>{(window.appT||(s=>s))("Regular tasks")}</label>
            {form.job_function && roleCatalog.length > 0 && (() => {
              const picked = roleCatalog.find(r => r.key === form.job_function);
              const fns = picked && Array.isArray(picked.functions) ? picked.functions : [];
              if (!fns.length) return null;
              return (
                <div style={{ display:'flex', flexWrap:'wrap', gap: 4, marginBottom: 4 }}>
                  {fns.map((fn, i) => (
                    <button
                      key={i}
                      type="button"
                      className="chip"
                      style={{ fontSize: 10.5, cursor:'pointer', background:'var(--bg)', color:'var(--text-2)', border:'1px solid var(--line)', padding:'2px 8px' }}
                      onClick={() => setForm(f => {
                        const cur = String(f.regular_tasks || '').trimEnd();
                        const append = fn.name || fn;
                        return { ...f, regular_tasks: cur ? cur + '\n' + append : append };
                      })}
                      title={(window.appT||(s=>s))("Append to regular tasks")}
                    >+ {fn.name || fn}</button>
                  ))}
                </div>
              );
            })()}
            <textarea
              className="inp"
              rows={3}
              value={form.regular_tasks}
              onChange={set('regular_tasks')}
              placeholder={(window.appT||(s=>s))("one task per line")}
              style={{ width:'100%', resize:'vertical', fontFamily:'inherit', fontSize: 13 }}
            />
            <div className="muted" style={{ fontSize: 10.5, lineHeight: 1.4 }}>
              {(window.appT||(s=>s))("The specific functions this person owns (e.g. from the role's function list).")}
            </div>
          </div>

          {/* ── Contact (short fields, paired) ── */}
          <div style={{ gridColumn:'1 / -1', display:'flex', alignItems:'center', justifyContent:'space-between', gap: 8, marginTop: 4 }}>
            <span style={{ fontSize: 11.5, fontWeight: 600, color:'var(--text-2)', textTransform:'uppercase', letterSpacing: .4 }}>{(window.appT||(s=>s))("Contact")}</span>
            <button
              type="button"
              className="btn btn-ghost"
              style={{ fontSize: 11.5, padding:'3px 10px' }}
              onClick={requestFromEmployee}
              disabled={requesting}
              title={(window.appT||(s=>s))("DM this employee and ask them to fill in their contact details")}
            >
              {requesting
                ? (window.appT||(s=>s))('Requesting…')
                : <><Icon name="msg" size={12}/> {(window.appT||(s=>s))("Request from employee")}</>}
            </button>
          </div>
          <ProfileField label={(window.appT||(s=>s))("Work email")} value={form.work_email} type="email" onChange={set('work_email')} placeholder="name@company.com" />
          <ProfileField label={(window.appT||(s=>s))("Personal email")} value={form.personal_email} type="email" onChange={set('personal_email')} placeholder="me@example.com" />
          <ProfileField label={(window.appT||(s=>s))("WhatsApp")} value={form.whatsapp} onChange={set('whatsapp')} placeholder="+1 555-555-1234" />
          <ProfileField label={(window.appT||(s=>s))("Phone")} value={form.phone} onChange={set('phone')} placeholder="+1 555-555-1234" />
          <ProfileField label={(window.appT||(s=>s))("Alt phone")} value={form.alt_phone} onChange={set('alt_phone')} placeholder={(window.appT||(s=>s))("optional backup #")} />
          <ProfileField label={(window.appT||(s=>s))("Private email")} value={form.private_email} type="email" onChange={set('private_email')} placeholder="personal@example.com" />
          <div style={{ gridColumn:'1 / -1' }}>
            <ProfileField label={(window.appT||(s=>s))("Contact note")} value={form.contact_note} onChange={set('contact_note')} placeholder={(window.appT||(s=>s))("optional — best time to reach, preferred channel…")} />
          </div>
        </div>
        )}
        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end' }}>
          <button className="btn btn-ghost" onClick={onClose}>{(window.appT||(s=>s))("Cancel")}</button>
          <button className="btn btn-pri" onClick={save} disabled={busy || loading}>
            {busy ? (window.appT||(s=>s))('Saving…') : <><Icon name="check" size={13}/> {(window.appT||(s=>s))("Save profile")}</>}
          </button>
        </div>
      </div>
    </div>,
    document.body
  );
}

/* ── Multi-slice attendance pie used in EmployeeScoreboardModal ── */
function _ScorePie({ slices, size = 120 }) {
  const total = slices.reduce((s, x) => s + (x.value || 0), 0) || 1;
  const r = size / 2 - 8;
  const c = 2 * Math.PI * r;
  const cx = size / 2, cy = size / 2;
  let off = 0;
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{flexShrink:0}}>
      <circle cx={cx} cy={cy} r={r} fill="none" stroke="var(--line)" strokeWidth={12} />
      {slices.map((s, i) => {
        if (!s.value) return null;
        const len = (s.value / total) * c;
        const node = (
          <circle key={i} cx={cx} cy={cy} r={r} fill="none" stroke={s.color} strokeWidth={12}
            strokeDasharray={`${len} ${c - len}`} strokeDashoffset={-off} strokeLinecap="butt">
            <title>{s.label}: {s.value} {s.unit || ''}</title>
          </circle>
        );
        off += len;
        return node;
      })}
      <text x={cx} y={cy-2} textAnchor="middle" fontSize={18} fontWeight={700}
            fontFamily="var(--font-mono)" fill="var(--text)">{Math.round(total)}</text>
      <text x={cx} y={cy+14} textAnchor="middle" fontSize={9} fill="var(--text-3)"
            fontFamily="var(--font-mono)">{(window.appT||(s=>s))("total")}</text>
    </svg>
  );
}

/* ── Employee scoreboard modal — opens from the report icon on each row ── */
function EmployeeScoreboardModal({ person, onClose }) {
  const [data, setData]       = useState(null);
  const [aiSummary, setAi]    = useState(null);
  const [aiLoading, setAiLd]  = useState(false);
  const [days, setDays]       = useState(30);
  const [sending, setSending] = useState(false);
  const [error, setError]     = useState('');
  const toast = useToast();

  const fetchScoreboard = async () => {
    setError('');
    try {
      const r = await fetch(`/api/employees/${person.id}/scoreboard?days=${days}`, { credentials:'include' });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(employeeApiErrorText(body?.detail, `${r.status}`));
      }
      setData(await r.json());
    } catch(e) {
      setError(e.message);
    }
  };

  useEffect(() => { fetchScoreboard(); }, [days, person.id]);

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const generateAi = async () => {
    setAiLd(true);
    try {
      const r = await fetch(`/api/reports/accomplishments-ai?days=${days}`, { credentials:'include' });
      if (!r.ok) throw new Error(`${r.status}`);
      const j = await r.json();
      const me = (j.users || []).find(u => u.user_id === person.id);
      setAi(me ? me.ai_summary : (window.appT||(s=>s))('_No accomplishments data for this user._'));
    } catch(e) {
      toast.push({ msg: (window.appT||(s=>s))('AI summary failed: ') + e.message, kind: 'err' });
    } finally { setAiLd(false); }
  };

  const sendReportToUser = async () => {
    if (!confirm((window.appLang&&window.appLang()==='ar') ? `إرسال ملخص الإنجازات بالذكاء الاصطناعي كرسالة خاصة إلى ${person.display_name}؟` : `Send the AI accomplishments summary as a K9 DM to ${person.display_name}?`)) return;
    setSending(true);
    try {
      const r = await fetch(`/api/reports/accomplishments-ai/send-to-user/${person.id}?days=${days}`, {
        method: 'POST', credentials: 'same-origin',
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(employeeApiErrorText(body?.detail, `${r.status}`));
      }
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم إرسال التقرير إلى ${person.display_name}` : `Report DM'd to ${person.display_name}`, kind: 'ok' });
    } catch(e) {
      toast.push({ msg: (window.appT||(s=>s))('Send failed: ') + e.message, kind: 'err' });
    } finally { setSending(false); }
  };

  // Per-user PDF/XLSX download. The backend's /api/summary/export accepts
  // user_id; daysToPeriod maps days picker -> the period the endpoint
  // expects (item #4 — daily download for per-user reports).
  const [downloading, setDownloading] = useState(false);
  const daysToPeriod = (d) => {
    if (d <= 1) return 'day';
    if (d <= 10) return 'week';
    if (d <= 45) return 'month';
    if (d <= 200) return '6_months';
    return 'year';
  };
  const downloadPdf = async (fmt) => {
    setDownloading(true);
    try {
      const period = daysToPeriod(days);
      const r = await fetch(`/api/summary/export?format=${fmt}&period=${period}&user_id=${person.id}`, { credentials: 'same-origin' });
      if (!r.ok) throw new Error(`${r.status}`);
      const blob = await r.blob();
      const link = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = link;
      const first = (person.display_name || person.matrix_id).split(' ')[0].toLowerCase();
      a.download = `attendance-${first}-${period}-${new Date().toISOString().slice(0,10)}.${fmt}`;
      a.click();
      URL.revokeObjectURL(link);
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم تنزيل ${fmt.toUpperCase()}` : `${fmt.toUpperCase()} downloaded`, kind: 'ok' });
    } catch(e) {
      toast.push({ msg: (window.appT||(s=>s))('Download failed: ') + e.message, kind: 'err' });
    } finally { setDownloading(false); }
  };

  const a = data?.attendance || {};
  const p = data?.progress || {};
  const v = data?.violations || {};

  const attSlices = [
    { key:'on_time',  label:(window.appT||(s=>s))('On-time'),  color:'var(--ok)',   value: a.on_time || 0,  unit:'d' },
    { key:'late',     label:(window.appT||(s=>s))('Late'),     color:'var(--warn)', value: a.late || 0,     unit:'d' },
    { key:'sick',     label:(window.appT||(s=>s))('Sick'),     color:'#f472b6',     value: a.sick || 0,     unit:'d' },
    { key:'vacation', label:(window.appT||(s=>s))('Vacation'), color:'#06b6d4',     value: a.vacation || 0, unit:'d' },
    { key:'half_day', label:(window.appT||(s=>s))('Half-day'), color:'#a78bfa',     value: a.half_day || 0, unit:'d' },
    { key:'holiday',  label:(window.appT||(s=>s))('Holiday'),  color:'#facc15',     value: a.holiday || 0,  unit:'d' },
    { key:'absent',   label:(window.appT||(s=>s))('Absent'),   color:'var(--err)',  value: a.absent || 0,   unit:'d' },
  ];
  const progSlices = [
    { key:'made',     label:(window.appT||(s=>s))('Made progress'), color:'var(--ok)',   value: p.made || 0 },
    { key:'partial',  label:(window.appT||(s=>s))('Partial'),       color:'var(--warn)', value: p.partial || 0 },
    { key:'none',     label:(window.appT||(s=>s))('No progress'),   color:'var(--err)',  value: p.none || 0 },
    { key:'unscored', label:(window.appT||(s=>s))('Unscored'),      color:'var(--text-3)', value: p.unscored || 0 },
  ];

  return (
    <div className="modal-overlay employee-scoreboard-overlay" onClick={onClose}>
      <div className="modal employee-scoreboard-modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head">
          <div>
            <strong>{person.display_name} — {(window.appT||(s=>s))("scoreboard")}</strong>
            <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>{person.matrix_id}</div>
          </div>
          <div style={{ display:'flex', gap: 8, alignItems:'center' }}>
            <select className="sel" value={days} onChange={e => setDays(parseInt(e.target.value))}
              style={{ width: 110, fontSize: 12, padding:'4px 8px' }}>
              <option value={7}>{(window.appLang&&window.appLang()==='ar') ? '7 أيام' : '7 days'}</option>
              <option value={14}>{(window.appLang&&window.appLang()==='ar') ? '14 يومًا' : '14 days'}</option>
              <option value={30}>{(window.appLang&&window.appLang()==='ar') ? '30 يومًا' : '30 days'}</option>
              <option value={60}>{(window.appLang&&window.appLang()==='ar') ? '60 يومًا' : '60 days'}</option>
              <option value={90}>{(window.appLang&&window.appLang()==='ar') ? '90 يومًا' : '90 days'}</option>
              <option value={180}>{(window.appLang&&window.appLang()==='ar') ? '180 يومًا' : '180 days'}</option>
            </select>
            <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
          </div>
        </div>

        <div className="modal-body employee-scoreboard-body">
          {error && <div className="employee-scoreboard-error">{(window.appT||(s=>s))("Error:")} {error}</div>}
          {!data && !error && <Loading label={(window.appT||(s=>s))("Loading scoreboard…")} rows={3} />}

          {data && (
            <>
              {/* KPI strip */}
              <div className="kpi-grid kpi-grid-4 employee-scoreboard-kpis">
                <div className="mini-stat"><div className="lbl">{(window.appT||(s=>s))("Attendance")}</div><div className="val mono" style={{ color: a.attendance_pct >= 90 ? 'var(--ok)' : a.attendance_pct >= 70 ? 'var(--warn)' : 'var(--err)' }}>{a.attendance_pct}%</div></div>
                <div className="mini-stat"><div className="lbl">{(window.appT||(s=>s))("Total hours")}</div><div className="val mono">{a.total_hours}h</div></div>
                <div className="mini-stat"><div className="lbl">{(window.appT||(s=>s))("Strikes")}</div><div className="val mono" style={{ color: v.current_strikes >= 3 ? 'var(--err)' : v.current_strikes > 0 ? 'var(--warn)' : 'var(--ok)' }}>{v.current_strikes}</div></div>
                <div className="mini-stat"><div className="lbl">{(window.appT||(s=>s))("Working days")}</div><div className="val mono">{data.working_days}</div></div>
              </div>

              {/* Two pies side-by-side */}
              <div className="employee-scoreboard-chart-grid">
                <div className="card employee-scoreboard-chart-card">
                  <SectionHeader title={(window.appT||(s=>s))("Attendance breakdown")}
          tip={(window.appT||(s=>s))("On-time / late / absent / sick / vacation / half-day / holiday days over the selected period. Half-days count 0.5 toward on-time.")} sub={`${data.range_start} → ${data.range_end}`} />
                  <div className="employee-scoreboard-chart">
                    <_ScorePie slices={attSlices} size={152} />
                    <div className="employee-scoreboard-legend">
                      {attSlices.map(s => (
                        <React.Fragment key={s.key}>
                          <span><span style={{ color: s.color }}>●</span> {s.label}</span>
                          <span className="mono num">{s.value}d</span>
                        </React.Fragment>
                      ))}
                      <span className="employee-scoreboard-legend-foot">{(window.appT||(s=>s))("Blocker / no sign-off")}</span>
                      <span className="mono employee-scoreboard-legend-foot num">{a.had_blocker_days} / {a.no_signoff_days}</span>
                    </div>
                  </div>
                </div>
                <div className="card employee-scoreboard-chart-card">
                  <SectionHeader title={(window.appT||(s=>s))("Progress flags")}
          tip={(window.appT||(s=>s))("Per-day progress score (0/1/2). Bot scores each sign-in vs yesterday: 2 = made progress, 1 = partial, 0 = none. Card shows daily breakdown.")} sub={(window.appT||(s=>s))("Bot's verdict per signed-in day")} />
                  <div className="employee-scoreboard-chart">
                    <_ScorePie slices={progSlices} size={152} />
                    <div className="employee-scoreboard-legend">
                      {progSlices.map(s => (
                        <React.Fragment key={s.key}>
                          <span><span style={{ color: s.color }}>●</span> {s.label}</span>
                          <span className="mono num">{s.value}</span>
                        </React.Fragment>
                      ))}
                    </div>
                  </div>
                </div>
              </div>

              {/* Phase-1: overall Efficiency% + KPI + pillar breakdown — leads
                  the drawer as the bottom-line indicator. */}
              <div className="card employee-scoreboard-card">
                <EfficiencyCard userId={person.id} />
                <GoalCard userId={person.id} />
                <WorkHealthCard userId={person.id} />
                <DayTimelineCard userId={person.id} />
              </div>

              {/* #24 (d): Progress timeline chart — per-day score line with
                  PTO bands, blocker dots, and strike triangles overlaid. */}
              <div className="card employee-scoreboard-card">
                <ProgressTimelineCard
                  byDay={data.by_day || []}
                  ptoHistory={data.pto_history || []}
                  violations={(data.violations && data.violations.history) || []}
                  rangeStart={data.range_start}
                  rangeEnd={data.range_end}
                />
              </div>

              {/* #24 (c): Admin-pickable period metrics grid */}
              <div className="card employee-scoreboard-card">
                <MetricsPickerCard metrics={data.metrics || {}} />
              </div>

              {/* C2: sign-in time consistency heatmap */}
              <div className="card employee-scoreboard-card">
                <SigninHeatmapCard userId={person.id} />
              </div>

              {/* C3: HR documents vault */}
              <div className="card employee-scoreboard-card">
                <HrDocumentsCard userId={person.id} />
              </div>

              {/* Accomplishments AI summary */}
              <div className="card employee-scoreboard-card">
                <SectionHeader
                  title={(window.appT||(s=>s))("AI accomplishments summary")}
          tip={(window.appT||(s=>s))("AI-written narrative summarizing this employee's signed-in days. Click Generate to re-render; Send to user DMs the summary.")}
                  sub={(window.appLang&&window.appLang()==='ar') ? `مُجمَّع من رسائل تسجيل الدخول خلال آخر ${days} يومًا` : `Compiled from sign-in messages over the last ${days} days`}
                  right={
                    <button className="btn btn-ghost" onClick={generateAi} disabled={aiLoading}>
                      <Icon name="ai" size={12}/> {aiLoading ? (window.appT||(s=>s))('Generating…') : (aiSummary ? (window.appT||(s=>s))('Regenerate') : (window.appT||(s=>s))('Generate'))}
                    </button>
                  }
                />
                {aiSummary ? (
                  <div className="ai-summary" style={{ fontSize: 13, lineHeight: 1.55 }}
                    dangerouslySetInnerHTML={{ __html: (() => {
                      // Single source of truth: rely on _renderMd's internal escape when
                      // present; otherwise escape inline (closes the original XSS path).
                      const _esc = (s) => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
                      return window._renderMd ? window._renderMd(String(aiSummary == null ? '' : aiSummary)) : _esc(aiSummary).replace(/\n/g, '<br/>');
                    })() }} />
                ) : (
                  <div className="muted" style={{ fontSize: 12, padding: 6 }}>
                    {(window.appT||(s=>s))("Click")} <strong>{(window.appT||(s=>s))("Generate")}</strong> {(window.appLang&&window.appLang()==='ar') ? `لتجميع ${data.by_day.length} يوم تسجيل دخول في سرد.` : `to compile ${data.by_day.length} sign-in day(s) into a narrative.`}
                  </div>
                )}
              </div>

              {/* PTO history */}
              <div className="employee-scoreboard-detail-grid">
              <div className="card employee-scoreboard-card">
                <SectionHeader title={(window.appT||(s=>s))("PTO history")}
          tip={(window.appT||(s=>s))("Every approved PTO/sick/vacation/half-day block covering this range. Pending requests live on the Ops tab.")} sub={(window.appLang&&window.appLang()==='ar') ? `${data.pto_history.length} فترة تغطي هذا النطاق` : `${data.pto_history.length} block(s) covering this range`} />
                {data.pto_history.length === 0 ? (
                  <div className="muted" style={{ fontSize: 12, padding: 6 }}>{(window.appT||(s=>s))("No PTO in this range.")}</div>
                ) : (
                  <table className="t" style={{ fontSize: 12 }}>
                    <thead><tr><th>{(window.appT||(s=>s))("Kind")}</th><th>{(window.appT||(s=>s))("From")}</th><th>{(window.appT||(s=>s))("To")}</th><th>{(window.appT||(s=>s))("Source")}</th><th>{(window.appT||(s=>s))("Reason")}</th></tr></thead>
                    <tbody>
                      {data.pto_history.map(p => (
                        <tr key={p.id}>
                          <td><Badge kind="info">{p.kind}</Badge></td>
                          <td className="mono">{p.start_date}</td>
                          <td className="mono">{p.end_date}</td>
                          <td className="muted mono" style={{ fontSize: 11 }}>{p.source}</td>
                          <td style={{ color:'var(--text-2)', fontSize: 11.5 }}>{p.reason || '—'}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                )}
              </div>

              {/* Violations */}
              <div className="card employee-scoreboard-card">
                <SectionHeader title={(window.appT||(s=>s))("Violations")}
          tip={(window.appT||(s=>s))("Unified violations card (#1, #28). Donut + grid + trend + table read from the same counts+events arrays — so the donut and the count next to it never disagree.")} sub={(window.appLang&&window.appLang()==='ar') ? `عدد المخالفات الحالي: ${v.current_strikes}` : `Current strike count: ${v.current_strikes}`} />
                {v.history.length === 0 ? (
                  <div className="muted" style={{ fontSize: 12, padding: 6 }}>{(window.appT||(s=>s))("No violations recorded in this range.")}</div>
                ) : (
                  <table className="t" style={{ fontSize: 12 }}>
                    <thead><tr><th>{(window.appT||(s=>s))("When")}</th><th>{(window.appT||(s=>s))("Type")}</th><th>{(window.appT||(s=>s))("Trigger")}</th><th>{(window.appT||(s=>s))("Detail")}</th></tr></thead>
                    <tbody>
                      {v.history.map((vv, i) => (
                        <tr key={i}>
                          <td className="mono" style={{ fontSize: 11 }}>{vv.occurred_at ? new Date(vv.occurred_at).toLocaleString([], {month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}) : '—'}</td>
                          {/* Issue #32: prefer the API-supplied action_label
                              (humanized via humanize_action_type on backend);
                              fall back to the FE table for older cached rows. */}
                          <td><Badge kind="warn" title={vv.action_type || ''}>{vv.action_label || (window.humanizeActionType ? window.humanizeActionType(vv.action_type) : vv.action_type.replace(/_/g,' '))}</Badge></td>
                          <td className="muted mono" style={{ fontSize: 11 }}>{vv.trigger}</td>
                          <td style={{ fontSize: 11.5 }}>{vv.body || '—'}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                )}
              </div>

              {/* Daily breakdown */}
              <div className="card employee-scoreboard-card employee-scoreboard-daily">
                <SectionHeader title={(window.appT||(s=>s))("Daily sign-in details")}
          tip={(window.appT||(s=>s))("Every sign-in posted in the selected window. Yesterday/today/blocker text are extracted from the raw Matrix message body.")} sub={(window.appLang&&window.appLang()==='ar') ? `${data.by_day.length} يوم يحتوي على محتوى` : `${data.by_day.length} day(s) with content`} />
                {data.by_day.length === 0 ? (
                  <div className="muted" style={{ fontSize: 12, padding: 6 }}>{(window.appT||(s=>s))("No sign-in messages recorded.")}</div>
                ) : (
                  <div style={{ display:'flex', flexDirection:'column', gap: 6, maxHeight: 280, overflowY:'auto' }}>
                    {data.by_day.map(d => (
                      <div key={d.date} style={{ borderLeft: '2px solid var(--line)', paddingLeft: 10, fontSize: 12 }}>
                        <div style={{ display:'flex', gap: 8, alignItems:'center', marginBottom: 2 }}>
                          <span className="mono muted" style={{ fontSize: 10 }}>{d.date}</span>
                          <Badge kind={d.progress_score === 2 ? 'ok' : d.progress_score === 1 ? 'warn' : d.progress_score === 0 ? 'err' : 'mute'}>
                            {d.progress_label}
                          </Badge>
                        </div>
                        {d.today && <div><span className="muted">{(window.appT||(s=>s))("Today:")}</span> {d.today}</div>}
                        {d.yesterday && <div><span className="muted">{(window.appT||(s=>s))("Yesterday:")}</span> {d.yesterday}</div>}
                        {d.blocker && <div style={{ color:'var(--err)' }}><span className="muted" style={{ color:'var(--text-2)' }}>{(window.appT||(s=>s))("Blocker:")}</span> {d.blocker}</div>}
                      </div>
                    ))}
                  </div>
                )}
              </div>
              </div>
            </>
          )}
        </div>

        <div className="modal-foot" style={{ justifyContent:'space-between' }}>
          <span className="muted" style={{ fontSize: 11 }}>
            {data ? ((window.appLang&&window.appLang()==='ar') ? `النطاق: ${data.range_start} → ${data.range_end}` : `Range: ${data.range_start} → ${data.range_end}`) : ''}
          </span>
          <div style={{ display:'flex', gap: 8, flexWrap:'wrap' }}>
            <button className="btn btn-ghost" onClick={onClose}>{(window.appT||(s=>s))("Close")}</button>
            <button className="btn btn-ghost" onClick={() => downloadPdf('xlsx')} disabled={downloading || !data}
              title={(window.appLang&&window.appLang()==='ar') ? `تنزيل ملف XLSX تنفيذي لـ ${person.display_name}` : `Download executive XLSX for ${person.display_name}`}>
              <Icon name="download" size={12}/> XLSX
            </button>
            <button className="btn btn-ghost" onClick={() => downloadPdf('pdf')} disabled={downloading || !data}
              title={(window.appLang&&window.appLang()==='ar') ? `تنزيل ملف PDF تنفيذي لـ ${person.display_name} (رسوم بيانية + مخططات دائرية لكل مستخدم + جداول + ملخص الذكاء الاصطناعي)` : `Download executive PDF for ${person.display_name} (charts + per-user pies + tables + AI summary)`}>
              <Icon name="download" size={12}/> {downloading ? (window.appT||(s=>s))('Downloading…') : 'PDF'}
            </button>
            <button className="btn btn-pri" onClick={sendReportToUser} disabled={sending || !data}
              title={(window.appLang&&window.appLang()==='ar') ? `إرسال ملخص الإنجازات بالذكاء الاصطناعي إلى ${person.display_name}` : `DM the AI accomplishments summary to ${person.display_name}`}>
              <Icon name="msg" size={12}/> {sending ? (window.appT||(s=>s))('Sending…') : ((window.appLang&&window.appLang()==='ar') ? `إرسال إلى ${person.display_name.split(' ')[0]}` : `Send to ${person.display_name.split(' ')[0]}`)}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}


/* Generic drill-down sheet for the drawer's mini-stat boxes (#8: every KPI
   box is clickable and shows the exact rows behind its number). Data comes
   from /api/employees/{id}/metric-evidence — a display-ready generic table,
   so this one component serves every metric with zero per-metric code. */
function MetricEvidenceSheet({ person, spec, onClose }) {
  const [data, setData]   = useState(null);
  const [error, setError] = useState(null);
  // Deferred item (2026-07-11): user-adjustable window. Starts at the box's
  // natural window; ip_log is a fixed "last 20 connections" (no days param).
  const [days, setDays]   = useState(spec.days || 14);
  // ip_log now honors the day window too (was a fixed last-20 that ignored the
  // selected period, so a month view only showed the last few busy days).
  const hasWindow = true;
  useEffect(() => {
    let dead = false;
    setData(null); setError(null);
    (async () => {
      try {
        const qs = `kind=${encodeURIComponent(spec.kind)}${hasWindow ? `&days=${days}` : ''}`;
        const r = await fetch(`/api/employees/${person.id}/metric-evidence?${qs}`, { credentials: 'include' });
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        const j = await r.json();
        if (!dead) setData(j);
      } catch (e) {
        if (!dead) setError(e.message);
      }
    })();
    return () => { dead = true; };
  }, [person.id, spec.kind, days]);
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 1200,
      background: 'rgba(0,0,0,0.45)', display: 'flex',
      alignItems: 'center', justifyContent: 'center', padding: 16,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        background: 'var(--bg-elev-1)', border: '1px solid var(--line)',
        borderRadius: 12, width: 'min(720px, 96vw)', maxHeight: '80vh',
        display: 'flex', flexDirection: 'column', boxShadow: '0 12px 40px rgba(0,0,0,0.35)',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 16px', borderBottom: '1px solid var(--line)' }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--text-1)' }}>
              {(data && data.title) || (window.appT||(s=>s))(spec.label || 'Details')}
            </div>
            <div style={{ fontSize: 11, color: 'var(--text-3)' }}>{person.display_name}</div>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            {hasWindow && [7, 14, 30, 90].map(d => (
              <button
                key={d}
                className={days === d ? 'btn btn-pri' : 'btn btn-ghost'}
                style={{ padding: '2px 8px', fontSize: 11 }}
                onClick={() => setDays(d)}
                title={(window.appLang&&window.appLang()==='ar') ? `آخر ${d} يوم` : `Last ${d} days`}
              >
                {d}d
              </button>
            ))}
            <button className="btn btn-ghost" onClick={onClose} aria-label={(window.appT||(s=>s))('Close')}>✕</button>
          </div>
        </div>
        <div style={{ overflow: 'auto', padding: 12 }}>
          {error ? (
            <div style={{ color: 'var(--err)', fontSize: 13, padding: 20, textAlign: 'center' }}>{error}</div>
          ) : !data ? (
            <Loading label={(window.appT||(s=>s))('Loading…')} rows={3} />
          ) : data.rows.length === 0 ? (
            <div style={{ color: 'var(--text-3)', fontSize: 13, padding: 20, textAlign: 'center' }}>{(window.appT||(s=>s))('No records in this window.')}</div>
          ) : (
            <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
              <thead>
                <tr style={{ borderBottom: '1px solid var(--line)' }}>
                  {data.columns.map(c => (
                    <th key={c} style={{ padding: '6px 8px', textAlign: 'left', fontSize: 10, fontWeight: 700, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '0.05em', whiteSpace: 'nowrap' }}>
                      {(window.appT||(s=>s))(c)}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {data.rows.map((row, i) => (
                  <tr key={i} style={{ borderBottom: '1px solid var(--line)', background: i % 2 ? 'var(--bg-elev-2)' : 'transparent' }}>
                    {row.map((cell, j) => (
                      <td key={j} style={{ padding: '6px 8px', color: 'var(--text-2)', verticalAlign: 'top' }}>{cell}</td>
                    ))}
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
}

function PersonDrawer({ person, onClose }) {
  // Same precedence as the roster: absent → signed-off → late → present.
  const status =
    !person.signed_in   ? 'absent' :
    person.signed_off   ? (person.was_late ? 'late-off' : 'signed-off') :
    person.was_late     ? 'late' :
                          'present';
  const displayRole = String(person.org_role || person.role || 'employee');
  // Defensive: STATUS_DATA + AUDIT may be undefined if data.jsx bootstrap
  // is mid-flight or the /api/audit fetch failed. Treat both as empty rather
  // than crashing the whole drawer (was the "blank screen on click" bug).
  const D = window.STATUS_DATA || {};
  const meRole = String(D.ME?.org_role || '').toLowerCase();
  // Admin tier: admin or superadmin → can edit anyone.
  const isAdmin = meRole === 'admin' || meRole === 'superadmin';
  // Moderator can only edit their own row. Manager can edit team.
  // canEditProfile gates the Edit profile / Edit role buttons.
  const isSelf = D.ME?.id === person.id || D.ME?.matrix_id === person.matrix_id;
  const isModerator = meRole === 'moderator';
  const canEditProfile = isAdmin || (isModerator && isSelf) || (meRole === 'manager');
  // Editing the org_role specifically still requires admin/superadmin.
  const canEditRole = isAdmin;
  // Legacy `isManager` flag kept for the parts that gate DM / send-report
  // buttons; admin tier + moderator + manager all stay manager-or-above.
  const isManager = isAdmin || isModerator || meRole === 'manager';
  // Item 11 fix: D.AUDIT is the global 50-event log for ALL users — if this
  // person isn't in those 50, the filter returns []. Fetch per-user audit
  // instead so the "Recent activity" section always shows real data.
  const [recentActivity, setRecentActivity] = useState(
    (Array.isArray(D.AUDIT) ? D.AUDIT : []).filter(a => a && a.matrix_id === person.matrix_id)
  );
  useEffect(() => {
    let cancelled = false;
    fetch(`/api/audit?limit=50&${person.id ? `user_id=${person.id}` : `matrix_id=${encodeURIComponent(person.matrix_id)}`}`, { credentials: 'include' })
      .then(r => r.ok ? r.json() : null)
      .then(rows => {
        if (cancelled || !Array.isArray(rows)) return;
        // Normalise to match the shape data.jsx produces for D.AUDIT
        const normalised = rows.map(a => ({
          id:          a.id,
          matrix_id:   a.target || a.matrix_id || '',
          user_display_name: a.user_display_name || a.display_name || a.matrix_id || '?',
          action_type: a.action_type || '',
          trigger:     a.trigger || '',
          detail:      a.detail || a.action_type || '',
          body:        a.body || '',
          occurred_at: a.occurred_at || '',
        }));
        setRecentActivity(normalised);
      })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [person.matrix_id]);
  // Dev activity (GitLab/GitHub) — manager+ only, matches /api/devactivity RBAC.
  // null = loading/never-fetched; {configured:false} = integration off; else counts.
  const [devActivity, setDevActivity] = useState(null);
  useEffect(() => {
    if (!isManager || !person.id) return;
    let cancelled = false;
    fetch(`/api/devactivity/${person.id}?days=7`, { credentials: 'include' })
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (!cancelled && d) setDevActivity(d); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [person.id, isManager]);
  const [dmOpen, setDmOpen] = useState(false);
  const [roleOpen, setRoleOpen] = useState(false);
  const [profileOpen, setProfileOpen] = useState(false);
  const toast = useToast();

  // Team management
  const [teams, setTeams] = useState([]);
  const [teamVal, setTeamVal] = useState(person.team || '');
  const [teamBusy, setTeamBusy] = useState(false);
  useEffect(() => {
    fetch('/api/teams', { credentials: 'include' })
      .then(r => r.ok ? r.json() : [])
      .then(data => setTeams(data))
      .catch(() => {});
  }, []);
  const saveTeam = async (newTeam) => {
    setTeamBusy(true);
    try {
      const r = await fetch(`/api/employees/${person.id}/profile`, {
        method: 'PUT',
        credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ team: newTeam || null }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      setTeamVal(newTeam);
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم تحديث الفريق إلى ${newTeam || '(لا يوجد)'}` : `Team updated to ${newTeam || '(none)'}`, kind: 'ok' });
    } catch(e) {
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `فشل تحديث الفريق: ${e.message}` : `Team update failed: ${e.message}`, kind: 'err' });
    } finally { setTeamBusy(false); }
  };

  // Period selector for the whole modal. Point-in-time tiles (today hours,
  // live presence, on-time streak, leave quota) ignore it; everything else
  // re-fetches. Options + mappings:
  //   scoreboard / availability `days`: day→1 week→7 month→30 6_months→182 annual→365
  //   efficiency `period`:            day/week→week  month→month  6_months/annual→quarter
  const PM_PERIODS   = [['day','Day'],['week','Week'],['month','Month'],['6_months','6 Months'],['annual','Annual']];
  const PM_DAYS_MAP  = { day:1, week:7, month:30, '6_months':182, annual:365 };
  const PM_EFF_MAP   = { day:'week', week:'week', month:'month', '6_months':'quarter', annual:'quarter' };
  const [period, setPeriod] = useState('month');

  // Presence breakdown for this user — point-in-time (today), never re-periods.
  const [pres, setPres] = useState(null);
  // Point-in-time / YTD baseline (fetched once, period-independent) — drives the
  // on-time streak + leave-quota tiles so they stay fixed regardless of period.
  const [statsNow, setStatsNow] = useState(null);
  // Per-user counters fetched from /api/employees/:id/stats — PERIOD-SCOPED.
  const [stats, setStats] = useState(null);
  // Per-user attendance breakdown for the pie — PERIOD-SCOPED (scoreboard?days).
  const [att30, setAtt30] = useState(null);
  // IP-learning Phase 2: classified home/office/unknown sets + flag counts.
  // Point-in-time (learned profile), never re-periods.
  const [ipProfile, setIpProfile] = useState(null);
  // Pingpong (availability check) compliance for this user — PERIOD-SCOPED.
  const [pingStats, setPingStats] = useState(null);

  // Fixed baseline: presence (today), IP profile, and a period-independent
  // stats/profile merge for the point-in-time tiles. Fetched once per person.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        // statsNow is fed by the period-scoped effect below — its /stats +
        // /profile payloads carry the identical point-in-time fields, so the
        // old extra no-period pair here just doubled the (schedule-aware,
        // non-trivial) server work on every drawer open.
        const [presR, ipR] = await Promise.all([
          fetch(`/api/employees/${person.id}/presence-breakdown?range=today`, {credentials:'include'})
            .then(r => r.ok ? r.json() : null).catch(() => null),
          fetch(`/api/employees/${person.id}/ip-classification`, {credentials:'include'})
            .then(r => r.ok ? r.json() : null).catch(() => null),
        ]);
        if (!cancelled) { setPres(presR); setIpProfile(ipR); }
      } catch(_) {}
    })();
    return () => { cancelled = true; };
  }, [person.id]);

  // Period-scoped fetches: stats/profile carry `period`, scoreboard/availability
  // carry the mapped `days`. Re-runs whenever the period selector changes so
  // every period-scoped tile re-renders.
  useEffect(() => {
    let cancelled = false;
    const days = PM_DAYS_MAP[period] || 30;
    (async () => {
      try {
        const [statsR, profR, sbR, pingR] = await Promise.all([
          fetch(`/api/employees/${person.id}/stats?period=${period}`, {credentials:'include'})
            .then(r => r.ok ? r.json() : null).catch(() => null),
          fetch(`/api/employees/${person.id}/profile?period=${period}`, {credentials:'include'})
            .then(r => r.ok ? r.json() : null).catch(() => null),
          fetch(`/api/employees/${person.id}/scoreboard?days=${days}`, {credentials:'include'})
            .then(r => r.ok ? r.json() : null).catch(() => null),
          fetch(`/api/availability/stats?days=${days}&user_id=${person.id}`, {credentials:'include'})
            .then(r => r.ok ? r.json() : null).catch(() => null),
        ]);
        if (!cancelled) {
          // WoW hours / blockers / risk / location all read fields that ONLY
          // /profile returns — /stats carries just the counters — so merge both
          // into the one `stats` object the boxes read, /stats last so the keys
          // both define keep the counter value.
          const merged = (statsR || profR) ? { ...(profR || {}), ...(statsR || {}) } : null;
          setStats(merged);
          // Point-in-time tiles read fields that don't vary with `period`
          // (streak, PTO quota, hours_this_week, live presence keys), so the
          // same payload safely feeds statsNow — no separate fetch needed.
          setStatsNow(merged);
          setAtt30(sbR);
          // The endpoint is org-wide (it doesn't actually filter by
          // user_id), so pick this person's row out of the `users` list.
          setPingStats((pingR && pingR.users) ? (pingR.users.find(u => u.user_id === person.id) || null) : null);
        }
      } catch(_) {}
    })();
    return () => { cancelled = true; };
  }, [person.id, period]);

  // #8: which mini-stat box's evidence sheet is open ({kind, days, label}|null)
  const [evidence, setEvidence] = useState(null);
  const [reportMenuOpen, setReportMenuOpen] = useState(false);
  const [reportBusy, setReportBusy] = useState(false);
  // Report variant: 'admin' = full internal report (weights, location,
  // integrity); 'employee' = shareable outcomes-only copy. null = AUTO:
  // DM sends the employee copy (the recipient IS the employee) while the
  // manager's own download stays the full report — matching the server
  // defaults, so an untouched toggle never redacts the manager's download.
  const [reportAudience, setReportAudience] = useState(null);
  // Custom report window (from/to). Both must be set to override `range`.
  const [customStart, setCustomStart] = useState('');
  const [customEnd, setCustomEnd] = useState('');

  const handleSendReport = async (range = 'monthly', cStart = null, cEnd = null) => {
    setReportMenuOpen(false);
    setReportBusy(true);
    try {
      let url = `/api/reports/user/${person.id}/send?range=${range}&audience=${reportAudience || 'employee'}`;
      if (cStart && cEnd) url += `&start=${cStart}&end=${cEnd}`;
      const r = await fetch(url, {
        method: 'POST',
        credentials: 'same-origin',
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `تم إرسال التقرير إلى ${person.display_name}` : `Report DM'd to ${person.display_name}`, kind: 'ok' });
    } catch(e) {
      toast.push({ msg: (window.appLang&&window.appLang()==='ar') ? `فشل التقرير: ${e.message}` : `Report failed: ${e.message}`, kind: 'err' });
    } finally { setReportBusy(false); }
  };

  const handleDownloadPdf = (range = 'monthly', cStart = null, cEnd = null) => {
    setReportMenuOpen(false);
    // cache-bust so the browser never serves a stale (pre-update) PDF
    let url = `/api/reports/user/${person.id}/pdf?range=${range}&audience=${reportAudience || 'admin'}&t=${Date.now()}`;
    if (cStart && cEnd) url += `&start=${cStart}&end=${cEnd}`;
    window.open(url, '_blank');
  };

  // Backend returns shape {buckets:{online,away,offline,in_call}, total_work_minutes, samples}
  const buckets = pres?.buckets || {};
  const online  = buckets.online  ?? 0;
  const away    = buckets.away    ?? 0;
  const offline = buckets.offline ?? 0;
  const presStatus = online > 0 ? 'online' : away > 0 ? 'away' : 'offline';

  return (
    <>
      <div className="person-modal-overlay" onClick={onClose}>
        <div className="person-modal" onClick={e => e.stopPropagation()}>
          <div className="drawer-head">
            <div style={{ display:'flex', gap: 12, alignItems:'center', minWidth: 0 }}>
              <Avatar person={person} status={status} size={44} />
              <div style={{ minWidth: 0 }}>
                <div style={{ fontSize: 16, fontWeight: 500 }}>{person.display_name}</div>
                <div className="muted mono" style={{ fontSize: 12 }}>{person.matrix_id}</div>
              </div>
            </div>
            {/* Period selector — rescopes every period-scoped tile below.
                Point-in-time tiles (tagged now/YTD) ignore it. */}
            <div className="pm-periods" role="tablist" aria-label={(window.appT||(s=>s))("Period")}>
              {PM_PERIODS.map(([val, lbl]) => (
                <button key={val} type="button" role="tab" aria-selected={period === val}
                  className={period === val ? 'on' : ''}
                  onClick={() => setPeriod(val)}>{(window.appT||(s=>s))(lbl)}</button>
              ))}
            </div>
            <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
          </div>

          <div className="drawer-body">
            <div style={{ display:'flex', gap: 8, marginBottom: 12, flexWrap:'wrap', alignItems:'center' }}>
              <StatusBadge status={status} />
              <Badge kind="mute" title={(window.appT||(s=>s))("Team")}>{(window.appT||(s=>s))("Team:")} {teamVal || person.team || (window.appT||(s=>s))('(no team)')}</Badge>
              <Badge kind="info" title={(window.appT||(s=>s))("Dashboard role")}>{(window.appT||(s=>s))("Role:")} {(window.appT||(s=>s))((displayRole.charAt(0).toUpperCase() + displayRole.slice(1)).replace('_',' '))}</Badge>
              {(() => {
                const fmtMin = (m) => {
                  if (m === null || m === undefined) return null;
                  const x = ((m % 1440) + 1440) % 1440;
                  let h = Math.floor(x / 60), mi = x % 60;
                  const ap = h < 12 ? 'AM' : 'PM'; h = h % 12 || 12;
                  return `${h}:${String(mi).padStart(2,'0')} ${ap}`;
                };
                const tzS = (window.tzShort ? window.tzShort(person.timezone) : (person.tz_short || ''));
                const si = fmtMin(person.usual_start_min);
                const so = fmtMin(person.usual_end_min);
                if (!si && !so) return null;
                return (
                  <Badge kind="mute" title={(window.appT||(s=>s))("Learned from this person's own sign-in/out history (their timezone)")}>
                    {(window.appT||(s=>s))("Usual:")} {si || '—'}{so ? ` – ${so}` : ''}{tzS ? ` ${tzS}` : ''}
                  </Badge>
                );
              })()}
            </div>

            {/* Team assignment (admin) */}
            {D.ME?.org_role !== 'employee' && (
              <div style={{ display:'flex', gap: 8, marginBottom: 14, alignItems:'center' }}>
                <select className="inp" value={teamVal} onChange={e => setTeamVal(e.target.value)}
                  style={{ flex:1, fontSize:12 }} disabled={teamBusy || !isManager}>
                  <option value="">{(window.appT||(s=>s))("(no team)")}</option>
                  {teams.map(t => <option key={t.id || t.team_name} value={t.team_name}>{t.team_name}</option>)}
                </select>
                <button className="btn btn-pri" style={{ fontSize:12, padding:'4px 12px' }}
                  onClick={() => saveTeam(teamVal)}
                  disabled={teamBusy || !isManager || teamVal === (person.team || '')}
                  title={!isManager ? (window.appT||(s=>s))('Manager or admin required') : ''}>
                  {teamBusy ? '...' : (window.appT||(s=>s))('Set team')}
                </button>
                <a href="#" style={{ fontSize:11, color:'var(--text-3)', textDecoration:'underline', whiteSpace:'nowrap' }}
                  onClick={e => {
                    e.preventDefault();
                    // Was dispatching CustomEvent('nav', {detail:'Teams'}) — nothing
                    // has ever listened for 'nav', and __navigate keys off the
                    // lowercase PAGE_LABELS id, so 'Teams' would miss regardless.
                    if (typeof window.__navigate === 'function') window.__navigate('teams');
                    else window.dispatchEvent(new CustomEvent('nav-to', { detail: { page: 'teams' } }));
                  }}>
                  {(window.appT||(s=>s))("Manage teams")}
                </a>
              </div>
            )}

            {/* Counters — every box is clickable and opens the rows behind
                its number (#8). cursor:pointer signals it; MetricEvidenceSheet
                renders the generic evidence table. */}
            {evidence && (
              <MetricEvidenceSheet person={person} spec={evidence} onClose={() => setEvidence(null)} />
            )}
            <div className="pm-grid" style={{ marginBottom: 16 }}>
              {/* ── Period-scoped headline tiles — recomputed by the backend over
                  the selected window (/stats?period). They refetch + re-render on
                  every period change; no "now"/"YTD" tag (they ARE the period). ── */}
              <div className="mini-stat"
                title={(window.appT||(s=>s))("Share of working days present (on-time or late) over the selected period.")}>
                <div className="lbl">{(window.appT||(s=>s))("Attendance")}</div>
                <div className="val mono" style={{
                  color: (stats?.attendance_pct ?? 0) >= 90 ? 'var(--ok)'
                       : (stats?.attendance_pct ?? 0) >= 70 ? 'var(--warn)' : 'var(--err)',
                }}>{stats?.attendance_pct == null ? '—' : `${stats.attendance_pct}%`}</div>
              </div>
              <div className="mini-stat"
                title={(window.appT||(s=>s))("Of the days present in this period, the share signed in on-time. Blank when there were no present days.")}>
                <div className="lbl">{(window.appT||(s=>s))("On-time rate")}</div>
                <div className="val mono" style={{
                  color: stats?.on_time_rate == null ? 'var(--text-2)'
                       : stats.on_time_rate >= 90 ? 'var(--ok)'
                       : stats.on_time_rate >= 70 ? 'var(--warn)' : 'var(--err)',
                }}>{stats?.on_time_rate == null ? '—' : `${stats.on_time_rate}%`}</div>
              </div>
              <div className="mini-stat"
                title={(window.appT||(s=>s))("Total effective hours worked over the selected period.")}>
                <div className="lbl">{(window.appT||(s=>s))("Hours")}</div>
                <div className="val mono">{stats?.hours_worked != null ? `${Number(stats.hours_worked).toFixed(1)} h` : '—'}</div>
              </div>
              <div className="mini-stat"
                title={(window.appT||(s=>s))("Present / late / absent working days over the selected period.")}>
                <div className="lbl">{(window.appT||(s=>s))("Present · Late · Absent")}</div>
                <div className="val mono">
                  {stats?.days_present == null ? '—'
                    : <span><span style={{ color:'var(--ok)' }}>{stats.days_present}</span> · <span style={{ color:'var(--warn)' }}>{stats.days_late ?? 0}</span> · <span style={{ color:'var(--err)' }}>{stats.days_absent ?? 0}</span></span>}
                </div>
              </div>
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'violations', days: PM_DAYS_MAP[period] || 30, label: 'Strikes' })}
                title={(window.appT||(s=>s))("Attendance/format strikes accrued over the selected period.")}>
                <div className="lbl">{(window.appT||(s=>s))("Strikes")}</div>
                <div className="val mono" style={{
                  color: (stats?.strikes ?? 0) >= 3 ? 'var(--err)' : (stats?.strikes ?? 0) > 0 ? 'var(--warn)' : 'var(--ok)',
                }}>{stats?.strikes ?? '—'}</div>
              </div>
              {/* ── Fixed / rolling counters — the backend keeps these constant
                  regardless of ?period, so they read the point-in-time baseline
                  (statsNow) to avoid flashing "—" during a period refetch. ── */}
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'pto_blocks', days: 366, label: 'PTOs' })}>
                <div className="lbl">{(window.appT||(s=>s))("PTOs")}</div>
                <div className="val mono">{statsNow?.pto_days ?? '—'}</div>
              </div>
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'violations', days: 90, label: 'Violations' })}>
                <div className="lbl">{(window.appT||(s=>s))("Violations")}<span className="pm-fixed-tag">{(window.appT||(s=>s))("90d")}</span></div>
                <div className="val mono">{statsNow?.violations ?? '—'}</div>
              </div>
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'bot_dms', days: 7, label: 'Bot DMs' })}>
                <div className="lbl">{(window.appT||(s=>s))("Bot DMs")}<span className="pm-fixed-tag">{(window.appT||(s=>s))("7d")}</span></div>
                <div className="val mono">{statsNow?.bot_dms ?? '—'}</div>
              </div>
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'daily_hours', days: 7, label: 'Hours' })}>
                <div className="lbl">{(window.appT||(s=>s))("Today")}<span className="pm-fixed-tag">{(window.appT||(s=>s))("now")}</span></div>
                <div className="val mono">{person.hours_worked.toFixed(1)} h</div>
              </div>
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'daily_hours', days: 7, label: 'This week' })}>
                <div className="lbl">{(window.appT||(s=>s))("This week")}<span className="pm-fixed-tag">{(window.appT||(s=>s))("wk")}</span></div>
                <div className="val mono">{statsNow?.hours_this_week != null ? `${statsNow.hours_this_week.toFixed(1)} h` : '—'}</div>
              </div>
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'progress_days', days: PM_DAYS_MAP[period] || 14, label: 'Progress flags' })}
                title={(window.appT||(s=>s))("Progress flags raised over the selected period.")}>
                <div className="lbl">{(window.appT||(s=>s))("Progress flags")}</div>
                <div className="val mono">{stats?.progress_flags_period ?? stats?.progress_flags ?? '—'}</div>
              </div>
              {/* Issue #18: richer metrics from /api/employees/{id}/profile */}
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'daily_hours', days: 14, label: 'WoW hours' })}
                title={(window.appT||(s=>s))("This week's effective hours minus last week's. + = trending up.")}>
                <div className="lbl">{(window.appT||(s=>s))("WoW hours")}</div>
                <div className="val mono" style={{
                  color: (stats?.hours_wow_delta ?? 0) > 0 ? 'var(--ok)'
                       : (stats?.hours_wow_delta ?? 0) < 0 ? 'var(--err)' : 'var(--text-2)',
                }}>
                  {stats?.hours_wow_delta == null ? '—'
                    : `${stats.hours_wow_delta > 0 ? '+' : ''}${stats.hours_wow_delta.toFixed(1)} h`}
                </div>
              </div>
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'on_time_days', days: 14, label: 'On-time streak' })}
                title={(window.appT||(s=>s))("Consecutive days signed in AND on-time, walking back from today.")}>
                <div className="lbl">{(window.appT||(s=>s))("On-time streak")}<span className="pm-fixed-tag">{(window.appT||(s=>s))("now")}</span></div>
                <div className="val mono" style={{
                  color: (statsNow?.on_time_streak_days ?? 0) >= 5 ? 'var(--ok)'
                       : (statsNow?.on_time_streak_days ?? 0) >= 2 ? 'var(--warn)' : 'var(--text-2)',
                }}>{statsNow?.on_time_streak_days ?? '—'}d</div>
              </div>
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'blockers', days: 30, label: 'Blockers' })}
                title={(window.appT||(s=>s))("Last 30 days: % of reported blocker-days that aren't still open today.")}>
                <div className="lbl">{(window.appT||(s=>s))("Blockers resolved")}</div>
                <div className="val mono" style={{
                  color: (stats?.blocker_resolution_pct ?? 100) >= 90 ? 'var(--ok)'
                       : (stats?.blocker_resolution_pct ?? 100) >= 70 ? 'var(--warn)' : 'var(--err)',
                }}>{stats?.blocker_resolution_pct == null ? '—' : `${stats.blocker_resolution_pct}%`}</div>
              </div>
              {/* Trend insight: this week's on-time rate vs last week's. */}
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'on_time_days', days: 14, label: 'On-time trend' })}
                title={(window.appT||(s=>s))("On-time rate this week vs last week. ↑ improving, ↓ slipping.")}>
                <div className="lbl">{(window.appT||(s=>s))("On-time trend")}</div>
                <div className="val mono" style={{
                  color: stats?.attendance_trend === 'up' ? 'var(--ok)'
                       : stats?.attendance_trend === 'down' ? 'var(--err)' : 'var(--text-2)',
                }}>
                  {stats?.on_time_rate_7d == null ? '—'
                    : `${stats.attendance_trend === 'up' ? '↑' : stats.attendance_trend === 'down' ? '↓' : '→'} ${stats.on_time_rate_7d}%`}
                </div>
              </div>
              {/* Predictive risk: forward-looking flag so an admin can act early. */}
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'on_time_days', days: 14, label: 'Attendance risk' })}
                title={(window.appT||(s=>s))("Forward-looking attendance risk from recent on-time rate + direction. Check in early on medium/high.")}>
                <div className="lbl">{(window.appT||(s=>s))("Attendance risk")}</div>
                <div className="val" style={{
                  fontWeight: 600,
                  color: stats?.attendance_risk === 'high' ? 'var(--err)'
                       : stats?.attendance_risk === 'medium' ? 'var(--warn)'
                       : stats?.attendance_risk === 'low' ? 'var(--ok)' : 'var(--text-2)',
                }}>{stats?.attendance_risk ? (window.appT||(s=>s))(stats.attendance_risk[0].toUpperCase() + stats.attendance_risk.slice(1)) : '—'}</div>
              </div>
              {/* Burnout watch: sustained over-work vs the team hours cap. */}
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'daily_hours', days: 14, label: 'Burnout risk' })}
                title={(window.appLang&&window.appLang()==='ar') ? `متوسط الساعات اليومية الأخير مقابل حد الإرهاق للفريق (${stats?.burnout_cap ?? '—'}h). مرتفع = إفراط مستمر في العمل. لا يُعاقَب على العمل الإضافي — هذا مؤشر رفاهية.` : `Recent avg daily hours vs the team burnout cap (${stats?.burnout_cap ?? '—'}h). High = consistently over-working. Overtime isn't penalised — this is a wellbeing flag.`}>
                <div className="lbl">{(window.appT||(s=>s))("Burnout risk")}</div>
                <div className="val" style={{
                  fontWeight: 600,
                  color: stats?.burnout_risk === 'high' ? 'var(--err)'
                       : stats?.burnout_risk === 'medium' ? 'var(--warn)'
                       : stats?.burnout_risk === 'low' ? 'var(--ok)' : 'var(--text-2)',
                }}>
                  {stats?.burnout_risk
                    ? `${(window.appT||(s=>s))(stats.burnout_risk[0].toUpperCase() + stats.burnout_risk.slice(1))}${stats.burnout_avg_hours != null ? ` · ${stats.burnout_avg_hours}h` : ''}`
                    : '—'}
                </div>
              </div>
              {/* Leave balance against the annual quota (one bucket: all paid leave). */}
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'pto_blocks', days: 366, label: 'Leave left' })}
                title={(window.appLang&&window.appLang()==='ar') ? `أيام الإجازة المدفوعة المتبقية هذا العام. الحصة ${statsNow?.pto_allowance_days ?? '—'} يوم (مرضية + إجازة شخصية + إجازة مجتمعة)؛ ${statsNow?.pto_days_used_this_year ?? 0} مستخدمة.` : `Paid-leave days remaining this year. Quota ${statsNow?.pto_allowance_days ?? '—'} days (sick + PTO + vacation combined); ${statsNow?.pto_days_used_this_year ?? 0} used.`}>
                <div className="lbl">{(window.appT||(s=>s))("Leave left")}<span className="pm-fixed-tag">{(window.appT||(s=>s))("YTD")}</span></div>
                <div className="val mono" style={{
                  color: (statsNow?.pto_days_remaining ?? 99) <= 2 ? 'var(--err)'
                       : (statsNow?.pto_days_remaining ?? 99) <= 5 ? 'var(--warn)' : 'var(--ok)',
                }}>
                  {statsNow?.pto_days_remaining == null ? '—'
                    : `${statsNow.pto_days_remaining}/${statsNow.pto_allowance_days}d`}
                </div>
              </div>
              {/* IP/location check: does the last sign-in IP match the claimed
                  home/office? Learned from local Synapse IPs only (no geolocation). */}
              <div className="mini-stat" style={{ cursor: 'pointer' }}
                onClick={() => setEvidence({ kind: 'ip_log', days: PM_DAYS_MAP[period] || 30, label: 'Location check' })}
                title={(window.appLang&&window.appLang()==='ar') ? `آخر تسجيل دخول ادّعى "${stats?.location_claimed || '—'}"؛ يبدو عنوان IP مثل "${stats?.location_classified || 'unknown'}". مُستنتَج من عناوين IP الخاصة بتسجيل دخول هذا المستخدم — دون تحديد موقع جغرافي خارجي.` : `Last sign-in claimed "${stats?.location_claimed || '—'}"; IP looks like "${stats?.location_classified || 'unknown'}". Learned from this user's own sign-in IPs — no external geolocation.`}>
                <div className="lbl">{(window.appT||(s=>s))("Location check")}</div>
                {stats?.location_mix_ready ? (
                  <div className="val mono" style={{ color: 'var(--text)' }}>
                    {stats.location_home_pct}% {(window.appT||(s=>s))('home')} · {stats.location_office_pct}% {(window.appT||(s=>s))('office')}
                  </div>
                ) : (
                  <div className="val" style={{
                    color: stats?.location_status === 'mismatch' ? 'var(--err)'
                         : stats?.location_status === 'match' ? 'var(--ok)' : 'var(--text-2)',
                  }}>
                    {stats?.location_status === 'match' ? '✓ ' + (window.appT||(s=>s))('Match')
                      : stats?.location_status === 'mismatch' ? '⚠ ' + (window.appT||(s=>s))('Mismatch')
                      : stats?.location_status === 'unverified' ? (window.appT||(s=>s))('Unverified') : '—'}
                  </div>
                )}
              </div>
              {/* Pingpong (availability check) compliance — % of the bot's
                  are-you-there pings ANSWERED WITHIN their window over the last
                  30 days (a monthly average), from the org-wide
                  /api/availability/stats endpoint filtered to this user. A late
                  reply counts as missed — presence answered hours later ≠ at
                  desk when asked. */}
              <div className="mini-stat"
                title={(window.appT||(s=>s))("Availability-ping compliance over the last 30 days: share answered within the response window (a late reply counts as missed). Missed pings count toward warnings.")}>
                <div className="lbl">{(window.appT||(s=>s))("Pingpong")}</div>
                <div className="val mono" style={{
                  fontWeight: 600,
                  color: pingStats?.response_rate_pct == null ? 'var(--text-2)'
                       : pingStats.response_rate_pct >= 90 ? 'var(--ok)'
                       : pingStats.response_rate_pct >= 70 ? 'var(--warn)' : 'var(--err)',
                }}>
                  {pingStats?.response_rate_pct == null ? '—' : `${pingStats.response_rate_pct}%`}
                </div>
                {pingStats && pingStats.sent > 0 && (
                  <div className="muted" style={{ fontSize: 10, marginTop: 2, lineHeight: 1.4 }}>
                    {pingStats.answered}/{pingStats.sent} {(window.appT||(s=>s))('answered')}
                    {pingStats.late > 0 && <> · {pingStats.late} {(window.appT||(s=>s))('late')}</>}
                    {/* `missed` counts late replies too (a late reply is treated as
                        missed). Only surface a separate no-reply figure when there
                        were true no-replies (missed − late > 0) so we never show a
                        redundant "18 missed · 18 late". */}
                    {(pingStats.missed - pingStats.late) > 0 && <> · <span style={{ color: 'var(--err)' }}>{pingStats.missed - pingStats.late} {(window.appT||(s=>s))('no-reply')}</span></>}
                  </div>
                )}
              </div>
              {/* Device mix — which device this user is mostly online on. */}
              <div className="mini-stat"
                title={(window.appT||(s=>s))("Dominant device type across this user's recent presence sessions.")}>
                <div className="lbl">{(window.appT||(s=>s))("Mostly online on")}</div>
                <div className="val" style={{ fontWeight: 600, color: 'var(--text)' }}>
                  {stats?.device_mix_ready
                    ? (stats.device_dominant === 'mobile'
                        ? `📱 ${(window.appT||(s=>s))('Mobile')} ${stats.device_mobile_pct}%`
                        : `🖥 ${(window.appT||(s=>s))('Desktop')} ${stats.device_desktop_pct}%`)
                    : '—'}
                </div>
              </div>
            </div>

            {/* Presence pie */}
            <details open className="acc-section">
              <summary><SectionHeader title={<span>{(window.appT||(s=>s))("Today's presence")}<span className="pm-fixed-tag">{(window.appT||(s=>s))("now")}</span></span>}
                tip={(window.appT||(s=>s))("Pie of online/away/offline/in-call minutes during today's signed-in window. Sampled every minute from Synapse + cross-referenced with call sessions.")} sub={<Badge kind={presStatus === 'online' ? 'ok' : presStatus === 'away' ? 'warn' : 'mute'}>{(window.appT||(s=>s))(presStatus)}</Badge>} /></summary>
              <div style={{ display:'flex', gap: 16, alignItems:'center', marginBottom: 16 }}>
                <UserPie online={online} away={away} offline={offline} size={72} />
                <div style={{ fontSize: 12, display:'flex', flexDirection:'column', gap: 4 }}>
                  <span><span style={{ color:'var(--ok)' }}>●</span> {(window.appT||(s=>s))("Online:")} {online > 0 ? fmtMins(online) : '—'}</span>
                  <span><span style={{ color:'var(--warn)' }}>●</span> {(window.appT||(s=>s))("Away:")} {away > 0 ? fmtMins(away) : '—'}</span>
                  <span><span style={{ color:'var(--text-3)' }}>○</span> {(window.appT||(s=>s))("Offline:")} {offline > 0 ? fmtMins(offline) : '—'}</span>
                </div>
              </div>
            </details>

            {/* Attendance pie — 30-day breakdown */}
            {att30 && (() => {
              const a = att30.attendance || {};
              const slices = [
                { key:'on_time',  label:(window.appT||(s=>s))('On-time'),  color:'var(--ok)',     value: a.on_time || 0 },
                { key:'late',     label:(window.appT||(s=>s))('Late'),     color:'var(--warn)',   value: a.late || 0 },
                { key:'sick',     label:(window.appT||(s=>s))('Sick'),     color:'#f472b6',       value: a.sick || 0 },
                { key:'vacation', label:(window.appT||(s=>s))('Vacation'), color:'#06b6d4',       value: a.vacation || 0 },
                { key:'half_day', label:(window.appT||(s=>s))('Half-day'), color:'#a78bfa',       value: a.half_day || 0 },
                { key:'holiday',  label:(window.appT||(s=>s))('Holiday'),  color:'#facc15',       value: a.holiday || 0 },
                { key:'absent',   label:(window.appT||(s=>s))('Absent'),   color:'var(--err)',    value: a.absent || 0 },
              ];
              const pct = a.attendance_pct ?? 0;
              return (
                <details open className="acc-section">
                  <summary><SectionHeader
                    title={`${(window.appT||(s=>s))("Attendance")} — ${(window.appT||(s=>s))((PM_PERIODS.find(([v]) => v === period) || [null,'Month'])[1])}`}
          tip={(window.appT||(s=>s))("Attendance pie over the selected period: on-time / late / absent slices. Click slices for details.")}
                    sub={<span className="mono" style={{ color: pct >= 90 ? 'var(--ok)' : pct >= 70 ? 'var(--warn)' : 'var(--err)' }}>{pct}% · {a.total_hours || 0}h</span>}
                  /></summary>
                  <div style={{ display:'flex', gap: 16, alignItems:'center', marginBottom: 16 }}>
                    <_ScorePie slices={slices} size={110} />
                    <div style={{ flex: 1, fontSize: 12, display:'grid', gridTemplateColumns:'auto 1fr', gap:'2px 10px' }}>
                      {slices.filter(s => s.value > 0).map(s => (
                        <React.Fragment key={s.key}>
                          <span><span style={{ color: s.color }}>●</span> {s.label}</span>
                          <span className="mono num" style={{ textAlign:'right' }}>{s.value}d</span>
                        </React.Fragment>
                      ))}
                      {slices.every(s => !s.value) && <span className="muted" style={{ gridColumn: 'span 2' }}>{(window.appT||(s=>s))("No attendance data in the last 30 days.")}</span>}
                    </div>
                  </div>
                </details>
              );
            })()}

            <details open className="acc-section">
              <summary><SectionHeader title={(window.appT||(s=>s))("Today's attendance")}
                tip={(window.appT||(s=>s))("Snapshot of this employee's sign-in state for today. Late/early follows team's expected_start + buffer (suppressed on a flexible-signin team).")} /></summary>
              <div className="standup-card">
                <div className="standup-row">
                  <div className="lbl">{(window.appT||(s=>s))("Yesterday")}</div>
                  <div className="txt">{person.yesterday}</div>
                </div>
                <div className="standup-row">
                  <div className="lbl">{(window.appT||(s=>s))("Today")}</div>
                  <div className="txt">{person.signed_in ? person.today : <span className="muted">{(window.appT||(s=>s))("— not signed in —")}</span>}</div>
                </div>
                <div className="standup-row">
                  <div className="lbl">{(window.appT||(s=>s))("Issues")}</div>
                  <div className="txt" style={{ color: person.had_blocker ? 'var(--err)' : 'var(--text-2)' }}>
                    {person.blocker_text || (window.appT||(s=>s))('None')}
                  </div>
                </div>
              </div>
            </details>

            <details open className="acc-section">
              <summary><SectionHeader
                title={(window.appT||(s=>s))("Time today")}
                tip={(window.appT||(s=>s))("Wall-clock sign-in / sign-off / hours worked so far today. Times are in the EMPLOYEE'S local timezone (shown next to each time). 'Late' badge fires if In was past team's expected start + buffer (default 09:00 + 30 min). 'Still in' marker means the user has signed in but not yet signed off; Hours then keeps running.")}
                sub={
                  <span style={{ display:'inline-flex', gap: 8, alignItems:'center' }}>
                    {person.sign_in_at && !person.sign_off_at && (
                      <Badge kind="ok" dot title={(window.appT||(s=>s))("User has signed in but not yet signed off — hours are still accruing")}>{(window.appT||(s=>s))("still in")}</Badge>
                    )}
                    {person.was_late && (
                      <Badge kind="warn" title={(window.appT||(s=>s))("Signed in after team's expected start + late buffer")}>{(window.appT||(s=>s))("late")}</Badge>
                    )}
                    {!person.sign_in_at && (
                      <Badge kind="mute" title={(window.appT||(s=>s))("No sign-in recorded today")}>{(window.appT||(s=>s))("no sign-in")}</Badge>
                    )}
                  </span>
                }
              /></summary>
              <TimeBar inAt={person.sign_in_at} outAt={person.sign_off_at} late={person.was_late} />
              <WeekHoursBar hours={statsNow?.week_hours ?? 0} target={statsNow?.week_hours_target ?? 0} />
              <div style={{ display:'flex', justifyContent:'space-between', marginTop: 8, fontSize: 12, color:'var(--text-2)', flexWrap: 'wrap', gap: 8 }}>
                <span title={(window.appT||(s=>s))("Sign-in time in user's local timezone")}>
                  {(window.appT||(s=>s))("In:")} <span className="mono">{person.sign_in_at || '—'}</span>
                  {person.tz && person.sign_in_at && <span className="muted" style={{ fontSize: 10, marginLeft: 4 }}>{person.tz}</span>}
                </span>
                <span title={person.sign_off_at ? (window.appT||(s=>s))('Sign-off time') : (window.appT||(s=>s))('No sign-off yet — running hours are still accruing')}>
                  {(window.appT||(s=>s))("Out:")} <span className="mono">{person.sign_off_at || (window.appT||(s=>s))('— (still in)')}</span>
                </span>
                <span title={person.sign_off_at ? (window.appT||(s=>s))('Total hours worked (signed_off_at − signed_in_at)') : (window.appT||(s=>s))("Running hours so far today — recompute as the user works")}>
                  {person.sign_off_at ? (window.appT||(s=>s))('Total: ') : (window.appT||(s=>s))('Running: ')}
                  <span className="mono">{(person.hours_worked || 0).toFixed(1)} h</span>
                  {!person.sign_off_at && person.sign_in_at && (
                    <span className="muted" style={{ fontSize: 10, marginLeft: 4 }}>· {(window.appT||(s=>s))("still in")}</span>
                  )}
                </span>
              </div>
            </details>

            {/* Efficiency % + KPI + full pillar breakdown — behind an expander
                (progressive disclosure). The card is driven by the modal's
                period selector, mapped day/week→week, month→month,
                6_months/annual→quarter, and labels itself with the period
                actually used so it stays honest. */}
            <details className="acc-section">
              <summary><SectionHeader
                title={(window.appT||(s=>s))("Efficiency & pillars")}
                tip={(window.appT||(s=>s))("Overall weighted efficiency score and its pillar breakdown. Uses the closest supported window to the selected period (week / month / quarter).")}
                sub={<span className="mono" style={{ color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'.06em', fontSize:11 }}>{(window.appT||(s=>s))({ week:'Week', month:'Month', quarter:'Quarter' }[PM_EFF_MAP[period]] || 'Month')}</span>}
              /></summary>
              <div className="card" style={{ marginTop: 8 }}>
                <EfficiencyCard userId={person.id} forcePeriod={PM_EFF_MAP[period]} showSelect={false} />
              </div>
            </details>

            {/* IP-learning Phase 2 — wrapped in try/catch via IIFE so render errors don't break the drawer */}
            {(() => {
              try {
                if (!ipProfile) return null;
                const ready = !!ipProfile.ready;
                const need = Math.max(0, 5 - (ipProfile.training_size || 0));
                const home = ipProfile.home_ips || [];
                const office = ipProfile.office_ips || [];
                const mobile = ipProfile.mobile_ips || [];
                const unknown = ipProfile.unknown_ips || [];
                const red = ipProfile.red_flags_30d || 0;
                const yellow = ipProfile.yellow_flags_30d || 0;
                const renderList = (label, items, color) => (
                  <div style={{ flex:1, minWidth: 0 }}>
                    <div style={{ fontSize: 11, color:'var(--text-3)', marginBottom: 4 }}>{label} ({items.length})</div>
                    {/* label is pre-translated by callers */}
                    {items.length === 0 ? (
                      <div className="muted" style={{ fontSize: 11 }}>—</div>
                    ) : (
                      <div style={{ display:'flex', flexDirection:'column', gap: 2 }}>
                        {items.slice(0, 5).map(it => (
                          <div key={it.ip} className="mono" style={{ fontSize: 11, color }}>
                            {it.ip}{' '}
                            <span className="muted">×{it.occurrences}</span>
                            {/* Item 12: enrich with days/hours/shared_users when backend provides them */}
                            {(it.days != null || it.hours != null) && (
                              <span className="muted" style={{ fontSize: 10, marginLeft: 4 }}>
                                {it.days != null ? `· ${it.days}d` : ''}
                                {it.hours != null ? ` · ${Number(it.hours).toFixed(1)}h` : ''}
                              </span>
                            )}
                            {it.shared_users != null && it.shared_users > 1 && (
                              <span style={{
                                marginLeft: 5, fontSize: 9, fontFamily: 'inherit',
                                background: 'var(--bg-elev-2)', border: '1px solid var(--line)',
                                borderRadius: 3, padding: '1px 4px',
                                color: 'var(--text-2)', verticalAlign: 'middle',
                              }}>
                                {(window.appLang&&window.appLang()==='ar') ? `مشترك ×${it.shared_users}` : `shared ×${it.shared_users}`}
                              </span>
                            )}
                          </div>
                        ))}
                      </div>
                    )}
                  </div>
                );
                return (
                  <details className="acc-section">
                    <summary><SectionHeader
                      title={(window.appT||(s=>s))("IP patterns")}
          tip={(window.appT||(s=>s))("Login IPs the user has signed in from + a red-flag indicator when a new IP looks suspicious (per IP-learning thresholds in team settings).")}
                      sub={
                        <span style={{ display:'inline-flex', gap: 6, alignItems:'center' }}>
                          <Badge kind={ready ? 'ok' : 'warn'}>
                            {ready ? (window.appT||(s=>s))('profile: ready') : ((window.appLang&&window.appLang()==='ar') ? `قيد التعلّم (يلزم ${need} المزيد)` : `learning (need ${need} more)`)}
                          </Badge>
                          {red > 0 && <Badge kind="err" title={(window.appT||(s=>s))("Red-flag IP mismatches in last 30 days")}>{(window.appLang&&window.appLang()==='ar') ? `${red} حمراء` : `${red} red`}</Badge>}
                          {yellow > 0 && <Badge kind="warn" title={(window.appT||(s=>s))("New IPs first observed in last 30 days")}>{(window.appLang&&window.appLang()==='ar') ? `${yellow} جديدة` : `${yellow} new`}</Badge>}
                        </span>
                      }
                    /></summary>
                    <div style={{ display:'flex', gap: 12, marginBottom: 16, flexWrap:'wrap' }}>
                      {renderList((window.appT||(s=>s))('Home IPs'), home, 'var(--ok)')}
                      {renderList((window.appT||(s=>s))('Office IPs'), office, 'var(--info, #60a5fa)')}
                      {renderList((window.appT||(s=>s))('Mobile / on-the-go'), mobile, 'var(--warn, #f59e0b)')}
                      {renderList((window.appT||(s=>s))('Other'), unknown, 'var(--text-2)')}
                    </div>
                  </details>
                );
              } catch (_e) {
                return null;
              }
            })()}

            <details className="acc-section">
              <summary><SectionHeader title={(window.appT||(s=>s))("Recent activity")}
                tip={(window.appT||(s=>s))("Last 50 audit-log events involving this user — DMs sent, strikes, PTO submissions, admin actions. Labels are humanized (e.g. format_strike → 'Format strike').")} sub={(window.appLang&&window.appLang()==='ar') ? `${recentActivity.length} حدث` : `${recentActivity.length} events`} /></summary>
              {recentActivity.length === 0 ? (
                <Empty icon="·" title={(window.appT||(s=>s))("No recent events")} subtitle={(window.appT||(s=>s))("Activity from the last 7 days will appear here.")} />
              ) : (
                <div className="audit-list" style={{ maxHeight: 320, overflowY: 'auto' }}>
                  {recentActivity.map((a, i) => (
                    <div key={i} className="audit-row">
                      <div className="mono ts">{new Date(a.occurred_at).toLocaleString([],{ month:'short', day:'numeric', hour:'2-digit', minute:'2-digit' })}</div>
                      <Badge kind="mute">{a.trigger}</Badge>
                      <div className="detail">{a.detail}</div>
                    </div>
                  ))}
                </div>
              )}
            </details>

            {isManager && devActivity && !devActivity.configured ? null : (
              isManager && devActivity && (
                <details className="acc-section">
                  <summary><SectionHeader title={(window.appT||(s=>s))("Dev activity")}
                    tip={(window.appT||(s=>s))("GitLab / GitHub commits and merge requests over the last 7 days. Requires the GitLab / GitHub integration to be configured and enabled.")}
                    sub={(window.appLang&&window.appLang()==='ar') ? "آخر ٧ أيام" : "last 7 days"} /></summary>
                  <div className="audit-row" style={{ gap: 10 }}>
                    <Badge kind="mute">{(devActivity.commits ?? 0)} {(window.appT||(s=>s))("commits")}</Badge>
                    <Badge kind="mute">{((devActivity.merge_requests ?? 0) + (devActivity.prs ?? 0))} {(window.appT||(s=>s))("MRs")}</Badge>
                    {Array.isArray(devActivity.repos) && devActivity.repos.length > 0 && (
                      <div className="detail mono" style={{ fontSize: 11, opacity: 0.75 }}>{devActivity.repos.slice(0, 5).join(', ')}</div>
                    )}
                  </div>
                </details>
              )
            )}
          </div>

          <div className="drawer-foot">
            <button className="btn btn-ghost" onClick={() => setDmOpen(true)} disabled={!isManager} title={!isManager ? (window.appT||(s=>s))('Manager or admin required') : ''}><Icon name="msg" size={13}/> {(window.appT||(s=>s))("DM")}</button>
            <button
              className="btn btn-ghost"
              onClick={() => setRoleOpen(true)}
              disabled={!canEditRole}
              title={!canEditRole ? (window.appT||(s=>s))('Admin or superadmin required to change role') : ''}
            ><Icon name="edit" size={13}/> {(window.appT||(s=>s))("Edit role")}</button>
            <button
              className="btn btn-ghost"
              onClick={() => setProfileOpen(true)}
              disabled={!canEditProfile}
              title={
                !canEditProfile
                  ? (isModerator ? (window.appT||(s=>s))('Moderators can only edit their own profile') : (window.appT||(s=>s))('Manager or admin required'))
                  : (isModerator && isSelf ? (window.appT||(s=>s))('Editing your own profile') : '')
              }
            ><Icon name="edit" size={13}/> {(window.appT||(s=>s))("Edit profile")}</button>
            <div style={{ marginLeft:'auto', position:'relative', display:'inline-flex' }}>
              <button className="btn btn-pri" style={{ borderRadius:'var(--r-md) 0 0 var(--r-md)', paddingRight: 8 }} onClick={() => handleSendReport('monthly')} disabled={reportBusy} title={(window.appT||(s=>s))("Send monthly report via DM")}>
                <Icon name="report" size={13}/> {reportBusy ? '…' : (window.appT||(s=>s))('Full report')}
              </button>
              <button className="btn btn-pri" style={{ borderRadius:'0 var(--r-md) var(--r-md) 0', borderLeft:'1px solid rgba(255,255,255,.2)', padding:'0 7px' }} onClick={() => setReportMenuOpen(v => !v)} disabled={reportBusy} title={(window.appT||(s=>s))("More report options")}>
                ▾
              </button>
              {reportMenuOpen && (
                <div onClick={e => e.stopPropagation()} style={{
                  position:'absolute', bottom:'calc(100% + 6px)', right:0, zIndex:60,
                  background:'var(--bg-elev-2)', border:'1px solid var(--line)',
                  borderRadius:'var(--r-md)', padding:6, minWidth:170,
                  boxShadow:'0 8px 24px -6px rgba(0,0,0,.4)',
                }}>
                  <div style={{ fontSize:10, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'.06em', padding:'2px 8px 4px' }}>{(window.appT||(s=>s))("Variant")}</div>
                  <div style={{ display:'flex', gap:4, padding:'0 8px 2px' }}>
                    {[[null,(window.appT||(s=>s))('Auto')],['employee',(window.appT||(s=>s))('Employee copy')],['admin',(window.appT||(s=>s))('Admin (full)')]].map(([a,label]) => (
                      <button key={String(a)} className={`btn ${reportAudience===a ? 'btn-pri' : 'btn-ghost'}`}
                        style={{ flex:1, padding:'4px 6px', fontSize:11 }}
                        title={a===null
                          ? (window.appT||(s=>s))('DM sends the employee copy; download is the full report')
                          : a==='employee'
                          ? (window.appT||(s=>s))('Shareable: outcomes only — no weights, location or integrity detail')
                          : (window.appT||(s=>s))('Full internal report — weights, location mix, integrity signal')}
                        onClick={() => setReportAudience(a)}>{label}</button>
                    ))}
                  </div>
                  <div style={{ fontSize:9.5, color:'var(--text-3)', padding:'0 8px 6px' }}>
                    {(window.appT||(s=>s))('Auto: DM → employee copy · download → full report')}
                  </div>
                  <div style={{ height:1, background:'var(--line)', margin:'4px 0' }}/>
                  <div style={{ fontSize:10, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'.06em', padding:'2px 8px 4px' }}>{(window.appT||(s=>s))("Send via DM")}</div>
                  {[['monthly',(window.appT||(s=>s))('Monthly')],['weekly',(window.appT||(s=>s))('Weekly')],['daily',(window.appT||(s=>s))('Daily')],['6month',(window.appT||(s=>s))('6 months')],['annual',(window.appT||(s=>s))('Annual')]].map(([r,label]) => (
                    <button key={r} className="btn btn-ghost" style={{ width:'100%', justifyContent:'flex-start', padding:'5px 8px', fontSize:12 }}
                      onClick={() => handleSendReport(r)}>{label}</button>
                  ))}
                  <div style={{ height:1, background:'var(--line)', margin:'4px 0' }}/>
                  <div style={{ fontSize:10, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'.06em', padding:'2px 8px 4px' }}>{(window.appT||(s=>s))("Download PDF")}</div>
                  {[['monthly',(window.appT||(s=>s))('Monthly')],['weekly',(window.appT||(s=>s))('Weekly')],['daily',(window.appT||(s=>s))('Daily')],['6month',(window.appT||(s=>s))('6 months')],['annual',(window.appT||(s=>s))('Annual')]].map(([r,label]) => (
                    <button key={r} className="btn btn-ghost" style={{ width:'100%', justifyContent:'flex-start', padding:'5px 8px', fontSize:12 }}
                      onClick={() => handleDownloadPdf(r)}>{label}</button>
                  ))}
                  <div style={{ height:1, background:'var(--line)', margin:'4px 0' }}/>
                  <div style={{ fontSize:10, color:'var(--text-3)', textTransform:'uppercase', letterSpacing:'.06em', padding:'2px 8px 4px' }}>{(window.appT||(s=>s))("Custom range")}</div>
                  <div style={{ display:'flex', gap:4, padding:'0 8px 2px' }}>
                    <input type="date" className="inp" value={customStart}
                      max={customEnd || undefined}
                      onChange={e => setCustomStart(e.target.value)}
                      title={(window.appT||(s=>s))("From")}
                      style={{ flex:1, fontSize:11, padding:'3px 4px' }} />
                    <input type="date" className="inp" value={customEnd}
                      min={customStart || undefined}
                      onChange={e => setCustomEnd(e.target.value)}
                      title={(window.appT||(s=>s))("To")}
                      style={{ flex:1, fontSize:11, padding:'3px 4px' }} />
                  </div>
                  <div style={{ display:'flex', gap:4, padding:'2px 8px 4px' }}>
                    <button className="btn btn-ghost" disabled={!customStart || !customEnd}
                      style={{ flex:1, justifyContent:'center', padding:'5px 8px', fontSize:11 }}
                      title={(window.appT||(s=>s))("Send the custom-period report via DM")}
                      onClick={() => handleSendReport('monthly', customStart, customEnd)}>{(window.appT||(s=>s))('Send')}</button>
                    <button className="btn btn-ghost" disabled={!customStart || !customEnd}
                      style={{ flex:1, justifyContent:'center', padding:'5px 8px', fontSize:11 }}
                      title={(window.appT||(s=>s))("Download the custom-period PDF")}
                      onClick={() => handleDownloadPdf('monthly', customStart, customEnd)}>{(window.appT||(s=>s))('Download')}</button>
                  </div>
                </div>
              )}
            </div>
          </div>
        </div>
      </div>

      {isManager && dmOpen && <DmModal person={person} onClose={() => setDmOpen(false)} />}
      {roleOpen && <EditRoleModal person={person} onClose={() => setRoleOpen(false)} onSaved={(newRole) => { person.org_role = newRole; person.role = newRole; try { window.dispatchEvent(new CustomEvent("attendance-updated")); window.dispatchEvent(new CustomEvent("status-data-refresh-requested")); } catch(_) {} }} />}
      {profileOpen && (
        <ModalErrorBoundary onClose={() => setProfileOpen(false)}>
          <EditProfileModal person={person} onClose={() => setProfileOpen(false)} />
        </ModalErrorBoundary>
      )}
    </>
  );
}

/* ── Add Employee Wizard (D5) ── */
function AddEmployeeWizard({ onClose }) {
  const D = window.STATUS_DATA;
  const toast = useToast();

  const [step, setStep] = useState(1);
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');

  const [matrixId, setMatrixId]   = useState('');
  const [displayName, setDisplay] = useState('');
  const [email, setEmail]         = useState('');
  const [timezone, setTimezone]   = useState(Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC');
  // Auto-seed working_days from the initial TZ (Cairo / ME → 7d, else Mon-Fri).
  // Admin can override via the chip row in Step 1.
  const [workingDays, setWorkingDays] = useState(
    (window.defaultWorkingDaysForTz || ((tz) => 'mon,tue,wed,thu,fri'))(
      Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'
    )
  );
  // Has the admin manually edited working_days? If yes we never overwrite
  // it from a later TZ change. Defaults to false so the initial TZ seed
  // remains "implicit" and stays in sync with TZ until the admin touches it.
  const [workingDaysTouched, setWorkingDaysTouched] = useState(false);

  const [teamChoice, setTeamChoice] = useState('existing');
  const [teamId, setTeamId]         = useState('');
  const [newTeamName, setNewTeamName] = useState('');
  const [selectedRooms, setSelectedRooms] = useState([]);
  const [sendWelcome, setSendWelcome] = useState(true);
  const [inviteToAll, setInviteToAll] = useState(true);  // default: invite to every bot-managed room


  const teams = D.TEAMS || [];
  const rooms = D.ROOMS || [];

  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  useEffect(() => {
    if (inviteToAll) {
      const all = (D.ROOMS || []).map(r => r.room_id).filter(Boolean);
      setSelectedRooms(all);
    }
  }, [inviteToAll]);

  const toggleRoom = (roomId) => {
    setSelectedRooms(prev =>
      prev.includes(roomId) ? prev.filter(r => r !== roomId) : [...prev, roomId]
    );
  };

  const canNext1 = matrixId.trim().length > 3;
  const canNext2 = teamChoice === 'none' || (teamChoice === 'existing' && teamId) || (teamChoice === 'new' && newTeamName.trim());

  const submit = async () => {
    setErr(''); setBusy(true);
    try {
      const body = {
        matrix_id: matrixId.trim(),
        display_name: displayName.trim(),
        email: email.trim() || undefined,
        timezone: timezone || undefined,
        // Always persist working_days (TZ-defaulted or admin-edited)
        // so the bot's is_working_day() returns the right answer from
        // the user's first sign-in.
        working_days: workingDays || undefined,
        send_welcome_dm: sendWelcome,
        invite_to_all_rooms: inviteToAll,
        rooms: selectedRooms,
      };
      if (teamChoice === 'existing' && teamId) {
        body.team_id = parseInt(teamId, 10);
      } else if (teamChoice === 'new' && newTeamName.trim()) {
        body.new_team = { name: newTeamName.trim(), timezone };
      }

      const r = await fetch('/api/employees/wizard', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify(body),
      });
      if (!r.ok) {
        const errBody = await r.json().catch(() => null);
        throw new Error(parseDetail(errBody, r.status));
      }
      const data = await r.json();
      toast.push({ kind: 'ok', msg: (window.appLang&&window.appLang()==='ar') ? `تمت إضافة ${data.user?.display_name || matrixId} إلى الفريق` : `${data.user?.display_name || matrixId} added to the team` });
      onClose();
    } catch (e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  const STEPS = [(window.appT||(s=>s))('Identity'), (window.appT||(s=>s))('Team & Rooms'), (window.appT||(s=>s))('Confirm')];

  return (
    <div className="drawer-overlay" onClick={onClose} style={{ zIndex: 300 }}>
      <div
        onClick={e => e.stopPropagation()}
        style={{
          background: 'var(--bg-elev-1)',
          border: '1px solid var(--line-hi)',
          borderRadius: 'var(--r-lg)',
          width: 480, maxWidth: '95vw',
          padding: 24,
          position: 'fixed',
          top: '50%', left: '50%',
          transform: 'translate(-50%,-50%)',
          boxShadow: '0 24px 80px -20px #000000c0',
        }}
      >
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 20 }}>
          <div>
            <div style={{ fontWeight: 600, fontSize: 15 }}>{(window.appT||(s=>s))("Add team member")}</div>
            <div className="muted" style={{ fontSize: 12 }}>{(window.appLang&&window.appLang()==='ar') ? `الخطوة ${step} من 3 — ${STEPS[step-1]}` : `Step ${step} of 3 — ${STEPS[step-1]}`}</div>
          </div>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>

        <div style={{ display:'flex', gap: 6, marginBottom: 24 }}>
          {STEPS.map((s, i) => (
            <div key={s} style={{ flex: 1, height: 3, borderRadius: 99,
              background: i + 1 <= step ? 'var(--brand)' : 'var(--bg-elev-2)' }} />
          ))}
        </div>

        {step === 1 && (
          <div style={{ display:'flex', flexDirection:'column', gap: 14 }}>
            <label style={{ display:'flex', flexDirection:'column', gap: 5 }}>
              <span style={{ fontSize: 12, color:'var(--text-2)' }}>{(window.appT||(s=>s))("K9 Chat ID")} <span style={{ color:'var(--err)' }}>*</span></span>
              <input className="inp" value={matrixId} onChange={e => setMatrixId(e.target.value)} placeholder="@username:k9.ms" autoFocus />
              <span style={{ fontSize: 11, color:'var(--text-3)' }}>{(window.appT||(s=>s))("Full K9 chat ID including the @user:server part")}</span>
            </label>
            <label style={{ display:'flex', flexDirection:'column', gap: 5 }}>
              <span style={{ fontSize: 12, color:'var(--text-2)' }}>{(window.appT||(s=>s))("Display name")}</span>
              <input className="inp" value={displayName} onChange={e => setDisplay(e.target.value)} placeholder="Jane Smith" />
            </label>
            <label style={{ display:'flex', flexDirection:'column', gap: 5 }}>
              <span style={{ fontSize: 12, color:'var(--text-2)' }}>{(window.appT||(s=>s))("Email (optional)")}</span>
              <input className="inp" type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="jane@company.com" />
            </label>
            <label style={{ display:'flex', flexDirection:'column', gap: 5 }}>
              <span style={{ fontSize: 12, color:'var(--text-2)' }}>{(window.appT||(s=>s))("Timezone")}</span>
              <input
                className="inp"
                value={timezone}
                onChange={e => {
                  const tz = e.target.value;
                  setTimezone(tz);
                  // Auto-seed working_days from TZ ONLY if admin hasn't
                  // manually adjusted them. Cairo / Middle East → all 7
                  // days; Americas / Europe → Mon-Fri.
                  if (!workingDaysTouched) {
                    setWorkingDays((window.defaultWorkingDaysForTz || (() => 'mon,tue,wed,thu,fri'))(tz));
                  }
                }}
                placeholder="America/New_York"
              />
              <div className="muted" style={{ fontSize: 10.5, marginTop: 1 }}>
                {(window.appT||(s=>s))("Working days auto-default to")}{' '}
                <span className="mono">{workingDays || '—'}</span>{' '}
                {(window.appT||(s=>s))("based on this timezone. Edit below if needed.")}
              </div>
            </label>
            <label style={{ display:'flex', flexDirection:'column', gap: 5 }}>
              <span style={{ fontSize: 12, color:'var(--text-2)' }}>{(window.appT||(s=>s))("Working days")}</span>
              <WorkingDaysChips
                value={workingDays}
                onChange={(v) => { setWorkingDays(v); setWorkingDaysTouched(true); }}
              />
              <div className="muted" style={{ fontSize: 10.5, marginTop: 4, lineHeight: 1.45 }}>
                <strong style={{ color: 'var(--text-2)' }}>{(window.appT||(s=>s))("Each employee can have a different schedule")}</strong>{' '}
                {(window.appT||(s=>s))("— even in the same timezone. Toggle chips to match what this person actually works. The bot will count only those days toward their attendance and DM them on the rest.")}
              </div>
            </label>
          </div>
        )}

        {step === 2 && (
          <div style={{ display:'flex', flexDirection:'column', gap: 16 }}>
            <div>
              <div style={{ fontSize: 12, color:'var(--text-2)', marginBottom: 8 }}>{(window.appT||(s=>s))("Team")}</div>
              <Segmented
                value={teamChoice} onChange={setTeamChoice}
                options={[
                  { value:'existing', label:(window.appT||(s=>s))('Existing team') },
                  { value:'new',      label:(window.appT||(s=>s))('New team') },
                  { value:'none',     label:(window.appT||(s=>s))('No team') },
                ]}
              />
              {teamChoice === 'existing' && (
                <select className="inp" value={teamId} onChange={e => setTeamId(e.target.value)} style={{ marginTop: 10, width:'100%' }}>
                  <option value="">{(window.appT||(s=>s))("Select a team…")}</option>
                  {teams.map(t => <option key={t.id || t.team_name} value={t.id || t.team_name}>{t.team_name}</option>)}
                </select>
              )}
              {teamChoice === 'new' && (
                <input
                  className="inp" style={{ marginTop: 10, width:'100%' }}
                  value={newTeamName} onChange={e => setNewTeamName(e.target.value)}
                  placeholder={(window.appT||(s=>s))("New team name…")}
                />
              )}
            </div>
            <div>
              <div style={{ fontSize: 12, color:'var(--text-2)', marginBottom: 8, display:'flex', alignItems:'center', justifyContent:'space-between' }}>
                <span>{(window.appT||(s=>s))("Invite to rooms")}</span>
                <label style={{ display:'flex', alignItems:'center', gap: 6, cursor:'pointer', fontSize: 12, color:'var(--text-2)' }}>
                  <input type="checkbox" checked={inviteToAll} onChange={e => setInviteToAll(e.target.checked)} />
                  {(window.appT||(s=>s))("Invite to ALL rooms")}
                </label>
              </div>
              <div style={{ display:'flex', flexDirection:'column', gap: 6, maxHeight: 160, overflowY:'auto' }}>
                {rooms.length === 0 && <span className="muted" style={{ fontSize: 12 }}>{(window.appT||(s=>s))("No rooms configured.")}</span>}
                {rooms.map(room => (
                  <label key={room.room_id} style={{ display:'flex', alignItems:'center', gap: 8, cursor: inviteToAll ? 'not-allowed' : 'pointer', fontSize: 13, opacity: inviteToAll ? 0.55 : 1 }}>
                    <input
                      type="checkbox"
                      disabled={inviteToAll}
                      checked={selectedRooms.includes(room.room_id)}
                      onChange={() => toggleRoom(room.room_id)}
                    />
                    <span>{room.display_name || room.room_id}</span>
                  </label>
                ))}
              </div>
            </div>
            <label style={{ display:'flex', alignItems:'center', gap: 8, fontSize: 13, cursor:'pointer' }}>
              <input type="checkbox" checked={sendWelcome} onChange={e => setSendWelcome(e.target.checked)} />
              {(window.appT||(s=>s))("Send welcome DM via bot")}
            </label>
          </div>
        )}

        {step === 3 && (
          <div style={{ display:'flex', flexDirection:'column', gap: 10, fontSize: 13 }}>
            <div style={{ background:'var(--bg-elev-2)', borderRadius:'var(--r)', padding: 14, display:'flex', flexDirection:'column', gap: 8 }}>
              <Row label={(window.appT||(s=>s))("K9 Chat ID")}     value={matrixId} />
              <Row label={(window.appT||(s=>s))("Display name")}  value={displayName || (window.appT||(s=>s))('(from K9 Chat ID)')} />
              {email && <Row label={(window.appT||(s=>s))("Email")} value={email} />}
              <Row label={(window.appT||(s=>s))("Timezone")}      value={timezone} />
              <Row label={(window.appT||(s=>s))("Team")}
                value={
                  teamChoice === 'none' ? (window.appT||(s=>s))('None') :
                  teamChoice === 'new'  ? ((window.appLang&&window.appLang()==='ar') ? `جديد: ${newTeamName}` : `New: ${newTeamName}`) :
                  teams.find(t => String(t.id) === teamId)?.team_name || teamId
                }
              />
              <Row label={(window.appT||(s=>s))("Room invites")}  value={inviteToAll ? ((window.appLang&&window.appLang()==='ar') ? `كل الغرف (${(D.ROOMS||[]).length})` : `All rooms (${(D.ROOMS||[]).length})`) : (selectedRooms.length > 0 ? ((window.appLang&&window.appLang()==='ar') ? `${selectedRooms.length} غرفة` : `${selectedRooms.length} room(s)`) : (window.appT||(s=>s))('None'))} />
              <Row label={(window.appT||(s=>s))("Welcome DM")}    value={sendWelcome ? (window.appT||(s=>s))('Yes') : (window.appT||(s=>s))('No')} />
            </div>
            {err && <div style={{ color:'var(--err)', fontSize: 12 }}>{err}</div>}
          </div>
        )}

        <div style={{ display:'flex', justifyContent:'space-between', marginTop: 24 }}>
          <button className="btn btn-ghost" onClick={step === 1 ? onClose : () => setStep(s => s - 1)}>
            {step === 1 ? (window.appT||(s=>s))('Cancel') : (window.appT||(s=>s))('Back')}
          </button>
          {step < 3 ? (
            <button
              className="btn btn-pri"
              disabled={step === 1 ? !canNext1 : !canNext2}
              onClick={() => setStep(s => s + 1)}
            >
              {(window.appT||(s=>s))("Next")}
            </button>
          ) : (
            <button className="btn btn-pri" disabled={busy} onClick={submit}>
              {busy ? (window.appT||(s=>s))('Adding…') : (window.appT||(s=>s))('Add member')}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

function Row({ label, value }) {
  return (
    <div style={{ display:'flex', gap: 12 }}>
      <span style={{ color:'var(--text-2)', width: 100, flexShrink: 0 }}>{label}</span>
      <span style={{ fontWeight: 500, wordBreak:'break-all' }}>{value}</span>
    </div>
  );
}

window.EmployeesPage = EmployeesPage;
