// AdminViews: UsersView (admins and developers) and TicketTypesView.
// Both reuse the layout styles and small parts defined in ClientsView.jsx and ClientLogo.jsx.
// Client users live under the Clients tab.
// Exports: UsersView, TicketTypesView

const adminAuthH = () => ({ Authorization: 'Bearer ' + (localStorage.getItem('hub_token') || ''), 'Content-Type': 'application/json' });

// ── Users: admins and developers ─────────────────────────────────────────────
// Same layout language as the Clients page: a searchable list on the left, the selected
// person's profile on the right. A developer's profile also shows, and edits, the clients they serve.
const UV_ROLE = {
  admin: { label: 'Admin',     bg: '#E8EEF6', fg: '#0F2540' },
  agent: { label: 'Developer', bg: '#EAF1FA', fg: '#1A5090' },
};

// a function, because CV is defined in ClientsView.jsx, which loads after this file
const UV_CSS = () => `
.uv-feed { container-type: inline-size; container-name: uv; }
.uv-cols, .uv-wide { display: none; }
.uv-list { overflow: hidden; }
.uv-row + .uv-row { border-top: 1px solid ${CV.line}; }
.uv-btn { display: block; width: 100%; padding: 14px 16px; border: 0; background: transparent; font: inherit; color: inherit; text-align: left; cursor: pointer; transition: background .14s; }
.uv-btn:hover { background: #FAFBFD; }
.uv-btn:focus-visible { outline: 2px solid ${CV.orange}; outline-offset: -2px; }
.uv-who { display: flex; align-items: center; gap: 12px; min-width: 0; }
.uv-who b { display: block; font-size: 14.5px; font-weight: 800; color: ${CV.navy}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.uv-who small { display: block; font-size: 12.5px; color: ${CV.mute}; margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.uv-pill { display: inline-flex; align-items: center; font-size: 12px; font-weight: 800; border-radius: 999px; padding: 3px 11px; white-space: nowrap; }
.uv-cell { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: 13px; color: ${CV.mute}; flex-wrap: wrap; }
.uv-wide .uv-cell { flex-wrap: nowrap; white-space: nowrap; }
.uv-narrow-meta { display: flex; align-items: center; gap: 6px 8px; flex-wrap: wrap; margin: 10px 0 0 56px; }
.uv-cols { padding: 10px 16px; font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: ${CV.navy}; position: sticky; top: 0; z-index: 3; background: ${CV.wash}; border-bottom: 1px solid ${CV.line}; }
@container uv (min-width: 500px) {
  .uv-narrow, .uv-narrow-meta { display: none; }
  .uv-wide, .uv-cols { display: grid; grid-template-columns: minmax(0, 2fr) 104px minmax(0, 1.25fr) 78px 88px 12px; column-gap: 16px; align-items: center; }
  .uv-cols { margin-bottom: 8px; border-radius: 10px; border: 1px solid ${CV.line}; }
  .uv-btn { padding: 13px 16px; }
  .uv-wide .uv-who { gap: 10px; }
}
@container uv (max-width: 819px) { .uv-wide .uv-count { display: none; } }
@container uv (min-width: 820px) {
  .uv-wide, .uv-cols { grid-template-columns: minmax(0, 2.2fr) 120px minmax(0, 1.4fr) 90px 104px 14px; column-gap: 24px; }
  .uv-wide { width: 100%; }
}
`;

function UsersView({ tickets = [], onNav }) {
  const [users, setUsers]     = React.useState([]);
  const [clients, setClients] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const { selId, setSelId, open, close, wrapRef } = useCvPage();
  const [query, setQuery]     = React.useState('');
  const [filter, setFilter]   = React.useState('all');          // all | agent | admin
  const [page, setPage]       = React.useState(1);
  const [size, setSize]       = useCvPageSize();
  const [adding, setAdding]   = React.useState(null);           // { name, email, role } while adding
  const [toast, setToast]     = React.useState(null);
  const [secret, setSecret]   = React.useState(null);           // { name, password } shown once
  const [pickOpen, setPickOpen] = React.useState(false);
  const [pickQuery, setPickQuery] = React.useState('');
  const popRef = React.useRef(null);
  const photoRef = React.useRef(null);
  const [drag, setDrag] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const me = window._synUser || {};

  async function load(keepSel) {
    try {
      const [u, c] = await Promise.all([
        fetch('/api/users',   { headers: adminAuthH() }).then(r => r.ok ? r.json() : []),
        fetch('/api/clients', { headers: adminAuthH() }).then(r => r.ok ? r.json() : []),
      ]);
      const staff = u.filter(x => x.role !== 'client');
      setUsers(staff);
      setClients(c);
      setSelId(cur => (keepSel && cur && staff.some(x => x.id === cur)) ? cur : null);
    } finally { setLoading(false); }
  }
  React.useEffect(() => { load(false); }, []);

  React.useEffect(() => {
    if (!toast) return;
    const t = setTimeout(() => setToast(null), 5000);
    return () => clearTimeout(t);
  }, [toast]);

  React.useEffect(() => {
    if (!pickOpen) return;
    const onDown = e => { if (popRef.current && !popRef.current.contains(e.target)) setPickOpen(false); };
    const onKey = e => { if (e.key === 'Escape') setPickOpen(false); };
    document.addEventListener('mousedown', onDown);
    document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onDown); document.removeEventListener('keydown', onKey); };
  }, [pickOpen]);

  async function call(method, url, body, okText) {
    const res = await fetch(url, { method, headers: adminAuthH(), body: body ? JSON.stringify(body) : undefined });
    const data = await res.json().catch(() => ({}));
    if (!res.ok) { setToast({ type: 'error', text: data.error || 'Something went wrong' }); return null; }
    if (okText) setToast({ type: 'ok', text: typeof okText === 'function' ? okText(data) : okText });
    await load(true);
    return data;
  }

  const sel = users.find(u => u.id === selId) || null;
  const isMe = sel && sel.id === me.id;

  async function uploadPhoto(file) {
    if (!file || !sel) return;
    setBusy(true);
    try {
      const blob = await cvPrepPhoto(file);
      const res = await fetch(`/api/users/${sel.id}/photo`, { method: 'PUT', headers: { Authorization: adminAuthH().Authorization, 'Content-Type': 'image/jpeg' }, body: blob });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || 'Upload failed');
      setToast({ type: 'ok', text: 'Photo updated.' });
      await load(true);
    } catch (err) { setToast({ type: 'error', text: err.message || 'Upload failed' }); }
    finally { setBusy(false); }
  }

  // workload per developer, from the concerns the app already holds
  const work = React.useMemo(() => {
    const m = {};
    for (const t of tickets) {
      const id = t.assignedTo && t.assignedTo.id;
      if (!id) continue;
      const w = m[id] || (m[id] = { open: 0, prog: 0, all: [] });
      w.all.push(t);
      if (t.status === 'Open') w.open++;
      if (t.status === 'In Progress') w.prog++;
    }
    return m;
  }, [tickets]);

  const q = query.trim().toLowerCase();
  const shown = users.filter(u => (filter === 'all' || u.role === filter) && (u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q)));
  const pages = Math.max(1, Math.ceil(shown.length / size));
  const pg = Math.min(page, pages);
  const rows = shown.slice((pg - 1) * size, pg * size);
  const counts = { all: users.length, agent: users.filter(u => u.role === 'agent').length, admin: users.filter(u => u.role === 'admin').length };

  const w = sel ? (work[sel.id] || { open: 0, prog: 0, all: [] }) : null;
  const myClientIds = sel ? (sel.clients || []).map(c => c.id) : [];
  const pickable = clients.filter(c => !myClientIds.includes(c.id) && c.name.toLowerCase().includes(pickQuery.trim().toLowerCase()));
  const recent = w ? [...w.all].sort((a, b) => new Date(b.createdRaw) - new Date(a.createdRaw)).slice(0, 5) : [];
  const active = sel && sel.status === 'active';

  async function addUser(e) {
    e.preventDefault();
    const d = await call('POST', '/api/users/onboard', adding);
    if (d) { setSecret({ name: adding.name, password: d.tempPassword }); setAdding(null); if (d.userId) open(d.userId); }
  }

  const setClientsFor = (ids, okText) => call('PUT', `/api/users/${sel.id}/clients`, { client_ids: ids }, okText);

  // one row of the account section: what it does, and the button that does it
  const Action = ({ title, text, label, tone = 'soft', onClick }) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: 16, padding: '12px 0', borderTop: '1px solid ' + CV.line }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 14, fontWeight: 700, color: CV.ink }}>{title}</div>
        <div style={{ fontSize: 12, color: CV.mute, marginTop: 2, lineHeight: 1.45 }}>{text}</div>
      </div>
      <button className={'cv-btn ' + (tone === 'danger' ? 'cv-danger' : 'cv-soft')} onClick={onClick}>{label}</button>
    </div>
  );

  return (
    <div className="cv1" ref={wrapRef}>
      <style>{CV_CSS + CV_PAGE_CSS + UV_CSS()}</style>

      {/* ── step 1: everyone ── */}
      {!sel && (
        <div>
          <div className="cv-top">
            <div>
              <h2 style={{ margin: 0, fontSize: 28, fontWeight: 800, letterSpacing: '-0.02em', color: CV.navy }}>People <span style={{ color: CV.faint, fontWeight: 600, fontSize: 18 }}>{users.length}</span></h2>
              <p style={{ margin: '4px 0 0', fontSize: 14, color: CV.mute }}>Admins and developers. Pick someone to manage their access.</p>
            </div>
            <div className="cv-top-actions">
              <div style={{ position: 'relative' }}>
                <span style={{ position: 'absolute', left: 11, top: 11, display: 'flex' }}><IC name="search" size={15} color={CV.faint} /></span>
                <input className="cv-input" style={{ width: 250, maxWidth: '100%', boxSizing: 'border-box', paddingLeft: 34 }} placeholder="Search by name or email" value={query} onChange={e => { setQuery(e.target.value); setPage(1); }} aria-label="Search people" />
              </div>
              <button className="cv-btn cv-primary" onClick={() => setAdding(a => a ? null : { name: '', email: '', role: 'agent' })}>
                <IC name="plus" size={14} color="#fff" strokeWidth={2.4} />New user
              </button>
            </div>
          </div>

          {adding && (
            <form onSubmit={addUser} className="cv-enter cv-panel" style={{ display: 'flex', gap: 10, padding: 14, marginBottom: 18, flexWrap: 'wrap', alignItems: 'center' }}>
              <input className="cv-input" style={{ flex: '1 1 170px' }} autoFocus placeholder="Full name" value={adding.name} onChange={e => setAdding(f => ({ ...f, name: e.target.value }))} required aria-label="Full name" />
              <input className="cv-input" style={{ flex: '2 1 230px' }} type="email" placeholder="Email address" value={adding.email} onChange={e => setAdding(f => ({ ...f, email: e.target.value }))} required aria-label="Email address" />
              <div role="radiogroup" aria-label="Role" style={{ display: 'flex', gap: 6 }}>
                {['agent', 'admin'].map(r => (
                  <button key={r} type="button" role="radio" aria-checked={adding.role === r} onClick={() => setAdding(f => ({ ...f, role: r }))}
                    className="cv-btn" style={{ background: adding.role === r ? CV.navy : '#fff', color: adding.role === r ? '#fff' : CV.mute, border: '1.5px solid ' + (adding.role === r ? CV.navy : '#E2E8F0') }}>
                    {UV_ROLE[r].label}
                  </button>
                ))}
              </div>
              <button className="cv-btn cv-primary" type="submit">Create account</button>
              <button className="cv-btn cv-ghost" type="button" onClick={() => setAdding(null)}>Cancel</button>
            </form>
          )}

          <div role="tablist" aria-label="Filter by role" style={{ display: 'inline-flex', gap: 4, background: '#fff', border: '1px solid ' + CV.line, padding: 3, borderRadius: 12, marginBottom: 18 }}>
            {[['all', 'All'], ['agent', 'Developers'], ['admin', 'Admins']].map(([k, label]) => (
              <button key={k} role="tab" aria-selected={filter === k} onClick={() => { setFilter(k); setPage(1); }} className="cv-btn"
                style={{ padding: '7px 14px', background: filter === k ? CV.navy : 'transparent', color: filter === k ? '#fff' : CV.mute }}>
                {label} <span style={{ fontWeight: 600, opacity: 0.7, fontVariantNumeric: 'tabular-nums' }}>{counts[k]}</span>
              </button>
            ))}
          </div>

          {loading && <div style={{ fontSize: 14, color: CV.faint }}>Loading people…</div>}
          {!loading && shown.length === 0 && <div style={{ fontSize: 14, color: CV.faint }}>No one matches.</div>}

          {shown.length > 0 && (
            <div className="uv-feed">
              <div className="uv-cols" aria-hidden="true"><span>Person</span><span>Role</span><span>Clients</span><span>Assigned</span><span>Status</span><span /></div>
              <div className="cv-panel uv-list">
                {rows.map(u => {
                  const wk = work[u.id], n = wk ? wk.open + wk.prog : 0;
                  const dim = u.status !== 'active', cl = u.clients || [], r = UV_ROLE[u.role];
                  const rolePill = <span className="uv-pill" style={{ color: r.fg, background: r.bg }}>{r.label}</span>;
                  const statusPill = dim
                    ? <span className="uv-pill" style={{ color: '#92400E', background: '#FEF3C7' }}>Deactivated</span>
                    : <span className="uv-pill" style={{ color: '#17745A', background: '#E6F4EF' }}>Active</span>;
                  const clients = u.role === 'admin' ? <span>Every client</span>
                    : cl.length === 0 ? <span className="uv-pill" style={{ color: '#B45309', background: '#FFF7E8' }}>None assigned</span>
                    : <><span className="cv-stack" style={{ display: 'inline-flex' }}>{cl.slice(0, 3).map(c => <span key={c.id} title={c.name} style={{ display: 'flex' }}><ClientLogo clientId={c.id} name={c.name} hasLogo={c.has_logo} version={c.logo_version} size={34} radius={17} /></span>)}</span>{cl.length > 3 && <b style={{ color: CV.mute }}>+{cl.length - 3}</b>}<span className="uv-count">{cl.length} client{cl.length > 1 ? 's' : ''}</span></>;
                  const assigned = u.role === 'admin' ? <span style={{ color: CV.faint }}>None</span> : <b style={{ color: n ? CV.orange : CV.faint, fontVariantNumeric: 'tabular-nums' }}>{n}</b>;
                  return (
                    <div key={u.id} className="uv-row" style={{ opacity: dim ? 0.65 : 1 }}>
                      <button className="uv-btn" aria-label={`${u.name}, ${r.label}${dim ? ', deactivated' : ''}`} onClick={() => { setSecret(null); setPickOpen(false); open(u.id); }}>
                        <span className="uv-wide">
                          <span className="uv-who"><CvAvatar name={u.name} size={40} id={u.id} hasPhoto={u.has_photo} version={u.photo_version} /><span style={{ minWidth: 0 }}><b>{u.name}{u.id === me.id ? ' (you)' : ''}</b><small>{u.email}</small></span></span>
                          <span>{rolePill}</span>
                          <span className="uv-cell">{clients}</span>
                          <span className="uv-cell">{assigned}</span>
                          <span>{statusPill}</span>
                          <IC name="chevron_r" size={15} color={CV.faint} />
                        </span>
                        <span className="uv-who uv-narrow">
                          <CvAvatar name={u.name} size={44} id={u.id} hasPhoto={u.has_photo} version={u.photo_version} /><span style={{ minWidth: 0 }}><b>{u.name}{u.id === me.id ? ' (you)' : ''}</b><small>{u.email}</small></span>
                        </span>
                        <span className="uv-narrow-meta">{rolePill}{statusPill}<span className="uv-cell">{clients}</span>{u.role !== 'admin' && n > 0 && <span className="uv-pill" style={{ color: CV.orange, background: '#FDEEE6' }}>{n} assigned</span>}</span>
                      </button>
                    </div>
                  );
                })}
              </div>
              <CvPager total={shown.length} page={pg} size={size} onPage={setPage} onSize={n => { setPage(1); setSize(n); }} noun="people" />
            </div>
          )}
        </div>
      )}

      {/* ── step 2: one person ── */}
      {sel && (
        <nav className="cv-crumb" aria-label="Back">
          <button className="cv-btn cv-ghost" style={{ paddingLeft: 0 }} onClick={close}><IC name="arrow_left" size={15} />Back to people</button>
        </nav>
      )}
      {sel && <main className="cv-panel cv-detail" aria-live="polite">
        {sel && (
          <div key={sel.id} className="cv-enter">
            <div style={{ padding: '26px 28px 22px' }}>
              <div style={{ display: 'flex', gap: 22, alignItems: 'center', flexWrap: 'wrap' }}>
                <button
                  className={'cv-logo' + (drag ? ' cv-drag' : '')} style={{ borderRadius: '50%' }}
                  title={sel.has_photo ? 'Change photo' : 'Add a photo'} aria-label={sel.has_photo ? 'Change photo' : 'Add a photo'}
                  onClick={() => photoRef.current && photoRef.current.click()}
                  onDragOver={e => { e.preventDefault(); setDrag(true); }} onDragLeave={() => setDrag(false)}
                  onDrop={e => { e.preventDefault(); setDrag(false); uploadPhoto(e.dataTransfer.files[0]); }}>
                  <CvAvatar name={sel.name} size={92} id={sel.id} hasPhoto={sel.has_photo} version={sel.photo_version} />
                  <span className="cv-over" style={{ borderRadius: '50%' }}><IC name="camera" size={20} color="#fff" />{busy ? 'Uploading…' : sel.has_photo ? 'Change' : 'Add photo'}</span>
                </button>
                <input ref={photoRef} type="file" accept="image/png,image/jpeg,image/webp" hidden onChange={e => { uploadPhoto(e.target.files[0]); e.target.value = ''; }} />
                <div style={{ flex: 1, minWidth: 220 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
                    <h2 style={{ margin: 0, fontSize: 26, fontWeight: 800, letterSpacing: '-0.02em', color: CV.navy, lineHeight: 1.15 }}>{sel.name}</h2>
                    <span style={{ fontSize: 12, fontWeight: 800, color: UV_ROLE[sel.role].fg, background: UV_ROLE[sel.role].bg, borderRadius: 999, padding: '3px 11px' }}>{UV_ROLE[sel.role].label}</span>
                    {!active && <span style={{ fontSize: 12, fontWeight: 800, color: '#92400E', background: '#FEF3C7', borderRadius: 999, padding: '3px 11px' }}>Deactivated</span>}
                  </div>
                  <div style={{ marginTop: 6, fontSize: 14, color: CV.mute }}>{sel.email}</div>
                  <div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: '4px 18px', fontSize: 13, color: CV.mute, fontVariantNumeric: 'tabular-nums' }}>
                    {sel.role === 'agent' ? (
                      <>
                        <span><b style={{ color: CV.blue }}>●</b>&nbsp; {w.open} open</span>
                        <span><b style={{ color: CV.orange }}>●</b>&nbsp; {w.prog} in progress</span>
                        <span>serves {(sel.clients || []).length} client{(sel.clients || []).length !== 1 ? 's' : ''}</span>
                      </>
                    ) : <span>Sees every client and every concern</span>}
                  </div>
                  <div style={{ marginTop: 10, display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
                    <button className="cv-btn cv-soft" onClick={() => photoRef.current && photoRef.current.click()} disabled={busy}>
                      <IC name="camera" size={14} />{busy ? 'Uploading…' : sel.has_photo ? 'Change photo' : 'Upload photo'}
                    </button>
                    {sel.has_photo && <button className="cv-btn cv-ghost" onClick={() => window.confirm('Remove this photo?') && call('DELETE', `/api/users/${sel.id}/photo`, null, 'Photo removed.')}>Remove photo</button>}
                    {!sel.has_photo && <span style={{ fontSize: 12, color: CV.faint }}>PNG, JPEG or WebP. You can also drop an image on the avatar.</span>}
                  </div>
                </div>
              </div>
            </div>

            {/* clients a developer serves */}
            {sel.role === 'agent' && (
              <section className="cv-section" aria-labelledby="uv-cl">
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
                  <h3 id="uv-cl" className="cv-h" style={{ margin: 0 }}>Clients they serve <small>{(sel.clients || []).length}</small></h3>
                  {active && (
                    <div style={{ position: 'relative' }} ref={popRef}>
                      <button className="cv-btn cv-soft" onClick={() => { setPickOpen(o => !o); setPickQuery(''); }} aria-haspopup="listbox" aria-expanded={pickOpen}>
                        <IC name="plus" size={14} strokeWidth={2.4} />Assign to client
                      </button>
                      {pickOpen && (
                        <div className="cv-pop cv-enter" role="listbox" aria-label="Clients to assign">
                          <input className="cv-input" style={{ width: '100%', boxSizing: 'border-box', marginBottom: 6 }} autoFocus placeholder="Search clients" value={pickQuery} onChange={e => setPickQuery(e.target.value)} />
                          <div style={{ maxHeight: 240, overflowY: 'auto' }}>
                            {pickable.length === 0 && <div style={{ padding: '14px 10px', fontSize: 13, color: CV.faint }}>{clients.length === 0 ? 'No clients exist yet. Add some under Clients.' : 'Already assigned to every client.'}</div>}
                            {pickable.map(c => (
                              <button key={c.id} role="option" aria-label={c.name} className="cv-row" style={{ padding: '8px 8px' }}
                                onClick={() => { setPickOpen(false); setClientsFor([...myClientIds, c.id], `${sel.name} now serves ${c.name}.`); }}>
                                <ClientLogo clientId={c.id} name={c.name} hasLogo={c.has_logo} version={c.logo_version} size={30} />
                                <span style={{ fontSize: 13, fontWeight: 700 }}>{c.name}</span>
                              </button>
                            ))}
                          </div>
                        </div>
                      )}
                    </div>
                  )}
                </div>
                {(sel.clients || []).length === 0 ? (
                  <div style={{ fontSize: 13, color: '#B45309', background: '#FFF7E8', borderRadius: 10, padding: '12px 14px' }}>
                    Not assigned to any client, so {sel.name.split(' ')[0]} sees no concerns. Assign them to at least one.
                  </div>
                ) : (
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
                    {sel.clients.map(c => (
                      <span key={c.id} className="cv-chip">
                        <ClientLogo clientId={c.id} name={c.name} hasLogo={c.has_logo} version={c.logo_version} size={26} radius={13} />{c.name}
                        {active && <button aria-label={`Remove ${c.name}`} title="Remove" onClick={() => setClientsFor(myClientIds.filter(x => x !== c.id), `${sel.name} no longer serves ${c.name}.`)}><IC name="x" size={13} strokeWidth={2.2} /></button>}
                      </span>
                    ))}
                  </div>
                )}
              </section>
            )}

            {/* workload */}
            {sel.role === 'agent' && (
              <section className="cv-section" aria-labelledby="uv-wk">
                <h3 id="uv-wk" className="cv-h">Assigned concerns <small>{w.all.length} in total</small></h3>
                {recent.length === 0 && <div style={{ fontSize: 13, color: CV.faint }}>Nothing is assigned to {sel.name.split(' ')[0]} yet.</div>}
                {recent.map(t => (
                  <button key={t.dbId} className="cv-row" style={{ padding: '10px 8px' }} onClick={() => onNav && onNav('ticket_detail', t)}>
                    <span style={{ width: 9, height: 9, borderRadius: '50%', background: CV_STATUS[t.status] || CV.faint, flexShrink: 0 }} title={t.status} />
                    <span style={{ flex: 1, minWidth: 0 }}>
                      <span style={{ display: 'block', fontSize: 14, fontWeight: 600, color: CV.ink, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.title}</span>
                      <span style={{ display: 'block', fontSize: 12, color: CV.faint, marginTop: 1, fontVariantNumeric: 'tabular-nums' }}>{t.id} · {t.clientName || 'No client'} · {t.status}</span>
                    </span>
                    <span style={{ fontSize: 12, color: CV.faint, whiteSpace: 'nowrap' }}>{cvAge(t.createdRaw)}</span>
                  </button>
                ))}
              </section>
            )}

            {/* account */}
            <section className="cv-section" aria-labelledby="uv-ac">
              <h3 id="uv-ac" className="cv-h" style={{ marginBottom: 4 }}>Account</h3>

              {secret && (
                <div className="cv-enter" role="status" style={{ background: '#ECFDF5', border: '1px solid #A7F3D0', borderRadius: 12, padding: '12px 14px', margin: '8px 0 4px', display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
                  <div style={{ flex: 1, minWidth: 200 }}>
                    <div style={{ fontSize: 13, fontWeight: 700, color: '#065F46' }}>Temporary password for {secret.name}</div>
                    <div style={{ fontSize: 12, color: '#047857', marginTop: 2 }}>Share it with them directly. It is shown only once, and they must change it at first login.</div>
                  </div>
                  <code style={{ fontFamily: 'ui-monospace, Menlo, Consolas, monospace', fontSize: 14, fontWeight: 700, color: '#064E3B', background: '#fff', border: '1px solid #A7F3D0', borderRadius: 8, padding: '7px 10px' }}>{secret.password}</code>
                  <button className="cv-btn cv-soft" onClick={() => { navigator.clipboard && navigator.clipboard.writeText(secret.password); setToast({ type: 'ok', text: 'Password copied.' }); }}><IC name="copy" size={14} />Copy</button>
                  <button className="cv-btn cv-ghost" onClick={() => setSecret(null)} aria-label="Dismiss"><IC name="x" size={14} /></button>
                </div>
              )}

              {isMe && <div style={{ fontSize: 13, color: CV.mute, padding: '10px 0' }}>This is your own account, so account changes are disabled here. Use the lock icon in the sidebar to change your password.</div>}
              {!isMe && (
                <>
                  <Action title="Reset password" label="Reset" text={`Creates a new temporary password for ${sel.name.split(' ')[0]} that you share with them.`}
                    onClick={() => window.confirm(`Reset the password for ${sel.name}?`) && call('POST', `/api/users/${sel.id}/reset-password`, null, r => { setSecret({ name: sel.name, password: r.tempPassword }); return 'Password reset.'; })} />
                  <Action title={sel.role === 'admin' ? 'Make a developer' : 'Make an admin'}
                    label={sel.role === 'admin' ? 'Make developer' : 'Make admin'}
                    text={sel.role === 'admin' ? 'Removes full access. They will only see the clients they are assigned to.' : 'Gives full access to every client, concern, user and setting.'}
                    onClick={() => window.confirm(`Make ${sel.name} ${sel.role === 'admin' ? 'a developer' : 'an admin'}?`) && call('PUT', `/api/users/${sel.id}/change-role`, { role: sel.role === 'admin' ? 'agent' : 'admin' }, `${sel.name} is now ${sel.role === 'admin' ? 'a developer' : 'an admin'}.`)} />
                  {sel.role !== 'admin' && (active
                    ? <Action title="Offboard" label="Offboard" tone="danger" text="Blocks sign in straight away. Their history stays, and you can reactivate them later."
                        onClick={() => window.confirm(`Offboard ${sel.name}? They will no longer be able to sign in.`) && call('PUT', `/api/users/${sel.id}/offboard`, null, `${sel.name} offboarded.`)} />
                    : <Action title="Reactivate" label="Reactivate" text="Lets them sign in again with their current password."
                        onClick={() => call('PUT', `/api/users/${sel.id}/reactivate`, null, `${sel.name} reactivated.`)} />)}
                  {sel.role !== 'admin' && (
                    <Action title="Delete account" label="Delete" tone="danger" text="Removes the account for good. Concerns they raised or worked on are kept."
                      onClick={() => window.confirm(`Delete ${sel.name} permanently? This cannot be undone.`) && call('DELETE', `/api/users/${sel.id}`, null, `${sel.name} deleted.`)} />
                  )}
                </>
              )}
            </section>
          </div>
        )}
      </main>}

      {toast && (
        <div role="status" className="cv-enter" style={{
          position: 'fixed', left: 0, right: 0, margin: '0 auto', width: 'fit-content', bottom: 24, zIndex: 60, maxWidth: 'calc(100vw - 32px)',
          background: toast.type === 'error' ? '#7F1D1D' : CV.navy, color: '#fff', padding: '11px 16px', borderRadius: 12,
          fontSize: 13, fontWeight: 600, boxShadow: '0 16px 40px -10px rgba(15,37,64,.45)',
        }}>{toast.text}</div>
      )}
    </div>
  );
}

// ── Ticket types: the list clients choose from when raising a concern ────────
// Same layout language as Clients and Users. The profile shows where a type is used, which is
// what an admin needs to know before renaming or deleting it.
function TicketTypesView({ tickets = [], onNav }) {
  const [types, setTypes]     = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const { selId, setSelId, open, close, wrapRef } = useCvPage();
  const [query, setQuery]     = React.useState('');
  const [adding, setAdding]   = React.useState(false);
  const [newName, setNewName] = React.useState('');
  const [renaming, setRenaming] = React.useState(null);
  const [toast, setToast]     = React.useState(null);

  async function load(keepSel) {
    try {
      const res = await fetch('/api/ticket-types', { headers: adminAuthH() });
      const rows = res.ok ? await res.json() : [];
      setTypes(rows);
      setSelId(cur => (keepSel && cur && rows.some(x => x.id === cur)) ? cur : null);
      if (window.reloadTicketTypes) window.reloadTicketTypes();    // keep the app's colours and labels in step
    } finally { setLoading(false); }
  }
  React.useEffect(() => { load(false); }, []);

  React.useEffect(() => {
    if (!toast) return;
    const t = setTimeout(() => setToast(null), 5000);
    return () => clearTimeout(t);
  }, [toast]);

  async function call(method, url, body, okText) {
    const res = await fetch(url, { method, headers: adminAuthH(), body: body ? JSON.stringify(body) : undefined });
    const data = await res.json().catch(() => ({}));
    if (!res.ok) { setToast({ type: 'error', text: data.error || 'Something went wrong' }); return null; }
    if (okText) setToast({ type: 'ok', text: okText });
    await load(true);
    return data;
  }

  // each type keeps the colour the rest of the app gives it (assigned by position)
  const palette = id => {
    const c = (window.MOCK.categories || []).find(x => x.id === String(id));
    return c ? [c.bg, c.color] : ['#EAF1FA', '#1A5090'];
  };

  const byType = React.useMemo(() => {
    const m = {};
    for (const t of tickets) {
      if (!t.categoryId) continue;
      const g = m[t.categoryId] || (m[t.categoryId] = { all: [], open: 0, prog: 0, clients: {} });
      g.all.push(t);
      if (t.status === 'Open') g.open++;
      if (t.status === 'In Progress') g.prog++;
      if (t.clientId) {
        const c = g.clients[t.clientId] || (g.clients[t.clientId] = { id: t.clientId, name: t.clientName, has: t.clientHasLogo, ver: t.clientLogoVer, n: 0 });
        c.n++;
      }
    }
    return m;
  }, [tickets]);

  const sel = types.find(t => t.id === selId) || null;
  const g = sel ? (byType[String(sel.id)] || { all: [], open: 0, prog: 0, clients: {} }) : null;
  const usage = g ? Object.values(g.clients).sort((a, b) => b.n - a.n) : [];
  const maxUse = usage.length ? usage[0].n : 1;
  const recent = g ? [...g.all].sort((a, b) => new Date(b.createdRaw) - new Date(a.createdRaw)).slice(0, 5) : [];
  const inUse = sel ? sel.ticket_count > 0 : false;
  const shown = types.filter(t => t.name.toLowerCase().includes(query.trim().toLowerCase()));

  async function addType(e) {
    e.preventDefault();
    const name = newName.trim();
    if (!name) return;
    const d = await call('POST', '/api/ticket-types', { name }, `"${name}" added. Clients can pick it right away.`);
    if (d) { setNewName(''); setAdding(false); open(d.id); }
  }

  async function saveRename(e) {
    e.preventDefault();
    const name = (renaming || '').trim();
    if (!name || name === sel.name) { setRenaming(null); return; }
    if (await call('PUT', `/api/ticket-types/${sel.id}`, { name }, 'Renamed.')) setRenaming(null);
  }

  return (
    <div className="cv1" ref={wrapRef}>
      <style>{CV_CSS + CV_PAGE_CSS}</style>

      {/* ── step 1: every type ── */}
      {!sel && (
        <div>
          <div className="cv-top">
            <div>
              <h2 style={{ margin: 0, fontSize: 28, fontWeight: 800, letterSpacing: '-0.02em', color: CV.navy }}>Ticket types <span style={{ color: CV.faint, fontWeight: 600, fontSize: 18 }}>{types.length}</span></h2>
              <p style={{ margin: '4px 0 0', fontSize: 14, color: CV.mute }}>What clients choose from when they raise a concern. Pick one to edit it.</p>
            </div>
            <div className="cv-top-actions">
              {types.length > 6 && (
                <div style={{ position: 'relative' }}>
                  <span style={{ position: 'absolute', left: 11, top: 11, display: 'flex' }}><IC name="search" size={15} color={CV.faint} /></span>
                  <input className="cv-input" style={{ width: 230, maxWidth: '100%', boxSizing: 'border-box', paddingLeft: 34 }} placeholder="Search types" value={query} onChange={e => setQuery(e.target.value)} aria-label="Search ticket types" />
                </div>
              )}
              <button className="cv-btn cv-primary" onClick={() => { setAdding(a => !a); setNewName(''); }}>
                <IC name="plus" size={14} color="#fff" strokeWidth={2.4} />New type
              </button>
            </div>
          </div>

          {adding && (
            <form onSubmit={addType} className="cv-enter cv-panel" style={{ display: 'flex', gap: 10, padding: 14, marginBottom: 18, flexWrap: 'wrap' }}>
              <input className="cv-input" style={{ flex: '1 1 220px' }} autoFocus placeholder="For example Bug" value={newName} onChange={e => setNewName(e.target.value)} aria-label="New ticket type" />
              <button className="cv-btn cv-primary" type="submit">Add type</button>
              <button className="cv-btn cv-ghost" type="button" onClick={() => setAdding(false)}>Cancel</button>
            </form>
          )}

          {loading && <div style={{ fontSize: 14, color: CV.faint }}>Loading types…</div>}
          {!loading && types.length === 0 && (
            <div className="cv-panel" style={{ padding: '56px 24px', textAlign: 'center', fontSize: 14, color: CV.mute, lineHeight: 1.6 }}>
              No ticket types yet.<br />Clients cannot raise a concern until you add one.
            </div>
          )}
          {!loading && types.length > 0 && shown.length === 0 && <div style={{ fontSize: 14, color: CV.faint }}>No type matches “{query}”.</div>}

          <div className="cv-grid">
            {shown.map((t, i) => {
              const [bg, fg] = palette(t.id);
              const g2 = byType[String(t.id)] || { open: 0, prog: 0, clients: {} };
              const cl = Object.values(g2.clients).sort((a, b) => b.n - a.n);
              return (
                <button key={t.id} className="cv-tile" style={{ animationDelay: i * 45 + 'ms' }}
                  aria-label={`${t.name}, ${t.ticket_count} concerns, ${g2.open} open`}
                  onClick={() => { setRenaming(null); open(t.id); }}>
                  <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 10 }}>
                    <TypeBadge icon={t.icon} bg={bg} fg={fg} name={t.name} size={84} radius={22} />
                    {g2.open > 0 && <span title={`${g2.open} open`} style={{ fontSize: 13, fontWeight: 800, color: CV.orange, background: '#FDEEE6', borderRadius: 999, padding: '3px 11px', fontVariantNumeric: 'tabular-nums' }}>{g2.open} open</span>}
                  </div>
                  <div>
                    <div style={{ fontSize: 18, fontWeight: 800, letterSpacing: '-0.01em', color: CV.navy }}>{t.name}</div>
                    <div style={{ fontSize: 13, color: CV.mute, marginTop: 3, fontVariantNumeric: 'tabular-nums' }}>
                      {t.ticket_count ? `${t.ticket_count} concern${t.ticket_count > 1 ? 's' : ''} · ${g2.prog} in progress` : 'Not used yet'}
                    </div>
                  </div>
                </button>
              );
            })}
          </div>
        </div>
      )}

      {/* ── step 2: one type ── */}
      {sel && (
        <nav className="cv-crumb" aria-label="Back">
          <button className="cv-btn cv-ghost" style={{ paddingLeft: 0 }} onClick={close}><IC name="arrow_left" size={15} />Back to ticket types</button>
        </nav>
      )}
      {sel && <main className="cv-panel cv-detail" aria-live="polite">
        {sel && (() => {
          const [bg, fg] = palette(sel.id);
          return (
            <div key={sel.id} className="cv-enter">
              <div style={{ padding: '26px 28px 22px' }}>
                <div style={{ display: 'flex', gap: 22, alignItems: 'center', flexWrap: 'wrap' }}>
                  <TypeBadge icon={sel.icon} bg={bg} fg={fg} name={sel.name} size={124} radius={30} />
                  <div style={{ flex: 1, minWidth: 220 }}>
                    {renaming === null ? (
                      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                        <h2 style={{ margin: 0, fontSize: 26, fontWeight: 800, letterSpacing: '-0.02em', color: CV.navy, lineHeight: 1.15 }}>{sel.name}</h2>
                        <button className="cv-btn cv-ghost" style={{ padding: 6 }} onClick={() => setRenaming(sel.name)} aria-label="Rename type" title="Rename"><IC name="edit" size={15} /></button>
                      </div>
                    ) : (
                      <form onSubmit={saveRename} style={{ display: 'flex', gap: 8 }}>
                        <input className="cv-input" style={{ fontSize: 18, fontWeight: 700, flex: 1 }} autoFocus value={renaming} onChange={e => setRenaming(e.target.value)} onKeyDown={e => e.key === 'Escape' && setRenaming(null)} aria-label="Type name" />
                        <button className="cv-btn cv-primary" type="submit">Save</button>
                        <button className="cv-btn cv-ghost" type="button" onClick={() => setRenaming(null)}>Cancel</button>
                      </form>
                    )}
                    <div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: '4px 18px', fontSize: 13, color: CV.mute, fontVariantNumeric: 'tabular-nums' }}>
                      <span>{sel.ticket_count} concern{sel.ticket_count !== 1 ? 's' : ''}</span>
                      <span><b style={{ color: CV.blue }}>●</b>&nbsp; {g.open} open</span>
                      <span><b style={{ color: CV.orange }}>●</b>&nbsp; {g.prog} in progress</span>
                    </div>
                  </div>
                </div>
              </div>

              <section className="cv-section" aria-labelledby="tt-ap">
                <h3 id="tt-ap" className="cv-h">Appearance <small>{sel.icon || sel.color ? 'customised' : 'default'}</small></h3>
                <div style={{ fontSize: 12, fontWeight: 700, color: CV.mute, marginBottom: 8 }}>Icon</div>
                <div role="radiogroup" aria-label="Icon" style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
                  {Object.keys(TYPE_ICONS).map(k => {
                    const on = sel.icon === k;
                    return (
                      <button key={k} role="radio" aria-checked={on} aria-label={TYPE_ICON_LABELS[k]} title={TYPE_ICON_LABELS[k]} className="cv-btn"
                        onClick={() => call('PUT', `/api/ticket-types/${sel.id}`, { icon: k }, 'Icon updated.')}
                        style={{ width: 44, height: 44, padding: 0, justifyContent: 'center', background: on ? bg : '#fff', color: on ? fg : CV.mute, border: '1.5px solid ' + (on ? fg : '#E2E8F0') }}>
                        <TypeIcon name={k} size={20} />
                      </button>
                    );
                  })}
                </div>
                <div style={{ fontSize: 12, fontWeight: 700, color: CV.mute, margin: '18px 0 8px' }}>Colour</div>
                <div role="radiogroup" aria-label="Colour" style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
                  {Object.keys(TYPE_PALETTE).map(k => {
                    const on = sel.color === k;
                    return (
                      <button key={k} role="radio" aria-checked={on} aria-label={k} title={k} className="cv-btn"
                        onClick={() => call('PUT', `/api/ticket-types/${sel.id}`, { color: k }, 'Colour updated.')}
                        style={{ width: 34, height: 34, padding: 0, borderRadius: '50%', background: TYPE_PALETTE[k][1], boxShadow: on ? `0 0 0 2px #fff, 0 0 0 4px ${TYPE_PALETTE[k][1]}` : 'none' }}>
                        {on && <IC name="check" size={15} color="#fff" strokeWidth={3} />}
                      </button>
                    );
                  })}
                </div>
                {(sel.icon || sel.color) && (
                  <button className="cv-btn cv-ghost" style={{ marginTop: 14, paddingLeft: 0 }}
                    onClick={() => call('PUT', `/api/ticket-types/${sel.id}`, { icon: null, color: null }, 'Back to the default look.')}>Reset to default</button>
                )}
              </section>

              <section className="cv-section" aria-labelledby="tt-use">
                <h3 id="tt-use" className="cv-h">Who has raised it</h3>
                {usage.length === 0 && <div style={{ fontSize: 13, color: CV.faint }}>No client has raised a concern of this type yet.</div>}
                {usage.map(c => (
                  <div key={c.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '7px 0' }}>
                    <ClientLogo clientId={c.id} name={c.name} hasLogo={c.has} version={c.ver} size={42} radius={11} />
                    <span style={{ width: 130, fontSize: 14, fontWeight: 600, color: CV.ink, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flexShrink: 0 }}>{c.name}</span>
                    <span style={{ flex: 1, height: 8, background: CV.wash, borderRadius: 999, overflow: 'hidden' }} aria-hidden="true">
                      <span style={{ display: 'block', height: '100%', width: Math.max(6, Math.round((c.n / maxUse) * 100)) + '%', background: fg, opacity: 0.85, borderRadius: 999 }} />
                    </span>
                    <span style={{ width: 28, textAlign: 'right', fontSize: 13, fontWeight: 700, color: CV.mute, fontVariantNumeric: 'tabular-nums' }}>{c.n}</span>
                  </div>
                ))}
              </section>

              <section className="cv-section" aria-labelledby="tt-rec">
                <h3 id="tt-rec" className="cv-h">Recent concerns</h3>
                {recent.length === 0 && <div style={{ fontSize: 13, color: CV.faint }}>Nothing yet.</div>}
                {recent.map(t => (
                  <button key={t.dbId} className="cv-row" style={{ padding: '10px 8px' }} onClick={() => onNav && onNav('ticket_detail', t)}>
                    <span style={{ width: 9, height: 9, borderRadius: '50%', background: CV_STATUS[t.status] || CV.faint, flexShrink: 0 }} title={t.status} />
                    <span style={{ flex: 1, minWidth: 0 }}>
                      <span style={{ display: 'block', fontSize: 14, fontWeight: 600, color: CV.ink, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{t.title}</span>
                      <span style={{ display: 'block', fontSize: 12, color: CV.faint, marginTop: 1, fontVariantNumeric: 'tabular-nums' }}>{t.id} · {t.clientName || 'No client'} · {t.status}</span>
                    </span>
                    <span style={{ fontSize: 12, color: CV.faint, whiteSpace: 'nowrap' }}>{cvAge(t.createdRaw)}</span>
                  </button>
                ))}
              </section>

              <section className="cv-section" aria-labelledby="tt-ac">
                <h3 id="tt-ac" className="cv-h" style={{ marginBottom: 4 }}>Manage</h3>
                <div style={{ display: 'flex', alignItems: 'center', gap: 16, padding: '12px 0', borderTop: '1px solid ' + CV.line }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 14, fontWeight: 700, color: CV.ink }}>Delete type</div>
                    <div style={{ fontSize: 12, color: CV.mute, marginTop: 2, lineHeight: 1.45 }}>
                      {inUse ? `${sel.ticket_count} concern${sel.ticket_count > 1 ? 's use' : ' uses'} this type, so it cannot be deleted. Rename it instead.` : 'Nothing uses this type, so it can be removed safely.'}
                    </div>
                  </div>
                  <button className="cv-btn cv-danger" disabled={inUse} onClick={() => window.confirm(`Delete "${sel.name}"?`) && call('DELETE', `/api/ticket-types/${sel.id}`, null, `"${sel.name}" deleted.`)}>
                    <IC name="trash" size={14} />Delete
                  </button>
                </div>
              </section>
            </div>
          );
        })()}
      </main>}

      {toast && (
        <div role="status" className="cv-enter" style={{
          position: 'fixed', left: 0, right: 0, margin: '0 auto', width: 'fit-content', bottom: 24, zIndex: 60, maxWidth: 'calc(100vw - 32px)',
          background: toast.type === 'error' ? '#7F1D1D' : CV.navy, color: '#fff', padding: '11px 16px', borderRadius: 12,
          fontSize: 13, fontWeight: 600, boxShadow: '0 16px 40px -10px rgba(15,37,64,.45)',
        }}>{toast.text}</div>
      )}
    </div>
  );
}

Object.assign(window, { UsersView, TicketTypesView });
