/* Dispatch — Hub tab
 * Cross-lane feed (live via window.__DISPATCH_LIVE__, sample fallback), lane badges, AI triager.
 * Loaded by index.html as a <script type="text/babel"> module. Shared symbols
 * (React hook aliases, api, cn, UI primitives, helpers) live in lib.jsx
 * and are declared once there; other modules use them without redeclaring. */
// ── Hub: cross-lane feed ─────────────────────────────────────────────────
// Sample data (the demo only has live Help Desk data; the Hub spans all lanes).
const LANE_META = {
  helpdesk: { label: 'Help Desk', color: '#6c3994' },
  netadmin: { label: 'Net Admin', color: '#008687' },
  architect: { label: 'Architects', color: '#b5751a' },
  dev: { label: 'Developers', color: '#1a5896' },
  qa: { label: 'QA', color: '#5a7a10' },
};
const HUB_FEED = [
  { id: 'n1', num: 'NET-318', title: 'Core switch failover flapping in DC-2', lane: 'netadmin', priority: 'critical', status: 'in_progress', owner: 'Luc Tremaine', age: '40m' },
  { id: 'h1', num: 'IT-2401', title: 'Outlook stuck syncing — VPN reconnect prompt every 90s', lane: 'helpdesk', priority: 'high', status: 'in_progress', owner: 'Mira Halpern', age: '12m' },
  { id: 'd1', num: 'DEV-1290', title: 'N+1 query on the policy list endpoint', lane: 'dev', priority: 'high', status: 'in_progress', owner: 'Sam Okafor', age: '3h' },
  { id: 'q1', num: 'QA-455', title: 'Regression: quote PDF totals off by tax', lane: 'qa', priority: 'high', status: 'new', owner: 'Dana Cole', age: '5h' },
  { id: 'a1', num: 'ARC-77', title: 'Design review: multi-tenant billing schema', lane: 'architect', priority: 'medium', status: 'new', owner: 'Priya Nair', age: '2h' },
  { id: 'h2', num: 'IT-2399', title: 'Second monitor cable — workspace 4B', lane: 'helpdesk', priority: 'low', status: 'new', owner: 'Cole Burrell', age: '38m' },
  { id: 'n2', num: 'NET-315', title: 'VPN split-tunnel rollout — phase 2', lane: 'netadmin', priority: 'medium', status: 'in_progress', owner: 'Luc Tremaine', age: '1d' },
  { id: 'd2', num: 'DEV-1284', title: 'Refit client retry/backoff on 429s', lane: 'dev', priority: 'medium', status: 'new', owner: 'Sam Okafor', age: '6h' },
  { id: 'q2', num: 'QA-451', title: 'Flaky e2e: login redirect race', lane: 'qa', priority: 'medium', status: 'in_progress', owner: 'Dana Cole', age: '1d' },
  { id: 'h3', num: 'IT-2395', title: 'Password reset for partner portal admin', lane: 'helpdesk', priority: 'medium', status: 'resolved', owner: 'Mira Halpern', age: '2h' },
  { id: 'a2', num: 'ARC-74', title: 'ADR: event bus vs. polling for sync', lane: 'architect', priority: 'low', status: 'resolved', owner: 'Priya Nair', age: '3d' },
  { id: 'd3', num: 'DEV-1277', title: 'Cache invalidation on rate-table publish', lane: 'dev', priority: 'critical', status: 'new', owner: 'Sam Okafor', age: '8h' },
];

function LaneBadge({ lane }) {
  const m = LANE_META[lane];
  if (!m) return null;
  return (
    <span className="inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-semibold"
      style={{ color: m.color, borderColor: m.color + '55', background: m.color + '1a' }}>{m.label}</span>
  );
}

// AI Triager drawer — faithful port of the old Hub AITriager, in Tailwind.
// Right-side scrollable drawer: loads pending AI suggestions (auto-refreshing
// every 60s while open), filters by a confidence floor, and lets a human
// accept or reject each one. Accept applies the suggestion: Help Desk tickets
// are updated here (priority/category/assignee); GitHub tickets are routed
// server-side via lane:/priority: labels (see decideSuggestion). The
// confidence floor is a view filter only — it does not auto-route.
function AiTriagerDrawer({ open, onClose }) {
  const [items, setItems] = useState([]);
  const [confidence, setConfidence] = useState(70);
  const [dismissing, setDismissing] = useState(null); // { id, mode }
  const [busyId, setBusyId] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!open) return;
    let cancelled = false;
    async function reload() {
      setLoading(true);
      try {
        const list = await api.fetchAiTriagePending({ lookbackDays: 7, limit: 50 });
        if (!cancelled) { setItems(Array.isArray(list) ? list : []); setError(null); }
      } catch (e) { if (!cancelled) setError(e.message || 'Failed to load suggestions'); }
      finally { if (!cancelled) setLoading(false); }
    }
    reload();
    const t = setInterval(reload, 60000);
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => { cancelled = true; clearInterval(t); window.removeEventListener('keydown', onKey); document.body.style.overflow = ''; };
  }, [open, onClose]);

  async function handleAccept(item) {
    setBusyId(item.id); setDismissing({ id: item.id, mode: 'accepting' });
    try {
      if (item.ticketSource === 'helpdesk') {
        const s = item.suggestion || {};
        const update = {};
        if (s.priority) update.priority = s.priority;
        if (s.category) update.category = s.category;
        if (s.assigneeEmail) update.assignee = s.assigneeEmail;
        if (Object.keys(update).length) { try { await api.updateHelpdeskTicket(item.ticketId, update); } catch {} }
      }
      await api.decideAiTriage(item.ticketSource, item.ticketId, 'accepted');
      setTimeout(() => { setItems(a => a.filter(x => x.id !== item.id)); setDismissing(null); setBusyId(null); }, 280);
    } catch (e) { setError(e.message || 'Accept failed'); setDismissing(null); setBusyId(null); }
  }
  async function handleReject(item) {
    setBusyId(item.id); setDismissing({ id: item.id, mode: 'dismissing' });
    try {
      await api.decideAiTriage(item.ticketSource, item.ticketId, 'rejected');
      setTimeout(() => { setItems(a => a.filter(x => x.id !== item.id)); setDismissing(null); setBusyId(null); }, 280);
    } catch (e) { setError(e.message || 'Reject failed'); setDismissing(null); setBusyId(null); }
  }

  if (!open) return null;
  const filtered = items.filter(it => (it.suggestion?.confidence ?? 0) >= confidence);

  return (
    <div>
      <div className="fixed inset-0 z-40 bg-black/60" onClick={onClose} />
      <aside className="fixed inset-y-0 right-0 z-50 flex h-full w-full max-w-md flex-col border-l bg-background shadow-lg">
        {/* Fixed header with the confidence floor */}
        <div className="space-y-3 border-b p-4">
          <div className="flex items-start justify-between gap-2">
            <div>
              <div className="flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wider text-muted-foreground">
                <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-emerald-500" />Live · ✦ AI Triager
              </div>
              <h2 className="mt-1 text-base font-semibold">Conveyor belt</h2>
              <p className="text-xs text-muted-foreground">
                {loading && items.length === 0 ? 'Loading…' : `${filtered.length} of ${items.length} suggestions above your confidence floor.`}
              </p>
            </div>
            <Button variant="ghost" size="icon" onClick={onClose} aria-label="Close">✕</Button>
          </div>
          <div>
            <div className="flex items-center justify-between text-xs"><span className="text-muted-foreground">Confidence floor</span><span className="font-semibold text-primary">{confidence}%</span></div>
            <input type="range" min="50" max="99" value={confidence} onChange={e => setConfidence(Number(e.target.value))} className="mt-1 w-full accent-primary" />
            <div className="mt-0.5 flex justify-between text-[9px] font-bold uppercase tracking-wider text-muted-foreground"><span>review all</span><span>high confidence only</span></div>
          </div>
        </div>
        {/* Scrollable suggestion list */}
        <div className="flex-1 space-y-3 overflow-y-auto p-4">
          {error && <div className="rounded-md bg-destructive/10 px-3 py-2 text-sm text-destructive">{error}</div>}
          {!loading && filtered.length === 0 && (
            <div className="py-10 text-center text-sm text-muted-foreground">
              <div className="mb-2 text-lg">✦</div>
              {items.length === 0 ? 'No pending suggestions in the last 7 days.' : `No suggestions above ${confidence}%.`}
              <div className="mt-1 text-xs">{items.length === 0 ? 'New tickets get triaged automatically.' : 'Drop the floor to review more.'}</div>
            </div>
          )}
          {filtered.map(item => {
            const s = item.suggestion || {};
            const isBusy = busyId === item.id;
            const isDismissing = dismissing?.id === item.id;
            return (
              <div key={item.id} className={cn('rounded-lg border bg-card p-3 shadow-sm transition-all duration-300', isDismissing && 'scale-95 opacity-0')}>
                <div className="flex items-center justify-between gap-2 text-xs">
                  <span className="font-mono text-muted-foreground">{item.ticketSource} · {String(item.ticketId).slice(0, 8)}</span>
                  <span className="flex items-center gap-1.5">
                    <span className="font-semibold text-primary">{s.confidence}%</span>
                    <span className="block h-1.5 w-12 overflow-hidden rounded-full bg-muted"><span className="block h-full rounded-full bg-primary" style={{ width: `${s.confidence || 0}%` }} /></span>
                  </span>
                </div>
                <p className="mt-1.5 text-sm italic text-muted-foreground">{s.reasoning || '(no reasoning)'}</p>
                <div className="mt-2 grid grid-cols-2 gap-x-3 gap-y-2 text-xs">
                  <div><div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">Route to</div><div className="mt-0.5"><LaneBadge lane={s.lane} /></div></div>
                  <div><div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">Priority</div><div className="mt-0.5"><Badge variant={prioVariant(s.priority)}>{s.priority || '—'}</Badge></div></div>
                  <div><div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">Suggested assignee</div><div className="mt-0.5 truncate">{s.assigneeEmail || 'Auto-assign'}</div></div>
                  <div><div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">Category</div><div className="mt-0.5">{s.category || '—'}</div></div>
                </div>
                {s.shouldEscalateToDev && (
                  <div className="mt-2 rounded bg-blue-500/10 px-2 py-1 text-xs text-blue-600 dark:text-blue-300">↗ Looks like a real engineering issue — consider Send to GitHub.</div>
                )}
                <div className="mt-3 flex gap-2">
                  <Button size="sm" disabled={isBusy} onClick={() => handleAccept(item)}>✓ Accept</Button>
                  <Button size="sm" variant="outline" disabled={isBusy} onClick={() => handleReject(item)}>✕ Reject</Button>
                </div>
              </div>
            );
          })}
        </div>
      </aside>
    </div>
  );
}

function Hub() {
  const [laneTab, setLaneTab] = useState('all');
  const [search, setSearch] = useState('');
  const [priority, setPriority] = useState('all');
  const [triagerOpen, setTriagerOpen] = useState(false);
  const [openItem, setOpenItem] = useState(null);

  const [page, setPage] = useState(1);

  // Prefer the live cross-lane feed the API shim builds (GitHub issues + real
  // Help Desk tickets from helpdesk.tickets), exactly like the original Hub.
  // Falls back to the sample feed if the API isn't reachable / not signed in.
  const [live, setLive] = useState(() => (window.__DISPATCH_LIVE__ && window.__DISPATCH_LIVE__.tickets) || null);
  useEffect(() => {
    const read = () => setLive((window.__DISPATCH_LIVE__ && window.__DISPATCH_LIVE__.tickets) || null);
    read();
    window.addEventListener('dispatch:data-loaded', read);
    return () => window.removeEventListener('dispatch:data-loaded', read);
  }, []);

  // Pending AI-triage suggestions — the SAME source the drawer lists. The
  // badge/KPI used to count "new" feed tickets, which drifted from the drawer
  // (badge said 3 while the drawer had 0). Poll it, and refresh on open/close
  // so accepting/rejecting updates the count.
  const [aiPending, setAiPending] = useState(null);
  useEffect(() => {
    let cancelled = false;
    const load = () => api.fetchAiTriagePending({ lookbackDays: 7, limit: 50 })
      .then(list => { if (!cancelled) setAiPending(Array.isArray(list) ? list.length : 0); })
      .catch(() => { if (!cancelled) setAiPending(0); });
    load();
    const t = setInterval(load, 60000);
    return () => { cancelled = true; clearInterval(t); };
  }, [triagerOpen]);

  const isLive = Array.isArray(live) && live.length > 0;
  const feed = isLive ? live : HUB_FEED;
  const who = (t) => t.requester || t.owner || '—';

  const [sort, toggleSort] = useSort(null);
  const q = search.trim().toLowerCase();
  const filtered = feed.filter(t =>
    (laneTab === 'all' || t.lane === laneTab) &&
    (priority === 'all' || t.priority === priority) &&
    (!q || (t.title || '').toLowerCase().includes(q) || (t.num || '').toLowerCase().includes(q) || String(who(t)).toLowerCase().includes(q)));
  const hubSortVal = (t, k) => k === 'priority' ? (PRIORITY_RANK[t.priority] ?? 9) : k === 'requester' ? String(who(t)) : (t[k] ?? '');
  const rows = sortRows(filtered, sort, hubSortVal);
  useEffect(() => { setPage(1); }, [laneTab, search, priority, isLive, sort]);
  const pageCount = Math.max(1, Math.ceil(rows.length / PER_PAGE));
  const paged = rows.slice((page - 1) * PER_PAGE, page * PER_PAGE);

  // KPI set mirrors the original Hub. `aiQueue` = pending AI-triage suggestions
  // (the drawer's real source), not a count of "new" feed tickets.
  const aiQueue = aiPending ?? 0;
  const kpis = [
    { label: 'Urgent IT/Net', value: feed.filter(t => (t.priority === 'high' || t.priority === 'critical') && (t.lane === 'helpdesk' || t.lane === 'netadmin')).length },
    { label: 'Dev backlog', value: feed.filter(t => t.lane === 'dev').length },
    { label: 'QA Ready', value: feed.filter(t => t.lane === 'qa' && (t.status === 'new' || t.status === 'in_progress')).length },
    { label: 'AI Triage Queue', value: aiQueue },
  ];
  const tabs = [{ id: 'all', label: 'All', count: feed.length }, ...Object.keys(LANE_META).map(id => ({
    id, label: LANE_META[id].label, count: feed.filter(t => t.lane === id).length,
  }))];

  const [overviewOpen, setOverviewOpen] = useOverviewOpen();

  return (
    <div className="space-y-4">
      {/* Overview (description card + KPI cards) — whole block collapses; the
          toggle lives on the tabs row below. */}
      {overviewOpen && (
        <>
          <Card className="border-primary/20 bg-primary/5">
            <CardContent className="pt-6">
              <h2 className="text-2xl font-semibold tracking-tight">One hub for every lane.</h2>
              <p className="mt-1 text-sm text-muted-foreground">Cross-team work flows here — Help Desk, Net Admin, Architects, Developers, and QA. Tickets are auto-routed by AI, but you call the shots.</p>
            </CardContent>
          </Card>
          <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
            {kpis.map(k => (
              <Card key={k.label}><CardContent className="pt-5">
                <div className="text-sm text-muted-foreground">{k.label}</div>
                <div className="mt-1 text-3xl font-semibold tracking-tight">{k.value}</div>
              </CardContent></Card>
            ))}
          </div>
        </>
      )}

      {/* Lane tab navigation + AI Triager button + overview collapse toggle */}
      <div className="flex flex-wrap items-center gap-2">
        <div className="inline-flex flex-wrap gap-1 rounded-lg bg-muted p-1">
          {tabs.map(t => (
            <button key={t.id} onClick={() => setLaneTab(t.id)}
              className={cn('inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium transition-colors', laneTab === t.id ? 'bg-background text-foreground shadow' : 'text-muted-foreground hover:text-foreground')}>
              {t.label}<span className="rounded bg-foreground/10 px-1.5 text-xs">{t.count}</span>
            </button>
          ))}
        </div>
        <div className="ml-auto flex items-center gap-3">
          <Button onClick={() => setTriagerOpen(true)}>✦ AI Triager · {aiQueue}</Button>
        </div>
      </div>

      {/* Table with filter inside */}
      <Card>
        <div className="flex items-center gap-2 border-b p-3">
          <Input className="w-56" placeholder="Search across lanes…" value={search} onChange={e => setSearch(e.target.value)} />
          <Select className="w-40" value={priority} onChange={e => setPriority(e.target.value)}>
            <option value="all">All priorities</option>
            {['low', 'medium', 'high', 'critical'].map(p => <option key={p} value={p}>{titleCase(p)}</option>)}
          </Select>
          <span className="ml-auto text-xs text-muted-foreground">{isLive ? `Live · ${feed.length} tickets` : 'Sample cross-lane feed'}</span>
        </div>
        <div className="overflow-x-auto">
          <table className="w-full text-sm">
            <thead className="border-b text-muted-foreground">
              <tr className="[&>th]:px-4 [&>th]:py-2.5 [&>th]:text-left [&>th]:font-medium">
                <SortHeader label="Item" sortKey="title" sort={sort} onSort={toggleSort} />
                <SortHeader label="Lane" sortKey="lane" sort={sort} onSort={toggleSort} />
                <SortHeader label="Status" sortKey="status" sort={sort} onSort={toggleSort} />
                <SortHeader label="Priority" sortKey="priority" sort={sort} onSort={toggleSort} />
                <SortHeader label="Requester" sortKey="requester" sort={sort} onSort={toggleSort} />
                <SortHeader label="Age" sortKey="age" sort={sort} onSort={toggleSort} align="right" />
              </tr>
            </thead>
            <tbody className="divide-y">
              {rows.length === 0 && <tr><td colSpan="6" className="px-4 py-6 text-center text-muted-foreground">No items match.</td></tr>}
              {paged.map(t => (
                <tr key={t.id} onClick={() => setOpenItem(t)} className="cursor-pointer transition-colors hover:bg-muted/50">
                  <td className="max-w-[320px] px-4 py-2.5">
                    <div className="truncate font-medium">{t.title}</div>
                    <div className="font-mono text-xs text-muted-foreground">{t.num}</div>
                  </td>
                  <td className="px-4 py-2.5"><LaneBadge lane={t.lane} /></td>
                  <td className="px-4 py-2.5"><Badge variant="outline">{titleCase(t.status)}</Badge></td>
                  <td className="px-4 py-2.5"><Badge variant={prioVariant(t.priority)}>{t.priority}</Badge></td>
                  <td className="max-w-[160px] truncate px-4 py-2.5 text-muted-foreground">{who(t)}</td>
                  <td className="px-4 py-2.5 text-right text-muted-foreground">{t.age}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        <div className="flex flex-wrap items-center justify-between gap-2 border-t px-4 py-2">
          <span className="text-xs text-muted-foreground">{rows.length} shown · {feed.length} total</span>
          <Pagination page={page} pageCount={pageCount} onPage={setPage} />
        </div>
      </Card>

      {/* AI Triager — a scrollable right-side drawer (Tailwind), faithful to the
          old Hub AITriager: live pending suggestions, confidence floor, and
          human accept/reject via the ai-triage API. */}
      <AiTriagerDrawer open={triagerOpen} onClose={() => setTriagerOpen(false)} />

      {/* Row click → ticket details. Help Desk items get the full Help Desk
          detail modal (read-only here); GitHub-synced lanes get a lightweight
          in-memory modal with a link out to the issue. */}
      {openItem && (openItem.source === 'helpdesk'
        ? <TicketDrawer ticketId={openItem.id} isTriager={false} metadata={null} assignees={[]} onClose={() => setOpenItem(null)} />
        : <TicketDetailModal item={openItem} onClose={() => setOpenItem(null)} />)}
    </div>
  );
}
