// tickets.jsx — Inbox table, Triage kanban, Ticket drawer, Convert-to-task modal

// ── helpers ────────────────────────────────────────────────────
function getChannel(id) { return TICKET_CHANNELS.find(c => c.id === id); }
function getSeverity(id) { return TICKET_SEVERITIES.find(s => s.id === id); }
function getTicketStatus(id) { return TICKET_STATUSES.find(s => s.id === id); }

function SeverityChip({ id }) {
  const s = getSeverity(id); if (!s) return null;
  return <span className={`sev-chip ${s.cls}`}>{s.short}</span>;
}
function TicketStatusChip({ id }) {
  const s = getTicketStatus(id); if (!s) return null;
  return <span className={`ts-chip ${s.cls}`}>{s.label}</span>;
}
function ChannelChip({ id, compact }) {
  const c = getChannel(id); if (!c) return null;
  const Ic = Icons[c.icon] || Icons.Inbox;
  return (
    <span className="ch-chip" title={c.label}>
      <Ic size={13}/>{!compact && <span>{c.label}</span>}
    </span>
  );
}
function SlaChip({ state, label }) {
  if (!state) return null;
  const map = { met: "Met", "on-track": "On track", "at-risk": "At risk", breached: "Breached" };
  return <span className={`sla-chip ${state}`}>{label || map[state]}</span>;
}

// ── Inbox table ────────────────────────────────────────────────────
function TicketsInbox({ tickets, onOpen, onNew, view, setView }) {
  const [tab, setTab] = React.useState("open");
  const [q, setQ] = React.useState("");
  const [sev, setSev] = React.useState("all");
  const [ch, setCh] = React.useState("all");
  const [assignee, setAssignee] = React.useState("all");

  const tabs = [
    { id: "open",      label: "Open",      test: t => !["resolved","rejected","duplicate"].includes(t.status) },
    { id: "new",       label: "Needs triage", test: t => t.status === "new" },
    { id: "mine",      label: "Mine",      test: t => t.assignee === "ay" && !["resolved","rejected"].includes(t.status) },
    { id: "breached",  label: "SLA breached", test: t => t.slaFirstResp === "breached" || t.slaResolve === "breached" },
    { id: "resolved",  label: "Resolved",  test: t => t.status === "resolved" },
    { id: "all",       label: "All",       test: () => true },
  ];
  const activeTab = tabs.find(x => x.id === tab);

  const filtered = tickets.filter(t => {
    if (!activeTab.test(t)) return false;
    if (q && !(t.subject.toLowerCase().includes(q.toLowerCase()) || t.requester.name.toLowerCase().includes(q.toLowerCase()) || t.id.toLowerCase().includes(q.toLowerCase()))) return false;
    if (sev !== "all" && t.severity !== sev) return false;
    if (ch !== "all" && t.channel !== ch) return false;
    if (assignee !== "all" && t.assignee !== assignee) return false;
    return true;
  });

  // KPIs (computed from full list, not filtered)
  const kOpen = tickets.filter(t => !["resolved","rejected","duplicate"].includes(t.status)).length;
  const kNew = tickets.filter(t => t.status === "new").length;
  const kBreached = tickets.filter(t => t.slaFirstResp === "breached").length;
  const kMine = tickets.filter(t => t.assignee === "ay" && !["resolved","rejected"].includes(t.status)).length;

  return (
    <div className="tickets-page">
      <div className="tickets-header">
        <div className="tickets-title-row">
          <div>
            <div className="tickets-title">Inbox</div>
            <div className="tickets-sub">Customer tickets, bug reports, and feature requests — triage and convert to tasks</div>
          </div>
          <div className="tickets-kpis">
            <div className="tickets-kpi"><span className="tickets-kpi-label">Open</span><span className="tickets-kpi-value">{kOpen}</span></div>
            <div className={`tickets-kpi ${kNew ? "warn" : ""}`}><span className="tickets-kpi-label">New</span><span className="tickets-kpi-value">{kNew}</span></div>
            <div className={`tickets-kpi ${kBreached ? "danger" : "good"}`}><span className="tickets-kpi-label">SLA breached</span><span className="tickets-kpi-value">{kBreached}</span></div>
            <div className="tickets-kpi"><span className="tickets-kpi-label">Assigned to me</span><span className="tickets-kpi-value">{kMine}</span></div>
          </div>
        </div>
        <div className="tickets-tabs">
          {tabs.map(t => {
            const count = tickets.filter(x => t.test(x)).length;
            return (
              <button key={t.id} className={`tickets-tab ${tab === t.id ? "is-active" : ""}`} onClick={() => setTab(t.id)}>
                {t.label}<span className="tickets-tab-count">{count}</span>
              </button>
            );
          })}
          <div className="tickets-view-switch">
            <button className={view === "inbox" ? "is-active" : ""} onClick={() => setView("inbox")}>
              <Icons.Table size={13}/> List
            </button>
            <button className={view === "triage" ? "is-active" : ""} onClick={() => setView("triage")}>
              <Icons.Board size={13}/> Triage
            </button>
          </div>
        </div>
      </div>

      {/* Filters */}
      <div className="tickets-filters">
        <div style={{ position: "relative", flex: "0 0 280px" }}>
          <Icons.Search size={14} style={{ position: "absolute", left: 10, top: "50%", transform: "translateY(-50%)", color: "var(--ink-muted)" }}/>
          <input className="tb-search" style={{ paddingLeft: 30, width: "100%" }}
                 placeholder="Search tickets, requesters…"
                 value={q} onChange={e => setQ(e.target.value)}/>
        </div>
        <select className="tb-search" style={{ width: 130 }} value={sev} onChange={e => setSev(e.target.value)}>
          <option value="all">All severities</option>
          {TICKET_SEVERITIES.map(s => <option key={s.id} value={s.id}>{s.label}</option>)}
        </select>
        <select className="tb-search" style={{ width: 130 }} value={ch} onChange={e => setCh(e.target.value)}>
          <option value="all">All channels</option>
          {TICKET_CHANNELS.map(c => <option key={c.id} value={c.id}>{c.label}</option>)}
        </select>
        <select className="tb-search" style={{ width: 150 }} value={assignee} onChange={e => setAssignee(e.target.value)}>
          <option value="all">All assignees</option>
          <option value="unassigned">Unassigned</option>
          {PEOPLE.filter(p => p.status !== "deactivated").slice(0,10).map(p => <option key={p.id} value={p.id}>{p.name.split(" ")[0]}</option>)}
        </select>
        <div style={{ flex: 1 }}/>
        <button className="btn btn-primary" onClick={onNew}>
          <Icons.Plus size={14}/> New ticket
        </button>
      </div>

      <div className="tickets-table-wrap">
        <table className="tickets-table">
          <thead>
            <tr>
              <th style={{ width: 68 }}>ID</th>
              <th style={{ width: 70 }}>Status</th>
              <th>Subject</th>
              <th style={{ width: 68 }}>Severity</th>
              <th style={{ width: 170 }}>Requester</th>
              <th style={{ width: 86 }}>Channel</th>
              <th style={{ width: 110 }}>SLA (1st resp)</th>
              <th style={{ width: 120 }}>Linked</th>
              <th style={{ width: 40 }}>Owner</th>
            </tr>
          </thead>
          <tbody>
            {filtered.map(t => (
              <tr key={t.id} onClick={() => onOpen(t)}>
                <td><span className="ticket-id">{t.id}</span></td>
                <td><TicketStatusChip id={t.status}/></td>
                <td>
                  <div className="ticket-subject">
                    <span className="ticket-subject-body">{t.subject}</span>
                    {t.tags?.slice(0,2).map(tg => <span key={tg} className="tag-chip">{tg}</span>)}
                  </div>
                </td>
                <td><SeverityChip id={t.severity}/></td>
                <td>
                  <div className="ticket-requester">
                    <span className="ticket-requester-name">{t.requester.name}</span>
                    <span className="ticket-requester-co">{t.requester.company}</span>
                  </div>
                </td>
                <td><ChannelChip id={t.channel}/></td>
                <td><SlaChip state={t.slaFirstResp}/></td>
                <td>
                  {t.linkedTasks.length > 0 ? (
                    <span className="ticket-linked">
                      <Icons.Link size={11}/> {t.linkedTasks.length} task{t.linkedTasks.length > 1 ? "s" : ""}
                    </span>
                  ) : t.duplicateOf ? (
                    <span className="ticket-linked" style={{ background: "var(--surface-2)", color: "var(--ink-muted)" }}>
                      dup of {t.duplicateOf}
                    </span>
                  ) : <span style={{ color: "var(--ink-muted)", fontSize: 11 }}>—</span>}
                </td>
                <td>{t.assignee ? <Avatar person={PEOPLE.find(p => p.id === t.assignee)} size={24}/> : <span style={{ color: "var(--ink-muted)", fontSize: 11 }}>—</span>}</td>
              </tr>
            ))}
            {filtered.length === 0 && (
              <tr><td colSpan={9} style={{ textAlign: "center", color: "var(--ink-muted)", padding: 40, fontSize: 13 }}>No tickets match your filters.</td></tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ── Triage Kanban ────────────────────────────────────────────────────
function TicketsKanban({ tickets, onOpen, view, setView }) {
  const columns = [
    { id: "new",       label: "New",       dot: "#0073ea" },
    { id: "triaging",  label: "Triaging",  dot: "#a25ddc" },
    { id: "accepted",  label: "Accepted",  dot: "#00c875" },
    { id: "waiting",   label: "Waiting",   dot: "#fdab3d" },
    { id: "resolved",  label: "Resolved",  dot: "#037f4c" },
  ];
  return (
    <div className="tickets-page" style={{ display: "flex", flexDirection: "column" }}>
      <div className="tickets-header">
        <div className="tickets-title-row">
          <div>
            <div className="tickets-title">Triage board</div>
            <div className="tickets-sub">Drag tickets between columns · focus on New and SLA-breached first</div>
          </div>
          <div className="tickets-kpis">
            <div className="tickets-kpi"><span className="tickets-kpi-label">Open</span><span className="tickets-kpi-value">{tickets.filter(t => !["resolved","rejected","duplicate"].includes(t.status)).length}</span></div>
            <div className="tickets-kpi danger"><span className="tickets-kpi-label">Breached</span><span className="tickets-kpi-value">{tickets.filter(t => t.slaFirstResp === "breached").length}</span></div>
          </div>
        </div>
        <div className="tickets-tabs">
          <button className="tickets-tab is-active">All open</button>
          <button className="tickets-tab">P0/P1 only</button>
          <button className="tickets-tab">Unassigned</button>
          <div className="tickets-view-switch">
            <button className={view === "inbox" ? "is-active" : ""} onClick={() => setView("inbox")}>
              <Icons.Table size={13}/> List
            </button>
            <button className={view === "triage" ? "is-active" : ""} onClick={() => setView("triage")}>
              <Icons.Board size={13}/> Triage
            </button>
          </div>
        </div>
      </div>

      <div className="tickets-kanban">
        {columns.map(col => {
          const col_tickets = tickets.filter(t => t.status === col.id)
            .sort((a,b) => (a.severity || "p3").localeCompare(b.severity || "p3"));
          return (
            <div key={col.id} className="tk-column">
              <div className="tk-column-header">
                <span className="tk-column-dot" style={{ background: col.dot }}/>
                <span className="tk-column-name">{col.label}</span>
                <span className="tk-column-count">{col_tickets.length}</span>
              </div>
              <div className="tk-column-body">
                {col_tickets.map(t => (
                  <div key={t.id} className="tk-card" onClick={() => onOpen(t)}>
                    <div className="tk-card-top">
                      <span className="ticket-id">{t.id}</span>
                      <span style={{ marginLeft: "auto" }}><ChannelChip id={t.channel} compact/></span>
                    </div>
                    <div className="tk-card-title">{t.subject}</div>
                    <div className="tk-card-meta">
                      <SeverityChip id={t.severity}/>
                      {t.linkedTasks.length > 0 && (
                        <span className="ticket-linked"><Icons.Link size={10}/> {t.linkedTasks.length}</span>
                      )}
                      {t.slaFirstResp === "breached" && <SlaChip state="breached" label="SLA breached"/>}
                      {t.slaFirstResp === "at-risk" && <SlaChip state="at-risk" label="At risk"/>}
                      <span className="tk-card-assignee">
                        {t.assignee ? <Avatar person={PEOPLE.find(p => p.id === t.assignee)} size={22}/>
                                    : <span style={{ fontSize: 11, color: "var(--ink-muted)" }}>—</span>}
                      </span>
                    </div>
                    <div style={{ fontSize: 11, color: "var(--ink-muted)", fontWeight: 500 }}>
                      {t.requester.name} · {t.requester.company}
                    </div>
                  </div>
                ))}
                {col_tickets.length === 0 && (
                  <div style={{ padding: 24, textAlign: "center", color: "var(--ink-muted)", fontSize: 12 }}>
                    No tickets
                  </div>
                )}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ── Ticket Drawer ────────────────────────────────────────────────────
function TicketDrawer({ ticket, open, onClose, onConvert, onUpdate, tasks }) {
  const [tab, setTab] = React.useState("public");
  const [reply, setReply] = React.useState("");
  if (!open || !ticket) return null;

  const channel = getChannel(ticket.channel);
  const assignee = PEOPLE.find(p => p.id === ticket.assignee);
  const project = PROJECTS.find(p => p.id === ticket.project);

  return (
    <>
      <div className="td-backdrop" onClick={onClose}/>
      <div className="td-drawer" onClick={e => e.stopPropagation()}>
        <div className="td-header">
          <div className="td-header-main">
            <div className="td-header-meta">
              <span className="ticket-id">{ticket.id}</span>
              <TicketStatusChip id={ticket.status}/>
              <SeverityChip id={ticket.severity}/>
              <ChannelChip id={ticket.channel}/>
              {ticket.duplicateOf && <span className="ticket-linked" style={{ background: "var(--surface-2)", color: "var(--ink-muted)" }}>dup of {ticket.duplicateOf}</span>}
            </div>
            <div className="td-header-title">{ticket.subject}</div>
          </div>
          <button className="td-close" onClick={onClose}>
            <Icons.Close size={16}/>
          </button>
        </div>

        <div className="td-body">
          <div style={{ display: "flex", flexDirection: "column", overflow: "hidden" }}>
            <div className="td-thread">
              {ticket.thread.map((m, i) => (
                <div key={i} className={`td-msg ${m.role === "internal" ? "is-internal" : ""}`}>
                  <div className="td-msg-head">
                    <span className="td-msg-from">{m.from}</span>
                    <span className={`td-msg-role ${m.role}`}>{m.role === "customer" ? "Customer" : "Internal note"}</span>
                    <span className="td-msg-time">{m.at}</span>
                  </div>
                  <div className="td-msg-body">{m.body}</div>
                </div>
              ))}
            </div>
            <div className="td-reply">
              <div className="td-reply-tabs">
                <button className={tab === "public" ? "is-active" : ""} onClick={() => setTab("public")}>
                  Public reply
                </button>
                <button className={tab === "internal" ? "is-active" : ""} onClick={() => setTab("internal")}>
                  Internal note
                </button>
              </div>
              <textarea placeholder={tab === "public" ? "Reply to the customer…" : "Note visible only to your team…"}
                        value={reply} onChange={e => setReply(e.target.value)}/>
              <div className="td-reply-actions">
                <button className="btn btn-ghost"><Icons.Paperclip size={13}/> Attach</button>
                <div style={{ flex: 1 }}/>
                <select className="btn btn-ghost" style={{ width: 140 }} value={ticket.status}
                        onClick={e => e.stopPropagation()}
                        onChange={e => onUpdate(ticket.id, { status: e.target.value })}>
                  {TICKET_STATUSES.map(s => <option key={s.id} value={s.id}>{s.label}</option>)}
                </select>
                <button className="btn btn-primary" disabled={!reply.trim()}>
                  Send {tab === "public" ? "reply" : "note"}
                </button>
              </div>
            </div>
          </div>

          <div className="td-side">
            <button className="td-convert-btn" onClick={() => onConvert(ticket)}>
              <Icons.Plus size={14}/> Convert to task{ticket.linkedTasks.length > 0 ? "s" : ""}
            </button>

            {ticket.linkedTasks.length > 0 && (
              <div className="td-side-section">
                <div className="td-side-label">Linked tasks ({ticket.linkedTasks.length})</div>
                {ticket.linkedTasks.map(tid => {
                  const tk = tasks.find(x => x.id === tid);
                  if (!tk) return null;
                  const status = STATUSES.find(s => s.id === tk.status);
                  return (
                    <div key={tid} className="td-linked-task">
                      <span className="td-linked-task-id">{tk.id.toUpperCase()}</span>
                      <span className="td-linked-task-name">{tk.name}</span>
                      <span className={`pill ${status?.cls}`} style={{ fontSize: 10 }}>{status?.label}</span>
                    </div>
                  );
                })}
              </div>
            )}

            <div className="td-side-section">
              <div className="td-side-label">Requester</div>
              <div className="td-requester-card">
                <div className="td-requester-name">{ticket.requester.name}</div>
                <div className="td-requester-email">{ticket.requester.email}</div>
                <div className="td-requester-co">{ticket.requester.company}</div>
              </div>
            </div>

            <div className="td-side-section">
              <div className="td-side-label">Details</div>
              <div className="td-side-field"><span className="td-side-key">Assignee</span>
                <span className="td-side-val">{assignee ? assignee.name.split(" ")[0] : "—"}</span></div>
              <div className="td-side-field"><span className="td-side-key">Project</span>
                <span className="td-side-val">{project ? project.name : <span style={{ color: "var(--ink-muted)", fontWeight: 500 }}>Not set</span>}</span></div>
              <div className="td-side-field"><span className="td-side-key">Opened</span>
                <span className="td-side-val">{ticket.openedAt}</span></div>
              <div className="td-side-field"><span className="td-side-key">First reply</span>
                <span className="td-side-val">{ticket.firstResponseAt || <span style={{ color: "#e2445c" }}>—</span>}</span></div>
              {ticket.resolvedAt && (
                <div className="td-side-field"><span className="td-side-key">Resolved</span>
                  <span className="td-side-val">{ticket.resolvedAt}</span></div>
              )}
            </div>

            <div className="td-side-section">
              <div className="td-side-label">SLA</div>
              <div className="td-side-field"><span className="td-side-key">First response</span><SlaChip state={ticket.slaFirstResp}/></div>
              <div className="td-side-field"><span className="td-side-key">Resolution</span><SlaChip state={ticket.slaResolve}/></div>
            </div>

            {ticket.tags && ticket.tags.length > 0 && (
              <div className="td-side-section">
                <div className="td-side-label">Tags</div>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
                  {ticket.tags.map(t => <span key={t} className="tag-chip">{t}</span>)}
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
    </>
  );
}

// ── Convert-to-task modal ────────────────────────────────────────────────────
function ConvertTicketModal({ open, onClose, ticket, onConvert }) {
  const [drafts, setDrafts] = React.useState([]);
  const [projectId, setProjectId] = React.useState("checkout");
  const [epicId, setEpicId] = React.useState(EPICS[0]?.id || "");
  const [sprintId, setSprintId] = React.useState("");
  const [assignee, setAssignee] = React.useState("ay");
  const [priority, setPriority] = React.useState("high");
  const [after, setAfter] = React.useState("keep-open");

  React.useEffect(() => {
    if (!open || !ticket) return;
    // Seed one draft from the ticket subject
    setDrafts([{ id: 1, name: ticket.subject.replace(/^\w+:\s*/, "") }]);
    setProjectId(ticket.project || "checkout");
    setAssignee(ticket.assignee || "ay");
    setPriority(ticket.severity === "p0" ? "critical"
             : ticket.severity === "p1" ? "high"
             : ticket.severity === "p2" ? "medium" : "low");
  }, [open, ticket]);

  if (!open || !ticket) return null;

  const addDraft = () => setDrafts(ds => [...ds, { id: Date.now(), name: "" }]);
  const removeDraft = (id) => setDrafts(ds => ds.filter(d => d.id !== id));
  const setName = (id, name) => setDrafts(ds => ds.map(d => d.id === id ? { ...d, name } : d));
  const valid = drafts.some(d => d.name.trim());

  return ReactDOM.createPortal(
    <div className="modal-backdrop" onMouseDown={onClose}>
      <div className="modal convert-modal" onMouseDown={e => e.stopPropagation()}>
        <div className="modal-header">
          <div>
            <div className="modal-title">Convert ticket to task{drafts.length > 1 ? "s" : ""}</div>
            <div className="modal-subtitle">{ticket.id} · {ticket.subject}</div>
          </div>
          <button className="modal-close" onClick={onClose}><Icons.Close size={16}/></button>
        </div>
        <div className="modal-body">
          <div className="convert-suggestion">
            <div className="convert-suggestion-icon"><Icons.Lightning size={14}/></div>
            <div>
              <strong>Split the ticket?</strong> One ticket can become multiple linked tasks (e.g. a bug fix, docs update, and migration).
              Add a row for each task — they'll all link back to {ticket.id} so this ticket auto-resolves when they're done.
            </div>
          </div>

          <div className="convert-tasks-list">
            {drafts.map((d, i) => (
              <div key={d.id} className="convert-task-row">
                <span style={{ color: "var(--ink-muted)", fontSize: 11, fontWeight: 700, minWidth: 24 }}>{i + 1}.</span>
                <input type="text" placeholder="Task title…" value={d.name}
                       autoFocus={i === drafts.length - 1}
                       onChange={e => setName(d.id, e.target.value)}/>
                {drafts.length > 1 && (
                  <button className="convert-task-row-remove" onClick={() => removeDraft(d.id)}>
                    <Icons.Close size={13}/>
                  </button>
                )}
              </div>
            ))}
            <div className="convert-add-task-row" onClick={addDraft}>
              <Icons.Plus size={13}/> Add another task
            </div>
          </div>

          <div className="convert-grid">
            <div className="convert-field">
              <label>Project</label>
              <select value={projectId} onChange={e => setProjectId(e.target.value)}>
                {PROJECTS.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
              </select>
            </div>
            <div className="convert-field">
              <label>Epic</label>
              <select value={epicId} onChange={e => setEpicId(e.target.value)}>
                <option value="">No epic</option>
                {EPICS.map(e => <option key={e.id} value={e.id}>{e.title}</option>)}
              </select>
            </div>
            <div className="convert-field">
              <label>Sprint</label>
              <select value={sprintId} onChange={e => setSprintId(e.target.value)}>
                <option value="">Backlog (no sprint)</option>
                {SPRINTS.filter(s => s.active).map(s => <option key={s.id} value={s.id}>{s.label} (active)</option>)}
              </select>
            </div>
            <div className="convert-field">
              <label>Assignee</label>
              <select value={assignee} onChange={e => setAssignee(e.target.value)}>
                {PEOPLE.filter(p => p.status !== "deactivated").slice(0, 10).map(p =>
                  <option key={p.id} value={p.id}>{p.name}</option>)}
              </select>
            </div>
            <div className="convert-field">
              <label>Priority</label>
              <select value={priority} onChange={e => setPriority(e.target.value)}>
                {PRIORITIES.filter(p => p.id !== "none").map(p => <option key={p.id} value={p.id}>{p.label}</option>)}
              </select>
            </div>
            <div className="convert-field">
              <label>Due date</label>
              <input type="text" placeholder="e.g. Apr 26" defaultValue={ticket.severity === "p0" ? "Today" : ticket.severity === "p1" ? "Apr 25" : "—"}/>
            </div>
          </div>

          <div className="convert-field convert-grid-full">
            <label>After conversion</label>
            <div className="convert-after">
              <label className={`convert-after-opt ${after === "keep-open" ? "is-active" : ""}`}>
                <input type="radio" name="after" checked={after === "keep-open"} onChange={() => setAfter("keep-open")}/>
                <div>
                  <div className="convert-after-opt-title">Keep ticket open & link (recommended)</div>
                  <div className="convert-after-opt-desc">Ticket stays in Triaging. When all linked tasks are Done, it auto-resolves and the customer is notified.</div>
                </div>
              </label>
              <label className={`convert-after-opt ${after === "accept" ? "is-active" : ""}`}>
                <input type="radio" name="after" checked={after === "accept"} onChange={() => setAfter("accept")}/>
                <div>
                  <div className="convert-after-opt-title">Mark ticket Accepted</div>
                  <div className="convert-after-opt-desc">Send the customer the canned "We've logged this" reply and move to Accepted.</div>
                </div>
              </label>
              <label className={`convert-after-opt ${after === "resolve" ? "is-active" : ""}`}>
                <input type="radio" name="after" checked={after === "resolve"} onChange={() => setAfter("resolve")}/>
                <div>
                  <div className="convert-after-opt-title">Resolve ticket now</div>
                  <div className="convert-after-opt-desc">Use when the task is already done (e.g. a quick fix or docs answer).</div>
                </div>
              </label>
            </div>
          </div>
        </div>
        <div className="modal-footer">
          <div style={{ flex: 1, color: "var(--ink-muted)", fontSize: 12 }}>
            {drafts.filter(d => d.name.trim()).length} task{drafts.filter(d => d.name.trim()).length !== 1 ? "s" : ""} will be created in {PROJECTS.find(p => p.id === projectId)?.name}
          </div>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" disabled={!valid}
                  onClick={() => onConvert({
                    drafts: drafts.filter(d => d.name.trim()),
                    projectId, epicId, sprintId, assignee, priority, after,
                  })}>
            <Icons.Plus size={13}/> Create {drafts.filter(d => d.name.trim()).length} task{drafts.filter(d => d.name.trim()).length > 1 ? "s" : ""}
          </button>
        </div>
      </div>
    </div>,
    document.body
  );
}

// ── TicketsApp — all-in-one for artboards ────────────────────────────────────
function TicketsApp({ startView = "inbox" }) {
  const [view, setView] = React.useState(startView);     // inbox | triage
  const [tickets, setTickets] = React.useState(TICKETS);
  const [tasks, setTasks] = React.useState(ALL_TASKS);
  const [openTicket, setOpenTicket] = React.useState(null);
  const [convertTicket, setConvertTicket] = React.useState(null);
  const [toast, setToast] = React.useState(null);
  const [paletteOpen, setPaletteOpen] = React.useState(false);

  React.useEffect(() => {
    const onOpen = () => setPaletteOpen(true);
    window.addEventListener("palette:open", onOpen);
    return () => window.removeEventListener("palette:open", onOpen);
  }, []);

  function showToast(msg) { setToast(msg); setTimeout(() => setToast(null), 3200); }

  function updateTicket(id, patch) {
    setTickets(ts => ts.map(t => t.id === id ? { ...t, ...patch } : t));
    setOpenTicket(t => t && t.id === id ? { ...t, ...patch } : t);
  }

  function onConvertFromDrawer(ticket) {
    setConvertTicket(ticket);
  }

  function doConvert({ drafts, projectId, epicId, sprintId, assignee, priority, after }) {
    const newTasks = drafts.map((d, i) => ({
      id: "tk" + Date.now() + "-" + i,
      name: d.name,
      status: "todo", prio: priority, owners: [assignee], due: "—", points: 3,
      sprint: sprintId || null, updated: "just now", subtasks: 0,
      epicId, epicTitle: EPICS.find(e => e.id === epicId)?.title || null,
      epicColor: EPICS.find(e => e.id === epicId)?.color || null,
      fromTicket: convertTicket.id,
    }));
    setTasks(ts => [...ts, ...newTasks]);
    updateTicket(convertTicket.id, {
      linkedTasks: [...convertTicket.linkedTasks, ...newTasks.map(t => t.id)],
      status: after === "resolve" ? "resolved" : after === "accept" ? "accepted" : convertTicket.status === "new" ? "triaging" : convertTicket.status,
      assignee: convertTicket.assignee || assignee,
      project: convertTicket.project || projectId,
    });
    setConvertTicket(null);
    showToast(`Created ${newTasks.length} task${newTasks.length > 1 ? "s" : ""} from ${convertTicket.id}`);
  }

  const openCount = tickets.filter(t => !["resolved","rejected","duplicate"].includes(t.status)).length;

  return (
    <div className="app" style={{ position: "relative", overflow: "hidden" }}>
      <Sidebar activeProject={null} onOpenTickets={() => setView("inbox")}
               ticketsActive={true} ticketsCount={openCount}
               currentUserId="ay"/>
      <div className="main">
        <Topbar crumbs={[WORKSPACE.name, "Inbox", view === "inbox" ? "All tickets" : "Triage"]}
                searchValue="" onSearch={() => {}}
                onOpenPalette={() => setPaletteOpen(true)}
                currentUserId="ay" people={PEOPLE}/>
        {view === "inbox"
          ? <TicketsInbox tickets={tickets} onOpen={setOpenTicket} onNew={() => showToast("New ticket form — coming soon")} view={view} setView={setView}/>
          : <TicketsKanban tickets={tickets} onOpen={setOpenTicket} view={view} setView={setView}/>}
      </div>
      <TicketDrawer ticket={openTicket} open={!!openTicket}
                    onClose={() => setOpenTicket(null)}
                    onConvert={onConvertFromDrawer}
                    onUpdate={updateTicket}
                    tasks={tasks}/>
      <ConvertTicketModal open={!!convertTicket} ticket={convertTicket}
                          onClose={() => setConvertTicket(null)}
                          onConvert={doConvert}/>
      <CommandPalette open={paletteOpen} onClose={() => setPaletteOpen(false)} onNavigate={() => {}}/>
      {/* Toast — same normalisation as app.jsx so an object payload
          (used by undo flows) doesn't throw "Objects are not valid as
          a React child" and white-screen the tickets module. */}
      {toast && (() => {
        const isObj = toast && typeof toast === "object" && !Array.isArray(toast);
        const text = isObj ? toast.msg : toast;
        const action = isObj ? toast.action : null;
        const ms = isObj ? toast.ms : null;
        return (
          <div className="fb-toast">
            <span>{text}</span>
            {action && action.label && (
              <button type="button" className="fb-toast-action"
                      onClick={() => {
                        try { action.onClick && action.onClick(); }
                        catch (e) { console.warn("[toast-action] threw:", e); }
                      }}>
                {action.label}
              </button>
            )}
            {action && ms ? (
              <span className="fb-toast-progress" style={{ animationDuration: ms + "ms" }}/>
            ) : null}
          </div>
        );
      })()}
    </div>
  );
}

Object.assign(window, {
  TicketsApp, TicketsInbox, TicketsKanban, TicketDrawer, ConvertTicketModal,
  SeverityChip, TicketStatusChip, ChannelChip, SlaChip,
});
