/* ============================================================
   page-ops.jsx — PTO, Teams, Rooms
   ============================================================ */

/* i18n helper — returns Arabic when language=Arabic, else the English string */
const T = (s) => (window.appT || ((x) => x))(s);

/* 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}`;
});

/* ── Request PTO Modal ── */
function RequestPtoModal({ onClose, onCreated }) {
  const D = window.STATUS_DATA;
  const [startDate, setStartDate] = useState(() => new Date().toISOString().slice(0,10));
  const [endDate, setEndDate]     = useState(() => new Date().toISOString().slice(0,10));
  const [ptoType, setPtoType]     = useState('pto');
  const [userId, setUserId]       = useState(D.ME?.id || '');
  const [reason, setReason]       = useState('');
  const [busy, setBusy]           = useState(false);
  const [err, setErr]             = useState('');
  const toast = useToast();

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

  const submit = async () => {
    setBusy(true); setErr('');
    try {
      const r = await fetch('/api/pto', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ user_id: userId, start_date: startDate, end_date: endDate, pto_type: ptoType, reason }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      toast.push({ msg: T('PTO request created'), kind: 'ok' });
      onCreated && onCreated();
      onClose();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  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: 420, maxWidth:'95vw',
        boxShadow:'0 24px 64px -16px #00000080', zIndex: 301,
      }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 20 }}>
          <div style={{ fontWeight: 600, fontSize: 15 }}>{T('Request time off')}</div>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        {err && <div className="alert-err" style={{ marginBottom: 14 }}><Icon name="alert" size={13}/> {err}</div>}

        <div style={{ marginBottom: 14 }}>
          <label className="login-label">{T('Employee')}</label>
          <select className="sel" value={userId} onChange={e => setUserId(e.target.value)} style={{ width:'100%' }}>
            {(D.EMPLOYEES || []).map(e => <option key={e.id} value={e.id}>{e.display_name}</option>)}
          </select>
        </div>

        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 12, marginBottom: 14 }}>
          <div>
            <label className="login-label">{T('Start')}</label>
            <input className="inp" type="date" value={startDate} onChange={e => setStartDate(e.target.value)} />
          </div>
          <div>
            <label className="login-label">{T('End')}</label>
            <input className="inp" type="date" value={endDate} onChange={e => setEndDate(e.target.value)} />
          </div>
        </div>

        <div style={{ marginBottom: 14 }}>
          <label className="login-label">{T('Type')}</label>
          <select className="sel" value={ptoType} onChange={e => setPtoType(e.target.value)} style={{ width:'100%' }}>
            <option value="pto">{T('Vacation / PTO')}</option>
            <option value="sick">{T('Sick leave')}</option>
            <option value="personal">{T('Personal day')}</option>
            <option value="holiday">{T('Holiday')}</option>
          </select>
        </div>

        <div style={{ marginBottom: 20 }}>
          <label className="login-label">{T('Reason (optional)')}</label>
          <input className="inp" value={reason} onChange={e => setReason(e.target.value)} placeholder={T('Brief description...')} />
        </div>

        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end' }}>
          <button className="btn btn-ghost" onClick={onClose}>{T('Cancel')}</button>
          <button className="btn btn-pri" onClick={submit} disabled={busy}>
            {busy ? T('Submitting...') : <><Icon name="check" size={13}/> {T('Submit')}</>}
          </button>
        </div>
      </div>
    </div>
  );
}

/* ── PTO Calendar component — real data from /api/pto/calendar ── */
function PtoCalendarPanel({ month, calEntries, loading }) {
  const entryMap = useMemo(() => {
    const m = {};
    (calEntries || []).forEach(e => {
      if (!e.start_date || !e.end_date) return;
      const start = new Date(e.start_date + 'T00:00:00');
      const end   = new Date(e.end_date   + 'T00:00:00');
      for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
        const key = d.toISOString().slice(0,10);
        if (!m[key]) m[key] = [];
        m[key].push(e);
      }
    });
    return m;
  }, [calEntries]);

  const userColor = (uid) => {
    let h = 0;
    const s = String(uid);
    for (let i = 0; i < s.length; i++) { h = (h * 31 + s.charCodeAt(i)) & 0xffff; }
    return `hsl(${(h % 360)}, 60%, 50%)`;
  };

  const [year, mon] = month.split('-').map(Number);
  const daysInMonth = new Date(year, mon, 0).getDate();
  const firstDow    = new Date(year, mon - 1, 1).getDay();

  const cells = [];
  for (let i = 0; i < firstDow; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(d);

  const DOW = ['Su','Mo','Tu','We','Th','Fr','Sa'];

  if (loading) return <Loading label={T('Loading calendar...')} rows={3} />;

  return (
    <div>
      <div style={{ display:'grid', gridTemplateColumns:'repeat(7, 1fr)', gap: 3, marginBottom: 4 }}>
        {DOW.map(d => (
          <div key={d} style={{ textAlign:'center', fontSize: 10, fontFamily:'var(--font-mono)', color:'var(--text-3)', paddingBottom: 4 }}>{T(d)}</div>
        ))}
        {cells.map((d, i) => {
          if (!d) return <div key={`e-${i}`} />;
          const dateStr = `${year}-${String(mon).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
          const entries = entryMap[dateStr] || [];
          const isWeekend = ((firstDow + d - 1) % 7) === 0 || ((firstDow + d - 1) % 7) === 6;
          return (
            <div key={d} style={{
              borderRadius: 4, padding: '3px 2px',
              background: entries.length ? 'var(--bg-elev-2)' : 'transparent',
              border: `1px solid ${entries.length ? 'var(--line-hi)' : 'var(--line)'}`,
              minHeight: 36, opacity: isWeekend ? 0.5 : 1,
            }}>
              <div style={{ fontSize: 10, color: 'var(--text-3)', textAlign:'center', marginBottom: 2 }}>{d}</div>
              <div style={{ display:'flex', flexDirection:'column', gap: 1 }}>
                {entries.slice(0,2).map((e, j) => (
                  <div key={j} title={`${e.user_name} · ${e.type} · ${e.status}`}
                    style={{ fontSize: 9, borderRadius: 2, padding: '1px 3px', background: userColor(e.user_id), color: '#fff', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }}>
                    {(e.user_name || '').split(' ')[0]}
                  </div>
                ))}
                {entries.length > 2 && (
                  <div style={{ fontSize: 9, color:'var(--text-3)', textAlign:'center' }}>+{entries.length - 2}</div>
                )}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ── PTO Balances panel ── */
function PtoBalancePie({ used, quota, size = 56 }) {
  const u = Math.max(0, Math.min(quota, used || 0));
  const total = Math.max(1, quota || 0);
  const r = size / 2 - 4;
  const c = 2 * Math.PI * r;
  const cx = size / 2, cy = size / 2;
  const usedLen = (u / total) * c;
  const remColor = (quota - u) <= 2 ? 'var(--err)' : (quota - u) <= 4 ? 'var(--warn)' : 'var(--ok)';
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{flexShrink:0, transform:'rotate(-90deg)'}}>
      <circle cx={cx} cy={cy} r={r} fill="none" stroke={remColor} strokeWidth={6} />
      <circle cx={cx} cy={cy} r={r} fill="none" stroke="var(--text-3)" strokeWidth={6}
        strokeDasharray={`${usedLen} ${c - usedLen}`} />
      <text x={cx} y={cy} dy="0.4em" textAnchor="middle" fill="var(--text)" fontSize={12} fontWeight={700}
        style={{transform:'rotate(90deg)', transformOrigin:`${cx}px ${cy}px`, fontFamily:'var(--font-mono)'}}>
        {quota - u}
      </text>
    </svg>
  );
}

function PtoBalancesPanel({ balances, loading }) {
  if (loading) return <Loading label={T('Loading balances...')} rows={3} />;
  if (!balances || balances.length === 0) {
    return <Empty icon={<Icon name="pto" size={24}/>} title={T('No balance data')} subtitle={T('PTO balances will appear once quota is configured.')} />;
  }
  return (
    <div style={{ flex: 1, minHeight: 0, overflowY: 'auto', display:'flex', flexDirection:'column', gap: 6 }}>
      {balances.map(b => {
        const remaining = b.remaining ?? (b.quota - b.used);
        return (
          <div key={b.user_id} style={{ display:'flex', alignItems:'center', gap: 10, padding:'5px 2px', borderBottom:'1px solid var(--line)' }}>
            <PtoBalancePie used={b.used} quota={b.quota} size={36} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 12, fontWeight: 500, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{b.display_name}</div>
              <div className="mono muted" style={{ fontSize: 10 }}>{b.used} {T('used')} · <span style={{ color: remaining <= 2 ? 'var(--err)' : remaining <= 4 ? 'var(--warn)' : 'var(--ok)' }}>{remaining} {T('left')}</span> · {T('quota')} {b.quota}</div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

function UpcomingPtoPanel() {
  const [items, setItems]     = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const t0 = new Date();
    const t1 = new Date(); t1.setDate(t1.getDate() + 14);
    const from = t0.toISOString().slice(0, 10);
    const to   = t1.toISOString().slice(0, 10);
    fetch(`/api/pto/calendar?from=${from}&to=${to}`, { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : [])
      .then(d => setItems(Array.isArray(d) ? d : (d.entries || [])))
      .catch(() => {})
      .finally(() => setLoading(false));
  }, []);

  const color = (uid) => {
    let h = 0; const s = String(uid);
    for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) & 0xffff;
    return `hsl(${h % 360}, 60%, 50%)`;
  };
  const fmt = (iso) => {
    try { return new Date(iso + 'T00:00:00').toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); }
    catch { return iso; }
  };
  const typeLabel = { pto: 'Vacation', vacation: 'Vacation', sick: 'Sick', half_day: 'Half-day', holiday: 'Holiday' };
  const todayIso = new Date().toISOString().slice(0, 10);
  const rows = (items || [])
    .filter(e => e.start_date && e.end_date && String(e.status || '').toLowerCase() === 'approved' && e.end_date >= todayIso)
    .sort((a, b) => a.start_date.localeCompare(b.start_date));

  return (
    <div style={{ display:'flex', flexDirection:'column', flex: 1 }}>
      <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-3)', textTransform: 'uppercase', marginBottom: 8 }}>
        {T('Upcoming time off')} <span style={{ opacity: 0.6 }}>· {T('next 14 days')}</span>
      </div>
      {loading ? (
        <Loading label={T('Loading…')} rows={2} />
      ) : rows.length === 0 ? (
        <div className="muted" style={{ fontSize: 12 }}>{T('No approved time off in the next 14 days.')}</div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {rows.map((e, i) => (
            <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <span style={{ width: 8, height: 8, borderRadius: '50%', background: color(e.user_id), flexShrink: 0 }} />
              <div style={{ flex: 1, minWidth: 0, fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                {e.user_name}
              </div>
              <div className="mono muted" style={{ fontSize: 11, flexShrink: 0 }}>
                {fmt(e.start_date)}{e.end_date !== e.start_date ? ` – ${fmt(e.end_date)}` : ''}
              </div>
              <span className="badge" style={{ fontSize: 9, padding: '1px 6px', flexShrink: 0 }}>
                {T(typeLabel[e.type] || e.type || 'Leave')}
              </span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ── Long-session review queue (2026-08-06) — admin only ──
   A day whose unbroken sign-in→sign-off span exceeds
   attendance.max_daily_actual_hours is NOT auto-credited: the backend clamps
   it and files a review flag, because a closed day credits the raw span with
   nothing anchoring its middle and presence can't arbitrate (org-wide online
   ratios run 1-15%). The bot deliberately refuses to decide — this panel is
   where the human does. Without it the flags were written and never read. */
function LongSessionReviewPanel() {
  const [items, setItems]     = useState([]);
  const [loading, setLoading] = useState(true);
  const [busyKey, setBusyKey] = useState(null);
  const [capHours, setCapHours] = useState(null);
  const toast = useToast();
  const D = window.STATUS_DATA;
  const isAdmin = (window.isAdminOrAbove
    ? window.isAdminOrAbove(D?.ME)
    : ['admin', 'moderator', 'superadmin'].includes((D?.ME?.org_role || '').toLowerCase()));

  const load = async () => {
    setLoading(true);
    try {
      const r = await fetch('/api/long-session-reviews', { credentials: 'same-origin' });
      if (!r.ok) {
        const b = await r.json().catch(() => null);
        throw new Error(parseDetail(b, r.status));
      }
      const j = await r.json();
      setItems(Array.isArray(j.items) ? j.items : []);
      setCapHours(j.cap_hours != null ? j.cap_hours : null);
    } catch (e) {
      toast.push({ kind: 'err', msg: T('Long-session queue failed: ') + (e.message || e) });
      setItems([]);
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    if (isAdmin) load(); else setLoading(false);
  }, [isAdmin]);

  if (!isAdmin) return null;

  const keyOf = (it) => `${it.user_id}:${it.work_date}`;

  const decide = async (item, decision) => {
    const key = keyOf(item);
    setBusyKey(key);
    try {
      const r = await fetch(
        `/api/long-session-reviews/${item.user_id}/${item.work_date}/${decision}`,
        {
          method: 'POST',
          credentials: 'include',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ note: null }),
        },
      );
      if (!r.ok) {
        const b = await r.json().catch(() => null);
        throw new Error(parseDetail(b, r.status));
      }
      const j = await r.json();
      // Decided items retire from the queue either way — the backend is
      // idempotent, so a duplicate click just reports the recorded decision.
      setItems(list => list.filter(x => keyOf(x) !== key));
      toast.push({
        kind: decision === 'confirm' ? 'ok' : 'err',
        icon: decision === 'confirm' ? '✓' : '✕',
        msg: j.already_decided
          ? `${T('Already decided')} — ${item.display_name}`
          : (decision === 'confirm'
              ? `${T('Credited')} ${item.claimed_hours}h — ${item.display_name}`
              : `${T('Cap kept')} — ${item.display_name}`),
      });
    } catch (e) {
      toast.push({ kind: 'err', msg: T('Failed: ') + (e.message || e) });
    } finally {
      setBusyKey(null);
    }
  };

  const clock = (iso) => {
    if (!iso) return '—';
    try {
      return new Date(iso).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
    } catch (_) { return iso; }
  };

  return (
    <div className="card" style={{ display:'flex', flexDirection:'column' }}>
      <SectionHeader
        title={T('Long-session review')}
        tip={T("Days whose unbroken sign-in→sign-off span passed the daily ceiling. The bot capped the credited hours instead of paying out or denying a span nothing corroborates. Confirm to credit the full span, Reject to keep the cap.")}
        sub={capHours != null
          ? `${T('Capped at')} ${capHours}${T('h')} ${T('pending a human call')}`
          : T('Pending a human call')}
        right={<Refresh onClick={load} loading={loading} />}
      />
      {loading ? (
        <Loading label={T('Loading…')} rows={2} />
      ) : items.length === 0 ? (
        <div className="muted" style={{ fontSize: 12 }}>
          {T('No capped days awaiting review.')}
        </div>
      ) : (
        <table className="t">
          <thead>
            <tr>
              <th>{T('Person')}</th>
              <th>{T('Day')}</th>
              <th className="num">{T('Claimed')}</th>
              <th className="num">{T('Credited')}</th>
              <th>{T('Evidence')}</th>
              <th style={{ textAlign:'right' }}>{T('Actions')}</th>
            </tr>
          </thead>
          <tbody>
            {items.map(it => {
              const ev = it.evidence || {};
              const busy = busyKey === keyOf(it);
              return (
                <tr key={keyOf(it)}>
                  <td>
                    <PersonCell
                      person={{
                        id: it.user_id, user_id: it.user_id,
                        display_name: it.display_name, matrix_id: it.matrix_id,
                        avatar: ['#3b66f5', '#1d3286'],
                        initials: (it.display_name || '?').split(' ').map(s => s[0]).join(''),
                      }}
                      sub={it.matrix_id}
                    />
                  </td>
                  <td className="mono">{it.work_date}</td>
                  <td className="num">
                    <Badge kind="warn">{it.claimed_hours != null ? `${it.claimed_hours}h` : '—'}</Badge>
                  </td>
                  <td className="num mono">
                    {it.credited_hours != null ? `${it.credited_hours}h` : '—'}
                  </td>
                  <td className="mono muted" style={{ fontSize: 11 }}>
                    {clock(ev.signed_in_at)} → {clock(ev.signed_off_at)}
                    {' · '}{ev.message_count || 0} {T('msg')}
                    {' · '}{T('pings')} {ev.pings_answered || 0}/{ev.pings_missed || 0}
                    {' '}{T('ans/missed')}
                  </td>
                  <td style={{ textAlign:'right' }}>
                    <div style={{ display:'inline-flex', gap: 6 }}>
                      <button className="btn btn-pri" disabled={busy || it.claimed_hours == null}
                        title={T('Credit the full span')}
                        onClick={() => decide(it, 'confirm')}>
                        <Icon name="check" size={12}/> {busy ? '…' : T('Confirm')}
                      </button>
                      <button className="btn btn-ghost" disabled={busy}
                        title={T('Keep the capped hours')}
                        onClick={() => decide(it, 'reject')}>
                        <Icon name="x" size={12}/> {busy ? '…' : T('Reject')}
                      </button>
                    </div>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      )}
    </div>
  );
}

function PtoPage({ ptoState, setPtoState }) {
  const [tab, setTab]           = useState('pending');
  const [ptoModalOpen, setPtoModalOpen] = useState(false);
  const [calMonth, setCalMonth] = useState(() => new Date().toISOString().slice(0,7));
  const [calEntries, setCalEntries] = useState([]);
  const [calLoading, setCalLoading] = useState(false);
  const [balances, setBalances]   = useState([]);
  const [balLoading, setBalLoading] = useState(false);
  const toast = useToast();

  const filtered = ptoState.filter(p => tab === 'all' ? true : p.status === tab);

  const fetchCalendar = async (month) => {
    setCalLoading(true);
    try {
      const [yr, mo] = month.split('-').map(Number);
      const from = `${month}-01`;
      const lastDay = new Date(yr, mo, 0).getDate();
      const to = `${month}-${String(lastDay).padStart(2,'0')}`;
      const r = await fetch(`/api/pto/calendar?from=${from}&to=${to}`, { credentials: 'same-origin' });
      if (!r.ok) throw new Error(`${r.status}`);
      const data = await r.json();
      setCalEntries(Array.isArray(data) ? data : []);
    } catch(e) {
      toast.push({ msg: T('Calendar fetch failed: ') + e.message, kind: 'err' });
      setCalEntries([]);
    } finally {
      setCalLoading(false);
    }
  };

  const fetchBalances = async () => {
    setBalLoading(true);
    try {
      const r = await fetch('/api/pto/balances', { credentials: 'same-origin' });
      if (!r.ok) throw new Error(`${r.status}`);
      const data = await r.json();
      setBalances(Array.isArray(data) ? data : []);
    } catch(e) {
      toast.push({ msg: T('Balances fetch failed: ') + e.message, kind: 'err' });
      setBalances([]);
    } finally {
      setBalLoading(false);
    }
  };

  useEffect(() => { fetchCalendar(calMonth); }, [calMonth]);
  useEffect(() => { fetchBalances(); }, []);

  const handleRefresh = async () => {
    // Global re-bootstrap so the Requests list, rooms, teams, balances, etc.
    // all refetch — not just the calendar.
    try {
      window.dispatchEvent(new CustomEvent('status-data-refresh-requested'));
      if (typeof window.STATUS_REFRESH === 'function') await window.STATUS_REFRESH();
    } catch (e) {}
    await Promise.all([fetchCalendar(calMonth), fetchBalances()]);
    toast.push({ msg: T('Refresh complete'), kind: 'ok' });
  };

  const decide = async (id, decision) => {
    let body = null;
    let ep;
    if (decision === 'approved') {
      ep = `/api/pto/requests/${id}/approve`;
    } else {
      // Backend's PTODeclineBody requires a JSON body even if reason is empty.
      // Prompt for an optional reason (visible to the user via DM).
      const reason = (typeof prompt === 'function')
        ? (prompt(T('Optional reason for declining (visible to the user). Leave blank to skip:'), '') || '')
        : '';
      ep = `/api/pto/requests/${id}/decline`;
      body = { reason: reason.trim() };
    }
    try {
      const r = await fetch(ep, {
        method: 'POST',
        credentials: 'include',
        headers: body ? { 'Content-Type': 'application/json' } : {},
        body: body ? JSON.stringify(body) : undefined,
      });
      if (!r.ok) {
        let msg;
        try {
          const j = await r.json();
          if (typeof j.detail === 'string') msg = j.detail;
          else if (Array.isArray(j.detail)) msg = j.detail.map(e => e.msg || JSON.stringify(e)).join('; ');
          else msg = `HTTP ${r.status}`;
        } catch (_) { msg = `HTTP ${r.status}`; }
        throw new Error(msg);
      }
      setPtoState(s => s.map(p => p.id === id ? { ...p, status: decision } : p));
      toast.push({ kind: decision === 'approved' ? 'ok' : 'err', icon: decision === 'approved' ? '✓' : '✕', msg: `${T('Request')} ${T(decision)}` });
    } catch (e) {
      toast.push({ kind:'err', icon:'!', msg: `${T('Failed:')} ${e.message || e}` });
    }
  };

  const handleExport = async () => {
    // Stub kept for any legacy callers; the real export now branches by
    // format via handleExportFmt below.
    return handleExportFmt('csv');
  };

  const handleExportFmt = async (fmt) => {
    const filename = `pto-requests.${fmt}`;
    let url;
    if (fmt === 'csv') {
      url = '/api/reports.csv?type=pto';
    } else {
      // PDF / XLSX go through the unified export endpoint which already
      // supports a request_type=pto switch (added in this commit).
      url = `/api/reports/export?type=pto&format=${fmt}`;
    }
    try {
      const r = await fetch(url, { credentials: 'same-origin' });
      if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
      const blob = await r.blob();
      const result = await window.saveDownload(blob, filename);
      if (!result || !result.ok) {
        throw new Error('Download failed via ' + (result && result.via || 'unknown'));
      }
      if (result.via !== 'share-cancelled') {
        toast.push({ msg: `${T('Downloaded')} ${filename}`, kind: 'ok' });
      }
    } catch(e) {
      toast.push({ msg: `${T('Export failed:')} ${e.message || e}`, kind: 'err' });
    }
  };

  const handlePtoCreated = async () => {
    try {
      const fresh = await fetch('/api/pto/requests', { credentials: 'same-origin' }).then(r => r.ok ? r.json() : ptoState);
      if (Array.isArray(fresh)) setPtoState(fresh.map(p => ({
        id: p.id, display_name: p.display_name || p.matrix_id, matrix_id: p.matrix_id,
        pto_type: p.pto_type || 'Vacation',
        start_date: p.start_date, end_date: p.end_date,
        days: p.days || 1,
        status: p.status || 'pending',
        approved: p.status === 'approved',
      })));
    } catch(_) {}
    await fetchCalendar(calMonth);
  };

  const pendingNoDates = calEntries.filter(e => e.status === 'pending' && !e.start_date);

  return (
    <div className="page">
      <PageHeader
        title={T('Requests')}
        meta={`${ptoState.filter(p=>p.status==='pending').length} ${T('pending')} · ${ptoState.filter(p=>p.status==='approved').length} ${T('approved')}`}
        right={
          <div style={{ display:'flex', gap: 8, alignItems:'center' }}>
            <Refresh onClick={handleRefresh} loading={calLoading || balLoading} />
            {/* Match Employees export — native <select> so the iOS picker
                fires on touch devices and the desktop sees a plain dropdown. */}
            <select
              className="btn btn-ghost"
              value=""
              onChange={(e) => { const f = e.target.value; if (f) handleExportFmt(f); e.target.value=''; }}
              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={T('Choose an export format')}
            >
              <option value="" disabled>⬇ {T('Export…')}</option>
              <option value="csv">{T('Download CSV')}</option>
              <option value="pdf">{T('Download PDF')}</option>
              <option value="xlsx">{T('Download XLSX')}</option>
            </select>
            <button className="btn btn-pri" onClick={() => setPtoModalOpen(true)}><Icon name="plus" size={14}/> {T('Request PTO')}</button>
          </div>
        }
      />

      <div className="card">
        <div className="toolbar">
          <Segmented
            value={tab} onChange={setTab}
            options={[
              { value:'pending',  label:`${T('Pending')} (${ptoState.filter(p=>p.status==='pending').length})` },
              { value:'approved', label:T('Approved') },
              { value:'denied',   label:T('Denied') },
              { value:'all',      label:T('All') },
            ]}
          />
        </div>

        {filtered.length === 0 ? (
          <Empty
            icon={
              <svg width="80" height="80" viewBox="0 0 80 80" fill="none" style={{opacity:0.35}}>
                <rect x="12" y="16" width="56" height="52" rx="6" stroke="currentColor" strokeWidth="3" fill="none"/>
                <line x1="12" y1="30" x2="68" y2="30" stroke="currentColor" strokeWidth="2.5"/>
                <line x1="26" y1="10" x2="26" y2="22" stroke="currentColor" strokeWidth="3" strokeLinecap="round"/>
                <line x1="54" y1="10" x2="54" y2="22" stroke="currentColor" strokeWidth="3" strokeLinecap="round"/>
                <path d="M30 48 L37 55 L52 40" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
            }
            title={tab === 'pending' ? T("No pending requests") : T("No requests yet")}
            subtitle={
              tab === 'pending'
                ? <span>{T('All clear!')} <button className="btn btn-ghost" style={{padding:'2px 8px', fontSize:12}} onClick={() => setPtoModalOpen(true)}>{T('Submit a request')}</button></span>
                : T("PTO requests will show up here for review.")
            }
          />
        ) : (
          <table className="t">
            <thead>
              <tr>
                <th>{T('Person')}</th><th>{T('Type')}</th><th>{T('Dates')}</th><th>{T('Days')}</th><th>{T('Status')}</th>
                <th style={{ textAlign:'right' }}>{T('Actions')}</th>
              </tr>
            </thead>
            <tbody>
              {filtered.map(p => (
                <tr key={p.id}>
                  <td><PersonCell person={{ id: p.user_id, user_id: p.user_id, display_name: p.display_name, matrix_id: p.matrix_id, avatar:['#3b66f5','#1d3286'], initials: (p.display_name||'?').split(' ').map(s=>s[0]).join('') }} sub={p.matrix_id} /></td>
                  <td>{p.pto_type}</td>
                  <td className="mono">
                    {p.start_date && p.end_date ? `${p.start_date} → ${p.end_date}` : <Badge kind="warn">{T('Dates TBD')}</Badge>}
                  </td>
                  <td className="num">{p.days || '—'}</td>
                  <td><StatusBadge status={p.status} /></td>
                  <td style={{ textAlign:'right' }}>
                    {p.status === 'pending' ? (
                      <div style={{ display:'inline-flex', gap: 6 }}>
                        <button className="btn btn-pri" onClick={() => decide(p.id, 'approved')}><Icon name="check" size={12}/> {T('Approve')}</button>
                        <button className="btn btn-ghost" onClick={() => decide(p.id, 'denied')}><Icon name="x" size={12}/> {T('Deny')}</button>
                      </div>
                    ) : (
                      <Badge kind={p.status === 'approved' ? 'ok' : 'err'} dot>{T(p.status)}</Badge>
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </div>

      <div style={{ display:'flex', flexDirection:'column', gap: 16, marginTop: 16 }}>
        <div className="card" style={{ display:'flex', flexDirection:'column' }}>
          <SectionHeader
            title={T("PTO calendar")}
          tip={T("Approved + pending PTO/sick/vacation blocks on a calendar grid. Click a block to approve/deny.")}
            sub={T("Approved blocks only")}
            right={
              <input type="month" value={calMonth} onChange={e => setCalMonth(e.target.value)}
                className="inp" style={{ fontSize: 12, padding:'3px 6px', width: 130 }} />
            }
          />
          <PtoCalendarPanel month={calMonth} calEntries={calEntries} loading={calLoading} />
          {pendingNoDates.length > 0 && (
            <div style={{ marginTop: 12, paddingTop: 12, borderTop: '1px solid var(--line)' }}>
              <div style={{ fontSize: 11, fontFamily:'var(--font-mono)', color:'var(--text-3)', textTransform:'uppercase', marginBottom: 8 }}>{T('Awaiting dates')}</div>
              {pendingNoDates.map((e, i) => (
                <div key={i} style={{ display:'flex', justifyContent:'space-between', alignItems:'center', padding:'6px 0', borderBottom:'1px solid var(--line)' }}>
                  <div style={{ fontSize: 12 }}>{e.user_name}</div>
                  <Badge kind="warn">{T('Pending — dates TBD')}</Badge>
                </div>
              ))}
            </div>
          )}
        </div>

        {/* Capped long sessions are a HELD PAY DECISION, not a report — they
            sit next to the other things waiting on an admin's judgement.
            Renders nothing at all for non-admins (own card wrapper). */}
        <LongSessionReviewPanel />

        <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(320px, 1fr))', gap: 16, alignItems:'stretch' }}>
          <div className="card" style={{ display:'flex', flexDirection:'column' }}>
            <UpcomingPtoPanel />
          </div>
          <div className="card" style={{ display:'flex', flexDirection:'column' }}>
            <SectionHeader
              title={T("Balances")}
            tip={T("Annual PTO quota per user vs days taken. Default 20 vacation + 10 sick (per team_settings). Surplus/deficit highlighted.")}
              sub={T("Per-person quota / used / remaining")}
              right={<PtoQuotaEditor onChanged={fetchBalances} />}
            />
            <PtoBalancesPanel balances={balances} loading={balLoading} />
          </div>
        </div>
      </div>

      {ptoModalOpen && (
        <RequestPtoModal onClose={() => setPtoModalOpen(false)} onCreated={handlePtoCreated} />
      )}
    </div>
  );
}

/* ── PTO quota editor (admin only) — sits in the Balances panel header ── */
function PtoQuotaEditor({ onChanged }) {
  const [quota, setQuota]     = useState(null);
  const [editing, setEditing] = useState(false);
  const [draft, setDraft]     = useState('5');
  const [busy, setBusy]       = useState(false);
  const toast = useToast();
  const D = window.STATUS_DATA;
  const isAdmin = (window.isAdminOrAbove ? window.isAdminOrAbove(D?.ME) : (D?.ME?.org_role || '').toLowerCase() === 'admin');

  useEffect(() => {
    fetch('/api/pto/quota', { credentials: 'include' })
      .then(r => r.ok ? r.json() : null)
      .then(j => { if (j?.quota != null) { setQuota(j.quota); setDraft(String(j.quota)); } })
      .catch(() => {});
  }, []);

  const save = async () => {
    const n = parseInt(draft, 10);
    if (isNaN(n) || n < 0 || n > 365) { toast.push({ kind:'err', msg: T('Quota must be 0–365 days') }); return; }
    setBusy(true);
    try {
      const r = await fetch('/api/pto/quota', {
        method: 'PUT', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ quota: n }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      setQuota(n); setEditing(false);
      toast.push({ kind:'ok', msg: `${T('Annual quota updated to')} ${n} ${n===1?T('day'):T('days')}` });
      onChanged && onChanged();
    } catch(e) {
      toast.push({ kind:'err', msg: T('Failed: ') + e.message });
    } finally { setBusy(false); }
  };

  if (quota == null) return <span className="muted" style={{fontSize:11}}>…</span>;
  if (!isAdmin) {
    return <span className="muted mono" style={{fontSize:11}}>{T('cap:')} {quota}d/yr</span>;
  }
  if (!editing) {
    return (
      <button className="btn btn-ghost" onClick={() => setEditing(true)}
        style={{fontSize: 11, padding:'3px 8px'}}
        title={T("Click to change the annual vacation quota for everyone")}>
        {T('cap:')} <span className="mono">{quota}d/yr</span> <Icon name="edit" size={11}/>
      </button>
    );
  }
  return (
    <div style={{display:'flex', gap:6, alignItems:'center'}}>
      <input className="inp" type="number" min="0" max="365" value={draft}
        onChange={e => setDraft(e.target.value)}
        style={{width: 70, fontSize: 11, padding:'3px 6px', height: 26}} />
      <button className="btn btn-pri" onClick={save} disabled={busy}
        style={{fontSize: 11, padding:'3px 8px'}}>
        {busy ? '…' : T('Save')}
      </button>
      <button className="btn btn-ghost" onClick={() => { setDraft(String(quota)); setEditing(false); }}
        style={{fontSize: 11, padding:'3px 8px'}}>{T('Cancel')}</button>
    </div>
  );
}

/* ============================================================
   TEAMS PAGE — collapsible cards + full wizard
   ============================================================ */

/* ── Timezone list ── */
function getTimezones() {
  try {
    const tz = Intl.supportedValuesOf('timeZone');
    return tz.length ? tz : null;
  } catch(_) { return null; }
}

const COMMON_TIMEZONES = [
  'UTC','America/New_York','America/Chicago','America/Denver','America/Los_Angeles',
  'America/Toronto','America/Vancouver','America/Sao_Paulo','America/Bogota',
  'Europe/London','Europe/Paris','Europe/Berlin','Europe/Amsterdam','Europe/Madrid',
  'Europe/Rome','Europe/Warsaw','Europe/Stockholm','Europe/Zurich','Europe/Athens',
  'Europe/Moscow','Europe/Istanbul',
  'Asia/Dubai','Asia/Karachi','Asia/Kolkata','Asia/Dhaka','Asia/Bangkok',
  'Asia/Singapore','Asia/Shanghai','Asia/Tokyo','Asia/Seoul','Asia/Hong_Kong',
  'Australia/Sydney','Australia/Melbourne','Australia/Perth',
  'Pacific/Auckland','Pacific/Honolulu',
  'Africa/Cairo','Africa/Johannesburg','Africa/Lagos',
];

function TimezoneSelect({ value, onChange, style }) {
  const tzList = useMemo(() => getTimezones() || COMMON_TIMEZONES, []);
  return (
    <select className="sel" value={value} onChange={e => onChange(e.target.value)} style={{ width:'100%', ...style }}>
      {tzList.map(tz => <option key={tz} value={tz}>{tz}</option>)}
    </select>
  );
}

const ALL_DAYS = [
  { key:'mon', label:'Mon' }, { key:'tue', label:'Tue' }, { key:'wed', label:'Wed' },
  { key:'thu', label:'Thu' }, { key:'fri', label:'Fri' }, { key:'sat', label:'Sat' },
  { key:'sun', label:'Sun' },
];

function OffDayMultiSelect({ value, onChange }) {
  // value is comma-separated string e.g. "mon,tue,wed,thu,fri"
  const selected = (value || '').split(',').map(s => s.trim()).filter(Boolean);
  const toggle = (key) => {
    const next = selected.includes(key) ? selected.filter(k => k !== key) : [...selected, key];
    onChange(next.join(','));
  };
  return (
    <div style={{ display:'flex', gap: 4, flexWrap:'wrap' }}>
      {ALL_DAYS.map(d => (
        <button key={d.key} type="button"
          onClick={() => toggle(d.key)}
          className={`btn ${selected.includes(d.key) ? 'btn-pri' : 'btn-ghost'}`}
          style={{ fontSize: 12, padding:'3px 8px', minWidth: 40 }}>
          {T(d.label)}
        </button>
      ))}
    </div>
  );
}

/* ── Step indicator ── */
function StepDots({ total, current }) {
  return (
    <div style={{ display:'flex', gap: 6, justifyContent:'center', marginBottom: 20 }}>
      {Array.from({ length: total }, (_, i) => (
        <div key={i} style={{
          width: i === current ? 20 : 8, height: 8,
          borderRadius: 99, transition:'all .2s',
          background: i <= current ? 'var(--brand)' : 'var(--line-hi)',
        }} />
      ))}
    </div>
  );
}

/* ── New Team Wizard Modal ── */
function NewTeamWizard({ onClose, onCreated, rooms, bots, employees }) {
  const STEPS = 6;
  const [step, setStep]   = useState(0);
  const [dirty, setDirty] = useState(false);
  const [busy, setBusy]   = useState(false);
  const [err, setErr]     = useState('');
  const toast = useToast();

  // Draft state
  const [form, setForm] = useState(() => {
    const saved = sessionStorage.getItem('team_wiz_draft');
    if (saved) try { return JSON.parse(saved); } catch(_) {}
    return {
      team_name: '', timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
      work_hours_target: 8, off_days: 'sat,sun',
      signoff_keyword: 'signing off', signin_deadline: '09:30',
      flexible_signin: false,
      member_user_ids: [],
      assigned_rooms: [], assigned_bot: '',
      auto_invite: false,
    };
  });

  const set = (key, val) => {
    setForm(f => { const n = { ...f, [key]: val }; sessionStorage.setItem('team_wiz_draft', JSON.stringify(n)); return n; });
    setDirty(true);
  };

  useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') {
        if (dirty) { if (confirm(T('Discard team draft?'))) { sessionStorage.removeItem('team_wiz_draft'); onClose(); } }
        else onClose();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [dirty, onClose]);

  const handleClose = () => {
    if (dirty) { if (!confirm(T('Discard team draft?'))) return; }
    sessionStorage.removeItem('team_wiz_draft');
    onClose();
  };

  const canNext = () => {
    if (step === 0) return form.team_name.trim().length > 0 && form.timezone;
    return true;
  };

  const submit = async () => {
    setBusy(true); setErr('');
    try {
      const payload = {
        team_name: form.team_name.trim(),
        timezone: form.timezone,
        work_hours_target: parseFloat(form.work_hours_target) || 8,
        working_days: form.off_days,
        signoff_keyword: form.signoff_keyword,
        flexible_signin: !!form.flexible_signin,
        // Only meaningful when not flexible.
        signin_deadline: form.flexible_signin ? null : form.signin_deadline,
        assigned_rooms: form.assigned_rooms,
        bot_id: form.assigned_bot || null,
      };
      const r = await fetch('/api/teams', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify(payload),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      // Assign selected members to the new team (sets each user's team field
      // via the same per-user profile endpoint the employee drawer uses).
      const memberIds = form.member_user_ids || [];
      let assigned = 0, failed = 0;
      for (const uid of memberIds) {
        try {
          const ar = await fetch(`/api/employees/${uid}/profile`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            credentials: 'same-origin',
            body: JSON.stringify({ team: form.team_name.trim() }),
          });
          if (ar.ok) assigned++; else failed++;
        } catch(_) { failed++; }
      }
      sessionStorage.removeItem('team_wiz_draft');
      const memberMsg = memberIds.length
        ? ` · ${assigned} ${assigned !== 1 ? T('members') : T('member')} ${T('added')}${failed ? ` (${failed} ${T('failed')})` : ''}`
        : '';
      toast.push({ msg: `${T('Team')} "${form.team_name}" ${T('created')}${memberMsg}`, kind: failed ? 'warn' : 'ok' });
      onCreated && onCreated();
      onClose();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  const STEP_TITLES = [
    T('Name & timezone'),
    T('Work hours & schedule'),
    T('Members'),
    T('Room assignment'),
    T('Bot assignment'),
    T('Confirm'),
  ];

  return (
    <div className="drawer-overlay" onClick={handleClose} 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: 28, width: 480, maxWidth:'95vw',
        boxShadow:'0 24px 64px -16px #00000080', zIndex: 301, maxHeight:'90vh', overflowY:'auto',
      }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 8 }}>
          <div style={{ fontWeight: 600, fontSize: 15 }}>{T('New team')}</div>
          <button className="btn btn-ghost btn-icon" onClick={handleClose}><Icon name="x" size={14}/></button>
        </div>
        <div className="muted" style={{ fontSize: 12, marginBottom: 16 }}>{STEP_TITLES[step]}</div>

        <StepDots total={STEPS} current={step} />

        {err && <div className="badge badge-err" style={{ marginBottom: 12, padding:'7px 12px', fontSize: 13 }}><Icon name="alert" size={13}/> {err}</div>}

        {/* Step 0: Name + timezone */}
        {step === 0 && (
          <div>
            <div style={{ marginBottom: 14 }}>
              <label className="login-label">{T('Team name *')}</label>
              <input className="inp" value={form.team_name} onChange={e => set('team_name', e.target.value)}
                placeholder={T('e.g. Engineering')} autoFocus />
            </div>
            <div style={{ marginBottom: 14 }}>
              <label className="login-label">{T('Timezone')}</label>
              <TimezoneSelect value={form.timezone} onChange={v => set('timezone', v)} />
            </div>
          </div>
        )}

        {/* Step 1: Work hours / off-days / signoff-keyword / signin-deadline */}
        {step === 1 && (
          <div>
            <div style={{ marginBottom: 14 }}>
              <label className="login-label">{T('Work hours target (per day)')}</label>
              <input className="inp" type="number" min="1" max="24" step="0.5"
                value={form.work_hours_target} onChange={e => set('work_hours_target', e.target.value)} />
            </div>
            <div style={{ marginBottom: 14 }}>
              <label className="login-label">{T('Working days (deselect off-days)')}</label>
              <OffDayMultiSelect value={form.off_days} onChange={v => set('off_days', v)} />
              <div className="muted" style={{ fontSize: 11, marginTop: 4 }}>{T('Selected:')} {form.off_days || T('none')}</div>
            </div>
            <div style={{ marginBottom: 14 }}>
              <label className="login-label">{T('Sign-off keyword')}</label>
              <input className="inp" value={form.signoff_keyword} onChange={e => set('signoff_keyword', e.target.value)} placeholder={T('signing off')} />
            </div>
            <div style={{ marginBottom: 4 }}>
              <label className="login-label">{T('Sign-in time')}</label>
              <div className="muted" style={{ fontSize: 11, marginBottom: 8 }}>
                {T("No fixed deadline. The bot learns each member's usual sign-in & sign-off time (in their own timezone, after ~4 days) and flags only when someone is later than their own average in — or earlier out. Signing in early is fine.")}
              </div>
              <label style={{ display:'flex', alignItems:'center', gap: 10, cursor:'pointer' }}>
                <input type="checkbox" checked={!!form.flexible_signin}
                  onChange={e => set('flexible_signin', e.target.checked)} />
                <span style={{ fontSize: 13 }}>{T("Fully flexible — don't track sign-in times at all (exec / async teams)")}</span>
              </label>
            </div>
          </div>
        )}

        {/* Step 2: Members */}
        {step === 2 && (
          <div>
            <div style={{ marginBottom: 8 }}>
              <label className="login-label">{T('Assign members to this team (multi-select)')}</label>
              <div className="muted" style={{ fontSize: 11, marginBottom: 6 }}>
                {T('Selected users will have their team set to')} <strong>{form.team_name || T('this team')}</strong>.
              </div>
              <div style={{ border:'1px solid var(--line)', borderRadius:'var(--r-sm)', padding: 8, maxHeight: 240, overflowY:'auto', display:'flex', flexDirection:'column', gap: 4 }}>
                {(employees || []).length === 0 && <div className="muted" style={{ fontSize: 12 }}>{T('No employees available.')}</div>}
                {(employees || []).map(u => {
                  const checked = form.member_user_ids.includes(u.id);
                  return (
                    <label key={u.id} style={{ display:'flex', alignItems:'center', gap: 8, cursor:'pointer', padding:'4px 4px', borderRadius:'var(--r-xs)', background: checked ? 'var(--bg-elev-2)' : 'transparent' }}>
                      <input type="checkbox" checked={checked}
                        onChange={() => {
                          const next = checked
                            ? form.member_user_ids.filter(x => x !== u.id)
                            : [...form.member_user_ids, u.id];
                          set('member_user_ids', next);
                        }} />
                      <span style={{ fontSize: 13 }}>{u.display_name || u.matrix_id}</span>
                      {u.team && <span className="mono muted" style={{ fontSize: 10, marginLeft: 'auto' }}>{u.team}</span>}
                    </label>
                  );
                })}
              </div>
              <div className="muted" style={{ fontSize: 11, marginTop: 4 }}>
                {form.member_user_ids.length} {T('selected')}
                {form.member_user_ids.length > 0 && ' · ' + T('members already on another team will be moved here')}
              </div>
            </div>
          </div>
        )}

        {/* Step 3: Room assignment */}
        {step === 3 && (
          <div>
            <div style={{ marginBottom: 14 }}>
              <label className="login-label">{T('Assign rooms (multi-select)')}</label>
              <div style={{ border:'1px solid var(--line)', borderRadius:'var(--r-sm)', padding: 8, maxHeight: 200, overflowY:'auto', display:'flex', flexDirection:'column', gap: 4 }}>
                {rooms.length === 0 && <div className="muted" style={{ fontSize: 12 }}>{T('No rooms available. Create rooms first.')}</div>}
                {rooms.map(r => {
                  const checked = form.assigned_rooms.includes(r.id);
                  return (
                    <label key={r.id} style={{ display:'flex', alignItems:'center', gap: 8, cursor:'pointer', padding:'4px 4px', borderRadius:'var(--r-xs)', background: checked ? 'var(--bg-elev-2)' : 'transparent' }}>
                      <input type="checkbox" checked={checked}
                        onChange={() => {
                          const next = checked ? form.assigned_rooms.filter(x => x !== r.id) : [...form.assigned_rooms, r.id];
                          set('assigned_rooms', next);
                        }} />
                      <span style={{ fontSize: 13 }}>{r.display_name}</span>
                      <span className="mono muted" style={{ fontSize: 10, marginLeft: 'auto' }}>{r.room_type}</span>
                    </label>
                  );
                })}
              </div>
            </div>
            <label style={{ display:'flex', alignItems:'center', gap: 8, cursor:'pointer', fontSize: 13 }}>
              <input type="checkbox" checked={form.auto_invite} onChange={e => set('auto_invite', e.target.checked)} />
              {T('Auto-invite team members to selected rooms (via assigned bot)')}
            </label>
          </div>
        )}

        {/* Step 4: Bot assignment */}
        {step === 4 && (
          <div>
            <div style={{ marginBottom: 14 }}>
              <label className="login-label">{T('Assign bot (optional)')}</label>
              <select className="sel" value={form.assigned_bot} onChange={e => set('assigned_bot', e.target.value)} style={{ width:'100%' }}>
                <option value="">{T('No bot assigned')}</option>
                {bots.filter(b => b.active).map(b => (
                  <option key={b.id} value={b.id}>{b.name} ({b.matrix_id})</option>
                ))}
              </select>
            </div>
          </div>
        )}

        {/* Step 5: Confirm */}
        {step === 5 && (
          <div style={{ display:'flex', flexDirection:'column', gap: 10 }}>
            <div style={{ background:'var(--bg-elev-2)', borderRadius:'var(--r-sm)', padding: 14, fontSize: 13 }}>
              <div style={{ display:'grid', gridTemplateColumns:'140px 1fr', gap:'6px 12px' }}>
                <span className="muted">{T('Team name')}</span><strong>{form.team_name}</strong>
                <span className="muted">{T('Timezone')}</span><span className="mono">{form.timezone}</span>
                <span className="muted">{T('Work hours/day')}</span><span>{form.work_hours_target} h</span>
                <span className="muted">{T('Working days')}</span><span className="mono">{form.off_days}</span>
                <span className="muted">{T('Sign-off keyword')}</span><span className="mono">{form.signoff_keyword}</span>
                <span className="muted">{T('Sign-in')}</span><span>{form.flexible_signin ? T('Fully flexible (not tracked)') : T('Adaptive (learns each person’s usual time)')}</span>
                <span className="muted">{T('Members')}</span><span>{form.member_user_ids.length > 0 ? `${form.member_user_ids.length} ${T('user(s)')}` : T('None')}</span>
                <span className="muted">{T('Rooms')}</span><span>{form.assigned_rooms.length > 0 ? `${form.assigned_rooms.length} ${T('room(s)')}` : T('None')}</span>
                <span className="muted">{T('Bot')}</span><span>{bots.find(b => String(b.id) === String(form.assigned_bot))?.name || T('None')}</span>
              </div>
            </div>
          </div>
        )}

        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end', marginTop: 24 }}>
          {step > 0 && (
            <button className="btn btn-ghost" onClick={() => setStep(s => s - 1)}>{T('Back')}</button>
          )}
          <button className="btn btn-ghost" onClick={handleClose}>{T('Cancel')}</button>
          {step < STEPS - 1 ? (
            <button className="btn btn-pri" onClick={() => setStep(s => s + 1)} disabled={!canNext()}>
              {T('Next')}
            </button>
          ) : (
            <button className="btn btn-pri" onClick={submit} disabled={busy}>
              {busy ? T('Creating...') : <><Icon name="check" size={13}/> {T('Create team')}</>}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

/* ── Team Schedule Drawer ── */
const DAY_LABELS = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'];

function TeamScheduleDrawer({ teamName, onClose }) {
  const [rows, setRows]   = useState(null);
  const [saving, setSaving] = useState(false);
  const [err, setErr]     = useState('');
  const toast = useToast();

  useEffect(() => {
    fetch(`/api/teams/${encodeURIComponent(teamName)}/schedule`, { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(data => setRows(data))
      .catch(() => setErr(T('Failed to load schedule')));
  }, [teamName]);

  const setRow = (idx, key, val) => setRows(prev => prev.map((r, i) => i === idx ? { ...r, [key]: val } : r));

  const save = async () => {
    setSaving(true); setErr('');
    try {
      const r = await fetch(`/api/teams/${encodeURIComponent(teamName)}/schedule`, {
        method: 'PUT',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(rows),
      });
      if (!r.ok) throw new Error(await r.text());
      toast.push({ msg: T('Schedule saved'), kind: 'ok' });
      onClose();
    } catch(e) { setErr(e.message); }
    finally { setSaving(false); }
  };

  return (
    <div style={{ position:'fixed', inset:0, background:'rgba(0,0,0,.45)', zIndex:900, display:'flex', alignItems:'center', justifyContent:'center' }}
         onClick={e => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="card" style={{ width: 540, maxWidth:'96vw', maxHeight:'90vh', overflowY:'auto', padding: 20 }}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 16 }}>
          <div style={{ fontWeight: 700, fontSize: 15 }}>{T('Schedule')} — {teamName || T('(default)')}</div>
          <button className="btn btn-ghost" onClick={onClose} style={{ padding:'2px 8px' }}>x</button>
        </div>
        {err && <div className="badge badge-err" style={{ marginBottom:12, padding:'6px 10px', fontSize:12 }}>{err}</div>}
        {!rows ? (
          <Loading label={T('Loading…')} rows={4} />
        ) : (
          <>
            <div style={{ display:'grid', gridTemplateColumns:'110px 1fr 1fr 80px', gap:'6px 10px', alignItems:'center', marginBottom:8 }}>
              <div className="muted" style={{ fontSize:11, fontWeight:600 }}>{T('Day')}</div>
              <div className="muted" style={{ fontSize:11, fontWeight:600 }}>{T('Start')}</div>
              <div className="muted" style={{ fontSize:11, fontWeight:600 }}>{T('End')}</div>
              <div className="muted" style={{ fontSize:11, fontWeight:600, textAlign:'center' }}>{T('Working')}</div>
              {rows.map((row, idx) => (
                <React.Fragment key={row.day_of_week}>
                  <div style={{ fontSize:13 }}>{T(DAY_LABELS[row.day_of_week])}</div>
                  <input className="inp" type="time" value={row.start_time}
                    onChange={e => setRow(idx, 'start_time', e.target.value)}
                    disabled={!row.is_working_day} style={{ opacity: row.is_working_day ? 1 : 0.4 }} />
                  <input className="inp" type="time" value={row.end_time}
                    onChange={e => setRow(idx, 'end_time', e.target.value)}
                    disabled={!row.is_working_day} style={{ opacity: row.is_working_day ? 1 : 0.4 }} />
                  <div style={{ textAlign:'center' }}>
                    <input type="checkbox" checked={row.is_working_day}
                      onChange={e => setRow(idx, 'is_working_day', e.target.checked)} />
                  </div>
                </React.Fragment>
              ))}
            </div>
            <div style={{ display:'flex', gap: 8, marginTop: 16 }}>
              <button className="btn btn-pri" onClick={save} disabled={saving}>
                {saving ? T('Saving…') : <><Icon name="check" size={13}/> {T('Save schedule')}</>}
              </button>
              <button className="btn btn-ghost" onClick={onClose}>{T('Cancel')}</button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

/* ── Team Bot Behavior Section ── */
// Reuses primitives from page-bot-behavior.jsx via window exports.
// Falls back gracefully if the page hasn't loaded yet.
const _BotBehaviorPage = window.BotBehaviorPage;

function TeamBotBehaviorSection({ teamName }) {
  const _useToast2 = window.useToast || (() => ({ toast: () => {} }));
  const { toast } = _useToast2();
  const [open, setOpen]           = React.useState(false);
  const [settings, setSettings]   = React.useState([]);
  const [loading, setLoading]     = React.useState(false);
  const [localEdits, setLocalEdits] = React.useState({});
  const [saving, setSaving]       = React.useState(false);
  const [showDiff, setShowDiff]   = React.useState(false);
  const [savedCount, setSavedCount] = React.useState(0);
  const [diffOpen, setDiffOpen]   = React.useState(false);
  // Per-category accordion open-state. MUST live here (one hook), NOT inside
  // the categories .map() — a hook in a loop violates rules-of-hooks and
  // crashed with React #310 once settings loaded (0 → N categories).
  const [openCats, setOpenCats]   = React.useState({});

  const load = React.useCallback(() => {
    if (!teamName) return;
    setLoading(true);
    fetch(`/api/bot-behavior/settings/team/${encodeURIComponent(teamName)}`, { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => setSettings(d.settings || []))
      .catch(() => {})
      .finally(() => setLoading(false));
  }, [teamName]);

  React.useEffect(() => {
    if (open && settings.length === 0) load();
  }, [open, load, settings.length]);

  const handleChange = (key, value) => {
    setLocalEdits(prev => {
      if (value === undefined) { const n = { ...prev }; delete n[key]; return n; }
      return { ...prev, [key]: value };
    });
  };

  const handleSave = async () => {
    const keys = Object.keys(localEdits);
    if (!keys.length) return;
    setSaving(true);
    let count = 0;
    for (const key of keys) {
      try {
        const r = await fetch(`/api/bot-behavior/settings/${encodeURIComponent(key)}`, {
          method: 'PUT',
          credentials: 'same-origin',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ value: localEdits[key], scope: 'team', team_name: teamName }),
        });
        if (r.ok) count++;
        else toast({ kind: 'warn', message: `${T('Failed:')} ${key}` });
      } catch (_) {}
    }
    setSaving(false);
    setLocalEdits({});
    setSavedCount(count);
    setShowDiff(true);
    load();
    if (count > 0) toast({ kind: 'ok', message: `${T('Saved')} ${count} ${count !== 1 ? T('overrides') : T('override')} ${T('for')} ${teamName}` });
  };

  const handleResetAll = async () => {
    if (!window.confirm(`${T('Remove ALL bot behavior overrides for team')} "${teamName}"? ${T('This cannot be undone.')}`)) return;
    const overrides = settings.filter(s => s.source === 'team');
    let count = 0;
    for (const s of overrides) {
      try {
        const r = await fetch(`/api/bot-behavior/settings/${encodeURIComponent(s.key)}?team_name=${encodeURIComponent(teamName)}`, {
          method: 'DELETE', credentials: 'same-origin',
        });
        if (r.ok) count++;
      } catch (_) {}
    }
    load();
    toast({ kind: 'ok', message: `${T('Removed')} ${count} ${count !== 1 ? T('team overrides') : T('team override')}` });
  };

  const dirtyCount = Object.keys(localEdits).length;
  const overrideCount = settings.filter(s => s.source === 'team').length;

  // Group settings by category
  const byCategory = React.useMemo(() => {
    const map = {};
    (settings || []).forEach(s => {
      if (!map[s.category]) map[s.category] = [];
      map[s.category].push(s);
    });
    return map;
  }, [settings]);

  // Categories that have team overrides
  const CATEGORIES_ORDER = ['Sign-in', 'Strikes', 'PTO', 'Hours', 'Reports', 'Voice', 'AI Prompts', 'Templates'];

  return (
    <div style={{ borderTop: '1px solid var(--line)', marginTop: 8 }}>
      <button
        className="btn btn-ghost"
        onClick={() => setOpen(o => !o)}
        style={{
          width: '100%', textAlign: 'left', padding: '10px 16px',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          fontSize: 13, fontWeight: 500, borderRadius: 0,
        }}
      >
        <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          {T('Bot Behavior overrides')}
          {overrideCount > 0 && (
            <span style={{ fontSize: 10, padding: '1px 6px', borderRadius: 4, background: 'var(--accent)', color: 'var(--bg)', fontWeight: 700 }}>
              {overrideCount} {T('active')}
            </span>
          )}
        </span>
        <span style={{ color: 'var(--text-3)' }}>{open ? '▲' : '▼'}</span>
      </button>

      {open && (
        <div style={{ padding: '0 16px 16px' }}>
          {loading ? (
            <Loading label={T('Loading...')} rows={2} />
          ) : (
            <>
              {showDiff && (
                <div style={{
                  background: 'color-mix(in srgb, var(--accent) 10%, var(--card))',
                  border: '1px solid var(--accent)', borderRadius: 6,
                  padding: '8px 12px', marginBottom: 12, fontSize: 12,
                  display: 'flex', justifyContent: 'space-between',
                }}>
                  <span>{savedCount} {savedCount !== 1 ? T('overrides') : T('override')} {T('saved for')} {teamName}</span>
                  <button className="btn btn-ghost btn-xs" onClick={() => setShowDiff(false)}>{T('Dismiss')}</button>
                </div>
              )}

              {/* Compare to global diff view */}
              <div style={{ marginBottom: 10, display: 'flex', gap: 8, alignItems: 'center' }}>
                <button
                  className="btn btn-ghost btn-xs"
                  onClick={() => setDiffOpen(d => !d)}
                  style={{ fontSize: 11 }}
                >
                  {diffOpen ? T('Hide diff') : T('Compare to global')}
                </button>
                {overrideCount > 0 && (
                  <button
                    className="btn btn-ghost btn-xs"
                    onClick={handleResetAll}
                    style={{ fontSize: 11, color: 'var(--err)' }}
                  >
                    {T('Reset all team overrides')}
                  </button>
                )}
              </div>

              {diffOpen && overrideCount === 0 && (
                <div style={{ color: 'var(--text-3)', fontSize: 12, marginBottom: 10 }}>
                  {T('No overrides set — all settings inherit from global.')}
                </div>
              )}

              {diffOpen && overrideCount > 0 && (
                <div style={{
                  border: '1px solid var(--border)', borderRadius: 6,
                  overflow: 'hidden', marginBottom: 12, fontSize: 12,
                }}>
                  <div style={{
                    display: 'grid', gridTemplateColumns: '1fr 1fr 1fr',
                    padding: '6px 10px', background: 'var(--card)',
                    fontWeight: 600, borderBottom: '1px solid var(--border)',
                  }}>
                    <span>{T('Setting')}</span>
                    <span>{T('Global value')}</span>
                    <span>{T('Team override')}</span>
                  </div>
                  {settings.filter(s => s.source === 'team').map(s => (
                    <div key={s.key} style={{
                      display: 'grid', gridTemplateColumns: '1fr 1fr 1fr',
                      padding: '5px 10px', borderBottom: '1px solid var(--border)',
                      background: 'color-mix(in srgb, var(--accent) 4%, transparent)',
                    }}>
                      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11 }}>{s.key}</span>
                      <span style={{ color: 'var(--text-3)' }}>
                        {Array.isArray(s.global_value) ? `[${s.global_value.length} ${T('items')}]` : String(s.global_value ?? '—')}
                      </span>
                      <span style={{ color: 'var(--accent)', fontWeight: 500 }}>
                        {Array.isArray(s.team_override_value) ? `[${s.team_override_value.length} ${T('items')}]` : String(s.team_override_value ?? '—')}
                      </span>
                    </div>
                  ))}
                </div>
              )}

              {/* Accordion per category — only show categories with settings */}
              {CATEGORIES_ORDER.filter(cat => byCategory[cat]).map(cat => {
                const catSettings = byCategory[cat];
                const catDirty = catSettings.filter(s => localEdits[s.key] !== undefined).length;
                const catOpen = !!openCats[cat];
                const setCatOpen = (fn) => setOpenCats(m => ({ ...m, [cat]: typeof fn === 'function' ? fn(!!m[cat]) : fn }));
                return (
                  <div key={cat} style={{ border: '1px solid var(--border)', borderRadius: 6, overflow: 'hidden', marginBottom: 6 }}>
                    <button
                      className="btn btn-ghost"
                      onClick={() => setCatOpen(o => !o)}
                      style={{
                        width: '100%', textAlign: 'left', padding: '7px 10px',
                        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                        fontWeight: 500, fontSize: 12, borderRadius: 0,
                      }}
                    >
                      <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                        {T(cat)}
                        <span style={{ fontSize: 10, color: 'var(--text-3)' }}>{catSettings.length}</span>
                        {catDirty > 0 && (
                          <span style={{ fontSize: 9, padding: '1px 4px', borderRadius: 3, background: 'var(--accent)', color: 'var(--bg)' }}>
                            {catDirty}
                          </span>
                        )}
                      </span>
                      <span style={{ fontSize: 12, color: 'var(--text-3)' }}>{catOpen ? '▲' : '▼'}</span>
                    </button>
                    {catOpen && (
                      <div>
                        {catSettings.map(s => {
                          const effectiveVal = localEdits[s.key] !== undefined
                            ? localEdits[s.key]
                            : (s.team_override_value !== null && s.team_override_value !== undefined
                                ? s.team_override_value
                                : s.global_value);
                          const isOverridden = s.source === 'team' || localEdits[s.key] !== undefined;

                          return (
                            <div key={s.key} style={{
                              padding: '8px 10px', borderTop: '1px solid var(--border)',
                              background: isOverridden ? 'color-mix(in srgb, var(--accent) 5%, transparent)' : 'transparent',
                            }}>
                              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
                                <span style={{ fontWeight: 500, fontSize: 12 }}>{s.label}</span>
                                {isOverridden && (
                                  <span style={{ fontSize: 9, padding: '1px 5px', borderRadius: 3, background: 'var(--accent)', color: 'var(--bg)', fontWeight: 700 }}>
                                    {T('OVERRIDE')}
                                  </span>
                                )}
                                <span style={{ fontSize: 10, color: 'var(--text-3)', marginLeft: 'auto' }}>
                                  {T('Global:')} {Array.isArray(s.global_value) ? `[${s.global_value.length}]` : String(s.global_value ?? '—')}
                                </span>
                              </div>
                              <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8 }}>
                                {/* Reuse SettingInput from page-bot-behavior.jsx if available */}
                                {s.type === 'bool' ? (
                                  <label style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 12 }}>
                                    <input
                                      type="checkbox"
                                      checked={Boolean(effectiveVal)}
                                      onChange={e => handleChange(s.key, e.target.checked)}
                                      style={{ accentColor: 'var(--accent)' }}
                                    />
                                    {Boolean(effectiveVal) ? T('Enabled') : T('Disabled')}
                                  </label>
                                ) : (s.type === 'int' || s.type === 'float') ? (
                                  <input
                                    type="number"
                                    className="input input-sm"
                                    value={effectiveVal ?? ''}
                                    min={s.min}
                                    max={s.max}
                                    step={s.type === 'float' ? 0.5 : 1}
                                    onChange={e => handleChange(s.key, s.type === 'float' ? parseFloat(e.target.value) || 0 : parseInt(e.target.value, 10) || 0)}
                                    style={{ width: 80, fontSize: 12 }}
                                  />
                                ) : (s.type === 'string_list') ? (
                                  <textarea
                                    className="input"
                                    rows={3}
                                    value={Array.isArray(effectiveVal) ? effectiveVal.join('\n') : (effectiveVal || '')}
                                    onChange={e => handleChange(s.key, e.target.value.split('\n').map(x => x.trim()).filter(Boolean))}
                                    style={{ fontSize: 11, fontFamily: 'var(--font-mono)', resize: 'vertical', minHeight: 60 }}
                                    placeholder={T("One item per line")}
                                  />
                                ) : (
                                  <textarea
                                    className="input"
                                    rows={3}
                                    value={effectiveVal || ''}
                                    onChange={e => handleChange(s.key, e.target.value)}
                                    style={{ fontSize: 12, resize: 'vertical', minHeight: 60 }}
                                  />
                                )}
                                {isOverridden && (
                                  <button
                                    className="btn btn-ghost btn-xs"
                                    title={T("Remove team override (inherit from global)")}
                                    onClick={() => handleChange(s.key, undefined)}
                                    style={{ fontSize: 10, flexShrink: 0 }}
                                  >
                                    {T('Remove override')}
                                  </button>
                                )}
                              </div>
                            </div>
                          );
                        })}
                      </div>
                    )}
                  </div>
                );
              })}

              {/* Save bar */}
              {dirtyCount > 0 && (
                <div style={{
                  marginTop: 10, display: 'flex', gap: 8, alignItems: 'center',
                  justifyContent: 'flex-end',
                }}>
                  <span style={{ fontSize: 12, color: 'var(--text-2)' }}>
                    {dirtyCount} {T('unsaved')}
                  </span>
                  <button className="btn btn-ghost btn-sm" onClick={() => setLocalEdits({})} style={{ fontSize: 12 }}>
                    {T('Discard')}
                  </button>
                  <button className="btn btn-primary btn-sm" onClick={handleSave} disabled={saving} style={{ fontSize: 12 }}>
                    {saving ? T('Saving...') : `${T('Save')} ${dirtyCount} ${dirtyCount !== 1 ? T('overrides') : T('override')}`}
                  </button>
                </div>
              )}
            </>
          )}
        </div>
      )}
    </div>
  );
}

/* ── Team Card ── */
/* TeamSettingsInline — Phase 5 inline-editable strip.
   Shown inside each TeamCard whenever admin Edit mode is on AND the
   current user can edit this team. Each scalar setting saves via PATCH
   /api/team-settings/{team_name} and emits a 90s undo toast. */
function TeamSettingsInline({ team, onSaved }) {
  const editMode = window.useEditMode ? window.useEditMode() : false;
  const EditableCell = window.EditableCell;
  const [me, setMe] = React.useState(null);
  const [holidayCalendars, setHolidayCalendars] = React.useState([]);
  const toast = useToast();

  React.useEffect(() => {
    let alive = true;
    fetch('/api/auth/me', { credentials:'same-origin' })
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (alive) setMe(d); })
      .catch(() => {});
    fetch('/api/holiday-calendars', { credentials:'same-origin' })
      .then(r => r.ok ? r.json() : [])
      .then(d => { if (alive) setHolidayCalendars(Array.isArray(d) ? d : []); })
      .catch(() => {});
    return () => { alive = false; };
  }, []);

  if (!editMode || !EditableCell) return null;
  const role = ((me && me.org_role) || '').toLowerCase();
  // RBAC (matches backend _can_edit_team exactly):
  //   - superadmin → can edit ANY team
  //   - admin / moderator / manager → ONLY their own team (users.team)
  //   - else → no inline editor at all
  // This is intentionally strict so the inline editor only appears where
  // the save will actually succeed — no more "save failed" 403s from
  // editing a team you don't own.
  if (role === 'superadmin') {
    // all teams editable
  } else if (['admin', 'moderator', 'manager'].includes(role)) {
    const ownTeam = (me && (me.team || '')) || '';
    if (!ownTeam || ownTeam !== (team.team_name || '')) return null;  // not your team
  } else {
    return null;
  }

  const patch = async (field, value) => {
    const r = await fetch(`/api/team-settings/${encodeURIComponent(team.team_name || '')}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify({ [field]: value }),
    });
    if (!r.ok) {
      const body = await r.json().catch(() => null);
      const msg = (body && (body.detail || body.error)) || `HTTP ${r.status}`;
      toast.push({ msg: `${T('Save failed:')} ${msg}`, kind: 'err' });
      throw new Error(msg);
    }
    const data = await r.json().catch(() => ({}));
    if (data && data.undo_token) {
      const token = data.undo_token;
      toast.push({
        msg: `${T('Saved')} ${field}. ${T('Undo?')}`,
        kind: 'ok',
        actions: [{
          label: T('Undo'),
          onClick: async () => {
            const ur = await fetch(`/api/admin/undo/v2/${token}`, {
              method: 'POST', credentials:'same-origin',
            });
            if (ur.ok) toast.push({ msg: T('Reverted'), kind:'ok' });
            else toast.push({ msg: T('Undo failed'), kind:'err' });
            onSaved && onSaved();
          },
        }],
      });
    } else {
      toast.push({ msg: `${T('Saved')} ${field}`, kind:'ok' });
    }
    onSaved && onSaved();
  };

  const setHolidayCalendar = async (value) => {
    const calendarId = value ? Number(value) : null;
    try {
      const r = await fetch(`/api/teams/${team.id}/holiday-calendar`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ calendar_id: calendarId }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        const msg = (body && (body.detail || body.error)) || `HTTP ${r.status}`;
        toast.push({ msg: `${T('Save failed:')} ${msg}`, kind: 'err' });
        return;
      }
      toast.push({ msg: `${T('Saved')} ${T('Holiday calendar')}`, kind: 'ok' });
      onSaved && onSaved();
    } catch (e) {
      toast.push({ msg: `${T('Save failed:')} ${e.message}`, kind: 'err' });
    }
  };

  const Row = ({ label, children }) => (
    <div style={{ display:'flex', alignItems:'center', gap:8, fontSize:12, padding:'4px 0' }}>
      <div style={{ width:170, color:'var(--text-2,#888)' }}>{label}</div>
      <div style={{ flex:1, minWidth:0 }}>{children}</div>
    </div>
  );

  return (
    <div style={{ borderTop:'1px dashed var(--line)', background:'var(--bg-elev-1, transparent)', padding:'10px 16px' }}>
      <div className="muted" style={{ fontSize:11, marginBottom:6, textTransform:'uppercase', letterSpacing:0.5 }}>
        {T('Inline settings (admin edit mode)')}
      </div>
      <Row label={T("Timezone")}>
        <EditableCell value={team.timezone || 'UTC'} kind="text"
          onSave={v => patch('timezone', v)} />
      </Row>
      <Row label={T("Sign-off keyword")}>
        <EditableCell value={team.signoff_keyword || ''} kind="text"
          placeholder={T("signing off")}
          onSave={v => patch('signoff_keyword', v || null)} />
      </Row>
      <Row label={T("Sign-in deadline (HH:MM)")}>
        <EditableCell value={team.signin_deadline || ''} kind="text"
          placeholder="09:30"
          onSave={v => patch('signin_deadline', v || null)} />
      </Row>
      <Row label={T("Late buffer (minutes)")}>
        <EditableCell value={team.late_buffer_minutes ?? 30} kind="text"
          onSave={v => patch('late_buffer_minutes', parseInt(v, 10) || 0)} />
      </Row>
      <Row label={T("Late lookback (days)")}>
        <EditableCell value={team.late_lookback_days ?? 30} kind="text"
          onSave={v => patch('late_lookback_days', parseInt(v, 10) || 0)} />
      </Row>
      <Row label={T("Late min samples")}>
        <EditableCell value={team.late_min_samples ?? 5} kind="text"
          onSave={v => patch('late_min_samples', parseInt(v, 10) || 0)} />
      </Row>
      <Row label={T("Min hours / day")}>
        <EditableCell value={team.min_hours_per_day ?? 8} kind="text"
          onSave={v => patch('min_hours_per_day', parseFloat(v) || 0)} />
      </Row>
      <Row label={T("Working days")}>
        <OffDayMultiSelect
          value={team.working_days || 'mon,tue,wed,thu,fri'}
          onChange={v => patch('working_days', v)}
        />
      </Row>
      <Row label={T("Week starts on")}>
        <select
          value={team.week_start_day != null ? String(team.week_start_day) : '6'}
          onChange={e => patch('week_start_day', parseInt(e.target.value, 10))}
          style={{ fontSize: 13, padding: '2px 6px' }}
        >
          <option value="0">{T("Monday")}</option>
          <option value="1">{T("Tuesday")}</option>
          <option value="2">{T("Wednesday")}</option>
          <option value="3">{T("Thursday")}</option>
          <option value="4">{T("Friday")}</option>
          <option value="5">{T("Saturday")}</option>
          <option value="6">{T("Sunday")}</option>
        </select>
      </Row>
      <Row label={T("Holiday calendar")}>
        <select
          value={team.holiday_calendar_id != null ? String(team.holiday_calendar_id) : ''}
          onChange={e => setHolidayCalendar(e.target.value)}
          style={{ fontSize: 13, padding: '2px 6px' }}
          title={T("Which holiday calendar this team observes. Org default applies when unset.")}
        >
          <option value="">{T("Org default")}</option>
          {(holidayCalendars || []).map(cal => (
            <option key={cal.id} value={cal.id}>{cal.name}{cal.is_org_default ? ` (${T('default')})` : ''}</option>
          ))}
        </select>
      </Row>
      <Row label={T("Allow password login")}>
        <input type="checkbox" checked={!!team.allow_password_login}
          onChange={e => patch('allow_password_login', e.target.checked)} />
      </Row>
      <Row label={T("Require @location tag")}>
        <input type="checkbox" checked={!!team.require_location_tag}
          onChange={e => patch('require_location_tag', e.target.checked)} />
      </Row>
      {/* C4: flexible sign-in toggle — skip deadline, only count total hours */}
      <Row label={T("Flexible sign-in (skip deadline, track hours only)")}>
        <input type="checkbox" checked={!!team.flexible_signin}
          onChange={e => patch('flexible_signin', e.target.checked)}
          title={T("When ON, bot skips fixed sign-in deadline enforcement and only tracks total hours worked per day. Useful for consulting / async-first remote teams.")} />
      </Row>

      {/* Per-team bot behaviour knobs (June 2026). Previously hardcoded
          in matrix_bot.py; surfaced here so admins can tune without
          touching code or asking ops. */}
      <div className="muted" style={{ fontSize: 11, marginTop: 14, marginBottom: 4, textTransform:'uppercase', letterSpacing:0.5 }}>
        {T('Bot ack behaviour')}
      </div>
      <Row label={T("Stays-late threshold (local hour 0–24)")}>
        <EditableCell value={team.stays_late_threshold_hour ?? 21} kind="text"
          onSave={v => patch('stays_late_threshold_hour', Math.max(0, Math.min(24, parseInt(v, 10) || 21)))}
          title={T("Bot silently skips the sign-off room ack at or after this local hour. Set 24 to disable. Default 21 (9 PM).")} />
      </Row>
      <Row label={T("Edit grace (minutes — edits within this window still ack)")}>
        <EditableCell value={team.edit_grace_minutes ?? 0} kind="text"
          onSave={v => patch('edit_grace_minutes', Math.max(0, parseInt(v, 10) || 0))}
          title={T("Edits arriving within this many minutes of the original sign-in/off STILL get a room ack (typo fix). Later edits stay silent. Default 0 = always silent on edits.")} />
      </Row>
      <Row label={T("Sign-in ack template ({first}/{name}/{tail})")}>
        <EditableCell value={team.signin_ack_template || ''} kind="text"
          placeholder="✅ Hi {first}, {tail}!"
          onSave={v => patch('signin_ack_template', v || null)}
          title={T("Override the default sign-in room ack. Placeholders: {first} = first name, {name} = full name, {tail} = welcome back / early start / late.")} />
      </Row>
      <Row label={T("Sign-off ack template ({name})")}>
        <EditableCell value={team.signoff_ack_template || ''} kind="text"
          placeholder="👋 {name} signed off"
          onSave={v => patch('signoff_ack_template', v || null)}
          title={T("Override the default sign-off room ack. Use {name} for the user's full display name.")} />
      </Row>

      <div className="muted" style={{ fontSize: 11, marginTop: 14, marginBottom: 4, textTransform:'uppercase', letterSpacing:0.5 }}>
        {T('IP red-flag detection')}
      </div>
      <Row label={T("IP red-flag enabled")}>
        <input type="checkbox" checked={team.ip_redflag_enabled !== false}
          onChange={e => patch('ip_redflag_enabled', e.target.checked)}
          title={T("Whether the bot flags claimed-vs-classified IP mismatches (e.g. user claims office from a known-home IP).")} />
      </Row>
      <Row label={T("Min samples before classification")}>
        <EditableCell value={team.ip_redflag_min_samples ?? 5} kind="text"
          onSave={v => patch('ip_redflag_min_samples', Math.max(1, parseInt(v, 10) || 5))} />
      </Row>
      <Row label={T("Min match % (60 = 60% agreement)")}>
        <EditableCell value={team.ip_redflag_min_match_pct ?? 60} kind="text"
          onSave={v => patch('ip_redflag_min_match_pct', Math.max(50, Math.min(100, parseInt(v, 10) || 60)))} />
      </Row>
      <Row label={T("Throttle hours between red-flag DMs")}>
        <EditableCell value={team.ip_redflag_throttle_hours ?? 24} kind="text"
          onSave={v => patch('ip_redflag_throttle_hours', Math.max(1, parseInt(v, 10) || 24))} />
      </Row>

      <div className="muted" style={{ fontSize: 11, marginTop: 14, marginBottom: 4, textTransform:'uppercase', letterSpacing:0.5 }}>
        {T('Hour expectations & detection')}
      </div>
      <Row label={T("Burnout cap (hours/day — bot flags above this)")}>
        <EditableCell value={team.burnout_hours_cap ?? 11} kind="text"
          onSave={v => patch('burnout_hours_cap', parseFloat(v) || 11)} />
      </Row>
      <Row label={T("Inactivity reminder (hours)")}>
        <EditableCell value={team.inactivity_hours ?? 3} kind="text"
          onSave={v => patch('inactivity_hours', parseFloat(v) || 3)} />
      </Row>
      <Row label={T("Missing-signoff poll cadence (minutes)")}>
        <EditableCell value={team.signoff_poll_minutes ?? 30} kind="text"
          onSave={v => patch('signoff_poll_minutes', Math.max(5, parseInt(v, 10) || 30))} />
      </Row>

      <div className="muted" style={{ fontSize: 11, marginTop: 14, marginBottom: 4, textTransform:'uppercase', letterSpacing:0.5 }}>
        {T('Pingpong (random availability checks)')}
      </div>
      <Row label={T("Pingpong enabled")}>
        <input type="checkbox" checked={!!team.ping_pong_enabled}
          onChange={e => patch('ping_pong_enabled', e.target.checked)} />
      </Row>
      <Row label={T("Interval (minutes between checks per user)")}>
        <EditableCell value={team.ping_pong_interval_minutes ?? 120} kind="text"
          onSave={v => patch('ping_pong_interval_minutes', Math.max(30, parseInt(v, 10) || 120))} />
      </Row>
      <Row label={T("Response window (minutes before miss)")}>
        <EditableCell value={team.ping_pong_response_window_minutes ?? 10} kind="text"
          onSave={v => patch('ping_pong_response_window_minutes', Math.max(2, parseInt(v, 10) || 10))} />
      </Row>
    </div>
  );
}

function TeamCard({ team, onSaved, rooms, bots, employees, onDelete }) {
  const [expanded, setExpanded]         = useState(false);
  const [form, setForm]                 = useState(null);
  const [saving, setSaving]             = useState(false);
  const [err, setErr]                   = useState('');
  const [scheduleOpen, setScheduleOpen] = useState(false);
  const [membersOpen, setMembersOpen]   = useState(false);
  const toast = useToast();

  // Initial set of member-user-ids currently on this team. Used to diff
  // additions/removals when the inline members grid saves.
  const initialMemberIds = React.useMemo(() => {
    const tn = team.team_name || '';
    return new Set((employees || []).filter(e => (e.team || '') === tn).map(e => e.id));
  }, [employees, team.team_name]);

  const openEdit = () => {
    setForm({
      team_name:        team.team_name || '',
      timezone:         team.timezone || 'UTC',
      work_hours_target: team.min_hours_per_day ?? team.work_hours_target ?? 8,
      short_day_grace_minutes: team.short_day_grace_minutes ?? 15,
      off_days:         team.working_days || 'mon,tue,wed,thu,fri',
      signoff_keyword:  team.signoff_keyword || 'signing off',
      signin_deadline:  team.signin_deadline || '09:30',
      assigned_rooms:   team.assigned_rooms || [],
      // New: inline members multi-select, parallel to assigned_rooms.
      // Save will diff this against initialMemberIds and PUT each
      // employee's profile to add/remove team= linkage.
      assigned_members: Array.from(initialMemberIds),
      bot_id:           team.bot_id ? String(team.bot_id) : '',
    });
    setExpanded(true);
  };

  const setF = (key, val) => setForm(f => ({ ...f, [key]: val }));

  const save = async () => {
    setSaving(true); setErr('');
    try {
      // Step 1 — if the team name changed, rename first (the PUT settings
      // endpoint is keyed by name, so a rename has to happen before the
      // settings save can target the right row). The rename endpoint
      // cascades to users.team + every table with a team_name column.
      const oldName = (team.team_name || '').trim();
      const newName = (form.team_name || '').trim();
      let targetName = oldName;
      if (newName && newName !== oldName) {
        const rr = await fetch(`/api/teams/${encodeURIComponent(oldName)}/rename`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          credentials: 'same-origin',
          body: JSON.stringify({ new_name: newName }),
        });
        if (!rr.ok) {
          const body = await rr.json().catch(() => null);
          throw new Error(parseDetail(body, rr.status) || T('Rename failed'));
        }
        targetName = newName;
      }
      // Step 2 — save the settings (under the possibly-new name)
      const r = await fetch(`/api/teams/${encodeURIComponent(targetName)}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({
          timezone:        form.timezone,
          min_hours_per_day: parseFloat(form.work_hours_target) || 8,
          short_day_grace_minutes: Math.max(0, Math.min(120, parseInt(form.short_day_grace_minutes, 10) || 0)),
          working_days:    form.off_days,
          signoff_keyword: form.signoff_keyword,
          signin_deadline: form.signin_deadline,
        }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      // Step 3 — apply member additions/removals. Each PUT updates one
      // user's profile.team field. Failures are tallied but don't abort
      // the rest of the save.
      const desired = new Set(form.assigned_members || []);
      const added   = [...desired].filter(id => !initialMemberIds.has(id));
      const removed = [...initialMemberIds].filter(id => !desired.has(id));
      let memberOk = 0, memberFail = 0;
      for (const id of added) {
        try {
          const rm = await fetch(`/api/employees/${id}/profile`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            credentials: 'same-origin',
            body: JSON.stringify({ team: targetName }),
          });
          if (rm.ok) memberOk++; else memberFail++;
        } catch(_) { memberFail++; }
      }
      for (const id of removed) {
        try {
          const rm = await fetch(`/api/employees/${id}/profile`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            credentials: 'same-origin',
            body: JSON.stringify({ team: null }),
          });
          if (rm.ok) memberOk++; else memberFail++;
        } catch(_) { memberFail++; }
      }
      const renamedMsg = (newName && newName !== oldName)
        ? `${T('Team renamed to')} "${newName}" ${T('and saved')}` : T('Team saved');
      const memberMsg = (added.length || removed.length)
        ? ` · ${T('members:')} +${added.length}/-${removed.length}${memberFail ? ` (${memberFail} ${T('failed')})` : ''}`
        : '';
      toast.push({ msg: renamedMsg + memberMsg, kind: memberFail ? 'warn' : 'ok' });
      setExpanded(false);
      setForm(null);
      onSaved && onSaved();
    } catch(e) {
      setErr(e.message);
    } finally {
      setSaving(false);
    }
  };

  // Derive summary strings
  const memberCount = team.members ?? team.member_count ?? '—';
  const tzShort = (team.timezone || 'UTC').split('/').pop().replace(/_/g,' ');
  const offDays = (team.working_days || '').split(',').filter(d => ['sat','sun'].includes(d));
  const offDayStr = offDays.length ? `${T('Off:')} ${offDays.join(', ')}` : T('No off-days set');

  return (
    <div className="card" style={{ padding: 0, overflow:'hidden' }}>
      {/* Card header — always visible */}
      <div
        style={{ display:'flex', alignItems:'center', gap: 12, padding:'14px 16px', cursor:'pointer', userSelect:'none' }}
        onClick={() => { if (!expanded) openEdit(); else { setExpanded(false); setForm(null); } }}
      >
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 14, fontWeight: 600 }}>{team.team_name || T('(default)')}</div>
          <div className="muted" style={{ fontSize: 12, marginTop: 2 }}>
            {memberCount} {T('members')} · {tzShort} · {offDayStr}
          </div>
        </div>
        <div style={{ display:'flex', gap: 6, alignItems:'center', flexShrink: 0 }}>
          {team.signoff_keyword && <Badge kind="mute">{team.signoff_keyword}</Badge>}
          <Badge kind="info">{team.flexible_signin ? T('Flexible') : (team.signin_deadline || '—')}</Badge>
          <button
            className="btn btn-ghost"
            style={{ fontSize: 11, padding: '2px 8px' }}
            onClick={e => { e.stopPropagation(); setMembersOpen(true); }}
            title={T("Add or remove team members")}
          >{T('Members')}</button>
          <button
            className="btn btn-ghost"
            style={{ fontSize: 11, padding: '2px 8px' }}
            onClick={e => { e.stopPropagation(); setScheduleOpen(true); }}
            title={T("Edit working hours schedule")}
          >{T('Schedule')}</button>
          <Icon name="chevron" size={14} style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)', transition:'transform .2s', opacity: 0.5 }} />
        </div>
      </div>

      {/* Phase 5 — inline editable settings strip. Only show when expanded
          so the card collapses ALL the way to a single-line header for
          dense team lists. Click the header to expand into edit mode. */}
      {expanded && <TeamSettingsInline team={team} onSaved={onSaved} />}

      {scheduleOpen && (
        <TeamScheduleDrawer teamName={team.team_name} onClose={() => setScheduleOpen(false)} />
      )}

      {membersOpen && (
        <TeamMembersDrawer
          team={team}
          employees={employees || []}
          onClose={() => setMembersOpen(false)}
          onSaved={onSaved}
        />
      )}

      {/* Expanded edit form */}
      {expanded && form && (
        <div style={{ borderTop:'1px solid var(--line)', padding:'16px 16px 20px' }}>
          {err && <div className="badge badge-err" style={{ marginBottom: 12, padding:'6px 10px', fontSize: 12 }}><Icon name="alert" size={12}/> {err}</div>}

          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 12, marginBottom: 12 }}>
            <div>
              <label className="login-label">{T('Team name')}</label>
              <input className="inp" value={form.team_name} onChange={e => setF('team_name', e.target.value)} />
            </div>
            <div>
              <label className="login-label">{T('Timezone')}</label>
              <TimezoneSelect value={form.timezone} onChange={v => setF('timezone', v)} />
            </div>
          </div>

          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 12, marginBottom: 12 }}>
            <div>
              <label className="login-label">{T('Work hours target / day')}</label>
              <input className="inp" type="number" min="1" max="24" step="0.5"
                value={form.work_hours_target} onChange={e => setF('work_hours_target', e.target.value)} />
            </div>
            <div>
              <label className="login-label">{T('Sign-in deadline')}</label>
              <input className="inp" type="time" value={form.signin_deadline} onChange={e => setF('signin_deadline', e.target.value)} />
            </div>
          </div>

          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 12, marginBottom: 12 }}>
            <div>
              <label className="login-label" title={T("Don't send the below-minimum-hours nudge when the shortfall is smaller than this many minutes (default 15, 0 = no grace).")}>{T('Short-day grace (min)')}</label>
              <input className="inp" type="number" min="0" max="120" step="1"
                value={form.short_day_grace_minutes} onChange={e => setF('short_day_grace_minutes', e.target.value)}
                title={T("Don't send the below-minimum-hours nudge when the shortfall is smaller than this many minutes (default 15, 0 = no grace).")} />
            </div>
          </div>

          <div style={{ marginBottom: 12 }}>
            <label className="login-label">{T('Working days')}</label>
            <OffDayMultiSelect value={form.off_days} onChange={v => setF('off_days', v)} />
          </div>

          <div style={{ marginBottom: 12 }}>
            <label className="login-label">{T('Sign-off keyword')}</label>
            <input className="inp" value={form.signoff_keyword} onChange={e => setF('signoff_keyword', e.target.value)} />
          </div>

          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap: 12, marginBottom: 16 }}>
            <div>
              <label className="login-label">{T('Assigned rooms')}</label>
              <div style={{ border:'1px solid var(--line)', borderRadius:'var(--r-sm)', padding: 6, maxHeight: 120, overflowY:'auto', display:'flex', flexDirection:'column', gap: 3 }}>
                {rooms.length === 0 && <div className="muted" style={{ fontSize: 11 }}>{T('No rooms available')}</div>}
                {rooms.map(r => {
                  const checked = (form.assigned_rooms || []).includes(r.id);
                  return (
                    <label key={r.id} style={{ display:'flex', alignItems:'center', gap: 6, cursor:'pointer', fontSize: 12 }}>
                      <input type="checkbox" checked={checked}
                        onChange={() => {
                          const curr = form.assigned_rooms || [];
                          setF('assigned_rooms', checked ? curr.filter(x => x !== r.id) : [...curr, r.id]);
                        }} />
                      {r.display_name}
                    </label>
                  );
                })}
              </div>
            </div>
            <div>
              <label className="login-label">{T('Assigned bot')}</label>
              <select className="sel" value={form.bot_id} onChange={e => setF('bot_id', e.target.value)} style={{ width:'100%' }}>
                <option value="">{T('No bot')}</option>
                {bots.filter(b => b.active).map(b => <option key={b.id} value={String(b.id)}>{b.name}</option>)}
              </select>
            </div>
          </div>

          {/* Inline members multi-select — parallel to "Assigned rooms".
              Lets admins toggle which active employees belong to this
              team without leaving the edit form. Save diffs against the
              initial roster and applies team= to each affected user. */}
          <div style={{ marginBottom: 16 }}>
            <label className="login-label">
              {T('Assigned members')}
              <span className="muted" style={{ marginLeft: 8, fontSize: 11, fontWeight: 400 }}>
                · {(form.assigned_members || []).length} {T('selected')}
              </span>
            </label>
            <div className="muted" style={{ fontSize: 11, marginBottom: 6 }}>
              {T('Click any name to add/remove from this team. Members are also editable via the dedicated Members drawer at the top.')}
            </div>
            <div style={{
              border:'1px solid var(--line)', borderRadius:'var(--r-sm)',
              padding: 8, maxHeight: 220, overflowY:'auto',
              display:'grid', gridTemplateColumns:'1fr 1fr', gap: 4,
            }}>
              {(employees || []).length === 0 && (
                <div className="muted" style={{ fontSize: 11, gridColumn:'1/-1' }}>
                  {T('No employees loaded')}
                </div>
              )}
              {(employees || []).map(emp => {
                const checked = (form.assigned_members || []).includes(emp.id);
                const otherTeam = emp.team && emp.team !== (form.team_name || team.team_name);
                return (
                  <label key={emp.id} style={{
                    display:'flex', alignItems:'center', gap: 6,
                    cursor:'pointer', fontSize: 12,
                    padding:'4px 6px', borderRadius: 4,
                    background: checked ? 'var(--brand-soft, rgba(124,58,237,.08))' : 'transparent',
                  }}>
                    <input
                      type="checkbox" checked={checked}
                      onChange={() => {
                        const curr = form.assigned_members || [];
                        setF('assigned_members', checked
                          ? curr.filter(x => x !== emp.id)
                          : [...curr, emp.id]);
                      }}
                    />
                    <span style={{ flex: 1, minWidth: 0, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                      {emp.display_name || emp.matrix_id}
                    </span>
                    {otherTeam && (
                      <Badge kind="mute" style={{ flexShrink: 0, fontSize: 9, padding:'0 4px' }}>
                        {emp.team}
                      </Badge>
                    )}
                  </label>
                );
              })}
            </div>
          </div>

          <div style={{ display:'flex', gap: 8 }}>
            <button className="btn btn-pri" onClick={save} disabled={saving}>
              {saving ? T('Saving...') : <><Icon name="check" size={13}/> {T('Save changes')}</>}
            </button>
            <button className="btn btn-ghost" onClick={() => { setExpanded(false); setForm(null); }}>{T('Cancel')}</button>
            <button
              className="btn btn-ghost"
              onClick={() => onDelete && onDelete(team)}
              style={{ marginLeft:'auto', color:'var(--err)', fontSize: 12 }}
              title={T("Delete this team")}
            >
              <Icon name="trash" size={12}/> {T('Delete team')}
            </button>
          </div>
        </div>
      )}

      {/* Bot Behavior overrides — visible whenever expanded (not just when editing settings) */}
      {expanded && team.team_name && (
        <TeamBotBehaviorSection teamName={team.team_name} />
      )}
    </div>
  );
}

function TeamsPage() {
  const [teams, setTeams]           = useState([]);
  const [rooms, setRooms]           = useState([]);
  const [bots, setBots]             = useState([]);
  const [employees, setEmployees]   = useState([]);
  const [loading, setLoading]       = useState(true);
  const [wizardOpen, setWizardOpen] = useState(false);
  const [deleteTeam, setDeleteTeam] = useState(null);
  const toast = useToast();

  const load = async () => {
    setLoading(true);
    try {
      const [tr, rr, br, er] = await Promise.all([
        fetch('/api/teams', { credentials:'same-origin' }),
        fetch('/api/rooms', { credentials:'same-origin' }),
        fetch('/api/bots',  { credentials:'same-origin' }),
        fetch('/api/employees', { credentials:'same-origin' }),
      ]);
      if (tr.ok) setTeams(await tr.json());
      if (rr.ok) setRooms(await rr.json());
      if (br.ok) setBots(await br.json());
      if (er.ok) setEmployees(await er.json());
    } catch(e) {
      toast.push({ msg: T('Load failed: ') + e.message, kind: 'err' });
    } finally {
      setLoading(false);
    }
  };

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

  // The team_name="" row is the GLOBAL defaults/config fallback — not a real
  // team. Hide it from the list (it can't be deleted and shouldn't be shown
  // as a deletable card; its values back-stop every team).
  const realTeams = teams.filter(t => (t.team_name || '').trim());

  return (
    <div className="page">
      <PageHeader
        title={T("Teams")}
        meta={`${realTeams.length} ${realTeams.length !== 1 ? T('teams') : T('team')} · ${T('click a card to edit')}`}
        right={
          <div style={{ display:'flex', gap: 8 }}>
            <Refresh onClick={load} loading={loading} />
            <button className="btn btn-pri" onClick={() => setWizardOpen(true)}><Icon name="plus" size={14}/> {T('New team')}</button>
          </div>
        }
      />

      {loading && teams.length === 0 ? (
        <div style={{ display:'flex', flexDirection:'column', gap: 12 }}>
          <Skel h={72} /><Skel h={72} /><Skel h={72} />
        </div>
      ) : realTeams.length === 0 ? (
        <Empty icon={<Icon name="building" size={24}/>} title={T("No teams yet")} subtitle={T("Create your first team to configure attendance rules.")} />
      ) : (
        <div style={{ display:'flex', flexDirection:'column', gap: 10 }}>
          {realTeams.map(t => (
            <TeamCard key={t.id} team={t} onSaved={load} rooms={rooms} bots={bots} employees={employees} onDelete={setDeleteTeam} />
          ))}
        </div>
      )}

      {wizardOpen && (
        <NewTeamWizard
          onClose={() => setWizardOpen(false)}
          onCreated={load}
          rooms={rooms}
          bots={bots}
          employees={employees}
        />
      )}
      {deleteTeam && (
        <DeleteTeamModal
          team={deleteTeam}
          onClose={() => setDeleteTeam(null)}
          onDeleted={load}
        />
      )}
    </div>
  );
}

function Setting({ label, value }) {
  return (
    <div className="setting">
      <div className="setting-l">{label}</div>
      <div className="setting-v mono">{value}</div>
    </div>
  );
}

/* ============================================================
   ROOMS PAGE — create + register modals
   ============================================================ */

function CreateRoomModal({ onClose, onCreated, roomTypes, bots, employees, initialRoomType }) {
  // Unified create-room modal. Same UX for every room_type
  // (general/standup/admin/pto/urgent/support/...). Editorial-calm layout:
  // a single serif heading, generous spacing, two-column field grid where
  // it helps, no card chrome around individual fields.
  const [name, setName]         = useState('');
  const [description, setDesc]  = useState('');
  const [topic, setTopic]       = useState('');
  const [roomType, setRType]    = useState(initialRoomType || 'general');
  const [inviteUsers, setInv]   = useState([]);
  const [extraMxids, setExtraMxids] = useState(''); // comma/space separated free text
  const [botId, setBotId]       = useState('');
  const [secondaryIds, setSecondaryIds] = useState([]); // multi-select of extra bots
  const [empQuery, setEmpQuery] = useState('');
  const [busy, setBusy]         = useState(false);
  const [err, setErr]           = useState('');
  const toast = useToast();

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

  const toggleUser = (id) => setInv(v => v.includes(id) ? v.filter(x => x !== id) : [...v, id]);
  const toggleSecondary = (id) => setSecondaryIds(v => v.includes(id) ? v.filter(x => x !== id) : [...v, id]);

  // Filter employees by free-text query (name or mxid)
  const empFilter = (e) => {
    const q = empQuery.trim().toLowerCase();
    if (!q) return true;
    return (e.display_name || '').toLowerCase().includes(q)
        || (e.matrix_id || '').toLowerCase().includes(q);
  };

  const parseMxidList = (raw) =>
    String(raw || '').split(/[\s,;]+/).map(s => s.trim()).filter(Boolean);

  const submit = async () => {
    if (!name.trim()) { setErr(T('Room name is required')); return; }
    const extras = parseMxidList(extraMxids);
    // Light client validation: keep matrix IDs in the @x:y format
    const bad = extras.find(m => !/^@[^:\s]+:[^\s]+$/.test(m));
    if (bad) { setErr(`${T('Invalid matrix ID:')} ${bad}`); return; }
    setBusy(true); setErr('');
    try {
      const r = await fetch('/api/rooms/create', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({
          name: name.trim(),
          description: description.trim() || null,
          topic: topic.trim() || null,
          room_type: roomType,
          bot_id: botId ? parseInt(botId) : null,
          invite_user_ids: inviteUsers,
          invite_matrix_ids: extras,
          secondary_bot_ids: secondaryIds.map(x => parseInt(x)),
        }),
      });
      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: `${T('Room created —')} ${data.invited_members} ${T('member(s),')} ${data.invited_bots} ${T('bot(s) invited')}`,
        kind: 'ok',
      });
      onCreated && onCreated();
      onClose();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  const totalInvitees = inviteUsers.length + parseMxidList(extraMxids).length;
  const activeBots = bots.filter(b => b.active);

  return (
    <div className="drawer-overlay" onClick={onClose} style={{ zIndex: 300 }}>
      <div onClick={e => e.stopPropagation()} className="editorial-modal" style={{
        // Anchor to the top of the viewport (not vertically centered) so
        // tall content scrolls within the modal instead of pushing the
        // header off-screen. The previous top:50% + translate(-50%,-50%)
        // hid the room-name field whenever the modal exceeded ~92vh.
        position:'fixed', top:'4vh', left:'50%', transform:'translateX(-50%)',
        background:'var(--bg-elev-1)', border:'1px solid var(--line)',
        borderRadius:'var(--r-lg)',
        padding: '32px 36px 28px',
        width: 560, maxWidth:'95vw',
        boxShadow:'var(--shadow)',
        zIndex: 301, maxHeight:'92vh', overflowY:'auto',
      }}>
        {/* Editorial header: serif title + small caps eyebrow */}
        <div style={{ marginBottom: 28 }}>
          <div style={{
            fontSize: 10.5, letterSpacing: '0.14em', textTransform:'uppercase',
            color:'var(--text-3)', marginBottom: 6,
          }}>{T('Rooms · New')}</div>
          <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap: 16 }}>
            <h2 style={{
              fontFamily:'var(--font-serif)', fontWeight: 500,
              fontSize: 28, lineHeight: 1.15, color:'var(--text)', margin: 0,
            }}>
              {T('Create a room')}
            </h2>
            <button className="btn btn-ghost btn-icon" onClick={onClose} aria-label={T("Close")}
              style={{ marginTop: 2 }}>
              <Icon name="x" size={14}/>
            </button>
          </div>
          <div style={{ marginTop: 10, fontSize: 13, color:'var(--text-2)', lineHeight: 1.55 }}>
            {T('The room is created in Matrix with end-to-end encryption. The primary AI bot is added automatically — invite teammates and add specialist bots below.')}
          </div>
        </div>

        {err && (
          <div className="badge badge-err" style={{ marginBottom: 16, padding:'8px 12px', fontSize: 12.5 }}>
            <Icon name="alert" size={13}/> {err}
          </div>
        )}

        {/* Field: name + type row */}
        <div className="ed-field" style={{ marginBottom: 18 }}>
          <label className="ed-label">{T('Room name')} <span style={{ color:'var(--err)' }}>*</span></label>
          <input className="inp ed-input" value={name} onChange={e => setName(e.target.value)}
            placeholder={T("e.g. Engineering Attendance")} autoFocus />
        </div>

        <div style={{ display:'grid', gridTemplateColumns:'1fr 180px', gap: 16, marginBottom: 18 }}>
          <div className="ed-field">
            <label className="ed-label">{T('Description')}</label>
            <textarea className="inp ed-input" value={description}
              onChange={e => setDesc(e.target.value)}
              placeholder={T("What this room is for. Shown on the room card.")}
              rows={2}
              style={{ resize:'vertical', minHeight: 60, lineHeight: 1.5 }} />
          </div>
          <div className="ed-field">
            <label className="ed-label">{T('Type')}</label>
            <select className="sel ed-input" value={roomType}
              onChange={e => setRType(e.target.value)} style={{ width:'100%' }}>
              {(roomTypes && roomTypes.length ? roomTypes : ['general','standup','admin','pto','urgent','support']).map(t =>
                <option key={t} value={t}>{t}</option>
              )}
            </select>
          </div>
        </div>

        <div className="ed-field" style={{ marginBottom: 22 }}>
          <label className="ed-label">{T('Matrix topic')} <span style={{ color:'var(--text-3)', fontWeight: 400 }}>{T('(optional)')}</span></label>
          <input className="inp ed-input" value={topic} onChange={e => setTopic(e.target.value)}
            placeholder={T("Shown inside Element as the room's topic")} />
        </div>

        {/* Invitee block */}
        <div className="ed-field" style={{ marginBottom: 20 }}>
          <div style={{ display:'flex', alignItems:'baseline', justifyContent:'space-between', marginBottom: 8 }}>
            <label className="ed-label" style={{ margin: 0 }}>{T('Invite people')}</label>
            <span style={{ fontSize: 11.5, color:'var(--text-3)' }}>
              {totalInvitees} {T('selected')}
            </span>
          </div>
          <input className="inp ed-input" value={empQuery}
            onChange={e => setEmpQuery(e.target.value)}
            placeholder={T("Search by name or matrix ID…")}
            style={{ marginBottom: 8 }} />
          <div style={{
            border:'1px solid var(--line)', borderRadius:'var(--r-sm)',
            padding: 4, maxHeight: 168, overflowY:'auto',
            display:'flex', flexDirection:'column',
            background:'var(--bg)',
          }}>
            {employees.filter(empFilter).length === 0 && (
              <div className="muted" style={{ fontSize: 12.5, padding: '10px 8px' }}>
                {T('No employees match')}
              </div>
            )}
            {employees.filter(empFilter).map(e => {
              const checked = inviteUsers.includes(e.id);
              return (
                <label key={e.id} style={{
                  display:'flex', alignItems:'center', gap: 10, cursor:'pointer',
                  fontSize: 13, padding:'6px 10px', borderRadius:'var(--r-xs)',
                  background: checked ? 'var(--brand-soft)' : 'transparent',
                  transition:'background 80ms',
                }}>
                  <input type="checkbox" checked={checked} onChange={() => toggleUser(e.id)} />
                  <span style={{ flex: 1, color:'var(--text)' }}>{e.display_name}</span>
                  <span className="mono" style={{ fontSize: 11, color:'var(--text-3)' }}>{e.matrix_id}</span>
                </label>
              );
            })}
          </div>
          <div style={{ marginTop: 14 }}>
            <label className="ed-label" style={{
              fontSize: 11, color:'var(--text-3)', textTransform:'uppercase',
              letterSpacing:'0.06em', fontWeight: 500,
            }}>
              {T('Or add by matrix ID')}
            </label>
            <input className="inp ed-input" value={extraMxids}
              onChange={e => setExtraMxids(e.target.value)}
              placeholder="@bassel.safwat:k9.ms, @new-hire:k9.ms"
              style={{ fontFamily:'var(--font-mono)', fontSize: 12.5 }} />
            <div style={{ fontSize: 11, color:'var(--text-3)', marginTop: 4, lineHeight: 1.45 }}>
              {T("Comma or space separated. Useful for invitees who haven't onboarded yet.")}
            </div>
          </div>
        </div>

        {/* Bots block */}
        <div className="ed-field" style={{ marginBottom: 28 }}>
          <label className="ed-label">{T('Primary AI bot')}</label>
          <select className="sel ed-input" value={botId}
            onChange={e => setBotId(e.target.value)} style={{ width:'100%' }}>
            <option value="">{T('Default — env bot (always added as creator)')}</option>
            {activeBots.map(b =>
              <option key={b.id} value={b.id}>{b.name} ({b.matrix_id})</option>
            )}
          </select>
          {activeBots.length > 0 && (
            <div style={{ marginTop: 16 }}>
              <label className="ed-label" style={{
                fontSize: 11, color:'var(--text-3)', textTransform:'uppercase',
                letterSpacing:'0.06em', fontWeight: 500,
              }}>
                {T('Additional bots (optional)')}
              </label>
              <div style={{
                display:'flex', flexWrap:'wrap', gap: 6, marginTop: 6,
                padding: '8px 8px', border:'1px solid var(--line)',
                borderRadius:'var(--r-sm)', background:'var(--bg)',
              }}>
                {activeBots.map(b => {
                  const on = secondaryIds.includes(b.id);
                  return (
                    <button key={b.id} type="button"
                      onClick={() => toggleSecondary(b.id)}
                      className="btn"
                      style={{
                        fontSize: 12, padding:'4px 10px',
                        border: `1px solid ${on ? 'var(--brand)' : 'var(--line-hi)'}`,
                        background: on ? 'var(--brand-soft)' : 'transparent',
                        color: on ? 'var(--brand)' : 'var(--text-2)',
                        borderRadius: 999,
                      }}>
                      {on ? '✓ ' : '+ '}{b.name}
                    </button>
                  );
                })}
              </div>
            </div>
          )}
        </div>

        <div style={{ display:'flex', gap: 10, justifyContent:'flex-end',
                      paddingTop: 16, borderTop: '1px solid var(--line)' }}>
          <button className="btn btn-ghost" onClick={onClose}>{T('Cancel')}</button>
          <button className="btn btn-pri" onClick={submit} disabled={busy}>
            {busy ? T('Creating…') : <><Icon name="check" size={13}/> {T('Create room')}</>}
          </button>
        </div>
      </div>
    </div>
  );
}

function RegisterRoomModal({ onClose, onCreated, roomTypes, bots }) {
  const [roomId, setRoomId]   = useState('');
  const [roomType, setRType]  = useState('standup');
  const [botId, setBotId]     = useState('');
  const [dispName, setDisp]   = useState('');
  const [busy, setBusy]       = useState(false);
  const [err, setErr]         = useState('');
  const toast = useToast();

  const validRoomId = /^![^:]+:.+$/.test(roomId.trim());

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

  const submit = async () => {
    if (!validRoomId) { setErr(T('Room ID must be in format !xxx:server')); return; }
    setBusy(true); setErr('');
    try {
      // Look up matrix_id of selected bot (backend register endpoint takes matrix_id, not numeric id)
      const selectedBot = botId ? bots.find(b => String(b.id) === String(botId)) : null;
      const r = await fetch('/api/rooms/register', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({
          room_id: roomId.trim(),
          room_type: roomType,
          assigned_bot_matrix_id: selectedBot ? selectedBot.matrix_id : null,
          name: dispName.trim() || undefined,
        }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      toast.push({ msg: `${T('Room registered:')} ${roomId}`, kind: 'ok' });
      onCreated && onCreated();
      onClose();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  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: 440, maxWidth:'95vw',
        boxShadow:'0 24px 64px -16px #00000080', zIndex: 301,
      }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 20 }}>
          <div style={{ fontWeight: 600, fontSize: 15 }}>{T('Register existing room')}</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' }}><Icon name="alert" size={13}/> {err}</div>}

        <div style={{ marginBottom: 14 }}>
          <label className="login-label">{T('K9 Room ID *')}</label>
          <input className="inp" value={roomId} onChange={e => setRoomId(e.target.value)}
            placeholder="!xxx:k9.ms" autoFocus
            style={{ borderColor: roomId && !validRoomId ? 'var(--err)' : undefined }} />
          {roomId && !validRoomId && (
            <div style={{ fontSize: 11, color:'var(--err)', marginTop: 3 }}>{T('Must match format !xxx:server')}</div>
          )}
        </div>
        <div style={{ marginBottom: 14 }}>
          <label className="login-label">{T('Display name (optional)')}</label>
          <input className="inp" value={dispName} onChange={e => setDisp(e.target.value)} placeholder={T("Human-readable name")} />
        </div>
        <div style={{ marginBottom: 14 }}>
          <label className="login-label">{T('Room type')}</label>
          <select className="sel" value={roomType} onChange={e => setRType(e.target.value)} style={{ width:'100%' }}>
            {roomTypes.map(t => <option key={t} value={t}>{t}</option>)}
          </select>
        </div>
        <div style={{ marginBottom: 20 }}>
          <label className="login-label">{T('Assigned bot (optional)')}</label>
          <select className="sel" value={botId} onChange={e => setBotId(e.target.value)} style={{ width:'100%' }}>
            <option value="">{T('No bot')}</option>
            {bots.filter(b => b.active).map(b => <option key={b.id} value={b.id}>{b.name} ({b.matrix_id})</option>)}
          </select>
        </div>

        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end' }}>
          <button className="btn btn-ghost" onClick={onClose}>{T('Cancel')}</button>
          <button className="btn btn-pri" onClick={submit} disabled={busy || !validRoomId}>
            {busy ? T('Registering...') : <><Icon name="check" size={13}/> {T('Register room')}</>}
          </button>
        </div>
      </div>
    </div>
  );
}

function EditRoomModal({ room, onClose, onSaved, roomTypes, bots }) {
  const [roomName, setRoomName] = useState(room.display_name || '');
  const [roomType, setRType] = useState(room.room_type || 'general');
  const [botId, setBotId]    = useState(room.bot_id ? String(room.bot_id) : '');
  // Multi-select for additional bots beyond the primary @ai.bot. Each room
  // already has the env-default AI bot present implicitly — these
  // secondaries are EXTRA helpers (e.g. DevOps Bot living in
  // Devops-Standup alongside @ai.bot).
  const initialSecondaryIds = React.useMemo(() => {
    if (Array.isArray(room.secondary_bots) && room.secondary_bots.length) {
      return new Set(room.secondary_bots.map(b => String(b.id)));
    }
    return new Set();
  }, [room]);
  const [secondaryIds, setSecondaryIds] = useState(initialSecondaryIds);
  const [busy, setBusy]      = useState(false);
  const [err, setErr]        = useState('');
  const toast = useToast();

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

  const toggleSecondary = (id) => {
    const next = new Set(secondaryIds);
    if (next.has(id)) next.delete(id); else next.add(id);
    setSecondaryIds(next);
  };

  const save = async () => {
    setBusy(true); setErr('');
    try {
      const r = await fetch(`/api/rooms/${room.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({
          display_name: roomName.trim() || room.display_name,
          room_type: roomType,
          bot_id: botId ? parseInt(botId) : null,
          secondary_bot_ids: Array.from(secondaryIds).map(x => parseInt(x)),
        }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      toast.push({ msg: T('Room updated'), kind: 'ok' });
      onSaved && onSaved();
      onClose();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  // Primary bot's matrix_id (env-default) gets a special "always present"
  // badge so admins know they don't need to add @ai.bot manually.
  const activeBots = bots.filter(b => b.active);

  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: 460, maxWidth:'95vw',
        maxHeight: '90vh', overflowY: 'auto',
        boxShadow:'0 24px 64px -16px #00000080', zIndex: 301,
      }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom: 20 }}>
          <div>
            <div style={{ fontWeight: 600, fontSize: 15 }}>{T('Edit room')}</div>
            <div className="mono muted" style={{ fontSize: 11 }}>{room.room_id}</div>
          </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' }}><Icon name="alert" size={13}/> {err}</div>}

        <div style={{ marginBottom: 14 }}>
          <label className="login-label">{T('Room name')}</label>
          <input className="inp" value={roomName} onChange={e => setRoomName(e.target.value)}
                 placeholder={room.display_name} style={{ width:'100%' }} />
          <div className="muted" style={{ fontSize: 11, marginTop: 6, lineHeight: 1.4 }}>
            {T('Renames the room everywhere — dashboard + the live chat room.')}
          </div>
        </div>
        <div style={{ marginBottom: 14 }}>
          <label className="login-label">{T('Room type')}</label>
          <select className="sel" value={roomType} onChange={e => setRType(e.target.value)} style={{ width:'100%' }}>
            {roomTypes.map(t => <option key={t} value={t}>{t}</option>)}
          </select>
        </div>
        <div style={{ marginBottom: 14 }}>
          <label className="login-label">{T('Primary bot persona (Q&A replies)')}</label>
          <select className="sel" value={botId} onChange={e => setBotId(e.target.value)} style={{ width:'100%' }}>
            <option value="">{T('— None (use default @ai.bot) —')}</option>
            {activeBots.map(b => <option key={b.id} value={String(b.id)}>{b.name} ({b.matrix_id})</option>)}
          </select>
          <div className="muted" style={{ fontSize: 11, marginTop: 6, lineHeight: 1.4 }}>
            {T('For standup rooms, leave this empty — the primary @ai.bot owns sign-in parsing & format checks. The picker is for Q&A rooms where you want a specific specialist to own replies.')}
          </div>
        </div>
        <div style={{ marginBottom: 20 }}>
          <label className="login-label">{T('Additional bots in this room')}</label>
          <div className="muted" style={{ fontSize: 11, marginBottom: 8, lineHeight: 1.4 }}>
            {T('The primary @ai.bot is always present. Add specialist bots here (e.g. DevOps Bot in Devops-Standup, Tech Bot in Tech-Room) — they join as additional members and can answer @-mentions in their domain without taking over the standup flow.')}
          </div>
          <div style={{
            display: 'flex', flexWrap: 'wrap', gap: 6,
            border: '1px solid var(--line)', borderRadius: 'var(--r-md)',
            padding: 10, maxHeight: 180, overflowY: 'auto',
          }}>
            {activeBots.length === 0 && (
              <div className="muted" style={{ fontSize: 12 }}>{T('No additional bots configured.')}</div>
            )}
            {activeBots.map(b => {
              const sid = String(b.id);
              const on  = secondaryIds.has(sid);
              const isPrimaryPick = botId === sid;
              return (
                <button
                  key={b.id}
                  type="button"
                  onClick={() => toggleSecondary(sid)}
                  disabled={isPrimaryPick}
                  title={isPrimaryPick ? T('Already selected as primary persona') : T('Click to add/remove from this room')}
                  className={'badge ' + (on ? 'badge-ok' : 'badge-muted')}
                  style={{
                    padding: '5px 10px', cursor: isPrimaryPick ? 'not-allowed' : 'pointer',
                    opacity: isPrimaryPick ? 0.5 : 1, fontSize: 12,
                  }}
                >
                  {on ? <Icon name="check" size={11}/> : <Icon name="plus" size={11}/>}
                  {' '}{b.name}
                </button>
              );
            })}
          </div>
        </div>

        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end' }}>
          <button className="btn btn-ghost" onClick={onClose}>{T('Cancel')}</button>
          <button className="btn btn-pri" onClick={save} disabled={busy}>
            {busy ? T('Saving...') : <><Icon name="check" size={13}/> {T('Save')}</>}
          </button>
        </div>
      </div>
    </div>
  );
}

/* ── Room Members Drawer ── */
/* ── Per-(room, user) attendance opt-out drawer ── */
function RoomAttendanceMutesDrawer({ room, onClose }) {
  const [data, setData]     = useState(null);     // { employees:[…], muted_count }
  const [loading, setLoad]  = useState(true);
  const [busy, setBusy]     = useState({});       // user_id → bool
  const [q, setQ]           = useState('');
  const toast = useToast();

  const load = React.useCallback(() => {
    if (!room) return;
    setLoad(true);
    fetch(`/api/rooms/${room.id}/attendance-mutes`, { credentials:'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => setData(d))
      .catch(() => setData({ employees: [], muted_count: 0 }))
      .finally(() => setLoad(false));
  }, [room]);

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

  const toggle = async (uid, currentlyMuted) => {
    setBusy(b => ({ ...b, [uid]: true }));
    try {
      const r = await fetch(`/api/rooms/${room.id}/attendance-mutes`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ user_id: uid, muted: !currentlyMuted }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      // Update local state without refetch.
      setData(d => ({
        ...d,
        employees: (d.employees || []).map(e =>
          e.user_id === uid ? { ...e, muted: !currentlyMuted } : e),
        muted_count: (d.muted_count || 0) + (!currentlyMuted ? 1 : -1),
      }));
    } catch(e) {
      toast.push({ msg: `${T('Save failed:')} ${e.message}`, kind: 'err' });
    } finally {
      setBusy(b => { const n = { ...b }; delete n[uid]; return n; });
    }
  };

  const filtered = (data?.employees || []).filter(e => {
    const s = q.trim().toLowerCase();
    if (!s) return true;
    return (e.display_name || '').toLowerCase().includes(s)
        || (e.matrix_id || '').toLowerCase().includes(s);
  });

  return (
    <div className="drawer-overlay" onClick={onClose}>
      <aside className="drawer" onClick={e => e.stopPropagation()} style={{ width: 420 }}>
        <div className="drawer-head">
          <div>
            <div style={{ fontSize: 15, fontWeight: 600 }}>{T('Attendance tracking')}</div>
            <div className="muted" style={{ fontSize: 12 }}>
              {room.display_name || room.room_id}
            </div>
            <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>
              {data
                ? (() => {
                    const trackable = (data.employees || []).filter(e => e.can_toggle !== false).length;
                    return `${data.muted_count} ${T('of')} ${trackable} ${trackable !== 1 ? T('trackable members') : T('trackable member')} ${T('muted here')}`;
                  })()
                : T('Loading…')}
            </div>
          </div>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        <div style={{ padding: '10px 16px', borderBottom: '1px solid var(--line)' }}>
          <div className="muted" style={{ fontSize: 11, marginBottom: 8, lineHeight: 1.5 }}>
            <strong>{T('Off')}</strong> {T('= sign-ins/sign-offs here will NOT be recorded for that user. Use for people who are in the room for visibility but report to a manager elsewhere.')}
          </div>
          <input className="inp" value={q} onChange={e => setQ(e.target.value)}
            placeholder={T("Search by name or matrix ID…")} />
        </div>
        <div className="drawer-body" style={{ padding: 0 }}>
          {loading ? (
            <div style={{ padding: 20 }}><Skel h={48} /><Skel h={48} /><Skel h={48} /></div>
          ) : filtered.length === 0 ? (
            <div style={{ padding: 20 }}>
              <Empty icon={<Icon name="users" size={22}/>} title={T("No members match")} subtitle="" />
            </div>
          ) : (
            <div>
              {filtered.map(e => {
                const rowKey = e.matrix_id || e.user_id;
                const isMuted = !!e.muted;
                const canToggle = e.can_toggle !== false; // default true if missing
                const isBusy = e.user_id ? !!busy[e.user_id] : false;
                // Reason for not being toggleable (superadmin / bot / guest)
                let reasonChip = null;
                if (e.is_bot) reasonChip = { label: T('Bot — not tracked'), kind: 'brand' };
                else if (e.is_superadmin) reasonChip = { label: T('Superadmin — never tracked'), kind: 'mute' };
                else if (!e.is_employee) reasonChip = { label: T('Not an employee'), kind: 'mute' };
                return (
                  <div key={rowKey} style={{
                    display:'flex', alignItems:'center', gap: 10,
                    padding:'10px 16px', borderBottom: '1px solid var(--line)',
                    opacity: isBusy ? 0.5 : 1,
                  }}>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 500, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{e.display_name || e.matrix_id}</div>
                      <div className="mono muted" style={{ fontSize: 10, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{e.matrix_id}</div>
                    </div>
                    {canToggle ? (
                      <button
                        className="btn"
                        onClick={() => toggle(e.user_id, isMuted)}
                        disabled={isBusy}
                        title={isMuted ? T('Currently NOT tracked here — click to start tracking') : T('Currently tracked — click to stop tracking sign-ins for this user in this room')}
                        style={{
                          fontSize: 11.5, padding: '4px 10px', borderRadius: 999,
                          border: `1px solid ${isMuted ? 'var(--err)' : 'var(--ok)'}`,
                          background: isMuted ? 'color-mix(in oklch, var(--err) 12%, var(--bg))' : 'color-mix(in oklch, var(--ok) 12%, var(--bg))',
                          color: isMuted ? 'var(--err)' : 'var(--ok)',
                          flexShrink: 0, minWidth: 90, justifyContent: 'center',
                        }}>
                        {isMuted ? T('Off') : T('On')}
                      </button>
                    ) : reasonChip ? (
                      <Badge kind={reasonChip.kind} style={{ flexShrink: 0, fontSize: 10.5 }}>
                        {reasonChip.label}
                      </Badge>
                    ) : null}
                  </div>
                );
              })}
            </div>
          )}
        </div>
      </aside>
    </div>
  );
}

/* ── Room LIVE presence drawer ───────────────────────────────────────
 *  "Who's active in this room right now." Cross-references the room's
 *  members with /api/presence/live (Synapse-verified, idle-downgraded)
 *  and refreshes every 15s. Bots are excluded.
 * ────────────────────────────────────────────────────────────────── */
function RoomLiveDrawer({ room, onClose }) {
  const [members, setMembers] = useState([]);
  const [presence, setPresence] = useState({});
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (!room) return;
    fetch(`/api/rooms/${room.id}/members`, { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : [])
      .then(d => { setMembers(Array.isArray(d) ? d : []); setLoading(false); })
      .catch(() => setLoading(false));
  }, [room?.id]);

  useEffect(() => {
    let alive = true;
    const pull = () => fetch('/api/presence/live', { credentials: 'include' })
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (alive && d && Array.isArray(d.users)) {
        const m = {}; d.users.forEach(u => { if (u.matrix_id) m[u.matrix_id] = u; }); setPresence(m);
      }})
      .catch(() => {});
    pull();
    const id = setInterval(pull, 15000);
    return () => { alive = false; clearInterval(id); };
  }, []);

  const isBot = (mx) => /bot[:.]/i.test(mx || '');
  const statusOf = (mx) => {
    const p = presence[mx];
    if (!p || !p.presence) return 'offline';
    if (p.presence === 'online') return 'online';
    if (p.presence === 'offline') return 'offline';
    return 'away';   // unavailable / away / idle
  };
  const dot   = { online:'#22c55e', away:'#f59e0b', offline:'#6b7280' };
  const label = { online:'Active now', away:'Idle', offline:'Offline' };
  const agoTxt = (ms) => {
    if (ms == null) return '';
    const m = Math.round(ms/60000);
    if (m < 1) return T('just now');
    if (m < 60) return `${m}m ${T('ago')}`;
    return `${Math.round(m/60)}h ${T('ago')}`;
  };
  const rank = { online:0, away:1, offline:2 };
  const rows = members
    .filter(m => m.matrix_id && !isBot(m.matrix_id) && !m.is_bot)
    .map(m => ({ ...m, st: statusOf(m.matrix_id), pres: presence[m.matrix_id] }))
    .sort((a,b) => rank[a.st] - rank[b.st] || (a.display_name||'').localeCompare(b.display_name||''));
  const activeN = rows.filter(r => r.st === 'online').length;

  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: 22, width: 440, maxWidth:'95vw',
        maxHeight:'90vh', overflowY:'auto', boxShadow:'0 24px 64px -16px #00000080', zIndex: 301,
      }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom: 14 }}>
          <div>
            <div style={{ fontWeight: 600, fontSize: 16, display:'flex', alignItems:'center', gap: 8 }}>
              <span style={{ width:9, height:9, borderRadius:'50%', background:'#22c55e', boxShadow:'0 0 0 3px #22c55e33' }}/>
              {T('Live presence')}
            </div>
            <div className="muted" style={{ fontSize: 11, marginTop: 3 }}>
              {room.display_name} · <strong style={{ color:'var(--text-1)' }}>{activeN}</strong> {T('active now')} · {T('refreshes every 15s')}
            </div>
          </div>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        {loading ? (
          <Loading label={T('Loading…')} rows={3} />
        ) : rows.length === 0 ? (
          <div className="muted" style={{ padding: 22, textAlign:'center' }}>{T('No members to show.')}</div>
        ) : (
          <div style={{ display:'flex', flexDirection:'column', gap: 6 }}>
            {rows.map(r => (
              <div key={r.matrix_id} style={{ display:'flex', alignItems:'center', gap: 10, padding:'9px 11px',
                background:'var(--bg-elev-2)', border:'1px solid var(--line)', borderRadius:'var(--r-md)' }}>
                <span style={{ width:9, height:9, borderRadius:'50%', background: dot[r.st], flexShrink:0,
                  boxShadow: r.st==='online' ? `0 0 0 3px ${dot[r.st]}33` : 'none' }}/>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 500, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>
                    {r.display_name || r.matrix_id}
                  </div>
                  <div className="mono muted" style={{ fontSize: 10.5 }}>{r.matrix_id}</div>
                </div>
                <div style={{ textAlign:'right', flexShrink:0 }}>
                  <div style={{ fontSize: 12, fontWeight: 600, color: dot[r.st] }}>{T(label[r.st])}</div>
                  {r.pres && r.pres.last_active_ago_ms != null && r.st !== 'online' && (
                    <div className="muted" style={{ fontSize: 10 }}>{agoTxt(r.pres.last_active_ago_ms)}</div>
                  )}
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

function RoomMembersDrawer({ room, onClose }) {
  const [members, setMembers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [kickBusy, setKickBusy] = useState(null);
  const [powerBusy, setPowerBusy] = useState(null);
  const [addOpen, setAddOpen]   = useState(false);
  const [allEmps, setAllEmps]   = useState([]);
  const [addQ, setAddQ]         = useState('');
  const [addBusy, setAddBusy]   = useState(null);
  const toast = useToast();

  const loadMembers = React.useCallback(() => {
    if (!room) return;
    setLoading(true);
    fetch(`/api/rooms/${room.id}/members`, { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : [])
      .then(data => { setMembers(data); setLoading(false); })
      .catch(() => setLoading(false));
  }, [room?.id]);

  useEffect(() => { loadMembers(); }, [loadMembers]);

  // Load the full employee directory once, for the "Add member" picker.
  useEffect(() => {
    fetch('/api/employees', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : [])
      .then(d => setAllEmps(Array.isArray(d) ? d : []))
      .catch(() => {});
  }, []);

  const handleAdd = async (mxid, name) => {
    setAddBusy(mxid);
    try {
      const r = await fetch(`/api/rooms/${room.id}/invite`, {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ matrix_id: mxid }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      toast.push({ msg: `${T('Invited')} ${name} ${T('to')} ${room.display_name}`, kind: 'ok' });
      setTimeout(loadMembers, 800);  // give Synapse a beat, then refresh
    } catch(e) {
      toast.push({ msg: `${T('Invite failed:')} ${e.message}`, kind: 'err' });
    } finally {
      setAddBusy(null);
    }
  };

  const handleKick = async (matrix_id, display_name) => {
    if (!confirm(`${T('Kick')} ${display_name} ${T('from')} ${room.display_name || room.room_id}?`)) return;
    setKickBusy(matrix_id);
    try {
      const r = await fetch(`/api/rooms/${room.id}/kick`, {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ matrix_id }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(body?.detail || `${r.status}`);
      }
      toast.push({ msg: `${T('Kicked')} ${display_name}`, kind: 'ok' });
      setMembers(prev => prev.filter(m => m.matrix_id !== matrix_id));
    } catch(e) {
      toast.push({ msg: `${T('Kick failed:')} ${e.message}`, kind: 'err' });
    } finally {
      setKickBusy(null);
    }
  };

  // Per-room role (Matrix power level): 100 admin / 50 moderator / 0 member.
  const handleSetPower = async (matrix_id, level, display_name) => {
    setPowerBusy(matrix_id);
    try {
      const r = await fetch(`/api/rooms/${room.id}/set-power`, {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ matrix_id, level }),
      });
      if (!r.ok) { const b = await r.json().catch(() => null); throw new Error(parseDetail(b, r.status)); }
      const lbl = level >= 100 ? T('Admin') : level >= 50 ? T('Moderator') : T('Member');
      toast.push({ msg: `${display_name} → ${lbl}`, kind: 'ok' });
      setMembers(prev => prev.map(m => m.matrix_id === matrix_id ? { ...m, power_level: level } : m));
    } catch(e) {
      toast.push({ msg: `${T('Role change failed:')} ${e.message}`, kind: 'err' });
    } finally {
      setPowerBusy(null);
    }
  };

  // Bot / Guest first. Otherwise label by the user's org_role so Hany is
  // shown as "Superadmin" instead of falling back to "Employee".
  const _ROLE_META = {
    superadmin: { label: T('Superadmin'), kind: 'err'   },
    admin:      { label: T('Admin'),      kind: 'warn'  },
    moderator:  { label: T('Moderator'),  kind: 'info'  },
    manager:    { label: T('Manager'),    kind: 'info'  },
    team_lead:  { label: T('Team lead'),  kind: 'info'  },
    employee:   { label: T('Employee'),   kind: 'ok'    },
  };
  const chipKind = (m) => {
    if (m.is_bot) return 'brand';
    if (!m.is_employee) return 'mute';
    const meta = _ROLE_META[(m.org_role || '').toLowerCase()];
    return meta ? meta.kind : 'ok';
  };
  const chipLabel = (m) => {
    if (m.is_bot) return T('Bot');
    if (!m.is_employee) return T('Guest');
    const meta = _ROLE_META[(m.org_role || '').toLowerCase()];
    return meta ? meta.label : T('Employee');
  };

  return (
    <div className="drawer-overlay" onClick={onClose}>
      <aside className="drawer" onClick={e => e.stopPropagation()} style={{ width: 380 }}>
        <div className="drawer-head">
          <div>
            <div style={{ fontSize: 15, fontWeight: 600 }}>{room.display_name || room.room_id}</div>
            <div className="muted" style={{ fontSize: 12 }}>{loading ? T('Loading...') : `${members.length} ${members.length !== 1 ? T('members') : T('member')}`}</div>
          </div>
          <div style={{ display:'flex', gap: 6, alignItems:'center' }}>
            <button className="btn btn-pri" style={{ fontSize: 12, padding:'4px 10px' }}
              onClick={() => setAddOpen(v => !v)}>
              <Icon name="plus" size={12}/> {T('Add member')}
            </button>
            <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
          </div>
        </div>
        {/* Add-member picker — employees not already in the room. Invites
            them to the Matrix room via the bot (RBAC-gated server-side). */}
        {addOpen && (() => {
          const present = new Set(members.map(m => (m.matrix_id || '').toLowerCase()));
          const candidates = (allEmps || []).filter(e => {
            if (!e.matrix_id || present.has(e.matrix_id.toLowerCase())) return false;
            if (e.is_test_account) return false;
            const q = addQ.trim().toLowerCase();
            if (!q) return true;
            return (e.display_name || '').toLowerCase().includes(q)
                || (e.matrix_id || '').toLowerCase().includes(q);
          });
          return (
            <div style={{ padding:'10px 16px', borderBottom:'1px solid var(--line)', background:'var(--bg-elev-1)' }}>
              <input className="inp" value={addQ} onChange={e => setAddQ(e.target.value)}
                placeholder={T("Search employees to add…")} style={{ marginBottom: 8 }} />
              <div style={{ maxHeight: 220, overflowY:'auto', display:'flex', flexDirection:'column', gap: 4 }}>
                {candidates.length === 0 && (
                  <div className="muted" style={{ fontSize: 12, padding: 8 }}>
                    {(allEmps||[]).length === 0 ? T('Loading directory…') : T('Everyone is already in this room.')}
                  </div>
                )}
                {candidates.slice(0, 40).map(e => (
                  <div key={e.id} style={{ display:'flex', alignItems:'center', gap: 8, padding:'6px 8px',
                       border:'1px solid var(--line)', borderRadius:'var(--r-sm)' }}>
                    <div style={{ flex:1, minWidth:0 }}>
                      <div style={{ fontSize: 12.5, fontWeight: 500, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{e.display_name}</div>
                      <div className="mono muted" style={{ fontSize: 10 }}>{e.matrix_id}{e.team ? ` · ${e.team}` : ''}</div>
                    </div>
                    <button className="btn btn-ghost" style={{ fontSize: 11, padding:'2px 10px', color:'var(--ok)' }}
                      disabled={addBusy === e.matrix_id}
                      onClick={() => handleAdd(e.matrix_id, e.display_name)}>
                      {addBusy === e.matrix_id ? '…' : T('Add')}
                    </button>
                  </div>
                ))}
              </div>
            </div>
          );
        })()}
        <div className="drawer-body" style={{ padding: 0 }}>
          {loading ? (
            <div style={{ padding: 20 }}><Skel h={48} /><Skel h={48} /><Skel h={48} /></div>
          ) : members.length === 0 ? (
            <div style={{ padding: 20 }}><Empty icon={<Icon name="users" size={22}/>} title={T("No members found")} subtitle={T("Room may be empty or inaccessible.")} /></div>
          ) : (
            <div>
              {members.map(m => (
                <div key={m.matrix_id} style={{ display:'flex', alignItems:'center', gap: 10, padding:'10px 16px', borderBottom:'1px solid var(--line)' }}>
                  {m.avatar_url ? (
                    <img src={m.avatar_url} alt="" style={{ width: 32, height: 32, borderRadius: '50%', objectFit:'cover', flexShrink: 0 }} />
                  ) : (
                    <div style={{ width: 32, height: 32, borderRadius:'50%', background:'var(--bg-elev-2)', display:'flex', alignItems:'center', justifyContent:'center', fontSize: 13, fontWeight: 600, flexShrink: 0, color:'var(--text-2)' }}>
                      {(m.display_name || '?').slice(0,1).toUpperCase()}
                    </div>
                  )}
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 500, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{m.display_name}</div>
                    <div className="mono muted" style={{ fontSize: 10, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{m.matrix_id}</div>
                  </div>
                  <Badge kind={chipKind(m)} style={{ flexShrink: 0 }}>{chipLabel(m)}</Badge>
                  {!m.is_bot && (
                    <select
                      className="inp"
                      style={{ fontSize: 11, height: 26, padding: '0 4px', flexShrink: 0, maxWidth: 96 }}
                      value={(m.power_level >= 100 ? 100 : m.power_level >= 50 ? 50 : 0)}
                      disabled={powerBusy === m.matrix_id}
                      title={T('Room role (power level)')}
                      onChange={e => handleSetPower(m.matrix_id, parseInt(e.target.value, 10), m.display_name)}
                    >
                      <option value={0}>{T('Member')}</option>
                      <option value={50}>{T('Moderator')}</option>
                      <option value={100}>{T('Admin')}</option>
                    </select>
                  )}
                  {!m.is_bot && (
                    <button
                      className="btn btn-ghost"
                      style={{ fontSize: 11, padding:'2px 8px', color:'var(--err)', flexShrink: 0 }}
                      onClick={() => handleKick(m.matrix_id, m.display_name)}
                      disabled={kickBusy === m.matrix_id}
                      title={T("Kick from room")}
                    >
                      {kickBusy === m.matrix_id ? '...' : T('Kick')}
                    </button>
                  )}
                </div>
              ))}
            </div>
          )}
        </div>
      </aside>
    </div>
  );
}

/* ── Delete Room Confirm Modal ── */
function DeleteRoomModal({ room, onClose, onDeleted }) {
  const [busy, setBusy] = useState(false);
  const [err, setErr]   = useState('');
  const toast = useToast();

  const confirm = async () => {
    setBusy(true); setErr('');
    try {
      const r = await fetch(`/api/rooms/${room.id}`, {
        method: 'DELETE', credentials: 'same-origin',
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      const data = await r.json().catch(() => ({}));
      toast.push({ msg: T(`Room removed from dashboard`), kind: 'ok' });
      if (data.note) toast.push({ msg: data.note, kind: 'warn' });
      onClose();
      onDeleted();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" style={{ maxWidth: 420 }} onClick={e => e.stopPropagation()}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 16 }}>
          <span style={{ fontWeight: 600, fontSize: 15 }}>{T('Delete room')}</span>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        <p style={{ fontSize: 13, marginBottom: 8 }}>
          {T('Permanently remove')} <strong>{room.display_name || room.room_id}</strong> {T('from the dashboard?')}
        </p>
        <p style={{ fontSize: 12, color:'var(--muted)', marginBottom: 16 }}>
          {T('Note: The bot will still be in the K9 room. Leave manually in Element/Synapse if desired.')}
        </p>
        {err && <div className="badge badge-err" style={{ marginBottom: 12, padding:'6px 10px', fontSize: 12 }}><Icon name="alert" size={12}/> {err}</div>}
        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end' }}>
          <button className="btn btn-ghost" onClick={onClose} disabled={busy}>{T('Cancel')}</button>
          <button
            className="btn btn-pri"
            style={{ background:'var(--err)', borderColor:'var(--err)' }}
            onClick={confirm}
            disabled={busy}
          >
            {busy ? T('Deleting...') : T('Delete permanently')}
          </button>
        </div>
      </div>
    </div>
  );
}

/* ── Team Members Drawer — add/remove users from a team ── */
function TeamMembersDrawer({ team, employees, onClose, onSaved }) {
  const teamName = team.team_name || '';
  // Original membership = employees whose .team matches this team.
  const original = React.useMemo(
    () => new Set((employees || []).filter(e => (e.team || '') === teamName).map(e => e.id)),
    [employees, teamName],
  );
  const [sel, setSel]   = useState(() => new Set(original));
  const [q, setQ]       = useState('');
  const [busy, setBusy] = 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 toggle = (id) => setSel(prev => {
    const n = new Set(prev);
    n.has(id) ? n.delete(id) : n.add(id);
    return n;
  });

  const filtered = (employees || []).filter(e => {
    const s = q.trim().toLowerCase();
    if (!s) return true;
    return (e.display_name || '').toLowerCase().includes(s)
        || (e.matrix_id || '').toLowerCase().includes(s);
  });

  // Diff selection vs original → added / removed.
  const added   = [...sel].filter(id => !original.has(id));
  const removed = [...original].filter(id => !sel.has(id));
  const changes = added.length + removed.length;

  const save = async () => {
    setBusy(true);
    let ok = 0, fail = 0;
    const apply = async (id, newTeam) => {
      try {
        const r = await fetch(`/api/employees/${id}/profile`, {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          credentials: 'same-origin',
          body: JSON.stringify({ team: newTeam }),
        });
        if (r.ok) ok++; else fail++;
      } catch(_) { fail++; }
    };
    for (const id of added)   await apply(id, teamName);
    for (const id of removed) await apply(id, null);
    setBusy(false);
    toast.push({
      msg: `${T('Members updated —')} ${ok} ${ok !== 1 ? T('changes') : T('change')}${fail ? `, ${fail} ${T('failed')}` : ''}`,
      kind: fail ? 'warn' : 'ok',
    });
    onSaved && onSaved();
    onClose();
  };

  return (
    <div className="drawer-overlay" onClick={onClose}>
      <aside className="drawer" onClick={e => e.stopPropagation()} style={{ width: 400 }}>
        <div className="drawer-head">
          <div>
            <div style={{ fontSize: 15, fontWeight: 600 }}>{T('Manage members')}</div>
            <div className="muted" style={{ fontSize: 12 }}>
              {teamName || T('(default)')} · {sel.size} {sel.size !== 1 ? T('members') : T('member')}
            </div>
          </div>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--line)' }}>
          <input className="inp" value={q} onChange={e => setQ(e.target.value)}
            placeholder={T("Search by name or matrix ID…")} />
        </div>
        <div className="drawer-body" style={{ padding: 0 }}>
          {filtered.length === 0 ? (
            <div style={{ padding: 20 }}>
              <Empty icon={<Icon name="users" size={22}/>} title={T("No employees match")} subtitle="" />
            </div>
          ) : (
            <div>
              {filtered.map(e => {
                const checked = sel.has(e.id);
                const otherTeam = e.team && e.team !== teamName;
                return (
                  <label key={e.id} style={{
                    display:'flex', alignItems:'center', gap: 10, padding:'10px 16px',
                    borderBottom:'1px solid var(--line)', cursor:'pointer',
                    background: checked ? 'var(--brand-soft)' : 'transparent',
                  }}>
                    <input type="checkbox" checked={checked} onChange={() => toggle(e.id)} />
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 500, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{e.display_name || e.matrix_id}</div>
                      <div className="mono muted" style={{ fontSize: 10, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{e.matrix_id}</div>
                    </div>
                    {otherTeam && <Badge kind="mute" style={{ flexShrink: 0, fontSize: 10 }}>{e.team}</Badge>}
                  </label>
                );
              })}
            </div>
          )}
        </div>
        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end', padding: '12px 16px', borderTop: '1px solid var(--line)' }}>
          <span className="muted" style={{ fontSize: 12, marginRight: 'auto', alignSelf:'center' }}>
            {changes ? `${added.length} ${T('to add')} · ${removed.length} ${T('to remove')}` : T('No changes')}
          </span>
          <button className="btn btn-ghost" onClick={onClose} disabled={busy}>{T('Cancel')}</button>
          <button className="btn btn-pri" onClick={save} disabled={busy || !changes}>
            {busy ? T('Saving…') : <><Icon name="check" size={13}/> {T('Save')}</>}
          </button>
        </div>
      </aside>
    </div>
  );
}

/* ── Delete Team Confirm Modal ── */
function DeleteTeamModal({ team, onClose, onDeleted }) {
  const [busy, setBusy] = useState(false);
  const [err, setErr]   = useState('');
  const toast = useToast();

  const memberCount = team.members ?? team.member_count ?? 0;

  const confirm = async () => {
    setBusy(true); setErr('');
    try {
      const r = await fetch(`/api/teams/${encodeURIComponent(team.team_name)}`, {
        method: 'DELETE', credentials: 'same-origin',
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      const data = await r.json().catch(() => ({}));
      const unassigned = data.members_unassigned ?? 0;
      toast.push({ msg: `${T('Team')} "${team.team_name}" ${T('deleted')}${unassigned ? ` — ${unassigned} ${T('member(s) unassigned')}` : ''}`, kind: 'ok' });
      onClose();
      onDeleted();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" style={{ maxWidth: 420 }} onClick={e => e.stopPropagation()}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: 16 }}>
          <span style={{ fontWeight: 600, fontSize: 15 }}>{T('Delete team')}</span>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>
        <p style={{ fontSize: 13, marginBottom: 8 }}>
          {T('Permanently delete team')} <strong>{team.team_name}</strong>? {T('This cannot be undone.')}
        </p>
        {memberCount > 0 && (
          <p style={{ fontSize: 12, color:'var(--warn)', marginBottom: 16 }}>
            {memberCount} {T('member(s) will become unassigned from this team.')}
          </p>
        )}
        {err && <div className="badge badge-err" style={{ marginBottom: 12, padding:'6px 10px', fontSize: 12 }}><Icon name="alert" size={12}/> {err}</div>}
        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end' }}>
          <button className="btn btn-ghost" onClick={onClose} disabled={busy}>{T('Cancel')}</button>
          <button
            className="btn btn-pri"
            style={{ background:'var(--err)', borderColor:'var(--err)' }}
            onClick={confirm}
            disabled={busy}
          >
            {busy ? T('Deleting...') : T('Delete permanently')}
          </button>
        </div>
      </div>
    </div>
  );
}

function RoomsPage() {
  const [rooms, setRooms]         = useState([]);
  const [bots, setBots]           = useState([]);
  const [employees, setEmployees] = useState([]);
  const [roomTypes, setRoomTypes] = useState([]);
  const [loading, setLoading]     = useState(true);
  const [createOpen, setCreateOpen]   = useState(false);
  const [createInitialType, setCreateInitialType] = useState('general');
  const [registerOpen, setRegOpen]    = useState(false);
  const [editRoom, setEditRoom]       = useState(null);
  const [deleteRoom, setDeleteRoom]   = useState(null);
  const [membersRoom, setMembersRoom] = useState(null);
  const [trackingRoom, setTrackingRoom] = useState(null);
  const [botsRoom, setBotsRoom]       = useState(null);
  const [settingsRoom, setSettingsRoom] = useState(null);  // unified Settings panel
  const [liveRoom, setLiveRoom]       = useState(null);    // live-presence panel
  const toast = useToast();

  const load = async () => {
    setLoading(true);
    try {
      const [rr, br, er, tr] = await Promise.all([
        fetch('/api/rooms',        { credentials:'same-origin' }),
        fetch('/api/bots',         { credentials:'same-origin' }),
        fetch('/api/employees',    { credentials:'same-origin' }),
        fetch('/api/rooms/types',  { credentials:'same-origin' }),
      ]);
      if (rr.ok) setRooms(await rr.json());
      if (br.ok) setBots(await br.json());
      if (er.ok) setEmployees(await er.json());
      if (tr.ok) setRoomTypes(await tr.json());
    } catch(e) {
      toast.push({ msg: T('Load failed: ') + e.message, kind: 'err' });
    } finally {
      setLoading(false);
    }
  };

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

  // "Create admin room" is now just a shortcut into the unified Create
  // modal pre-set to room_type=admin. Same fields (name, description,
  // invitees, primary + secondary bots) for every type — keeps the UX
  // consistent and lets admins customise per-team admin rooms instead of
  // a single hardcoded one.
  const openCreateAdmin = () => {
    setCreateInitialType('admin');
    setCreateOpen(true);
  };

  const [supportBusy, setSupportBusy] = useState(false);
  const connectSupportRoom = async () => {
    // Wired to a single canonical source: #VPSie-Support:k9.ms. The bot
    // joins, arms ingestion (history skipped), and turns every new
    // top-level human message into a ticket auto-assigned via the on-call
    // calendar (role+chain_position; prefers whoever is currently online).
    if (!window.confirm(T('Connect the bot to #VPSie-Support:k9.ms? New messages there will become tickets, auto-assigned via the on-call calendar.'))) return;
    setSupportBusy(true);
    try {
      const r = await fetch('/api/support/room/join', {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ alias: '#VPSie-Support:k9.ms' }),
      });
      const d = await r.json();
      if (!r.ok || d.ok === false) throw new Error(d.detail || d.error || r.status);
      toast.push({ msg: T(`Connected to #VPSie-Support — ingestion armed`), kind: 'ok' });
      await load();
    } catch (e) {
      toast.push({ msg: T('Connect support room failed: ') + e.message, kind: 'err' });
    } finally { setSupportBusy(false); }
  };

  const openRoom = (r) => {
    const url = r.room_id
      ? `https://chat.k9.ms/#/room/${encodeURIComponent(r.room_id)}`
      : 'https://chat.k9.ms';
    window.open(url, '_blank', 'noopener');
  };

  const assignedBotName = (botId) => bots.find(b => b.id === botId)?.name || null;

  const ROOM_TYPE_KIND = {
    standup:'brand', dev:'ok', technical:'ok', office:'info', security:'err',
    hr:'warn', finance:'warn', general:'mute', pto:'info', urgent:'err',
  };

  return (
    <div className="page">
      <PageHeader
        title={T("Rooms")}
        meta={`${rooms.filter(r=>r.active).length} ${T('active')} · ${rooms.length} ${T('total')}`}
        right={
          <div style={{ display:'flex', gap: 8 }}>
            <Refresh onClick={load} loading={loading} />
            <button className="btn btn-ghost" onClick={openCreateAdmin} title={T("Open the create-room modal pre-set to type=admin (description + members + bots)")}>
              <Icon name="building" size={14}/> {T('Create admin room')}
            </button>
            <button className="btn btn-ghost" onClick={connectSupportRoom} disabled={supportBusy} title={T("Connect to #VPSie-Support:k9.ms — ingest tickets and auto-assign via the on-call calendar")}>
              <Icon name="alert" size={14}/> {supportBusy ? T('Connecting…') : T('Connect VPSie support')}
            </button>
            <button className="btn btn-ghost" onClick={() => setRegOpen(true)}><Icon name="plus" size={14}/> {T('Register existing')}</button>
            <button className="btn btn-pri"   onClick={() => { setCreateInitialType('general'); setCreateOpen(true); }}><Icon name="plus" size={14}/> {T('Create new room')}</button>
          </div>
        }
      />

      {loading && rooms.length === 0 ? (
        <div style={{ display:'flex', flexDirection:'column', gap: 10 }}>
          <Skel h={72} /><Skel h={72} /><Skel h={72} />
        </div>
      ) : rooms.length === 0 ? (
        <Empty icon={<Icon name="door" size={24}/>} title={T("No rooms yet")} subtitle={T("Create or register a K9 room to get started.")} />
      ) : (
        <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(300px, 1fr))', gap: 14 }}>
          {[...rooms].sort((a,b) => (a.room_type||'').localeCompare(b.room_type||'') || (a.display_name||'').localeCompare(b.display_name||'')).map(r => {
            const typeKind = ROOM_TYPE_KIND[r.room_type] || 'mute';
            const inactive = !r.active;
            return (
            <div key={r.id} className="card" style={{
              padding: 14,
              opacity: inactive ? 0.5 : 1,
              borderTop: `3px solid var(--${typeKind === 'mute' ? 'line' : typeKind})`,
              transition: 'transform .12s ease, border-color .12s ease',
              cursor: 'pointer',
            }}
              onClick={() => setMembersRoom(r)}
              onMouseEnter={e => e.currentTarget.style.transform = 'translateY(-1px)'}
              onMouseLeave={e => e.currentTarget.style.transform = 'translateY(0)'}>
              <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap: 10, marginBottom: 10 }}>
                <div style={{ minWidth: 0, flex: 1 }}>
                  <div style={{ fontSize: 14, fontWeight: 600, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{r.display_name || T('(unnamed)')}</div>
                  <div className="mono" style={{ fontSize: 10, marginTop: 3, color:'var(--text-3)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{r.room_id}</div>
                </div>
                <Badge kind={typeKind} style={{ flexShrink: 0, textTransform: 'capitalize' }}>{r.room_type || 'general'}</Badge>
              </div>

              <div style={{ display:'flex', flexWrap:'wrap', gap: 6, marginBottom: 12, minHeight: 22 }}>
                {r.bot_id && assignedBotName(r.bot_id) ? (
                  <Badge kind="info"><Icon name="ai" size={10}/> {assignedBotName(r.bot_id)}</Badge>
                ) : (
                  <span className="muted" style={{ fontSize: 11, fontStyle:'italic' }}>{T('No bot assigned')}</span>
                )}
                {r.team_name && <Badge kind="mute"><Icon name="users" size={10}/> {r.team_name}</Badge>}
                {inactive && <Badge kind="err">{T('inactive')}</Badge>}
              </div>

              {/* Action row — all 4 buttons share equal width inside the
                  card. overflow:hidden + minWidth:0 keeps the trash icon
                  from spilling past the card border on narrow grids.
                  The 3 text buttons use flex:1; the trash button uses
                  flex:0 with a fixed width so it lines up cleanly. */}
              <div style={{
                display:'flex', gap: 6, paddingTop: 10,
                borderTop: '1px solid var(--line)',
                overflow:'hidden',
              }} onClick={e => e.stopPropagation()}>
                {/* Single consolidated entry point — one Settings panel per
                    room with tabs for General / Bots / Members / Tracking.
                    Replaces the old scattered Members/Tracking/Bots/Edit
                    buttons. The +N badge shows additional bots at a glance. */}
                <button className="btn btn-ghost" onClick={() => setSettingsRoom(r)}
                  title={T("Room settings — general, bots, members, attendance tracking")}
                  style={{ fontSize: 12, flex:'1 1 0', minWidth: 0, justifyContent:'center', padding:'4px 6px',
                           display:'flex', alignItems:'center', gap: 4 }}>
                  <Icon name="cog" size={12}/> {T('Settings')}
                  {(r.secondary_bots && r.secondary_bots.length > 0) && (
                    <span className="badge badge-ok" style={{
                      padding:'1px 6px', fontSize: 10, lineHeight: 1.2,
                    }}>+{r.secondary_bots.length} {T('bot')}</span>
                  )}
                </button>
                <button className="btn btn-ghost" onClick={() => openRoom(r)}
                  style={{ fontSize: 12, flex:'1 1 0', minWidth: 0, justifyContent:'center', padding:'4px 6px' }}>
                  <Icon name="msg" size={12}/> {T('Open')}
                </button>
                <button className="btn btn-ghost" onClick={() => setDeleteRoom(r)}
                  style={{
                    fontSize: 12, color:'var(--err)',
                    flex:'0 0 32px', width: 32,
                    padding:'4px 0', justifyContent:'center',
                  }}
                  title={T("Delete room")}>
                  <Icon name="trash" size={12}/>
                </button>
              </div>
            </div>
            );
          })}
        </div>
      )}

      {createOpen && (
        <CreateRoomModal
          onClose={() => setCreateOpen(false)}
          onCreated={load}
          roomTypes={roomTypes}
          bots={bots}
          employees={employees}
          initialRoomType={createInitialType}
        />
      )}
      {registerOpen && (
        <RegisterRoomModal
          onClose={() => setRegOpen(false)}
          onCreated={load}
          roomTypes={roomTypes}
          bots={bots}
        />
      )}
      {editRoom && (
        <EditRoomModal
          room={editRoom}
          onClose={() => setEditRoom(null)}
          onSaved={load}
          roomTypes={roomTypes}
          bots={bots}
        />
      )}
      {deleteRoom && (
        <DeleteRoomModal
          room={deleteRoom}
          onClose={() => setDeleteRoom(null)}
          onDeleted={load}
        />
      )}
      {membersRoom && (
        <RoomMembersDrawer
          room={membersRoom}
          onClose={() => setMembersRoom(null)}
        />
      )}
      {liveRoom && (
        <RoomLiveDrawer
          room={liveRoom}
          onClose={() => setLiveRoom(null)}
        />
      )}
      {trackingRoom && (
        <RoomAttendanceMutesDrawer
          room={trackingRoom}
          onClose={() => setTrackingRoom(null)}
        />
      )}
      {botsRoom && (
        <RoomBotsDrawer
          room={botsRoom}
          bots={bots}
          onClose={() => setBotsRoom(null)}
          onSaved={async () => { await load(); }}
        />
      )}
      {settingsRoom && (
        <RoomSettingsDrawer
          room={settingsRoom}
          onClose={() => setSettingsRoom(null)}
          onGeneral={() => { const r = settingsRoom; setSettingsRoom(null); setEditRoom(r); }}
          onBots={() => { const r = settingsRoom; setSettingsRoom(null); setBotsRoom(r); }}
          onMembers={() => { const r = settingsRoom; setSettingsRoom(null); setMembersRoom(r); }}
          onLive={() => { const r = settingsRoom; setSettingsRoom(null); setLiveRoom(r); }}
          onTracking={() => { const r = settingsRoom; setSettingsRoom(null); setTrackingRoom(r); }}
        />
      )}
    </div>
  );
}

/* ── Per-room Settings hub ────────────────────────────────────────────
 *  One panel per room consolidating every room control: General config,
 *  Bots, Members, and Attendance tracking. Each section opens its
 *  focused editor. Replaces the old scattered card buttons so admins
 *  have a single "Settings" entry point per room.
 * ────────────────────────────────────────────────────────────────── */
function RoomSettingsDrawer({ room, onClose, onGeneral, onBots, onMembers, onLive, onTracking }) {
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  const secCount = (room.secondary_bots && room.secondary_bots.length) || 0;
  const isAdminRoom = room.room_type === 'admin';

  const Section = ({ icon, title, desc, onClick, badge }) => (
    <button
      onClick={onClick}
      style={{
        display:'flex', alignItems:'center', gap: 12, width:'100%',
        padding:'14px 14px', textAlign:'left', cursor:'pointer',
        background:'var(--bg-elev-2)', border:'1px solid var(--line)',
        borderRadius:'var(--r-md)',
      }}
      onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--brand)'}
      onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--line)'}
    >
      <Icon name={icon} size={18}/>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13, fontWeight: 600, display:'flex', alignItems:'center', gap: 8 }}>
          {title}
          {badge != null && badge > 0 && (
            <span className="badge badge-ok" style={{ fontSize: 10, padding:'1px 6px' }}>{badge}</span>
          )}
        </div>
        <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>{desc}</div>
      </div>
      <Icon name="chevron" size={14} style={{ opacity: 0.4, transform:'rotate(-90deg)' }}/>
    </button>
  );

  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: 22, width: 460, maxWidth:'95vw',
        maxHeight:'90vh', overflowY:'auto',
        boxShadow:'0 24px 64px -16px #00000080', zIndex: 301,
      }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom: 6 }}>
          <div>
            <div style={{ fontWeight: 600, fontSize: 16, display:'flex', alignItems:'center', gap:8 }}>
              <Icon name="cog" size={15}/> {room.display_name}
            </div>
            <div className="mono muted" style={{ fontSize: 11, marginTop: 2 }}>{room.room_id}</div>
            <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>
              {T('type:')} <strong>{room.room_type}</strong>{room.team_name ? ` · ${T('team:')} ${room.team_name}` : ''}
            </div>
          </div>
          <button className="btn btn-ghost btn-icon" onClick={onClose}><Icon name="x" size={14}/></button>
        </div>

        <div style={{ display:'flex', flexDirection:'column', gap: 10, marginTop: 16 }}>
          <Section icon="edit" title={T("General")} onClick={onGeneral}
            desc={T("Room type, team, primary bot persona")} />
          <Section icon="bot" title={T("Bots")} onClick={onBots} badge={secCount}
            desc={T("AI bot is always here. Add specialist bots (DevOps, Tech…)")} />
          <Section icon="users" title={T("Members")} onClick={onMembers}
            desc={T("View room members + their roles")} />
          <Section icon="pulse" title={T("Live presence")} onClick={onLive}
            desc={T("See who's active in this room right now (verified vs chat.k9.ms)")} />
          {!isAdminRoom && (
            <Section icon="clock" title={T("Attendance tracking")} onClick={onTracking}
              desc={T("Per-user sign-in mute (opt specific users out of tracking)")} />
          )}
        </div>

        <div className="muted" style={{ fontSize: 10.5, marginTop: 16, lineHeight: 1.4 }}>
          {T('Editing is permission-gated: superadmin can edit any room; managers & admins can only edit rooms for their own team.')}
        </div>
      </div>
    </div>
  );
}

/* ── Room bots drawer ────────────────────────────────────────────────
 *  Focused, single-purpose UI for the multi-bot pattern:
 *  every room implicitly hosts the primary @ai.bot (shown pinned at the
 *  top), and the admin can toggle any number of additional specialist
 *  bots on/off. Persists secondary_bot_ids via PUT /api/rooms/{id}.
 *  Auto-joins flip on the next 30s bot refresh tick.
 * ────────────────────────────────────────────────────────────────── */
function RoomBotsDrawer({ room, bots, onClose, onSaved }) {
  const toast = useToast();
  const initial = React.useMemo(() => {
    if (Array.isArray(room.secondary_bots) && room.secondary_bots.length) {
      return new Set(room.secondary_bots.map(b => String(b.id)));
    }
    return new Set();
  }, [room]);
  const [selected, setSelected] = React.useState(initial);
  const [busy, setBusy]         = React.useState(false);
  const [err, setErr]           = React.useState('');

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

  const activeBots = (bots || []).filter(b => b.active);

  const toggle = (id) => {
    const next = new Set(selected);
    if (next.has(id)) next.delete(id); else next.add(id);
    setSelected(next);
  };

  const save = async () => {
    setBusy(true); setErr('');
    try {
      // Preserve whatever primary persona bot_id the room already has —
      // this drawer only manages SECONDARIES.
      const r = await fetch(`/api/rooms/${room.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({
          secondary_bot_ids: Array.from(selected).map(x => parseInt(x)),
        }),
      });
      if (!r.ok) {
        const body = await r.json().catch(() => null);
        throw new Error(parseDetail(body, r.status));
      }
      toast.push({ msg: `${T('Bot line-up updated for')} ${room.display_name}`, kind: 'ok' });
      onSaved && (await onSaved());
      onClose();
    } catch(e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  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: 460, maxWidth:'95vw',
        maxHeight: '90vh', overflowY: 'auto',
        boxShadow:'0 24px 64px -16px #00000080', zIndex: 301,
      }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom: 18 }}>
          <div>
            <div style={{ fontWeight: 600, fontSize: 15, display:'flex', alignItems:'center', gap: 6 }}>
              <Icon name="bot" size={14}/> {T('Bots in')} {room.display_name}
            </div>
            <div className="muted" style={{ fontSize: 11, marginTop: 4 }}>
              {T('Default')} <strong>@ai.bot</strong> {T('is always here. Toggle extras on/off below.')}
            </div>
          </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' }}><Icon name="alert" size={13}/> {err}</div>}

        {/* Pinned default bot — non-removable */}
        <div style={{
          display:'flex', alignItems:'center', gap: 10, padding:'10px 12px',
          background:'var(--bg-elev-2)', border:'1px solid var(--ok)',
          borderRadius:'var(--r-md)', marginBottom: 14,
        }}>
          <Icon name="check" size={14}/>
          <div style={{ flex: 1 }}>
            <div style={{ fontWeight: 600, fontSize: 13 }}>{T('AI Bot')} <span className="muted">{T('(default · always present)')}</span></div>
            <div className="mono muted" style={{ fontSize: 11 }}>@ai.bot:k9.ms</div>
          </div>
          <span className="badge badge-ok" style={{ fontSize: 10 }}>{T('locked')}</span>
        </div>

        <div style={{ marginBottom: 10, fontWeight: 600, fontSize: 12, textTransform:'uppercase', letterSpacing: '.05em', color:'var(--text-3)' }}>
          {T('Additional bots')}
        </div>

        <div style={{
          display:'flex', flexDirection:'column', gap: 6,
          maxHeight: 320, overflowY: 'auto', marginBottom: 16,
        }}>
          {activeBots.length === 0 && (
            <div className="muted" style={{ fontSize: 12, padding: 12, textAlign:'center' }}>
              {T('No bots registered. Add some in the Bots tab first.')}
            </div>
          )}
          {activeBots.map(b => {
            const sid = String(b.id);
            const on  = selected.has(sid);
            return (
              <button
                key={b.id}
                type="button"
                onClick={() => toggle(sid)}
                style={{
                  display:'flex', alignItems:'center', gap: 10,
                  padding:'10px 12px', textAlign:'left', cursor:'pointer',
                  background: on ? 'var(--bg-elev-2)' : 'transparent',
                  border:'1px solid ' + (on ? 'var(--ok)' : 'var(--line)'),
                  borderRadius:'var(--r-md)',
                }}
              >
                <Icon name={on ? 'check' : 'plus'} size={13}/>
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 600, fontSize: 13 }}>{b.name}</div>
                  <div className="mono muted" style={{ fontSize: 11 }}>{b.matrix_id}</div>
                  {b.persona && <div className="muted" style={{ fontSize: 11, marginTop: 2 }}>{T('persona:')} {b.persona}</div>}
                </div>
                {on && <span className="badge badge-ok" style={{ fontSize: 10 }}>{T('in room')}</span>}
              </button>
            );
          })}
        </div>

        <div style={{ display:'flex', gap: 8, justifyContent:'flex-end' }}>
          <button className="btn btn-ghost" onClick={onClose}>{T('Cancel')}</button>
          <button className="btn btn-pri" onClick={save} disabled={busy}>
            {busy ? T('Saving...') : <><Icon name="check" size={13}/> {T('Save')}</>}
          </button>
        </div>
      </div>
    </div>
  );
}

window.PtoPage = PtoPage;
window.TeamsPage = TeamsPage;
window.RoomsPage = RoomsPage;
