// ClientsView: admin page for clients. A searchable client list on the left, and on the right the
// selected client's profile: logo, live ticket picture, developers, users and recent concerns.
// Props: tickets (normalised, for the live numbers), onNav (to open a concern)
// Exports: ClientsView

const CV = {
  navy: '#0F2540', blue: '#1A5090', orange: '#D96228',
  ink: '#1E293B', mute: '#64748B', faint: '#94A3B8', line: '#E8EDF3', wash: '#F5F7FA',
};
const CV_STATUS = { 'Open': '#1A5090', 'In Progress': '#D96228', 'Resolved': '#17745A', 'Closed': '#94A3B8' };

const CV_CSS = `
.cv { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 20px; padding: 24px; align-items: start; max-width: 1180px; margin: 0 auto; }
.cv-panel { background: #fff; border: 1px solid ${CV.line}; border-radius: 16px; box-shadow: 0 1px 2px rgba(15,37,64,.04), 0 12px 32px -18px rgba(15,37,64,.18); }
.cv-rail { position: sticky; top: 16px; display: flex; flex-direction: column; max-height: calc(100vh - 110px); overflow: hidden; }
.cv-list { overflow-y: auto; padding: 6px 8px 10px; scrollbar-width: thin; scrollbar-color: #CBD5E1 transparent; }
.cv-row { display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 10px; border: 0; background: transparent; border-radius: 12px; cursor: pointer; text-align: left; font: inherit; color: inherit; transition: background .14s; }
.cv-row:hover { background: ${CV.wash}; }
.cv-row[aria-current="true"] { background: #EAF1FA; }
.cv-row:focus-visible, .cv-btn:focus-visible, .cv-chip button:focus-visible, .cv-input:focus-visible { outline: 2px solid ${CV.orange}; outline-offset: 2px; }
.cv-input { font: inherit; font-size: 13px; color: ${CV.ink}; padding: 10px 12px; border: 1.5px solid #E2E8F0; border-radius: 10px; background: #fff; outline: none; min-width: 0; transition: border-color .14s, box-shadow .14s; }
.cv-input:focus { border-color: ${CV.blue}; box-shadow: 0 0 0 3px rgba(26,80,144,.12); }
.cv-input::placeholder { color: ${CV.faint}; }
.cv-btn { font: inherit; font-size: 13px; font-weight: 700; border: 0; border-radius: 10px; padding: 9px 14px; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; transition: transform .12s, background .14s, opacity .14s; }
.cv-btn:active { transform: translateY(1px); }
.cv-btn[disabled] { opacity: .5; cursor: not-allowed; }
.cv-primary { background: ${CV.orange}; color: #fff; }
.cv-primary:hover:not([disabled]) { background: #C4561F; }
.cv-soft { background: #EAF1FA; color: ${CV.blue}; }
.cv-soft:hover { background: #DCE8F6; }
.cv-ghost { background: transparent; color: ${CV.mute}; padding: 7px 10px; }
.cv-ghost:hover { background: ${CV.wash}; color: ${CV.ink}; }
.cv-danger { background: transparent; color: #B91C1C; padding: 7px 10px; }
.cv-danger:hover { background: #FEF2F2; }
.cv-chip { display: inline-flex; align-items: center; gap: 8px; padding: 5px 6px 5px 5px; border: 1px solid ${CV.line}; border-radius: 999px; background: #fff; font-size: 13px; font-weight: 600; color: ${CV.ink}; }
.cv-chip button { border: 0; background: transparent; color: ${CV.faint}; cursor: pointer; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center; padding: 0; }
.cv-chip button:hover { background: #FEE2E2; color: #B91C1C; }
.cv-logo { position: relative; display: block; line-height: 0; border: 0; padding: 0; background: none; cursor: pointer; border-radius: 30px; }
.cv-logo .cv-over { position: absolute; inset: 0; border-radius: 30px; background: rgba(15,37,64,.62); color: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; font-size: 11px; font-weight: 700; opacity: 0; transition: opacity .16s; }
.cv-logo:hover .cv-over, .cv-logo:focus-visible .cv-over, .cv-logo.cv-drag .cv-over { opacity: 1; }
.cv-logo.cv-drag { outline: 2px dashed ${CV.orange}; outline-offset: 3px; }
.cv-section { padding: 22px 28px; border-top: 1px solid ${CV.line}; }
.cv-h { margin: 0 0 12px; font-size: 14px; font-weight: 800; color: ${CV.navy}; display: flex; align-items: center; gap: 8px; }
.cv-h small { font-size: 12px; font-weight: 600; color: ${CV.faint}; font-variant-numeric: tabular-nums; }
.cv-pop { position: absolute; right: 0; top: calc(100% + 8px); z-index: 20; width: 300px; background: #fff; border: 1px solid ${CV.line}; border-radius: 14px; box-shadow: 0 20px 48px -12px rgba(15,37,64,.28); padding: 10px; }
.cv-enter { animation: cvIn .3s cubic-bezier(.16,1,.3,1) both; }
@keyframes cvIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.cv-back { display: none; }
@media (max-width: 1100px) {
  .cv { grid-template-columns: minmax(0, 1fr); padding: 16px; }
  .cv-rail { position: static; max-height: none; }
  .cv[data-detail="1"] .cv-rail { display: none; }
  .cv[data-detail="0"] .cv-detail { display: none; }
  .cv-back { display: inline-flex; }
  .cv-section { padding: 18px 18px; }
}
@media (prefers-reduced-motion: reduce) { .cv-enter { animation: none; } .cv-row, .cv-btn { transition: none; } }
`;

// ── small authored helpers ──────────────────────────────────────────────────
const CV_PAGE_CSS = `
.cv1 { padding: 28px 28px 48px; max-width: 1360px; margin: 0 auto; }
.cv-top { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 22px; }
.cv-top-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.cv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 16px; }
.cv-tile { text-align: left; font: inherit; color: inherit; background: #fff; border: 1px solid ${CV.line}; border-radius: 18px; padding: 20px; cursor: pointer; display: flex; flex-direction: column; gap: 16px; box-shadow: 0 1px 2px rgba(15,37,64,.04); transition: transform .2s cubic-bezier(.16,1,.3,1), box-shadow .2s, border-color .2s; animation: cvIn .38s cubic-bezier(.16,1,.3,1) both; }
.cv-tile:hover { transform: translateY(-3px); border-color: #CBD5E1; box-shadow: 0 20px 40px -22px rgba(15,37,64,.38); }
.cv-tile:focus-visible { outline: 2px solid ${CV.orange}; outline-offset: 2px; }
.cv-crumb { display: flex; align-items: center; gap: 8px; margin: 0 auto 14px; max-width: 940px; }
.cv1 .cv-detail { max-width: 940px; margin: 0 auto; }
.cv-stack > * { margin-left: -5px; box-shadow: 0 0 0 2px #fff; }
.cv-stack > *:first-child { margin-left: 0; }
@media (max-width: 640px) { .cv1 { padding: 16px 16px 40px; } .cv-grid { grid-template-columns: minmax(0, 1fr); } }
.cv-pager { display: flex; align-items: center; justify-content: space-between; gap: 10px 18px; flex-wrap: wrap; padding: 16px 2px 4px; font-size: 12.5px; color: ${CV.mute}; }
.cv-pager b { color: ${CV.ink}; font-variant-numeric: tabular-nums; }
.cv-pager-ctl { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cv-pager-size { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; }
.cv-pager-size input { width: 64px; padding: 7px 8px; text-align: center; font-variant-numeric: tabular-nums; }
.cv-pager .cv-btn { padding: 7px 12px; }
.cv-pager .cv-btn:disabled { opacity: .4; cursor: default; }
@media (prefers-reduced-motion: reduce) { .cv-tile { animation: none; transition: none; } .cv-tile:hover { transform: none; } }
`;

// Rows-per-page, remembered between visits. Default 15, anything from 1 to 100.
const CV_MAX_PAGE = 100;
function useCvPageSize() {
  const [size, set] = React.useState(() => { try { const n = parseInt(localStorage.getItem('hub_page_size'), 10); return n >= 1 && n <= CV_MAX_PAGE ? n : 15; } catch (e) { return 15; } });
  const setSize = n => { const v = Math.min(Math.max(parseInt(n, 10) || 15, 1), CV_MAX_PAGE); set(v); try { localStorage.setItem('hub_page_size', String(v)); } catch (e) {} };
  return [size, setSize];
}

// "Showing 1 to 15 of 38", a box to choose rows per page, and Previous / Next
function CvPager({ total, page, size, onPage, onSize, noun = 'people' }) {
  const [draft, setDraft] = React.useState(String(size));
  React.useEffect(() => setDraft(String(size)), [size]);
  const pages = Math.max(1, Math.ceil(total / size));
  const from = total === 0 ? 0 : (page - 1) * size + 1, to = Math.min(page * size, total);
  const commit = () => { onSize(draft); setDraft(String(Math.min(Math.max(parseInt(draft, 10) || 15, 1), CV_MAX_PAGE))); };
  if (total === 0) return null;
  const go = n => { onPage(n); const el = document.querySelector('.cv1'); if (el) el.scrollIntoView({ block: 'start' }); };
  return (
    <nav className="cv-pager" aria-label="Pages">
      <span>Showing <b>{from.toLocaleString()}</b> to <b>{to.toLocaleString()}</b> of <b>{total.toLocaleString()}</b> {noun}</span>
      <span className="cv-pager-ctl">
        <label className="cv-pager-size">Rows per page
          <input className="cv-input" type="number" min="1" max={CV_MAX_PAGE} inputMode="numeric" value={draft} aria-label="Rows per page"
            onChange={e => setDraft(e.target.value)} onBlur={commit} onKeyDown={e => { if (e.key === 'Enter') e.target.blur(); }} />
        </label>
        <button className="cv-btn cv-soft" disabled={page <= 1} onClick={() => go(page - 1)}><IC name="arrow_left" size={13} />Previous</button>
        <span style={{ fontWeight: 700 }}>Page <b>{page}</b> of <b>{pages}</b></span>
        <button className="cv-btn cv-soft" disabled={page >= pages} onClick={() => go(page + 1)}>Next<IC name="chevron_r" size={13} /></button>
      </span>
    </nav>
  );
}

// The overview -> one-item flow shared by Clients, Users and Ticket Types.
// An item's page is one browser-history step above the overview, so Back returns to the list.
// pushed says whether THIS visit pushed that step; entries left over from earlier visits are ignored.
function useCvPage() {
  const [selId, setSelId] = React.useState(null);
  const pushed  = React.useRef(false);
  const wrapRef = React.useRef(null);
  const open = id => {
    setSelId(id);
    if (pushed.current) history.replaceState({ cvClient: id }, '');
    else { history.pushState({ cvClient: id }, ''); pushed.current = true; }
  };
  const close = () => { if (pushed.current) history.back(); else setSelId(null); };
  React.useEffect(() => {
    if (history.state && history.state.cvClient != null) history.replaceState(null, '');   // stale entry from an earlier visit
    const onPop = e => { const id = (e.state && e.state.cvClient) || null; pushed.current = id != null; setSelId(id); };
    window.addEventListener('popstate', onPop);
    return () => {
      window.removeEventListener('popstate', onPop);
      if (pushed.current) history.replaceState(null, '');                                   // leaving with an item open
    };
  }, []);
  React.useEffect(() => { if (wrapRef.current) wrapRef.current.scrollIntoView({ block: 'start' }); }, [selId]);
  return { selId, setSelId, open, close, wrapRef };
}

function CvAvatar({ name, size = 28, id, hasPhoto, version = 0 }) {
  if (hasPhoto && id) return <UserAvatar userId={id} name={name} hasPhoto version={version} size={size} />;
  const [bg, fg] = window.monogramFor(name);
  return <span aria-hidden="true" style={{ width: size, height: size, borderRadius: '50%', background: bg, color: fg, fontSize: Math.round(size * 0.4), fontWeight: 800, display: 'inline-grid', placeItems: 'center', flexShrink: 0 }}>{window.monogramLetters(name)}</span>;
}

function cvAge(raw) {
  const ms = Date.now() - new Date(raw).getTime();
  if (!isFinite(ms)) return '';
  const d = Math.floor(ms / 86400000);
  if (d >= 1) return d + 'd ago';
  const h = Math.floor(ms / 3600000);
  return h >= 1 ? h + 'h ago' : 'just now';
}

// shrink the picked file to at most 320px so logos stay tiny and consistent
async function cvPrepLogo(file) {
  if (!/^image\/(png|jpe?g|webp)$/.test(file.type)) throw new Error('Please choose a PNG, JPEG or WebP image');
  const bmp = await createImageBitmap(file);
  const scale = Math.min(1, 320 / Math.max(bmp.width, bmp.height));
  const c = document.createElement('canvas');
  c.width = Math.max(1, Math.round(bmp.width * scale));
  c.height = Math.max(1, Math.round(bmp.height * scale));
  c.getContext('2d').drawImage(bmp, 0, 0, c.width, c.height);
  return new Promise(res => c.toBlob(res, 'image/png'));
}

// a square, centred crop of the picked photo at 256px
async function cvPrepPhoto(file) {
  if (!/^image\/(png|jpe?g|webp)$/.test(file.type)) throw new Error('Please choose a PNG, JPEG or WebP image');
  const bmp = await createImageBitmap(file);
  const side = Math.min(bmp.width, bmp.height), out = Math.min(256, side);
  const c = document.createElement('canvas');
  c.width = out; c.height = out;
  const g = c.getContext('2d');
  g.fillStyle = '#fff'; g.fillRect(0, 0, out, out);
  g.drawImage(bmp, (bmp.width - side) / 2, (bmp.height - side) / 2, side, side, 0, 0, out, out);
  return new Promise(res => c.toBlob(res, 'image/jpeg', 0.9));
}

// ── page ────────────────────────────────────────────────────────────────────
function ClientsView({ tickets = [], onNav }) {
  const [clients, setClients]   = React.useState([]);
  const [agents, setAgents]     = React.useState([]);
  const [loading, setLoading]   = React.useState(true);
  const { selId, setSelId, open: openClientPage, close: closeClient, wrapRef } = useCvPage();
  const [query, setQuery]       = React.useState('');
  const [adding, setAdding]     = React.useState(false);
  const [newName, setNewName]   = React.useState('');
  const [toast, setToast]       = React.useState(null);        // { type, text }
  const [secret, setSecret]     = React.useState(null);        // { name, password } shown once after adding a user
  const [renaming, setRenaming] = React.useState(null);        // string while editing the name
  const [userForm, setUserForm] = React.useState(null);        // { name, email } while adding a user
  const [pickOpen, setPickOpen] = React.useState(false);
  const [pickQuery, setPickQuery] = React.useState('');
  const [drag, setDrag]         = React.useState(false);
  const [busy, setBusy]         = React.useState(false);
  const fileRef = React.useRef(null);
  const popRef  = React.useRef(null);

  const authH = (json = true) => ({ Authorization: 'Bearer ' + (localStorage.getItem('hub_token') || ''), ...(json ? { 'Content-Type': 'application/json' } : {}) });

  async function load(keepSel) {
    try {
      const [c, u] = await Promise.all([
        fetch('/api/clients', { headers: authH() }).then(r => r.ok ? r.json() : []),
        fetch('/api/users',   { headers: authH() }).then(r => r.ok ? r.json() : []),
      ]);
      setClients(c);
      setAgents(u.filter(x => x.role === 'agent' && x.status === 'active'));
      setSelId(cur => (keepSel && cur && c.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]);

  // close the developer picker on outside click or Escape
  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: authH(), 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;
  }

  const sel = clients.find(c => c.id === selId) || null;

  function openClient(id) { setRenaming(null); setUserForm(null); setSecret(null); setPickOpen(false); openClientPage(id); }

  // live ticket picture per client, from the tickets the app already holds
  const stats = React.useMemo(() => {
    const m = {};
    for (const t of tickets) {
      if (!t.clientId) continue;
      const s = m[t.clientId] || (m[t.clientId] = { open: 0, prog: 0, oldest: 0, all: [] });
      s.all.push(t);
      if (t.status === 'Open') s.open++;
      if (t.status === 'In Progress') s.prog++;
      if (t.status === 'Open' || t.status === 'In Progress') {
        const age = Math.floor((Date.now() - new Date(t.createdRaw).getTime()) / 86400000);
        if (isFinite(age)) s.oldest = Math.max(s.oldest, age);
      }
    }
    return m;
  }, [tickets]);

  const shown = clients.filter(c => c.name.toLowerCase().includes(query.trim().toLowerCase()));

  async function addClient(e) {
    e.preventDefault();
    const name = newName.trim();
    if (!name) return;
    const d = await call('POST', '/api/clients', { name }, `${name} added. Upload a logo and assign developers next.`);
    if (d) { setNewName(''); setAdding(false); openClient(d.id); }
  }

  async function uploadLogo(file) {
    if (!file || !sel) return;
    setBusy(true);
    try {
      const blob = await cvPrepLogo(file);
      const res = await fetch(`/api/clients/${sel.id}/logo`, { method: 'PUT', headers: { ...authH(false), 'Content-Type': 'image/png' }, body: blob });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.error || 'Upload failed');
      setToast({ type: 'ok', text: 'Logo updated.' });
      await load(true);
    } catch (err) { setToast({ type: 'error', text: err.message || 'Upload failed' }); }
    finally { setBusy(false); }
  }

  async function saveRename(e) {
    e.preventDefault();
    const name = (renaming || '').trim();
    if (!name || name === sel.name) { setRenaming(null); return; }
    if (await call('PUT', `/api/clients/${sel.id}`, { name }, 'Renamed.')) setRenaming(null);
  }

  async function addUser(e) {
    e.preventDefault();
    const d = await call('POST', `/api/clients/${sel.id}/users`, userForm);
    if (d) { setSecret({ name: d.name, password: d.tempPassword }); setUserForm(null); }
  }

  async function setDevelopers(ids, okText) { await call('PUT', `/api/clients/${sel.id}/developers`, { user_ids: ids }, okText); }

  const s = sel ? (stats[sel.id] || { open: 0, prog: 0, oldest: 0, all: [] }) : null;
  const assignedIds = sel ? sel.developers.map(d => d.id) : [];
  const pickable = agents.filter(a => !assignedIds.includes(a.id) && a.name.toLowerCase().includes(pickQuery.trim().toLowerCase()));
  const recent = s ? [...s.all].sort((a, b) => new Date(b.createdRaw) - new Date(a.createdRaw)).slice(0, 5) : [];
  const activeUsers = sel ? sel.users.filter(u => u.status === 'active') : [];

  return (
    <div className="cv1" ref={wrapRef}>
      <style>{CV_CSS + CV_PAGE_CSS}</style>

      {/* ── step 1: every client ── */}
      {!sel && (
        <div>
          <div className="cv-top">
            <div>
              <h2 style={{ margin: 0, fontSize: 28, fontWeight: 800, letterSpacing: '-0.02em', color: CV.navy }}>Clients <span style={{ color: CV.faint, fontWeight: 600, fontSize: 18 }}>{clients.length}</span></h2>
              <p style={{ margin: '4px 0 0', fontSize: 14, color: CV.mute }}>Pick a client to see its people, developers and concerns.</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 clients" value={query} onChange={e => setQuery(e.target.value)} aria-label="Search clients" />
              </div>
              <button className="cv-btn cv-primary" onClick={() => { setAdding(a => !a); setNewName(''); }}>
                <IC name="plus" size={14} color="#fff" strokeWidth={2.4} />New client
              </button>
            </div>
          </div>

          {adding && (
            <form onSubmit={addClient} 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="Client name" value={newName} onChange={e => setNewName(e.target.value)} aria-label="New client name" />
              <button className="cv-btn cv-primary" type="submit">Add client</button>
              <button className="cv-btn cv-ghost" type="button" onClick={() => setAdding(false)}>Cancel</button>
            </form>
          )}

          {loading && <div style={{ fontSize: 14, color: CV.faint }}>Loading clients…</div>}
          {!loading && clients.length === 0 && (
            <div className="cv-panel" style={{ padding: '56px 24px', textAlign: 'center', fontSize: 14, color: CV.mute, lineHeight: 1.6 }}>
              No clients yet.<br />Add your first client to start routing concerns to developers.
            </div>
          )}
          {!loading && clients.length > 0 && shown.length === 0 && <div style={{ fontSize: 14, color: CV.faint }}>No client matches “{query}”.</div>}

          <div className="cv-grid">
            {shown.map((c, i) => {
              const st = stats[c.id] || { open: 0, prog: 0, oldest: 0 };
              const people = c.users.filter(u => u.status === 'active').length;
              return (
                <button key={c.id} className="cv-tile" style={{ animationDelay: i * 45 + 'ms' }} onClick={() => openClient(c.id)}
                  aria-label={`${c.name}, ${c.developers.length} developers, ${people} users, ${st.open} open`}>
                  <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 10 }}>
                    <ClientLogo clientId={c.id} name={c.name} hasLogo={c.has_logo} version={c.logo_version} size={84} radius={22} />
                    {st.open > 0 && <span title={`${st.open} open`} style={{ fontSize: 13, fontWeight: 800, color: CV.orange, background: '#FDEEE6', borderRadius: 999, padding: '3px 11px', fontVariantNumeric: 'tabular-nums' }}>{st.open} open</span>}
                  </div>
                  <div>
                    <div style={{ fontSize: 18, fontWeight: 800, letterSpacing: '-0.01em', color: CV.navy }}>{c.name}</div>
                    <div style={{ fontSize: 13, color: CV.mute, marginTop: 3, fontVariantNumeric: 'tabular-nums' }}>
                      {people} user{people !== 1 ? 's' : ''} · {st.prog} in progress{st.open + st.prog > 0 ? ` · waiting ${st.oldest === 0 ? '<1' : st.oldest}d` : ''}
                    </div>
                  </div>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10, minHeight: 28 }}>
                    {c.developers.length === 0 ? (
                      <span style={{ fontSize: 12, fontWeight: 700, color: '#B45309', background: '#FFF7E8', borderRadius: 999, padding: '4px 10px' }}>No developer assigned</span>
                    ) : (
                      <>
                        <span className="cv-stack" style={{ display: 'inline-flex' }}>
                          {c.developers.slice(0, 4).map(d => <CvAvatar key={d.id} name={d.name} size={28} id={d.id} hasPhoto={d.has_photo} version={d.photo_version} />)}
                        </span>
                        <span style={{ fontSize: 12, color: CV.mute }}>{c.developers.length} developer{c.developers.length > 1 ? 's' : ''}</span>
                      </>
                    )}
                  </div>
                </button>
              );
            })}
          </div>
        </div>
      )}

      {/* ── step 2: one client ── */}
      {sel && (
        <nav className="cv-crumb" aria-label="Back">
          <button className="cv-btn cv-ghost" style={{ paddingLeft: 0 }} onClick={closeClient}><IC name="arrow_left" size={15} />Back to clients</button>
        </nav>
      )}
      {sel && <main className="cv-panel cv-detail" aria-live="polite">
        {sel && (
          <div key={sel.id} className="cv-enter">
            {/* header */}
            <div style={{ padding: '26px 28px 22px' }}>
              <div style={{ display: 'flex', gap: 22, alignItems: 'center', flexWrap: 'wrap' }}>
                <button
                  className={'cv-logo' + (drag ? ' cv-drag' : '')}
                  title={sel.has_logo ? 'Change logo' : 'Add a logo'}
                  aria-label={sel.has_logo ? 'Change logo' : 'Add a logo'}
                  onClick={() => fileRef.current && fileRef.current.click()}
                  onDragOver={e => { e.preventDefault(); setDrag(true); }}
                  onDragLeave={() => setDrag(false)}
                  onDrop={e => { e.preventDefault(); setDrag(false); uploadLogo(e.dataTransfer.files[0]); }}>
                  <ClientLogo clientId={sel.id} name={sel.name} hasLogo={sel.has_logo} version={sel.logo_version} size={124} radius={30} />
                  <span className="cv-over"><IC name="camera" size={20} color="#fff" />{busy ? 'Uploading…' : sel.has_logo ? 'Change' : 'Add logo'}</span>
                </button>
                <input ref={fileRef} type="file" accept="image/png,image/jpeg,image/webp" hidden onChange={e => { uploadLogo(e.target.files[0]); e.target.value = ''; }} />

                <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 client" 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="Client 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><b style={{ color: CV.blue }}>●</b>&nbsp; {s.open} open</span>
                    <span><b style={{ color: CV.orange }}>●</b>&nbsp; {s.prog} in progress</span>
                    <span>{s.open + s.prog > 0 ? `oldest waiting ${s.oldest === 0 ? 'under a day' : s.oldest + ' day' + (s.oldest > 1 ? 's' : '')}` : 'nothing waiting'}</span>
                  </div>
                  <div style={{ marginTop: 10, display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
                    <button className="cv-btn cv-soft" onClick={() => fileRef.current && fileRef.current.click()} disabled={busy}>
                      <IC name="camera" size={14} />{busy ? 'Uploading…' : sel.has_logo ? 'Change logo' : 'Upload logo'}
                    </button>
                    {sel.has_logo && (
                      <button className="cv-btn cv-ghost" onClick={() => window.confirm('Remove this logo?') && call('DELETE', `/api/clients/${sel.id}/logo`, null, 'Logo removed.')}>Remove logo</button>
                    )}
                    {!sel.has_logo && <span style={{ fontSize: 12, color: CV.faint }}>PNG, JPEG or WebP. You can also drop an image on the logo.</span>}
                  </div>
                </div>
              </div>
            </div>

            {/* developers */}
            <section className="cv-section" aria-labelledby="cv-dev">
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
                <h3 id="cv-dev" className="cv-h" style={{ margin: 0 }}>Developers <small>{sel.developers.length}</small></h3>
                <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} />Add developer
                  </button>
                  {pickOpen && (
                    <div className="cv-pop cv-enter" role="listbox" aria-label="Developers to add">
                      <input className="cv-input" style={{ width: '100%', boxSizing: 'border-box', marginBottom: 6 }} autoFocus placeholder="Search developers" 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 }}>{agents.length === 0 ? 'No developers exist yet. Add some under Users.' : 'Everyone is already assigned.'}</div>}
                        {pickable.map(a => (
                          <button key={a.id} role="option" aria-label={`${a.name}, ${a.email}`} className="cv-row" style={{ padding: '8px 8px' }}
                            onClick={() => { setPickOpen(false); setDevelopers([...assignedIds, a.id], `${a.name} assigned to ${sel.name}.`); }}>
                            <CvAvatar name={a.name} size={30} id={a.id} hasPhoto={a.has_photo} version={a.photo_version} />
                            <span style={{ minWidth: 0 }}>
                              <span style={{ display: 'block', fontSize: 13, fontWeight: 700 }}>{a.name}</span>
                              <span style={{ display: 'block', fontSize: 12, color: CV.faint, overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.email}</span>
                            </span>
                          </button>
                        ))}
                      </div>
                    </div>
                  )}
                </div>
              </div>
              {sel.developers.length === 0 ? (
                <div style={{ fontSize: 13, color: '#B45309', background: '#FFF7E8', borderRadius: 10, padding: '12px 14px' }}>
                  No developer is assigned, so nobody can pick up this client's concerns. Add at least one.
                </div>
              ) : (
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
                  {sel.developers.map(d => (
                    <span key={d.id} className="cv-chip" title={d.email}>
                      <CvAvatar name={d.name} size={26} id={d.id} hasPhoto={d.has_photo} version={d.photo_version} />{d.name}
                      <button aria-label={`Unassign ${d.name}`} title="Unassign" onClick={() => setDevelopers(assignedIds.filter(x => x !== d.id), `${d.name} unassigned.`)}><IC name="x" size={13} strokeWidth={2.2} /></button>
                    </span>
                  ))}
                </div>
              )}
            </section>

            {/* users */}
            <section className="cv-section" aria-labelledby="cv-usr">
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
                <h3 id="cv-usr" className="cv-h" style={{ margin: 0 }}>People at {sel.name} <small>{activeUsers.length}</small></h3>
                {!userForm && <button className="cv-btn cv-soft" onClick={() => { setUserForm({ name: '', email: '' }); setSecret(null); }}><IC name="plus" size={14} strokeWidth={2.4} />Add user</button>}
              </div>

              {userForm && (
                <form onSubmit={addUser} className="cv-enter" style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 12 }}>
                  <input className="cv-input" style={{ flex: '1 1 150px' }} autoFocus placeholder="Full name" value={userForm.name} onChange={e => setUserForm(f => ({ ...f, name: e.target.value }))} required aria-label="Full name" />
                  <input className="cv-input" style={{ flex: '2 1 210px' }} type="email" placeholder="Email address" value={userForm.email} onChange={e => setUserForm(f => ({ ...f, email: e.target.value }))} required aria-label="Email address" />
                  <button className="cv-btn cv-primary" type="submit">Add user</button>
                  <button className="cv-btn cv-ghost" type="button" onClick={() => setUserForm(null)}>Cancel</button>
                </form>
              )}

              {secret && (
                <div className="cv-enter" role="status" style={{ background: '#ECFDF5', border: '1px solid #A7F3D0', borderRadius: 12, padding: '12px 14px', marginBottom: 12, display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
                  <div style={{ flex: 1, minWidth: 200 }}>
                    <div style={{ fontSize: 13, fontWeight: 700, color: '#065F46' }}>{secret.name} can now sign in</div>
                    <div style={{ fontSize: 12, color: '#047857', marginTop: 2 }}>Share this temporary password 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', letterSpacing: '0.02em' }}>{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>
              )}

              {sel.users.length === 0 && !userForm && <div style={{ fontSize: 13, color: CV.faint }}>No one from {sel.name} has an account yet.</div>}
              {sel.users.map(u => (
                <div key={u.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '9px 0', opacity: u.status === 'active' ? 1 : 0.5 }}>
                  <CvAvatar name={u.name} size={34} id={u.id} hasPhoto={u.has_photo} version={u.photo_version} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 14, fontWeight: 700, color: CV.ink }}>{u.name}{u.status !== 'active' && <span style={{ fontSize: 11, fontWeight: 700, color: CV.faint, marginLeft: 8 }}>Deactivated</span>}</div>
                    <div style={{ fontSize: 12, color: CV.mute, overflow: 'hidden', textOverflow: 'ellipsis' }}>{u.email}</div>
                  </div>
                  {u.status === 'active' && (
                    <button className="cv-btn cv-danger" onClick={() => window.confirm(`Deactivate ${u.name}? They will no longer be able to sign in.`) && call('DELETE', `/api/clients/${sel.id}/users/${u.id}`, null, `${u.name} deactivated.`)}>Remove</button>
                  )}
                </div>
              ))}
            </section>

            {/* recent concerns */}
            <section className="cv-section" aria-labelledby="cv-rec">
              <h3 id="cv-rec" className="cv-h">Recent concerns <small>{s.all.length} in total</small></h3>
              {recent.length === 0 && <div style={{ fontSize: 13, color: CV.faint }}>No concerns raised for {sel.name} 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.status}{t.priority === 'High' || t.priority === 'Urgent' ? ' · ' + t.priority : ''}</span>
                  </span>
                  <span style={{ fontSize: 12, color: CV.faint, whiteSpace: 'nowrap' }}>{cvAge(t.createdRaw)}</span>
                </button>
              ))}
            </section>

            <div className="cv-section" style={{ display: 'flex', justifyContent: 'flex-end' }}>
              <button className="cv-btn cv-danger" onClick={() => window.confirm(`Delete ${sel.name}? Its users will be deactivated. This only works when it has no concerns.`) && call('DELETE', `/api/clients/${sel.id}`, null, `${sel.name} deleted.`)}>
                <IC name="trash" size={14} />Delete client
              </button>
            </div>
          </div>
        )}
      </main>}

      {toast && (
        <div role="status" 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)',
        }} className="cv-enter">{toast.text}</div>
      )}
    </div>
  );
}

Object.assign(window, { ClientsView });
