// TicketViews — TicketList, TicketDetail, SubmitTicketModal, normalizeTicket, DeleteConfirmModal
// Exports: TicketList, TicketDetail, SubmitTicketModal, normalizeTicket, DeleteConfirmModal

function ListExportBtn({ tickets, filename }) {
  const [exporting, setExporting] = React.useState(false);
  async function handleExport() {
    setExporting(true);
    try {
      await window.exportTicketsToExcel(tickets, filename);
    } catch (e) { console.error('Export failed:', e); alert('Export failed — please try again.'); }
    finally { setExporting(false); }
  }
  return (
    <button onClick={handleExport} disabled={exporting}
      title="Export to Excel"
      onMouseEnter={e => { if (!exporting) { e.currentTarget.style.background = '#F0FDF4'; e.currentTarget.style.borderColor = '#16A34A'; }}}
      onMouseLeave={e => { e.currentTarget.style.background = '#fff'; e.currentTarget.style.borderColor = '#E2E8F0'; }}
      style={{
        display: 'flex', alignItems: 'center', gap: 6,
        padding: '10px 14px', borderRadius: 8,
        border: '1.5px solid #E2E8F0', background: '#fff',
        fontSize: 13, fontWeight: 600, color: exporting ? '#94A3B8' : '#15803D',
        cursor: exporting ? 'wait' : 'pointer', fontFamily: 'inherit',
        transition: 'all 0.15s', whiteSpace: 'nowrap',
      }}>
      <IC name="download" size={14} color={exporting ? '#94A3B8' : '#16A34A'} />
      {exporting ? 'Exporting…' : 'Export'}
    </button>
  );
}

// 2h 14m, 45m 10s, 30s. Time worked runs from In Progress until Resolved.
function fmtDuration(sec) {
  sec = Math.max(0, Math.floor(Number(sec) || 0));
  const d = Math.floor(sec / 86400), h = Math.floor((sec % 86400) / 3600), m = Math.floor((sec % 3600) / 60), s = sec % 60;
  if (d) return d + 'd ' + h + 'h';
  if (h) return h + 'h ' + String(m).padStart(2, '0') + 'm';
  if (m) return m + 'm ' + String(s).padStart(2, '0') + 's';
  return s + 's';
}

// Total time worked on a concern; keeps counting every second while it is In Progress
function useWorked(ticket) {
  const [, tick] = React.useState(0);
  React.useEffect(() => {
    if (!ticket.workRunning) return;
    const t = setInterval(() => tick(n => n + 1), 1000);
    return () => clearInterval(t);
  }, [ticket.workRunning, ticket.fetchedAt]);
  return ticket.workSeconds + (ticket.workRunning ? (Date.now() - (ticket.fetchedAt || Date.now())) / 1000 : 0);
}

function WorkedTime({ ticket, style }) {
  const sec = useWorked(ticket);
  if (!ticket.workRunning && ticket.workSeconds === 0) return <span style={{ color: '#CBD5E1', ...style }}>—</span>;
  return <span style={{ fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap', ...style }}>{ticket.workRunning && <span aria-hidden="true" style={{ display: 'inline-block', width: 7, height: 7, borderRadius: '50%', background: '#B45309', marginRight: 6, verticalAlign: 'middle' }} />}{fmtDuration(sec)}</span>;
}

function normalizeTicket(t) {
  function mkInitials(name) {
    return (name || '?').split(' ').filter(Boolean).map(n => n[0]).join('').toUpperCase().slice(0, 2);
  }
  function fmtDate(val) {
    if (!val) return '—';
    try { return new Date(val).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); }
    catch { return String(val); }
  }
  return {
    dbId:        t.id,
    id:          t.ticket_no || `TKT-${String(t.id).padStart(4, '0')}`,
    categoryId:  t.type_id != null ? String(t.type_id) : null,   // ticket type (kept under the old field name)
    clientId:    t.client_id || null,
    clientName:  t.client_name || null,
    title:       t.title,
    description: t.description || '',
    status:      t.status,
    priority:    t.priority,
    submittedBy: {
      id:       t.submitted_by,
      name:     t.submitted_by_name || 'Unknown',
      initials: mkInitials(t.submitted_by_name),
      role:     t.submitted_by_role || null,
    },
    workSeconds: Number(t.work_total) || 0,
    workRunning: !!Number(t.work_running),
    fetchedAt:   Date.now(),
    assignedTo: t.assigned_to ? {
      id:       t.assigned_to,
      name:     t.assigned_to_name || 'Unknown',
      initials: mkInitials(t.assigned_to_name),
    } : null,
    createdAt: fmtDate(t.created_at),
    createdRaw: t.created_at,
    clientHasLogo: !!t.client_has_logo,
    clientLogoVer: t.client_logo_version || 0,
    updatedAt: fmtDate(t.updated_at),
    comments: (t.comments || []).map(c => ({
      author: { name: c.author_name || 'Unknown', initials: mkInitials(c.author_name) },
      text:   c.comment,
      time:   fmtDate(c.created_at),
    })),
  };
}

// ── Submit Ticket Modal ──────────────────────────────────────────────────────
function SubmitTicketModal({ onClose, initialCategoryId, onSubmit }) {
  const types   = window.MOCK.categories;               // admin-managed ticket types, loaded at start-up
  const isAdmin = window._synUser?.role === 'admin';
  const isAgent = window._synUser?.role === 'agent';
  const picksClient = isAdmin || isAgent;               // admins pick any client, developers one of theirs
  const [clients, setClients] = React.useState([]);
  const [form, setForm] = React.useState({
    typeId: initialCategoryId || types[0]?.id || '',
    clientId: '', title: '', description: '', priority: 'Medium',
  });
  const [submitted, setSubmitted]     = React.useState(false);
  const [submitting, setSubmitting]   = React.useState(false);
  const [submitError, setSubmitError] = React.useState(null);

  const authH = () => ({ 'Content-Type': 'application/json', Authorization: 'Bearer ' + (localStorage.getItem('hub_token') || '') });
  const set = (key, val) => setForm(f => ({ ...f, [key]: val }));

  React.useEffect(() => {
    if (!picksClient) return;
    fetch(isAdmin ? '/api/clients' : '/api/clients/mine', { headers: authH() })
      .then(r => r.ok ? r.json() : [])
      .then(setClients)
      .catch(() => {});
  }, []);

  async function handleSubmit(e) {
    e.preventDefault();
    if (!form.title.trim() || !form.description.trim()) return;
    if (picksClient && !form.clientId) { setSubmitError('Please choose the client this concern is for'); return; }
    setSubmitting(true);
    setSubmitError(null);
    try {
      const res = await fetch('/api/tickets', {
        method: 'POST',
        headers: authH(),
        body: JSON.stringify({
          type_id:     Number(form.typeId),
          client_id:   picksClient ? Number(form.clientId) : undefined,
          title:       form.title.trim(),
          description: form.description.trim(),
          priority:    isAdmin ? form.priority : undefined,
        }),
      });
      if (!res.ok) {
        const err = await res.json();
        throw new Error(err.error || 'Failed to submit concern');
      }
      onSubmit();
      setSubmitted(true);
    } catch (err) {
      setSubmitError(err.message);
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <div style={{
      position: 'fixed', inset: 0, background: 'rgba(15,37,64,0.55)', zIndex: 1000,
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }} onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{
        background: '#fff', borderRadius: 16, width: '100%', maxWidth: 560,
        boxShadow: '0 24px 80px rgba(0,0,0,0.18)', overflow: 'hidden',
        display: 'flex', flexDirection: 'column', maxHeight: '90vh',
      }}>
        <div style={{ background: '#0F2540', padding: '22px 28px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
          <div>
            <div style={{ color: '#fff', fontWeight: 800, fontSize: 17 }}>Submit New Concern</div>
            <div style={{ color: 'rgba(255,255,255,0.5)', fontSize: 12, marginTop: 2 }}>We'll get back to you as soon as possible</div>
          </div>
          <button onClick={onClose} style={{ background: 'rgba(255,255,255,0.1)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', color: '#fff', display: 'flex' }}>
            <IC name="x" size={16} color="#fff" />
          </button>
        </div>

        {submitted ? (
          <div style={{ padding: '48px 28px', textAlign: 'center' }}>
            <div style={{ width: 56, height: 56, borderRadius: '50%', background: '#ECFDF5', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 16px' }}>
              <IC name="check" size={28} color="#10B981" strokeWidth={2.5} />
            </div>
            <div style={{ fontSize: 18, fontWeight: 800, color: '#0F172A', marginBottom: 8 }}>Concern Raised!</div>
            <div style={{ color: '#64748B', fontSize: 14, marginBottom: 24 }}>Your concern has been raised and will be reviewed shortly.</div>
            <Btn onClick={onClose} variant="navy">Close</Btn>
          </div>
        ) : (
          <form onSubmit={handleSubmit} style={{ padding: '24px 28px', display: 'flex', flexDirection: 'column', gap: 18, overflowY: 'auto', flex: 1 }}>
            {picksClient && (
              <div>
                <label style={labelStyle}>Client <span style={{ color: BRAND_ORANGE }}>*</span></label>
                <select value={form.clientId} onChange={e => set('clientId', e.target.value)} required style={{ ...inputStyle, cursor: 'pointer' }}>
                  <option value="">{isAgent && clients.length === 0 ? 'You are not linked to any client yet' : 'Choose a client'}</option>
                  {clients.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
                </select>
              </div>
            )}

            <div>
              <label style={labelStyle}>Type</label>
              {types.length === 0 && <div style={{ fontSize: 13, color: '#B91C1C', marginTop: 6 }}>No ticket types exist yet. Ask an admin to add some.</div>}
              <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, marginTop: 6 }}>
                {types.map(t => (
                  <button key={t.id} type="button" onClick={() => set('typeId', t.id)} style={{
                    padding: '8px 10px', border: `2px solid ${form.typeId === t.id ? t.color : '#E2E8F0'}`,
                    borderRadius: 8, cursor: 'pointer', background: form.typeId === t.id ? t.bg : '#fff',
                    fontFamily: 'inherit', fontSize: 11, fontWeight: 600, color: form.typeId === t.id ? t.color : '#64748B',
                    transition: 'all 0.12s', textAlign: 'center', lineHeight: 1.3,
                    display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
                  }}>{t.icon && <TypeIcon name={t.icon} size={14} />}{t.label}</button>
                ))}
              </div>
            </div>

            <div>
              <label style={labelStyle}>Subject <span style={{ color: BRAND_ORANGE }}>*</span></label>
              <input style={inputStyle} placeholder="Brief summary of the issue..." value={form.title}
                onChange={e => set('title', e.target.value)} required />
            </div>

            {isAdmin && (
              <div>
                <label style={labelStyle}>Priority</label>
                <div style={{ display: 'flex', gap: 8, marginTop: 6 }}>
                  {['Low', 'Medium', 'High', 'Urgent'].map(p => (
                    <button key={p} type="button" onClick={() => set('priority', p)} style={{
                      flex: 1, padding: '8px 0', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', fontSize: 12, fontWeight: 600,
                      border: `2px solid ${form.priority === p ? BRAND_ORANGE : '#E2E8F0'}`,
                      background: form.priority === p ? '#FFF4EE' : '#fff', color: form.priority === p ? BRAND_ORANGE : '#64748B',
                    }}>{p}</button>
                  ))}
                </div>
              </div>
            )}

            <div>
              <label style={labelStyle}>Description <span style={{ color: BRAND_ORANGE }}>*</span></label>
              <textarea style={{ ...inputStyle, minHeight: 110, resize: 'vertical' }}
                placeholder="Describe the issue in detail: steps to reproduce, expected outcome, and any other relevant information..."
                value={form.description} onChange={e => set('description', e.target.value)} required />
            </div>

            {submitError && <div style={{ padding: '10px 14px', background: '#FEF2F2', color: '#B91C1C', borderRadius: 8, fontSize: 13 }}>{submitError}</div>}

            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10 }}>
              <Btn type="button" variant="ghost" onClick={onClose}>Cancel</Btn>
              <Btn type="submit" variant="primary" disabled={submitting || types.length === 0}>{submitting ? 'Submitting…' : 'Submit Concern'}</Btn>
            </div>
          </form>
        )}
      </div>
    </div>
  );
}

const labelStyle = { fontSize: 12, fontWeight: 700, color: '#374151', textTransform: 'uppercase', letterSpacing: '0.05em', display: 'block' };
const inputStyle = {
  width: '100%', padding: '10px 14px', borderRadius: 8, border: '1.5px solid #E2E8F0',
  fontSize: 14, fontFamily: 'inherit', color: '#1E293B', marginTop: 6, outline: 'none',
  boxSizing: 'border-box', background: '#FAFBFF',
};

// ── Delete Confirm Modal ─────────────────────────────────────────────────────
function DeleteConfirmModal({ count, onCancel, onConfirm, deleting }) {
  return (
    <div style={{
      position: 'fixed', inset: 0, background: 'rgba(15,37,64,0.6)', zIndex: 2000,
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }} onClick={e => e.target === e.currentTarget && !deleting && onCancel()}>
      <div style={{ background: '#fff', borderRadius: 16, width: '100%', maxWidth: 440, boxShadow: '0 24px 80px rgba(0,0,0,0.2)', overflow: 'hidden' }}>
        <div style={{ background: '#0F2540', padding: '22px 28px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div>
            <div style={{ color: '#fff', fontWeight: 800, fontSize: 16 }}>Delete Concern{count > 1 ? 's' : ''}</div>
            <div style={{ color: 'rgba(255,255,255,0.5)', fontSize: 12, marginTop: 3 }}>This action cannot be undone</div>
          </div>
          <button onClick={onCancel} disabled={deleting} style={{ background: 'rgba(255,255,255,0.1)', border: 'none', borderRadius: 8, padding: 8, cursor: 'pointer', display: 'flex' }}>
            <IC name="x" size={16} color="#fff" />
          </button>
        </div>
        <div style={{ padding: '28px' }}>
          <div style={{ background: '#FEF2F2', border: '1.5px solid #FECACA', borderRadius: 10, padding: '14px 18px', marginBottom: 24, display: 'flex', gap: 12, alignItems: 'flex-start' }}>
            <IC name="alert" size={20} color="#B91C1C" style={{ flexShrink: 0, marginTop: 1 }} />
            <div>
              <div style={{ fontSize: 14, fontWeight: 700, color: '#B91C1C', marginBottom: 6 }}>
                You are about to permanently delete {count} concern{count > 1 ? 's' : ''}
              </div>
              <div style={{ fontSize: 13, color: '#991B1B', lineHeight: 1.5 }}>
                Once deleted, this data cannot be recovered. Are you sure you want to proceed?
              </div>
            </div>
          </div>
          <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
            <Btn variant="ghost" onClick={onCancel} disabled={deleting}>Cancel</Btn>
            <Btn variant="danger" onClick={onConfirm} disabled={deleting}
              style={{ background: '#DC2626', color: '#fff', opacity: deleting ? 0.6 : 1 }}>
              {deleting ? 'Deleting…' : `Delete ${count > 1 ? count + ' Concerns' : 'Permanently'}`}
            </Btn>
          </div>
        </div>
      </div>
    </div>
  );
}

// ── Ticket Row ───────────────────────────────────────────────────────────────
// One grid for the header and every row. In a narrow list the date and chevron columns drop away so the title keeps its room.
const TL_CSS = `
.tl-wrap { container-type: inline-size; container-name: tl; }
.tl-grid { display: grid; align-items: center; column-gap: 20px; padding: 0 20px; grid-template-columns: 28px 10px minmax(0, 1fr) 76px 90px 120px 84px 92px 20px; }
.tl-grid.tl-client { grid-template-columns: 10px minmax(0, 1fr) 76px 90px 120px 84px 92px 20px; }
.tl-time { font-size: 12.5px; color: #475569; font-weight: 600; }
.tl-assg { justify-self: center; }
.tl-by { display: none !important; }
@container tl (min-width: 1240px) {
  .tl-grid { grid-template-columns: 28px 10px minmax(0, 1fr) 76px 90px 120px 84px 170px 92px 20px; }
  .tl-grid.tl-client { grid-template-columns: 10px minmax(0, 1fr) 76px 90px 120px 84px 170px 92px 20px; }
  .tl-by { display: flex !important; }
  .tl-byinline { display: none; }
}
@container tl (max-width: 979px) {
  .tl-grid { grid-template-columns: 28px 10px minmax(0, 1fr) 76px 90px 120px; }
  .tl-grid.tl-client { grid-template-columns: 10px minmax(0, 1fr) 76px 90px 120px; }
  .tl-date, .tl-chev, .tl-time { display: none !important; }
}
.tl-badges { display: contents; }
@container tl (max-width: 759px) {
  .tl-grid { grid-template-columns: 28px 10px minmax(0, 1fr); row-gap: 0; }
  .tl-grid.tl-client { grid-template-columns: 10px minmax(0, 1fr); }
  .tl-badges { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; grid-column: -2 / -1; }
  .tl-grid > .tl-dot { align-self: start; margin-top: 7px; }
  .tl-grid > .tl-check { align-self: start; margin-top: 1px; }
  .tl-hdr { display: flex; flex-wrap: wrap; gap: 6px 18px; }
  .tl-n-hide { display: none !important; }
  .tl-badges .tl-time { display: inline !important; }
  .tl-badges .tl-empty { display: none !important; }
}
`;

function TicketRow({ ticket, onClick, isAdmin, isSelected, onToggle }) {
  const [hov, setHov] = React.useState(false);
  const cat = window.MOCK.categories.find(c => c.id === ticket.categoryId);
  return (
    <div onClick={() => onClick(ticket)} className={'tl-grid' + (isAdmin ? '' : ' tl-client')}
      onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)}
      style={{
        paddingTop: 15, paddingBottom: 15,
        background: isSelected ? '#FFF5F5' : hov ? '#FAFBFF' : '#fff', cursor: 'pointer',
        borderBottom: '1px solid #F1F5F9', transition: 'background 0.1s',
      }}>
      {isAdmin && (
        <div className="tl-check" onClick={e => { e.stopPropagation(); onToggle(ticket.dbId); }}
          style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <input type="checkbox" checked={!!isSelected} onChange={() => {}} aria-label={`Select ${ticket.id}`}
            style={{ cursor: 'pointer', width: 14, height: 14, accentColor: '#DC2626' }} />
        </div>
      )}
      {/* Type dot */}
      <div className="tl-dot" style={{ width: 8, height: 8, borderRadius: '50%', background: cat?.color || '#94A3B8', justifySelf: 'center' }} />
      {/* Title + meta line */}
      <div style={{ minWidth: 0 }}>
        <div style={{ fontSize: 15, fontWeight: 700, color: '#1E293B', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{ticket.title}</div>
        <div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: '5px 8px', marginTop: 5 }}>
          <span style={{ fontSize: 12, color: '#94A3B8', fontFamily: 'monospace', whiteSpace: 'nowrap' }}>{ticket.id}</span>
          <span style={{ fontSize: 12, color: cat?.color, fontWeight: 700 }}>{cat?.label}</span>
          {ticket.clientName && window._synUser?.role !== 'client' && (
            <>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 12.5, fontWeight: 700, color: '#475569', background: '#F1F5F9', padding: '2px 11px 2px 3px', borderRadius: 999, whiteSpace: 'nowrap' }}>
              <ClientLogo clientId={ticket.clientId} name={ticket.clientName} hasLogo={ticket.clientHasLogo} version={ticket.clientLogoVer} size={24} radius={12} />{ticket.clientName}
            </span>
            </>
          )}
          <span className="tl-byinline" style={{ fontSize: 12, color: '#94A3B8', whiteSpace: 'nowrap' }}>by {ticket.submittedBy.name}</span>
        </div>
      </div>
      <div className="tl-badges">
      {/* Assigned To — avatar only with tooltip */}
      <div className={'tl-assg' + (ticket.assignedTo ? '' : ' tl-empty')} title={ticket.assignedTo ? ticket.assignedTo.name : 'Unassigned'}>
        {ticket.assignedTo
          ? <Avatar name={ticket.assignedTo.name} initials={ticket.assignedTo.initials} size={32} />
          : <span style={{ fontSize: 11, color: '#CBD5E1', fontStyle: 'italic' }}>—</span>}
      </div>
      {/* Priority */}
      <PriorityBadge priority={ticket.priority} />
      {/* Status */}
      <StatusBadge status={ticket.status} />
      <span className={'tl-time' + (ticket.workRunning || ticket.workSeconds > 0 ? '' : ' tl-empty')} title="Time worked"><WorkedTime ticket={ticket} /></span>
      </div>
      {/* Raised by */}
      <div className="tl-by" title={'Raised by ' + ticket.submittedBy.name} style={{ alignItems: 'center', gap: 9, minWidth: 0 }}>
        <Avatar name={ticket.submittedBy.name} initials={ticket.submittedBy.initials} size={28} />
        <span style={{ fontSize: 13, fontWeight: 600, color: '#475569', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{ticket.submittedBy.name}</span>
      </div>
      {/* Updated */}
      <span className="tl-date" style={{ fontSize: 12.5, color: '#64748B', whiteSpace: 'nowrap' }}>{ticket.updatedAt}</span>
      {/* Chevron */}
      <span className="tl-chev" style={{ display: 'flex' }}><IC name="chevron_r" size={14} color="#CBD5E1" /></span>
    </div>
  );
}

// ── Ticket List ──────────────────────────────────────────────────────────────
function TicketList({ tickets: allTickets, categoryId, onTicketClick, onNewTicket, onRefresh }) {
  const { categories } = window.MOCK;
  const cat = categories.find(c => c.id === categoryId);
  const visibleCats = categories.filter(c => !c.adminOnly);
  const [search, setSearch] = React.useState('');
  const [filterStatus, setFilterStatus] = React.useState('All');
  const [filterPriority, setFilterPriority] = React.useState('All');
  const [filterCategory, setFilterCategory] = React.useState('All');
  const [sortCol, setSortCol] = React.useState('status');
  const [sortDir, setSortDir] = React.useState('asc');
  const [perPage, setPerPage] = React.useState(10);
  const [perPageInput, setPerPageInput] = React.useState('10');
  const [currentPage, setCurrentPage] = React.useState(1);
  const [selected, setSelected] = React.useState(new Set());
  const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
  const [deleting, setDeleting] = React.useState(false);

  React.useEffect(() => { setCurrentPage(1); }, [search, filterStatus, filterPriority, filterCategory, sortCol, sortDir, perPage]);

  const isAdmin = window._synUser?.role === 'admin';
  const isMyTickets = categoryId === 'my_tickets';
  const source = isMyTickets
    ? allTickets
    : allTickets.filter(t => t.categoryId === categoryId);

  const STATUS_RANK    = { 'Open': 0, 'In Progress': 1, 'Resolved': 2, 'Closed': 3 };
  const PRIORITY_RANK  = { 'Urgent': 0, 'High': 1, 'Medium': 2, 'Low': 3 };

  function handleSort(col) {
    if (sortCol === col) setSortDir(d => d === 'asc' ? 'desc' : 'asc');
    else { setSortCol(col); setSortDir('asc'); }
  }

  const filtered = source.filter(t => {
    const matchSearch = !search || t.title.toLowerCase().includes(search.toLowerCase()) || t.id.toLowerCase().includes(search.toLowerCase());
    const matchStatus = filterStatus === 'All' || t.status === filterStatus;
    const matchPri    = filterPriority === 'All' || t.priority === filterPriority;
    const matchCat    = filterCategory === 'All' || t.categoryId === filterCategory;
    return matchSearch && matchStatus && matchPri && matchCat;
  }).sort((a, b) => {
    const dir = sortDir === 'asc' ? 1 : -1;
    if (sortCol === 'title')    return dir * a.title.localeCompare(b.title);
    if (sortCol === 'priority') return dir * ((PRIORITY_RANK[a.priority] ?? 99) - (PRIORITY_RANK[b.priority] ?? 99));
    if (sortCol === 'status')   return dir * ((STATUS_RANK[a.status] ?? 99) - (STATUS_RANK[b.status] ?? 99));
    if (sortCol === 'by')       return dir * (a.submittedBy?.name || '').localeCompare(b.submittedBy?.name || '');
    if (sortCol === 'date')     return dir * String(b.updatedAt).localeCompare(String(a.updatedAt));
    return 0;
  });

  const totalPages = Math.max(1, Math.ceil(filtered.length / perPage));
  const paginated  = filtered.slice((currentPage - 1) * perPage, currentPage * perPage);

  const allOnPageSelected = paginated.length > 0 && paginated.every(t => selected.has(t.dbId));
  const someOnPageSelected = paginated.some(t => selected.has(t.dbId));

  function authH() { return { Authorization: 'Bearer ' + (localStorage.getItem('hub_token') || '') }; }
  function toggleSelect(dbId) {
    setSelected(prev => { const s = new Set(prev); s.has(dbId) ? s.delete(dbId) : s.add(dbId); return s; });
  }
  function toggleSelectAll() {
    const allIds = paginated.map(t => t.dbId);
    setSelected(prev => {
      const s = new Set(prev);
      allIds.forEach(id => allOnPageSelected ? s.delete(id) : s.add(id));
      return s;
    });
  }
  async function handleBulkDelete() {
    setDeleting(true);
    try {
      await fetch('/api/tickets/bulk', {
        method: 'DELETE',
        headers: { 'Content-Type': 'application/json', ...authH() },
        body: JSON.stringify({ ids: [...selected] }),
      });
      setSelected(new Set());
      setShowDeleteConfirm(false);
      if (onRefresh) onRefresh();
    } catch (e) { console.error(e); }
    finally { setDeleting(false); }
  }

  const title = categoryId === 'my_tickets' ? 'My Tickets' : cat?.label || 'Tickets';
  const color = cat?.color || '#1A5090';
  const bg    = cat?.bg    || '#E8EFF8';

  const statusCounts = { All: source.length, Open: 0, 'In Progress': 0, Resolved: 0, Closed: 0 };
  source.forEach(t => { if (statusCounts[t.status] !== undefined) statusCounts[t.status]++; });

  return (
    <div>

      {/* Status tabs */}
      <div style={{ display: 'flex', gap: 4, marginBottom: 16, background: '#F1F5F9', borderRadius: 10, padding: 4, width: 'fit-content' }}>
        {['All', 'Open', 'In Progress', 'Resolved', 'Closed'].map(s => (
          <button key={s} onClick={() => setFilterStatus(s)} style={{
            padding: '6px 14px', borderRadius: 7, border: 'none', cursor: 'pointer',
            fontFamily: 'inherit', fontSize: 12, fontWeight: 600,
            background: filterStatus === s ? '#fff' : 'transparent',
            color: filterStatus === s ? '#1E293B' : '#64748B',
            boxShadow: filterStatus === s ? '0 1px 4px rgba(0,0,0,0.08)' : 'none',
            transition: 'all 0.12s',
          }}>
            {s} {s !== 'All' && statusCounts[s] > 0 && <span style={{ color: '#94A3B8', marginLeft: 3 }}>{statusCounts[s]}</span>}
          </button>
        ))}
      </div>

      {/* Search + filter bar */}
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, marginBottom: 16, alignItems: 'stretch' }}>
        <div style={{ flex: '1 1 240px', minWidth: 0, position: 'relative' }}>
          <IC name="search" size={15} color="#94A3B8" style={{ position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none' }} />
          <input value={search} onChange={e => setSearch(e.target.value)}
            placeholder="Search concerns by title or ID..."
            style={{ ...inputStyle, paddingLeft: 36, marginTop: 0, width: '100%', height: '100%', boxSizing: 'border-box' }} />
        </div>
        {isMyTickets && (
          <select value={filterCategory} onChange={e => setFilterCategory(e.target.value)} style={{
            padding: '10px 14px', borderRadius: 8, border: '1.5px solid #E2E8F0',
            fontSize: 14, fontFamily: 'inherit', color: '#374151', background: '#fff', cursor: 'pointer',
          }}>
            <option value="All">All Types</option>
            {visibleCats.map(c => <option key={c.id} value={c.id}>{c.label}</option>)}
          </select>
        )}
        {window._synUser?.role === 'admin' && (
          <select value={filterPriority} onChange={e => setFilterPriority(e.target.value)} style={{
            padding: '10px 14px', borderRadius: 8, border: '1.5px solid #E2E8F0',
            fontSize: 14, fontFamily: 'inherit', color: '#374151', background: '#fff', cursor: 'pointer',
          }}>
            <option value="All">All Priorities</option>
            {['Urgent', 'High', 'Medium', 'Low'].map(p => <option key={p} value={p}>{p}</option>)}
          </select>
        )}
        <ListExportBtn tickets={allTickets} filename={isMyTickets ? 'my-concerns' : (cat?.label || 'concerns').toLowerCase().replace(/\s+/g, '-')} />
        {isAdmin && selected.size > 0 && (
          <button onClick={() => setShowDeleteConfirm(true)} style={{
            padding: '10px 16px', borderRadius: 8, border: 'none',
            background: '#DC2626', color: '#fff',
            fontSize: 13, fontWeight: 700, fontFamily: 'inherit',
            cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8,
            whiteSpace: 'nowrap',
          }}>
            <IC name="trash" size={14} color="#fff" />
            Delete ({selected.size})
          </button>
        )}
      </div>

      {/* Table */}
      <style>{TL_CSS}</style>
      <div className="tl-wrap" style={{ background: '#fff', borderRadius: 12, border: '1.5px solid #E2E8F0', overflow: 'hidden' }}>
        {/* Table header — sortable columns */}
        <div className={'tl-grid tl-hdr' + (isAdmin ? '' : ' tl-client')} style={{
          paddingTop: 12, paddingBottom: 12, background: '#F8FAFC',
          borderBottom: '1.5px solid #E2E8F0',
        }}>
          {isAdmin && (
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <input type="checkbox" aria-label="Select all on this page"
                checked={allOnPageSelected}
                ref={el => { if (el) el.indeterminate = someOnPageSelected && !allOnPageSelected; }}
                onChange={toggleSelectAll}
                style={{ cursor: 'pointer', width: 14, height: 14, accentColor: '#DC2626' }} />
            </div>
          )}
          {[
            { label: '',         col: null,   cls: 'tl-n-hide' },
            { label: 'Concern',  col: 'title' },
            { label: 'Assigned', col: null,  cls: 'tl-n-hide tl-assg' },
            { label: 'Priority', col: 'priority' },
            { label: 'Status',   col: 'status' },
            { label: 'Time',     col: null,   cls: 'tl-time' },
            { label: 'Raised by', col: 'by',  cls: 'tl-by' },
            { label: 'Date',     col: 'date', cls: 'tl-date' },
            { label: '',         col: null,   cls: 'tl-chev' },
          ].map(({ label, col, cls }, i) => (
            <span key={i} className={cls} onClick={col ? () => handleSort(col) : undefined} style={{
              fontSize: 11, fontWeight: 800, color: sortCol === col ? '#1A5090' : '#64748B',
              textTransform: 'uppercase', letterSpacing: '0.06em', whiteSpace: 'nowrap',
              cursor: col ? 'pointer' : 'default', userSelect: 'none',
              display: 'flex', alignItems: 'center', gap: 3,
            }}>
              {label}
              {col && sortCol === col && (
                <span style={{ fontSize: 9 }}>{sortDir === 'asc' ? '▲' : '▼'}</span>
              )}
            </span>
          ))}
        </div>
        {filtered.length === 0 ? (
          <div style={{ padding: '48px 28px', textAlign: 'center', color: '#94A3B8' }}>
            <IC name="inbox" size={36} color="#CBD5E1" style={{ display: 'block', margin: '0 auto 12px' }} />
            <div style={{ fontWeight: 600, color: '#CBD5E1' }}>No concerns found</div>
          </div>
        ) : (
          paginated.map(t => (
            <TicketRow key={t.id} ticket={t} onClick={onTicketClick}
              isAdmin={isAdmin} isSelected={selected.has(t.dbId)} onToggle={toggleSelect} />
          ))
        )}

        {/* Pagination footer */}
        {filtered.length > 0 && (
          <div style={{
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            padding: '12px 20px', borderTop: '1.5px solid #F1F5F9', background: '#FAFBFF',
          }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{ fontSize: 12, color: '#64748B', fontWeight: 500 }}>Rows per page:</span>
              <input
                type="number" min="1" max="200"
                value={perPageInput}
                onChange={e => {
                  setPerPageInput(e.target.value);
                  const val = parseInt(e.target.value);
                  if (val > 0) { setPerPage(val); setCurrentPage(1); }
                }}
                style={{
                  width: 58, padding: '4px 8px', borderRadius: 6,
                  border: '1.5px solid #E2E8F0', fontSize: 13, fontFamily: 'inherit',
                  textAlign: 'center', outline: 'none',
                }}
              />
            </div>
            <span style={{ fontSize: 12, color: '#64748B' }}>
              {`${(currentPage - 1) * perPage + 1}–${Math.min(currentPage * perPage, filtered.length)} of ${filtered.length}`}
            </span>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <button
                onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
                disabled={currentPage === 1}
                style={{
                  padding: '5px 12px', borderRadius: 7, border: '1.5px solid #E2E8F0',
                  background: currentPage === 1 ? '#F8FAFC' : '#fff',
                  color: currentPage === 1 ? '#CBD5E1' : '#374151',
                  fontSize: 12, fontWeight: 600, fontFamily: 'inherit',
                  cursor: currentPage === 1 ? 'not-allowed' : 'pointer',
                }}>← Prev</button>
              <span style={{ fontSize: 12, color: '#64748B', whiteSpace: 'nowrap' }}>
                Page {currentPage} of {totalPages}
              </span>
              <button
                onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
                disabled={currentPage === totalPages}
                style={{
                  padding: '5px 12px', borderRadius: 7, border: '1.5px solid #E2E8F0',
                  background: currentPage === totalPages ? '#F8FAFC' : '#fff',
                  color: currentPage === totalPages ? '#CBD5E1' : '#374151',
                  fontSize: 12, fontWeight: 600, fontFamily: 'inherit',
                  cursor: currentPage === totalPages ? 'not-allowed' : 'pointer',
                }}>Next →</button>
            </div>
          </div>
        )}
      </div>

      {showDeleteConfirm && (
        <DeleteConfirmModal
          count={selected.size}
          onCancel={() => setShowDeleteConfirm(false)}
          onConfirm={handleBulkDelete}
          deleting={deleting}
        />
      )}
    </div>
  );
}

// ── Ticket Detail ─────────────────────────────────────────────────────────────
// Conversation beside the details; stacked on anything narrower than a desktop window (the sidebar takes 220px of it)
const TD_CSS = `
.td-grid { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 20px; align-items: start; }
@media (max-width: 1000px) {
  .td-grid { grid-template-columns: minmax(0, 1fr); }
  /* stacked: what needs doing comes right after the title, the conversation and history follow */
  .td-main, .td-side { display: contents !important; }
  .td-title { order: 1; margin-bottom: 0 !important; }
  .td-actions { order: 2; }
  .td-details { order: 3; }
  .td-conv { order: 4; }
  .td-act { order: 5; }
}
`;
function TicketDetail({ ticket: initialTicket, onBack, onRefresh }) {
  const [detail, setDetail]     = React.useState(null);
  const [comment, setComment]   = React.useState('');
  const [status, setStatus]     = React.useState(initialTicket.status);
  const [priority, setPriority] = React.useState(initialTicket.priority);
  const [posting, setPosting]         = React.useState(false);
  const [showDeleteTicket, setShowDeleteTicket] = React.useState(false);
  const [deletingTicket, setDeletingTicket]     = React.useState(false);
  const [assignableUsers, setAssignableUsers]   = React.useState([]);
  const [assignedTo, setAssignedTo]             = React.useState(initialTicket.assignedTo?.id || '');
  const [assigning, setAssigning]               = React.useState(false);
  const [activity, setActivity]                 = React.useState(null);   // this concern's history

  const cat         = window.MOCK.categories.find(c => c.id === initialTicket.categoryId);
  const currentUser = window._synUser || {};
  const isAdmin     = currentUser.role === 'admin';
  function authH()  { return { Authorization: 'Bearer ' + (localStorage.getItem('hub_token') || '') }; }

  // Load full ticket (with comments) from the API
  React.useEffect(() => {
    if (!initialTicket.dbId) return;
    fetch(`/api/tickets/${initialTicket.dbId}`, { headers: authH() })
      .then(r => r.ok ? r.json() : null)
      .then(data => {
        if (data) { const n = normalizeTicket(data); setDetail(n); setStatus(n.status); setPriority(n.priority); setAssignedTo(n.assignedTo?.id || ''); }
      })
      .catch(() => {});
  }, [initialTicket.dbId]);

  // History of this concern (who changed what, and when); reloads after each change made on this page
  React.useEffect(() => {
    if (!initialTicket.dbId) return;
    fetch(`/api/tickets/${initialTicket.dbId}/activity`, { headers: authH() })
      .then(r => r.ok ? r.json() : [])
      .then(setActivity)
      .catch(() => setActivity([]));
  }, [initialTicket.dbId, status, priority, assignedTo, (detail && detail.comments ? detail.comments.length : 0)]);

  // The people a ticket can be assigned to are the developers assigned to its client (admin only)
  React.useEffect(() => {
    if (!isAdmin || !initialTicket.clientId) return;
    fetch('/api/clients', { headers: authH() })
      .then(r => r.ok ? r.json() : [])
      .then(list => setAssignableUsers((list.find(c => c.id === initialTicket.clientId)?.developers) || []))
      .catch(() => {});
  }, [initialTicket.clientId]);

  async function handleAssign(pick) {
    setAssigning(true);
    try {
      const chosen = pick !== undefined ? pick : assignedTo;
      const body = chosen ? { assigned_to: Number(chosen) } : { assigned_to: null };
      const res = await fetch(`/api/tickets/${initialTicket.dbId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json', ...authH() },
        body: JSON.stringify(body),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || 'Failed');
      const updated = await fetch(`/api/tickets/${initialTicket.dbId}`, { headers: authH() }).then(r => r.json());
      const n = normalizeTicket(updated);
      setDetail(n);
      setAssignedTo(n.assignedTo?.id || '');
      if (onRefresh) onRefresh();
    } catch (e) { console.error(e); }
    finally { setAssigning(false); }
  }

  const ticket   = detail || initialTicket;
  const comments = ticket.comments || [];

  async function addComment() {
    if (!comment.trim() || posting) return;
    setPosting(true);
    try {
      const res = await fetch(`/api/tickets/${initialTicket.dbId}/comment`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authH() },
        body: JSON.stringify({ comment: comment.trim() }),
      });
      if (!res.ok) throw new Error('Failed');
      setComment('');
      const data = await fetch(`/api/tickets/${initialTicket.dbId}`, { headers: authH() }).then(r => r.json());
      const n = normalizeTicket(data);
      setDetail(n);
      setStatus(n.status);
    } catch (e) { console.error(e); }
    finally { setPosting(false); }
  }

  const [statusError, setStatusError] = React.useState(null);
  const [confirming, setConfirming]     = React.useState(false);

  async function reloadDetail() {
    const data = await fetch(`/api/tickets/${initialTicket.dbId}`, { headers: authH() }).then(r => r.json());
    const n = normalizeTicket(data);
    setDetail(n); setStatus(n.status); setAssignedTo(n.assignedTo?.id || '');
    if (onRefresh) onRefresh();
  }

  async function changeStatus(newStatus) {
    setStatusError(null);
    try {
      const res = await fetch(`/api/tickets/${initialTicket.dbId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json', ...authH() },
        body: JSON.stringify({ status: newStatus }),
      });
      if (!res.ok) { const d = await res.json().catch(() => ({})); throw new Error(d.error || 'Could not change the status'); }
      await reloadDetail();
    } catch (e) { setStatusError(e.message); }
  }

  // The client's answer to "was this resolved?": yes closes it, no sends it back to the developer
  async function confirmResolved(yes) {
    setStatusError(null); setConfirming(true);
    try {
      const res = await fetch(`/api/tickets/${initialTicket.dbId}/confirm`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', ...authH() },
        body: JSON.stringify({ resolved: yes }),
      });
      if (!res.ok) { const d = await res.json().catch(() => ({})); throw new Error(d.error || 'Could not save your answer'); }
      await reloadDetail();
    } catch (e) { setStatusError(e.message); }
    finally { setConfirming(false); }
  }

  async function changePriority(newPriority) {
    try {
      await fetch(`/api/tickets/${initialTicket.dbId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json', ...authH() },
        body: JSON.stringify({ priority: newPriority }),
      });
      setPriority(newPriority);
      setDetail(d => d ? { ...d, priority: newPriority } : null);
      if (onRefresh) onRefresh();
    } catch (e) { console.error(e); }
  }

  async function handleDeleteTicket() {
    setDeletingTicket(true);
    try {
      await fetch(`/api/tickets/${initialTicket.dbId}`, {
        method: 'DELETE',
        headers: authH(),
      });
      setShowDeleteTicket(false);
      if (onRefresh) onRefresh();
      onBack();
    } catch (e) { console.error(e); }
    finally { setDeletingTicket(false); }
  }

  // Developers start work and mark it resolved; the client's yes closes it. Admins can move a concern anywhere.
  const nextStatuses = isAdmin ? {
    'Open':        ['In Progress', 'Closed'],
    'In Progress': ['Resolved', 'Open'],
    'Resolved':    ['In Progress', 'Closed'],
    'Closed':      ['Open'],
  } : currentUser.role === 'agent' ? {
    'Open':        ['In Progress'],
    'In Progress': ['Resolved'],
    'Resolved':    ['In Progress'],
  } : {};
  const canConfirm = currentUser.role === 'client' && status === 'Resolved'
    && (ticket.submittedBy.id === currentUser.id || ticket.submittedBy.role !== 'client');
  const waitingFor = currentUser.role === 'client' && status === 'Resolved' && !canConfirm ? ticket.submittedBy.name : null;
  const showStatusCard = Object.keys(nextStatuses).length > 0 && (nextStatuses[status] || []).length > 0;

  return (
    <div>
      {/* Back */}
      <button onClick={onBack} style={{
        display: 'flex', alignItems: 'center', gap: 6, background: 'none', border: 'none',
        cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 600,
        color: '#64748B', marginBottom: 20, padding: 0,
      }}>
        <IC name="chevron_r" size={14} color="#64748B" style={{ transform: 'rotate(180deg)' }} /> Back to list
      </button>

      <style>{TD_CSS}</style>
      <div className="td-grid">
        {/* Main content */}
        <div className="td-main">
          {/* Title card */}
          <div className="td-title" style={{ background: '#fff', borderRadius: 12, border: '1.5px solid #E2E8F0', padding: '24px 28px', marginBottom: 16 }}>
            <div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 8, marginBottom: 12 }}>
              <span style={{ fontSize: 12, fontFamily: 'monospace', fontWeight: 700, color: '#94A3B8', background: '#F1F5F9', padding: '3px 8px', borderRadius: 5 }}>{ticket.id}</span>
              {cat && <span style={{ fontSize: 12, fontWeight: 600, color: cat.color, background: cat.bg, padding: '3px 10px', borderRadius: 20 }}>{cat.label}</span>}
              <StatusBadge status={status} />
              <PriorityBadge priority={priority} />
            </div>
            <h2 style={{ margin: '0 0 14px', fontSize: 20, fontWeight: 800, color: '#0F172A', lineHeight: 1.3 }}>{ticket.title}</h2>
            <p style={{ margin: 0, fontSize: 14, color: '#475569', lineHeight: 1.7 }}>{ticket.description}</p>
          </div>

          {/* Comments */}
          <div className="td-conv" style={{ background: '#fff', borderRadius: 12, border: '1.5px solid #E2E8F0', overflow: 'hidden' }}>
            <div style={{ padding: '16px 22px', borderBottom: '1px solid #F1F5F9', fontWeight: 700, color: '#0F172A', fontSize: 14 }}>
              Conversation ({comments.length})
            </div>
            <div style={{ padding: '8px 0' }}>
              {!detail && comments.length === 0 && (
                <div style={{ padding: '20px 22px', color: '#94A3B8', fontSize: 13 }}>Loading conversation…</div>
              )}
              {comments.map((c, i) => (
                <div key={i} style={{ padding: '14px 22px', display: 'flex', gap: 12, borderBottom: i < comments.length - 1 ? '1px solid #F8FAFC' : 'none' }}>
                  <Avatar name={c.author.name} size={32} />
                  <div style={{ flex: 1 }}>
                    <div style={{ display: 'flex', gap: 8, alignItems: 'baseline', marginBottom: 4 }}>
                      <span style={{ fontWeight: 700, fontSize: 13, color: '#1E293B' }}>{c.author.name}</span>
                      <span style={{ fontSize: 11, color: '#CBD5E1' }}>{c.time}</span>
                    </div>
                    <p style={{ margin: 0, fontSize: 13.5, color: '#475569', lineHeight: 1.6 }}>{c.text}</p>
                  </div>
                </div>
              ))}
            </div>
            {/* Add comment */}
            <div style={{ padding: '16px 22px', borderTop: '1.5px solid #F1F5F9', display: 'flex', gap: 10 }}>
              <Avatar name={currentUser.name || 'User'} size={32} color={BRAND_ORANGE} />
              <div style={{ flex: 1 }}>
                <textarea value={comment} onChange={e => setComment(e.target.value)}
                  placeholder="Add a comment or update..."
                  rows={3} style={{ ...inputStyle, marginTop: 0, width: '100%', boxSizing: 'border-box' }} />
                <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 8 }}>
                  <Btn variant="navy" size="sm" onClick={addComment} disabled={posting || !comment.trim()}>
                    {posting ? 'Posting…' : 'Post Comment'}
                  </Btn>
                </div>
              </div>
            </div>
          </div>

          {/* History: what changed on this concern, by whom */}
          <div className="td-act" style={{ background: '#fff', borderRadius: 12, border: '1.5px solid #E2E8F0', overflow: 'hidden' }}>
            <div style={{ padding: '16px 22px', borderBottom: '1px solid #F1F5F9', fontWeight: 700, color: '#0F172A', fontSize: 14 }}>Activity</div>
            <ConcernActivity events={activity} />
          </div>
        </div>

        {/* Sidebar details */}
        <div className="td-side" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {/* The client's answer once the developer says it is resolved */}
          {canConfirm && (
            <div className="td-actions" style={{ background: '#ECFDF5', borderRadius: 12, border: '1.5px solid #A7F3D0', padding: '18px 20px' }}>
              <div style={{ fontSize: 15, fontWeight: 800, color: '#065F46' }}>Is this resolved?</div>
              <div style={{ fontSize: 13, color: '#047857', marginTop: 4, lineHeight: 1.5 }}>
                The developer marked this as resolved after {fmtDuration(ticket.workSeconds)} of work.
              </div>
              <div style={{ display: 'flex', gap: 8, marginTop: 14 }}>
                <Btn variant="navy" size="sm" disabled={confirming} onClick={() => confirmResolved(true)} style={{ flex: 1, justifyContent: 'center' }}>Yes, close it</Btn>
                <Btn variant="ghost" size="sm" disabled={confirming} onClick={() => confirmResolved(false)} style={{ flex: 1, justifyContent: 'center' }}>No, not yet</Btn>
              </div>
            </div>
          )}
          {waitingFor && (
            <div className="td-actions" style={{ background: '#fff', borderRadius: 12, border: '1.5px solid #E2E8F0', padding: '16px 20px', fontSize: 13, color: '#64748B', lineHeight: 1.5 }}>
              Marked as resolved. Waiting for {waitingFor} to confirm.
            </div>
          )}

          {/* Status actions */}
          {showStatusCard && (
          <div className="td-actions" style={{ background: '#fff', borderRadius: 12, border: '1.5px solid #E2E8F0', padding: '18px 20px' }}>
            <div style={{ fontSize: 11, fontWeight: 700, color: '#94A3B8', textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: 10 }}>Update Status</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
              {(nextStatuses[status] || []).map(s => (
                <Btn key={s} variant={s === 'Resolved' ? 'navy' : 'ghost'} size="sm"
                  onClick={() => changeStatus(s)} style={{ width: '100%', justifyContent: 'center' }}>
                  {s === 'In Progress' && status === 'Open' ? 'Start work' : s === 'In Progress' ? 'Back to In Progress' : 'Mark as ' + s}
                </Btn>
              ))}
            </div>
            {status === 'In Progress' && <div style={{ fontSize: 12, color: '#94A3B8', marginTop: 10, lineHeight: 1.45 }}>The timer is running. It stops when you mark this resolved.</div>}
          </div>
          )}
          {statusError && <div role="alert" className="td-actions" style={{ padding: '10px 14px', background: '#FEF2F2', color: '#B91C1C', borderRadius: 8, fontSize: 13 }}>{statusError}</div>}

          {/* Details */}
          <div className="td-details" style={{ background: '#fff', borderRadius: 12, border: '1.5px solid #E2E8F0', padding: '18px 20px' }}>
            <div style={{ fontSize: 11, fontWeight: 700, color: '#94A3B8', textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: 12 }}>Details</div>
            {[
              ...(ticket.clientName ? [{ label: 'Client', content: (
                <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <ClientLogo clientId={ticket.clientId} name={ticket.clientName} hasLogo={ticket.clientHasLogo} version={ticket.clientLogoVer} size={26} radius={7} />
                  <span style={{ fontSize: 13, color: '#1E293B', fontWeight: 600 }}>{ticket.clientName}</span>
                </div>
              ) }] : []),
              { label: 'Submitted by', content: (
                <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
                  <Avatar name={ticket.submittedBy.name} size={24} />
                  <span style={{ fontSize: 13, color: '#1E293B', fontWeight: 600 }}>{ticket.submittedBy.name}</span>
                </div>
              )},
              { label: 'Assigned to', content: isAdmin && assignableUsers.length > 0
                ? (
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                    <select
                      value={assignedTo}
                      onChange={e => { setAssignedTo(e.target.value); handleAssign(e.target.value); }}
                      disabled={assigning}
                      style={{
                        width: '100%', padding: '6px 10px', borderRadius: 7,
                        border: '1.5px solid #E2E8F0', fontSize: 13, fontFamily: 'inherit',
                        color: '#1E293B', background: '#FAFBFF', cursor: 'pointer',
                      }}
                    >
                      <option value="">— Unassigned —</option>
                      {assignableUsers.map(u => (
                        <option key={u.id} value={u.id}>{u.name}</option>
                      ))}
                    </select>
                    {assigning && <span style={{ fontSize: 12, color: '#94A3B8' }}>Saving…</span>}
                  </div>
                )
                : ticket.assignedTo
                  ? <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
                      <Avatar name={ticket.assignedTo.name} size={24} />
                      <span style={{ fontSize: 13, color: '#1E293B', fontWeight: 600 }}>{ticket.assignedTo.name}</span>
                    </div>
                  : <span style={{ fontSize: 13, color: '#CBD5E1', fontStyle: 'italic' }}>Unassigned</span>
              },
              { label: 'Time worked', content: <WorkedTime ticket={ticket} style={{ fontSize: 14, fontWeight: 800, color: ticket.workRunning ? '#B45309' : '#1E293B' }} /> },
              { label: 'Type',        content: <span style={{ fontSize: 13, color: cat?.color, fontWeight: 600 }}>{cat?.label}</span> },
              { label: 'Priority', content: currentUser.role === 'admin'
                ? (
                  <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap' }}>
                    {['Low', 'Medium', 'High', 'Urgent'].map(p => {
                      const pColors = { Low: '#64748B', Medium: '#B45309', High: '#B91C1C', Urgent: '#fff' };
                      const pBgs    = { Low: '#F1F5F9', Medium: '#FFFBEB', High: '#FEF2F2', Urgent: '#DC2626' };
                      const active  = priority === p;
                      return (
                        <button key={p} type="button" onClick={() => changePriority(p)} style={{
                          padding: '3px 9px', border: active ? `2px solid ${p === 'Urgent' ? '#DC2626' : pColors[p]}` : '2px solid #E2E8F0',
                          borderRadius: 5, cursor: 'pointer',
                          background: active ? pBgs[p] : '#fff',
                          color: active ? pColors[p] : '#94A3B8',
                          fontFamily: 'inherit', fontSize: 11, fontWeight: 700, transition: 'all 0.12s',
                        }}>{p}</button>
                      );
                    })}
                  </div>
                )
                : <PriorityBadge priority={priority} />
              },
              { label: 'Created',     content: <span style={{ fontSize: 13, color: '#475569' }}>{ticket.createdAt}</span> },
              { label: 'Last update', content: <span style={{ fontSize: 13, color: '#475569' }}>{ticket.updatedAt}</span> },
            ].map(({ label, content }) => (
              <div key={label} style={{ marginBottom: 12 }}>
                <div style={{ fontSize: 11, color: '#94A3B8', fontWeight: 600, marginBottom: 4 }}>{label}</div>
                {content}
              </div>
            ))}
          </div>

          {/* Danger Zone — admin only */}
          {currentUser.role === 'admin' && (
            <div style={{ background: '#fff', borderRadius: 12, border: '1.5px solid #FECACA', padding: '18px 20px' }}>
              <div style={{ fontSize: 11, fontWeight: 700, color: '#B91C1C', textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: 10 }}>Danger Zone</div>
              <Btn variant="danger" size="sm" icon="trash" onClick={() => setShowDeleteTicket(true)}
                style={{ width: '100%', justifyContent: 'center', background: '#DC2626', color: '#fff' }}>
                Delete Concern
              </Btn>
            </div>
          )}
        </div>
      </div>

      {showDeleteTicket && (
        <DeleteConfirmModal
          count={1}
          onCancel={() => setShowDeleteTicket(false)}
          onConfirm={handleDeleteTicket}
          deleting={deletingTicket}
        />
      )}
    </div>
  );
}

Object.assign(window, { TicketList, TicketDetail, SubmitTicketModal, normalizeTicket, DeleteConfirmModal, fmtDuration });
