const { useState, useEffect, useMemo, useRef } = React;

// API host resolution:
//   1. window.JIRA_INTERFACE_API_URL — explicit override (set in auth-config.js for unusual setups)
//   2. Same-origin "" — used when FastAPI serves the SPA (production App Runner, or `uvicorn` dev)
//   3. http://hostname:8000 — fallback when SPA is on :8080 (legacy split-port dev workflow)
const BASE_API_URL = (() => {
  if (typeof window === "undefined") return "";
  if (typeof window.JIRA_INTERFACE_API_URL === "string") return window.JIRA_INTERFACE_API_URL;
  if (window.location.port === "8080") return `http://${window.location.hostname}:8000`;
  return "";
})();

/** Adds Cognito access token when auth-config enables useCognito. On 401, tries refresh_token once (60m access token). */
async function apiFetch(url, options) {
  const opts = { credentials: "include", ...(options || {}) };
  const buildHeaders = () => {
    const headers = { ...(opts.headers || {}) };
    if (typeof window !== "undefined" && window.JIRA_INTERFACE_AUTH && window.JIRA_INTERFACE_AUTH.useCognito) {
      const t = sessionStorage.getItem("cognito_access_token");
      if (t) headers.Authorization = `Bearer ${t}`;
    }
    return headers;
  };
  let res = await fetch(url, { ...opts, headers: buildHeaders() });
  if (
    res.status === 401 &&
    typeof window !== "undefined" &&
    window.JIRA_INTERFACE_AUTH &&
    window.JIRA_INTERFACE_AUTH.useCognito &&
    window.CognitoPkce &&
    typeof window.CognitoPkce.refreshAccessToken === "function"
  ) {
    const renewed = await window.CognitoPkce.refreshAccessToken();
    if (renewed) res = await fetch(url, { ...opts, headers: buildHeaders() });
  }
  return res;
}

const STATUS_BACKLOG = "Backlog";
const STATUS_WIP = "Work in progress";
const STATUS_ESCALATED = "Escalated";
const STATUS_WAITING_FOR_CUSTOMER = "Waiting for customer";
const STATUS_OPTIONS = [
  "Backlog",
  "Escalated",
  "Solution Provided",
  "Waiting for customer",
  "Work in progress",
  "Closed",
  "Done",
  "Canceled",
];

function formatCurrency(value) {
  if (value == null || Number.isNaN(value)) return "—";
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: "USD",
    maximumFractionDigits: 0,
  }).format(value);
}

function formatResolutionHours(hours) {
  if (hours == null || Number.isNaN(hours)) return "—";
  if (hours < 1) return `${Math.round(hours * 60)} min`;
  if (hours < 24) return `${hours.toFixed(1)} h`;
  return `${(hours / 24).toFixed(1)} days`;
}

function formatDurationSince(isoString) {
  if (!isoString) return "";
  const ms = Date.now() - new Date(isoString).getTime();
  if (ms < 0) return "";
  const mins = Math.floor(ms / 60000);
  const hours = Math.floor(ms / 3600000);
  const days = Math.floor(ms / 86400000);
  if (mins < 60) return `${mins}m`;
  if (hours < 24) return `${hours}h`;
  if (days < 7) return `${days}d`;
  const weeks = Math.floor(days / 7);
  return `${weeks}w`;
}

function getTierValueColorClass(tier, hours) {
  if (hours == null || Number.isNaN(hours)) return "";
  if (tier === "Standard" || tier === "No ARR") return "";
  const days = hours / 24;
  if (tier === "Diamond" || tier === "Platinum" || tier === "Gold") {
    if (days < 8) return "stat-tier-green";
    if (days < 10) return "stat-tier-orange";
    return "stat-tier-red";
  }
  if (tier === "Silver" || tier === "Bronze") {
    if (days < 12) return "stat-tier-green";
    if (days < 15) return "stat-tier-orange";
    return "stat-tier-red";
  }
  return "";
}

function stripHtmlToText(html) {
  if (!html || !html.trim()) return "";
  const div = document.createElement("div");
  div.innerHTML = html;
  return (div.innerText || div.textContent || "").trim();
}

function extractWithMarks(el) {
  const parts = [];
  const walk = (n, m = []) => {
    if (n.nodeType === Node.TEXT_NODE) {
      const t = n.textContent || "";
      if (t) parts.push({ text: t, marks: [...m] });
      return;
    }
    if (n.nodeType !== Node.ELEMENT_NODE) return;
    const tag = n.tagName ? n.tagName.toLowerCase() : "";
    const mentionId = n.getAttribute?.("data-mention-id");
    if (tag === "span" && mentionId) {
      parts.push({ type: "mention", attrs: { id: mentionId, text: n.getAttribute("data-mention-name") || n.textContent || "" } });
      return;
    }
    const newMarks = [...m];
    if (tag === "b" || tag === "strong") newMarks.push({ type: "strong" });
    else if (tag === "i" || tag === "em") newMarks.push({ type: "em" });
    else if (tag === "u") newMarks.push({ type: "underline" });
    else if (tag === "s" || tag === "strike") newMarks.push({ type: "strike" });
    else if (tag === "code") newMarks.push({ type: "code" });
    else if (tag === "sub") newMarks.push({ type: "subsup", attrs: { type: "sub" } });
    else if (tag === "sup") newMarks.push({ type: "subsup", attrs: { type: "sup" } });
    else if (tag === "a") newMarks.push({ type: "link", attrs: { href: n.getAttribute("href") || "" } });
    else if (tag === "br") parts.push({ text: "\n", marks: [...m] });
    for (const c of n.childNodes) walk(c, newMarks);
  };
  walk(el);
  return parts;
}

function htmlToAdf(html, issueKey = null) {
  if (!html || !html.trim()) {
    return { version: 1, type: "doc", content: [{ type: "paragraph", content: [{ type: "text", text: "" }] }] };
  }
  const doc = document.createElement("div");
  doc.innerHTML = html;
  const content = [];
  const addParagraph = (parts) => {
    if (!parts.length) {
      content.push({ type: "paragraph", content: [{ type: "text", text: "" }] });
      return;
    }
    content.push({
      type: "paragraph",
      content: parts.map((p) => {
        if (p.type === "mention") {
          const text = p.attrs?.text || "";
          return { type: "mention", attrs: { id: p.attrs?.id || "", text: text.startsWith("@") ? text : `@${text}` } };
        }
        const n = { type: "text", text: p.text };
        if (p.marks && p.marks.length) n.marks = p.marks;
        return n;
      }),
    });
  };
  const addCodeBlock = (text) => {
    content.push({
      type: "codeBlock",
      content: [{ type: "text", text: (text || "").replace(/\n$/, "") }],
    });
  };
  const addImageBlock = (img) => {
    const attId = img.getAttribute("data-attachment-id");
    const attUrl = img.getAttribute("data-attachment-url");
    const filename = img.getAttribute("data-attachment-filename") || img.alt || "image";
    if (!attId && !attUrl) return;
    const href = attUrl || (issueKey ? `${BASE_API_URL}/api/tickets/${encodeURIComponent(issueKey)}/attachments/${encodeURIComponent(attId)}/content` : "");
    if (href) {
      content.push({
        type: "paragraph",
        content: [{
          type: "text",
          text: `[${filename}]`,
          marks: [{ type: "link", attrs: { href } }],
        }],
      });
    }
  };
  const flushInline = (parts) => {
    if (parts.length) addParagraph(parts);
  };
  let inlineParts = [];
  const walk = (parent) => {
    for (const node of parent.childNodes) {
      if (node.nodeType === Node.TEXT_NODE) {
        const t = node.textContent || "";
        if (t) inlineParts.push({ text: t, marks: [] });
        continue;
      }
      if (node.nodeType !== Node.ELEMENT_NODE) continue;
      const tag = node.tagName ? node.tagName.toLowerCase() : "";
      if (tag === "img") {
        flushInline(inlineParts);
        inlineParts = [];
        addImageBlock(node);
      } else if (tag === "pre") {
        flushInline(inlineParts);
        inlineParts = [];
        addCodeBlock(node.textContent || "");
      } else if (tag === "p" || tag === "li") {
        flushInline(inlineParts);
        inlineParts = [];
        const parts = extractWithMarks(node);
        if (parts.length) addParagraph(parts);
      } else if (tag === "div") {
        walk(node);
      } else {
        inlineParts.push(...extractWithMarks(node));
      }
    }
  };
  walk(doc);
  flushInline(inlineParts);
  if (!content.length) {
    const parts = extractWithMarks(doc);
    if (parts.length) addParagraph(parts);
    else {
      const text = (doc.innerText || doc.textContent || "").trim();
      if (text) text.split("\n").forEach((line) => addParagraph([{ text: line, marks: [] }]));
      else addParagraph([]);
    }
  }
  if (content.length === 0) content.push({ type: "paragraph", content: [{ type: "text", text: "" }] });
  return { version: 1, type: "doc", content };
}

function adfToHtml(adf, opts = {}) {
  const { jiraBaseUrl = "", issueKey = "", attachments = [] } = typeof opts === "string" ? { jiraBaseUrl: opts } : opts;
  if (!adf || !adf.content) return "";
  const out = [];
  for (const block of adf.content) {
    if (block.type === "mediaSingle") {
      const media = (block.content || [])[0];
      if (media?.type === "media" && media.attrs?.id) {
        const mediaId = media.attrs.id;
        const alt = media.attrs.alt || "image";
        const byFilename = attachments.find((a) => (a.filename || "").toLowerCase() === (alt || "").toLowerCase());
        const byId = attachments.find((a) => String(a.id) === String(mediaId));
        const byMediaIdInFilename = attachments.find((a) => (a.filename || "").includes(mediaId));
        const attachmentId = byFilename?.id ?? byId?.id ?? byMediaIdInFilename?.id ?? mediaId;
        const url = issueKey
          ? `${BASE_API_URL}/api/tickets/${encodeURIComponent(issueKey)}/attachments/${encodeURIComponent(attachmentId)}/content`
          : jiraBaseUrl
            ? `${jiraBaseUrl}/rest/api/3/attachment/content/${attachmentId}`
            : "";
        out.push(`<p><img src="${escapeHtml(url)}" alt="${escapeHtml(alt)}" data-attachment-id="${escapeHtml(attachmentId)}" class="comment-inline-image" style="max-width:100%;max-height:200px;cursor:pointer" /></p>`);
      }
    } else if (block.type === "codeBlock") {
      const text = (block.content || []).map((c) => c.text || "").join("");
      const lines = text.split("\n");
      const lineNums = lines.map((_, i) => i + 1).join("\n");
      const lineNumsHtml = escapeHtml(lineNums);
      const codeHtml = lines.map((l) => `<span class="code-line">${escapeHtml(l)}</span>`).join("\n");
      out.push(`<div class="code-block"><span class="code-line-numbers">${lineNumsHtml}</span><pre><code>${codeHtml}</code></pre></div>`);
    } else if (block.type === "paragraph") {
      const parts = (block.content || []).map((frag) => {
        if (frag.type === "mention") {
          const id = frag.attrs?.id || "";
          const text = frag.attrs?.text || "";
          return `<span data-mention-id="${escapeHtml(id)}" data-mention-name="${escapeHtml(text.replace(/^@/, ""))}" contenteditable="false">${escapeHtml(text.startsWith("@") ? text : `@${text}`)}</span>`;
        }
        if (frag.type === "inlineCard" || frag.type === "blockCard") {
          const url = frag.attrs?.url || frag.attrs?.data?.url || "";
          const data = frag.attrs?.data || {};
          const name = data.name || data.title || data.generator?.name || url;
          return url ? `<a href="${escapeHtml(url)}" target="_blank" rel="noopener">${escapeHtml(name)}</a>` : "";
        }
        let t = escapeHtml(frag.text || "");
        const marks = frag.marks || [];
        for (const m of marks) {
          if (m.type === "strong") t = `<b>${t}</b>`;
          else if (m.type === "em") t = `<i>${t}</i>`;
          else if (m.type === "underline") t = `<u>${t}</u>`;
          else if (m.type === "strike") t = `<s>${t}</s>`;
          else if (m.type === "code") t = `<code>${t}</code>`;
          else if (m.type === "subsup" && m.attrs?.type === "sub") t = `<sub>${t}</sub>`;
          else if (m.type === "subsup" && m.attrs?.type === "sup") t = `<sup>${t}</sup>`;
          else if (m.type === "link" && m.attrs?.href) t = `<a href="${escapeHtml(m.attrs.href)}" target="_blank" rel="noopener">${t}</a>`;
        }
        return t;
      });
      out.push(`<p>${parts.join("") || "<br>"}</p>`);
    }
  }
  return out.join("");
}

function escapeHtml(s) {
  const div = document.createElement("div");
  div.textContent = s;
  return div.innerHTML;
}

function CommentItem({ comment, selectedKey, updateTicket, fetchTicketDetail, setUpdateBusy, users = [], attachments = [], onImageClick }) {
  const [editing, setEditing] = useState(false);
  const [editBody, setEditBody] = useState("");

  function handleStartEdit() {
    const html = comment.body_adf
      ? adfToHtml(comment.body_adf, { jiraBaseUrl: comment.jiraBaseUrl || "", issueKey: selectedKey || "" })
      : `<p>${escapeHtml(comment.body || "")}</p>`;
    setEditBody(html);
    setEditing(true);
  }

  async function handleDelete() {
    if (!confirm("Delete this comment?")) return;
    setUpdateBusy(true);
    try {
      const res = await apiFetch(
        `${BASE_API_URL}/api/tickets/${selectedKey}/comments/${comment.id}`,
        { method: "DELETE" }
      );
      if (!res.ok) throw new Error(await res.text());
      await fetchTicketDetail();
    } catch (e) {
      console.error(e);
      alert("Failed to delete comment.");
    } finally {
      setUpdateBusy(false);
    }
  }

  async function handleSaveEdit() {
    const hasHtml = /<[a-z][\s\S]*>/i.test(editBody);
    const adf = hasHtml ? htmlToAdf(editBody, selectedKey) : { version: 1, type: "doc", content: [{ type: "paragraph", content: [{ type: "text", text: editBody }] }] };
    setUpdateBusy(true);
    try {
      const res = await apiFetch(
        `${BASE_API_URL}/api/tickets/${selectedKey}/comments/${comment.id}`,
        {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ body: adf }),
        }
      );
      if (!res.ok) throw new Error(await res.text());
      setEditing(false);
      await fetchTicketDetail();
    } catch (e) {
      console.error(e);
      alert("Failed to update comment.");
    } finally {
      setUpdateBusy(false);
    }
  }

  return (
    <div className={`comment-item ${comment.internal ? "comment-internal" : ""}`}>
      <div className="comment-meta">
        <span>{comment.author || "Unknown"}</span>
        <span>
          {comment.created ? new Date(comment.created).toLocaleString() : ""}
          {comment.internal && <span className="comment-internal-badge"> · Internal note</span>}
        </span>
      </div>
      {editing ? (
        <div className="comment-edit">
          <RichTextEditor
            value={editBody}
            onChange={setEditBody}
            placeholder="Edit comment…"
            minRows={3}
            issueKey={selectedKey}
            users={users}
          />
          <div className="comment-edit-actions">
            <button type="button" className="ghost-button" onClick={() => setEditing(false)}>Cancel</button>
            <button type="button" className="primary-button" onClick={handleSaveEdit}>Save</button>
          </div>
        </div>
      ) : (
        <>
          <div
            className="comment-text"
            onClick={(e) => {
              const img = e.target?.closest?.("img.comment-inline-image");
              if (img?.src && onImageClick) onImageClick(img.src);
            }}
            role={onImageClick ? "presentation" : undefined}
            dangerouslySetInnerHTML={{
              __html: comment.body_adf
                ? adfToHtml(comment.body_adf, { jiraBaseUrl: comment.jiraBaseUrl || "", issueKey: selectedKey || "", attachments })
                : escapeHtml(stripHtmlToText(comment.body || "") || comment.body || ""),
            }}
          />
          <div className="comment-actions">
            <button type="button" className="comment-action-btn" onClick={handleStartEdit}>Edit</button>
            <button type="button" className="comment-action-btn" onClick={handleDelete}>Delete</button>
          </div>
        </>
      )}
    </div>
  );
}

function RichTextEditor({ value, onChange, placeholder, minRows = 5, issueKey = null, users = [] }) {
  const editorRef = useRef(null);
  const fileInputRef = useRef(null);
  const [mentionQuery, setMentionQuery] = useState("");
  const [mentionAnchor, setMentionAnchor] = useState(null);
  const [mentionSelectedIndex, setMentionSelectedIndex] = useState(0);
  const mentionListRef = useRef(null);

  useEffect(() => {
    mentionListRef.current?.querySelector(".selected")?.scrollIntoView({ block: "nearest" });
  }, [mentionSelectedIndex]);

  const filteredUsers = useMemo(() => {
    if (!mentionQuery.trim()) return users.slice(0, 8);
    const q = mentionQuery.toLowerCase();
    return users
      .filter((u) => (u.display_name || "").toLowerCase().includes(q))
      .slice(0, 8);
  }, [users, mentionQuery]);

  useEffect(() => {
    if (!editorRef.current || !value) return;
    const html = editorRef.current.innerHTML;
    const empty = !html || html === "<br>" || html === "<br/>";
    if (empty) editorRef.current.innerHTML = value;
  }, [value]);

  const execCmd = (cmd, value = null) => {
    document.execCommand(cmd, false, value);
    editorRef.current?.focus();
    onChange?.(editorRef.current?.innerHTML || "");
  };

  const handleInput = () => {
    onChange?.(editorRef.current?.innerHTML || "");
    checkMentionTrigger();
  };

  function getTextBeforeCaret() {
    const sel = window.getSelection();
    if (!sel.rangeCount || !editorRef.current || !editorRef.current.contains(sel.anchorNode)) return "";
    const range = sel.getRangeAt(0).cloneRange();
    range.selectNodeContents(editorRef.current);
    range.setEnd(sel.anchorNode, sel.anchorOffset);
    return range.toString();
  }

  function checkMentionTrigger() {
    const textBefore = getTextBeforeCaret();
    const match = textBefore.match(/@([\w\s]*)$/);
    if (match) {
      setMentionQuery(match[1]);
      setMentionAnchor({ node: window.getSelection().anchorNode, offset: window.getSelection().anchorOffset });
      setMentionSelectedIndex(0);
    } else {
      setMentionQuery("");
      setMentionAnchor(null);
    }
  }

  function insertMention(user) {
    if (!editorRef.current) return;
    const sel = window.getSelection();
    if (!sel.rangeCount) return;

    const textBefore = getTextBeforeCaret();
    const match = textBefore.match(/@([\w\s]*)$/);
    if (!match) return;

    const range = sel.getRangeAt(0).cloneRange();
    range.selectNodeContents(editorRef.current);
    range.setEnd(sel.anchorNode, sel.anchorOffset);
    const textBeforeRange = range.toString();
    const startOffset = textBeforeRange.length - match[0].length;

    let charCount = 0;
    let startNode = null;
    let startNodeOffset = 0;
    const walker = document.createTreeWalker(editorRef.current, NodeFilter.SHOW_TEXT, null, false);
    let n;
    while ((n = walker.nextNode())) {
      const len = n.textContent.length;
      if (charCount + len >= startOffset) {
        startNode = n;
        startNodeOffset = startOffset - charCount;
        break;
      }
      charCount += len;
    }
    if (!startNode) {
      startNode = editorRef.current;
      startNodeOffset = 0;
    }

    const rangeToReplace = document.createRange();
    rangeToReplace.setStart(startNode, startNodeOffset);
    rangeToReplace.setEnd(sel.anchorNode, sel.anchorOffset);

    const span = document.createElement("span");
    span.setAttribute("data-mention-id", user.account_id);
    span.setAttribute("data-mention-name", user.display_name || "");
    span.textContent = `@${user.display_name || ""}`;
    span.contentEditable = "false";
    span.className = "mention-chip";

    rangeToReplace.deleteContents();
    rangeToReplace.insertNode(span);
    const space = document.createTextNode("\u00A0");
    span.parentNode.insertBefore(space, span.nextSibling);

    rangeToReplace.setStartAfter(space);
    rangeToReplace.setEndAfter(space);
    sel.removeAllRanges();
    sel.addRange(rangeToReplace);

    setMentionQuery("");
    setMentionAnchor(null);
    onChange?.(editorRef.current?.innerHTML || "");
  }

  const handleKeyDown = (e) => {
    if (mentionQuery !== "" && filteredUsers.length > 0) {
      if (e.key === "ArrowDown") {
        e.preventDefault();
        setMentionSelectedIndex((i) => Math.min(i + 1, filteredUsers.length - 1));
        return;
      }
      if (e.key === "ArrowUp") {
        e.preventDefault();
        setMentionSelectedIndex((i) => Math.max(i - 1, 0));
        return;
      }
      if (e.key === "Enter" || e.key === "Tab") {
        e.preventDefault();
        const user = filteredUsers[mentionSelectedIndex];
        if (user) insertMention(user);
        return;
      }
      if (e.key === "Escape") {
        e.preventDefault();
        setMentionQuery("");
        setMentionAnchor(null);
        return;
      }
    }
  };

  async function insertImage(file) {
    if (!file || !file.type.startsWith("image/")) return;
    if (!issueKey) {
      alert("Select a ticket first to add images.");
      return;
    }
    const formData = new FormData();
    formData.append("file", file);
    try {
      const res = await apiFetch(`${BASE_API_URL}/api/tickets/${encodeURIComponent(issueKey)}/attachments`, {
        method: "POST",
        body: formData,
      });
      if (!res.ok) throw new Error(await res.text());
      const att = await res.json();
      const proxyUrl = `${BASE_API_URL}/api/tickets/${encodeURIComponent(issueKey)}/attachments/${encodeURIComponent(att.id)}/content`;
      const contentUrl = att.content || att.url;
      const img = document.createElement("img");
      img.src = proxyUrl;
      img.alt = att.filename || "image";
      img.setAttribute("data-attachment-id", String(att.id));
      img.setAttribute("data-attachment-url", contentUrl || proxyUrl);
      img.setAttribute("data-attachment-filename", att.filename || "image.png");
      img.style.maxWidth = "100%";
      img.style.maxHeight = "200px";
      editorRef.current?.focus();
      document.execCommand("insertHTML", false, img.outerHTML);
      onChange?.(editorRef.current?.innerHTML || "");
    } catch (e) {
      console.error(e);
      alert("Failed to upload image.");
    }
  }

  const handlePaste = (e) => {
    const items = e.clipboardData?.items;
    if (!items) return;
    for (const item of items) {
      if (item.type.startsWith("image/")) {
        e.preventDefault();
        insertImage(item.getAsFile());
        return;
      }
    }
  };

  const handleFileSelect = (e) => {
    const file = e.target.files?.[0];
    if (file) insertImage(file);
    e.target.value = "";
  };

  return (
    <div className="rich-editor">
      <input
        ref={fileInputRef}
        type="file"
        accept="image/*"
        style={{ display: "none" }}
        onChange={handleFileSelect}
      />
      <div className="rich-editor-toolbar">
        <button type="button" onClick={() => execCmd("bold")} title="Bold">B</button>
        <button type="button" onClick={() => execCmd("italic")} title="Italic">I</button>
        <button type="button" onClick={() => execCmd("underline")} title="Underline">U</button>
        <button type="button" onClick={() => execCmd("strikeThrough")} title="Strikethrough">S</button>
        <button type="button" onClick={() => execCmd("formatBlock", "pre")} title="Code block">{"</>"}</button>
        <span className="toolbar-sep" />
        {issueKey && (
          <>
            <button
              type="button"
              onClick={() => fileInputRef.current?.click()}
              title="Insert image (from disk). You can also paste images from clipboard."
            >
              🖼️
            </button>
            <span className="toolbar-sep" />
          </>
        )}
        <button type="button" onClick={() => execCmd("subscript")} title="Subscript">x₂</button>
        <button type="button" onClick={() => execCmd("superscript")} title="Superscript">x²</button>
        <span className="toolbar-sep" />
        <button type="button" onClick={() => execCmd("insertUnorderedList")} title="Bullet list">•</button>
        <button type="button" onClick={() => execCmd("insertOrderedList")} title="Numbered list">1.</button>
        <button type="button" onClick={() => execCmd("insertHorizontalRule")} title="Divider">—</button>
        <span className="toolbar-sep" />
        <button type="button" onClick={() => execCmd("createLink", prompt("URL:") || "https://")} title="Link">🔗</button>
      </div>
      <div className="rich-editor-body">
        <div
          ref={editorRef}
          className="rich-editor-content"
          contentEditable
          suppressContentEditableWarning
          onInput={handleInput}
          onKeyDown={handleKeyDown}
          onPaste={handlePaste}
          data-placeholder={placeholder}
          style={{ minHeight: minRows * 24 }}
        />
        {mentionQuery !== "" && (
          <div className="mention-dropdown" ref={mentionListRef}>
            {filteredUsers.length === 0 ? (
              <div className="mention-dropdown-empty">No users found</div>
            ) : (
              filteredUsers.map((u, i) => (
                <button
                  key={u.account_id}
                  type="button"
                  className={`mention-dropdown-item ${i === mentionSelectedIndex ? "selected" : ""}`}
                  onClick={() => insertMention(u)}
                  onMouseEnter={() => setMentionSelectedIndex(i)}
                >
                  {u.display_name || u.account_id}
                </button>
              ))
            )}
          </div>
        )}
      </div>
    </div>
  );
}

function getQuarterLabel(offset) {
  const now = new Date();
  const q = Math.floor(now.getMonth() / 3) + 1;
  const year = now.getFullYear();
  const totalMonths = (year - 1) * 12 + (q - 1) * 3 + 1 + offset * 3;
  const y = Math.floor((totalMonths - 1) / 12) + 1;
  const m = ((totalMonths - 1) % 12) + 1;
  const qLabel = Math.floor((m - 1) / 3) + 1;
  return `Q${qLabel} ${y}`;
}

function getQuarterBounds(offset) {
  const now = new Date();
  const q = Math.floor(now.getMonth() / 3) + 1;
  const year = now.getFullYear();
  const startMonth = (q - 1) * 3 + 1;
  const totalMonths = (year - 1) * 12 + startMonth + offset * 3;
  const y = Math.floor((totalMonths - 1) / 12) + 1;
  const m = ((totalMonths - 1) % 12) + 1;
  const start = new Date(y, m - 1, 1);
  const qNew = Math.floor((m - 1) / 3) + 1;
  const endMonth = qNew === 4 ? 12 : qNew * 3 + 1;
  const end = new Date(y, endMonth - 1, 1);
  end.setDate(0);
  end.setHours(23, 59, 59, 999);
  return [start, end];
}

function isTicketInQuarter(ticket, quarterOffset) {
  const created = ticket.created;
  if (!created) return true;
  const [start, end] = getQuarterBounds(quarterOffset);
  const d = new Date(created);
  return d >= start && d <= end;
}

function EscalationPieChart({ escalatedCount, fixedByCodeCount }) {
  const canvasRef = useRef(null);
  const chartRef = useRef(null);

  useEffect(() => {
    if (!canvasRef.current || escalatedCount === 0) return;

    const otherCount = escalatedCount - fixedByCodeCount;
    const fixedPct = escalatedCount ? Math.round(100 * fixedByCodeCount / escalatedCount) : 0;
    const fixedByCodeColor = fixedPct >= 80 ? "#16a34a" : "#dc2626";

    if (chartRef.current) {
      chartRef.current.destroy();
    }

    chartRef.current = new Chart(canvasRef.current, {
      type: "pie",
      data: {
        labels: [
          `Escalated to R&D (fixed_by_code) – ${fixedByCodeCount} (${fixedPct}%)`,
          `Other escalations – ${otherCount} (${100 - fixedPct}%)`,
        ],
        datasets: [{
          data: [fixedByCodeCount, otherCount],
          backgroundColor: [fixedByCodeColor, "#e5e7eb"],
          borderWidth: 0,
        }],
      },
      options: {
        responsive: true,
        maintainAspectRatio: true,
        plugins: {
          legend: {
            position: "bottom",
            labels: { boxWidth: 12, padding: 16 },
          },
        },
      },
    });

    return () => {
      if (chartRef.current) {
        chartRef.current.destroy();
        chartRef.current = null;
      }
    };
  }, [escalatedCount, fixedByCodeCount]);

  if (escalatedCount === 0) {
    return (
      <div className="stats-tier-section">
        <div className="section-label section-label-with-info">
          Percentage of bugs escalated to R&D of all escalations
          <span className="info-icon-wrapper">
            <span className="info-icon">ⓘ</span>
            <span className="info-tooltip">
              It is expected that 80% of escalations will end up with the code fix.
            </span>
          </span>
        </div>
        <div className="stats-pie-empty">No escalated tickets in this quarter.</div>
      </div>
    );
  }

  const fixedPct = Math.round(100 * fixedByCodeCount / escalatedCount);
  return (
    <div className="stats-tier-section">
      <div className="section-label section-label-with-info">
        Percentage of bugs escalated to R&D of all escalations
        <span className="info-icon-wrapper">
          <span className="info-icon">ⓘ</span>
          <span className="info-tooltip">
            It is expected that 80% of escalations will end up with the code fix.
          </span>
        </span>
      </div>
      <div className="stats-pie-summary">
        {fixedByCodeCount} of {escalatedCount} escalated tickets ({fixedPct}%) have the &quot;fixed_by_code&quot; label.
      </div>
      <div className="stats-pie-container">
        <canvas ref={canvasRef} />
      </div>
    </div>
  );
}

const STATS_TIER_ORDER = ["Diamond", "Platinum", "Gold", "Silver", "Bronze", "Standard", "No ARR"];
const UNASSIGNED_KEY = "__unassigned__";

function emptyAssigneeStatsRow(accountId, displayName) {
  return {
    account_id: accountId,
    display_name: displayName,
    total_tickets: 0,
    knowledge_gap: { count: 0, pct: 0 },
    reporter_responsibility: { count: 0, pct: 0 },
    known_issue: { count: 0, pct: 0 },
    median_resolution_hours: null,
    median_resolution_by_tier: {},
    escalated_count: 0,
    escalated_fixed_by_code_count: 0,
    escalated_fixed_by_code_pct: 0,
  };
}

function orderUsersByDisplayNameList(allUsers, nameOrder) {
  const byNorm = new Map();
  for (const u of allUsers || []) {
    const k = (u.display_name || "").trim().toLowerCase();
    if (k) byNorm.set(k, u);
  }
  const out = [];
  for (const n of nameOrder || []) {
    const u = byNorm.get(String(n).trim().toLowerCase());
    if (u) out.push(u);
  }
  return out;
}

/** Teammate stats when COE list is configured: one row per teammate, no Unassigned, no extra assignees. */
function mergeTeamStatsForCoeSubset(apiRows, coeUsersOrdered) {
  const byKey = new Map();
  for (const r of apiRows || []) {
    if (r.account_id == null) continue;
    byKey.set(r.account_id, r);
  }
  return (coeUsersOrdered || []).map((u) => byKey.get(u.account_id) || emptyAssigneeStatsRow(u.account_id, u.display_name));
}

/** No COE list: all assignable users, Unassigned if present, plus assignees on tickets not in assignable list. */
function mergeTeamStatsWithUsers(apiRows, usersList) {
  const byKey = new Map();
  for (const r of apiRows || []) {
    const k = r.account_id == null ? UNASSIGNED_KEY : r.account_id;
    byKey.set(k, r);
  }
  const sortedUsers = [...(usersList || [])].sort((a, b) =>
    (a.display_name || "").localeCompare(b.display_name || "", undefined, { sensitivity: "base" })
  );
  const userIds = new Set(sortedUsers.map((u) => u.account_id));
  const out = sortedUsers.map((u) => byKey.get(u.account_id) || emptyAssigneeStatsRow(u.account_id, u.display_name));
  const un = byKey.get(UNASSIGNED_KEY);
  if (un) out.unshift(un);
  for (const r of apiRows || []) {
    if (r.account_id != null && !userIds.has(r.account_id)) {
      out.push(r);
    }
  }
  return out;
}

/** Assignee filter + teammate stats row order. Prefer teamStatsAssigneeAccountIds over teamStatsAssigneeDisplayNames when both are set. */
function resolveAssigneeUiUsers(allUsers, accountIds, displayNames) {
  if (Array.isArray(accountIds) && accountIds.length > 0) {
    const map = new Map((allUsers || []).map((u) => [u.account_id, u]));
    const list = accountIds.map((id) => map.get(id)).filter(Boolean);
    return { list, coeSubset: true };
  }
  if (Array.isArray(displayNames) && displayNames.length > 0) {
    const list = orderUsersByDisplayNameList(allUsers, displayNames);
    return { list, coeSubset: true };
  }
  const list = [...(allUsers || [])].sort((a, b) =>
    (a.display_name || "").localeCompare(b.display_name || "", undefined, { sensitivity: "base" })
  );
  return { list, coeSubset: false };
}

function useTeamStatsCharts(mergedRows, quarterLabel) {
  const refPct = useRef(null);
  const refTotal = useRef(null);
  const refMedian = useRef(null);
  const refEscPct = useRef(null);
  const tierRefs = useRef({});
  const chartInstances = useRef([]);

  const tierCharts = useMemo(
    () =>
      STATS_TIER_ORDER.filter((tier) =>
        mergedRows.some((r) => r.median_resolution_by_tier && r.median_resolution_by_tier[tier] != null)
      ),
    [mergedRows]
  );

  useEffect(() => {
    if (typeof Chart === "undefined") return;
    chartInstances.current.forEach((c) => {
      try {
        c.destroy();
      } catch (e) {}
    });
    chartInstances.current = [];

    const labels = mergedRows.map((r) => r.display_name || "—");
    const reg = (c) => {
      if (c) chartInstances.current.push(c);
    };

    const tooltipPct = (ctx) => {
      const v = ctx.raw;
      if (v == null || Number.isNaN(v)) return "";
      return `${v}%`;
    };

    if (refPct.current && labels.length) {
      reg(
        new Chart(refPct.current, {
          type: "bar",
          data: {
            labels,
            datasets: [
              { label: "Knowledge gap %", data: mergedRows.map((r) => r.knowledge_gap?.pct ?? 0), backgroundColor: "rgba(59, 130, 246, 0.7)" },
              { label: "Poor escalation %", data: mergedRows.map((r) => r.reporter_responsibility?.pct ?? 0), backgroundColor: "rgba(245, 158, 11, 0.85)" },
              { label: "Known issue %", data: mergedRows.map((r) => r.known_issue?.pct ?? 0), backgroundColor: "rgba(139, 92, 246, 0.75)" },
            ],
          },
          options: {
            indexAxis: "y",
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
              title: { display: true, text: `Label mix (% of own tickets) · ${quarterLabel}` },
              tooltip: { callbacks: { label: (ctx) => `${ctx.dataset.label}: ${tooltipPct(ctx)}` } },
            },
            scales: { x: { beginAtZero: true, max: 100, stacked: false } },
          },
        })
      );
    }

    if (refTotal.current && labels.length) {
      reg(
        new Chart(refTotal.current, {
          type: "bar",
          data: {
            labels,
            datasets: [{ label: "Tickets", data: mergedRows.map((r) => r.total_tickets ?? 0), backgroundColor: "rgba(55, 65, 81, 0.65)" }],
          },
          options: {
            indexAxis: "y",
            responsive: true,
            maintainAspectRatio: false,
            plugins: { title: { display: true, text: `Total tickets · ${quarterLabel}` } },
            scales: { x: { beginAtZero: true } },
          },
        })
      );
    }

    if (refMedian.current && labels.length) {
      reg(
        new Chart(refMedian.current, {
          type: "bar",
          data: {
            labels,
            datasets: [
              {
                label: "Median resolution (hours)",
                data: mergedRows.map((r) => (r.median_resolution_hours != null ? r.median_resolution_hours : null)),
                backgroundColor: "rgba(22, 163, 74, 0.65)",
              },
            ],
          },
          options: {
            indexAxis: "y",
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
              title: { display: true, text: `Median resolution time · ${quarterLabel}` },
              tooltip: {
                callbacks: {
                  label: (ctx) => {
                    const h = ctx.raw;
                    const r = mergedRows[ctx.dataIndex];
                    return `Median: ${formatResolutionHours(h)} (${r?.total_tickets ?? 0} tickets, resolved subset)`;
                  },
                },
              },
            },
            scales: { x: { beginAtZero: true, title: { display: true, text: "Hours" } } },
          },
        })
      );
    }

    if (refEscPct.current && labels.length) {
      reg(
        new Chart(refEscPct.current, {
          type: "bar",
          data: {
            labels,
            datasets: [
              {
                label: "fixed_by_code % of escalations",
                data: mergedRows.map((r) => (r.escalated_count ? r.escalated_fixed_by_code_pct ?? 0 : null)),
                backgroundColor: mergedRows.map((r) => {
                  const p = r.escalated_fixed_by_code_pct ?? 0;
                  if (!r.escalated_count) return "rgba(229, 231, 235, 0.9)";
                  return p >= 80 ? "rgba(22, 163, 74, 0.75)" : "rgba(220, 38, 38, 0.7)";
                }),
              },
            ],
          },
          options: {
            indexAxis: "y",
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
              title: { display: true, text: `% escalated to R&D (fixed_by_code) · ${quarterLabel}` },
              tooltip: {
                callbacks: {
                  label: (ctx) => {
                    const r = mergedRows[ctx.dataIndex];
                    if (!r.escalated_count) return "No escalations";
                    return `${r.escalated_fixed_by_code_count ?? 0} / ${r.escalated_count} (${r.escalated_fixed_by_code_pct ?? 0}%)`;
                  },
                },
              },
            },
            scales: { x: { beginAtZero: true, max: 100, title: { display: true, text: "%" } } },
          },
        })
      );
    }

    return () => {
      chartInstances.current.forEach((c) => {
        try {
          c.destroy();
        } catch (e) {}
      });
      chartInstances.current = [];
    };
  }, [mergedRows, quarterLabel]);

  useEffect(() => {
    if (typeof Chart === "undefined") return;
    const tierCanvasMap = tierRefs.current;
    const charts = [];
    tierCharts.forEach((tier) => {
      const canvas = tierCanvasMap[tier];
      if (!canvas) return;
      const dataVals = mergedRows.map((r) => {
        const v = r.median_resolution_by_tier && r.median_resolution_by_tier[tier];
        return v != null ? v : null;
      });
      if (!dataVals.some((v) => v != null)) return;
      const bg = mergedRows.map((r, i) => {
        const h = dataVals[i];
        const cls = getTierValueColorClass(tier, h);
        if (cls === "stat-tier-green") return "rgba(22, 163, 74, 0.75)";
        if (cls === "stat-tier-orange") return "rgba(234, 179, 8, 0.85)";
        if (cls === "stat-tier-red") return "rgba(220, 38, 38, 0.72)";
        return "rgba(75, 85, 99, 0.65)";
      });
      const ch = new Chart(canvas, {
        type: "bar",
        data: {
          labels: mergedRows.map((r) => r.display_name || "—"),
          datasets: [{ label: `${tier} median (h)`, data: dataVals, backgroundColor: bg }],
        },
        options: {
          indexAxis: "y",
          responsive: true,
          maintainAspectRatio: false,
          plugins: {
            title: { display: true, text: `Median resolution · ${tier} · ${quarterLabel}` },
            tooltip: {
              callbacks: {
                label: (ctx) => {
                  const h = ctx.raw;
                  return h != null ? formatResolutionHours(h) : "—";
                },
              },
            },
          },
          scales: { x: { beginAtZero: true, title: { display: true, text: "Hours" } } },
        },
      });
      charts.push(ch);
    });
    return () => {
      charts.forEach((c) => {
        try {
          c.destroy();
        } catch (e) {}
      });
    };
  }, [mergedRows, quarterLabel, tierCharts]);

  return { refPct, refTotal, refMedian, refEscPct, tierRefs, tierCharts };
}

function useCompareCharts(data, compareA, compareB) {
  const refTotal = useRef(null);
  const refTier = useRef(null);
  const refEscFixed = useRef(null);
  const refKnowledgeGap = useRef(null);
  const chartInstances = useRef([]);

  useEffect(() => {
    if (typeof Chart === "undefined") return;
    chartInstances.current.forEach((c) => {
      try {
        c.destroy();
      } catch (e) {}
    });
    chartInstances.current = [];

    if (!data || !data.period_a?.stats || !data.period_b?.stats) return;
    const statsA = data.period_a.stats;
    const statsB = data.period_b.stats;
    const labelA = `A · ${compareA.map(getQuarterLabel).join(" + ")}`;
    const labelB = `B · ${compareB.map(getQuarterLabel).join(" + ")}`;
    const colorA = "rgba(0, 200, 83, 0.78)";
    const colorB = "rgba(148, 163, 184, 0.85)";

    const reg = (c) => {
      if (c) chartInstances.current.push(c);
    };

    if (refTotal.current) {
      reg(
        new Chart(refTotal.current, {
          type: "bar",
          data: {
            labels: [labelA, labelB],
            datasets: [
              {
                label: "Total tickets",
                data: [statsA.total_tickets ?? 0, statsB.total_tickets ?? 0],
                backgroundColor: [colorA, colorB],
              },
            ],
          },
          options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
              title: { display: true, text: "Total tickets" },
              legend: { display: false },
            },
            scales: { y: { beginAtZero: true, title: { display: true, text: "Tickets" } } },
          },
        })
      );
    }

    if (refTier.current) {
      const tiers = COMPARE_TIERS.filter(
        (t) =>
          (statsA.median_resolution_by_tier?.[t] != null) ||
          (statsB.median_resolution_by_tier?.[t] != null)
      );
      reg(
        new Chart(refTier.current, {
          type: "bar",
          data: {
            labels: tiers,
            datasets: [
              {
                label: labelA,
                data: tiers.map((t) => statsA.median_resolution_by_tier?.[t] ?? null),
                backgroundColor: colorA,
              },
              {
                label: labelB,
                data: tiers.map((t) => statsB.median_resolution_by_tier?.[t] ?? null),
                backgroundColor: colorB,
              },
            ],
          },
          options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
              title: { display: true, text: "Median resolution per customer tier (hours)" },
              tooltip: {
                callbacks: {
                  label: (ctx) => `${ctx.dataset.label}: ${ctx.raw == null ? "—" : ctx.raw.toFixed(1) + "h"}`,
                },
              },
            },
            scales: { y: { beginAtZero: true, title: { display: true, text: "Hours" } } },
          },
        })
      );
    }

    if (refEscFixed.current) {
      reg(
        new Chart(refEscFixed.current, {
          type: "bar",
          data: {
            labels: [labelA, labelB],
            datasets: [
              {
                label: "fixed_by_code % of escalations",
                data: [statsA.escalated_fixed_by_code_pct ?? 0, statsB.escalated_fixed_by_code_pct ?? 0],
                backgroundColor: [colorA, colorB],
              },
            ],
          },
          options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
              title: { display: true, text: "Escalated · fixed by code %" },
              legend: { display: false },
              tooltip: {
                callbacks: {
                  label: (ctx) => {
                    const stats = ctx.dataIndex === 0 ? statsA : statsB;
                    return `${stats.escalated_fixed_by_code_count ?? 0} / ${stats.escalated_count ?? 0} (${ctx.raw}%)`;
                  },
                },
              },
            },
            scales: { y: { beginAtZero: true, max: 100, title: { display: true, text: "%" } } },
          },
        })
      );
    }

    if (refKnowledgeGap.current) {
      reg(
        new Chart(refKnowledgeGap.current, {
          type: "bar",
          data: {
            labels: [labelA, labelB],
            datasets: [
              {
                label: "Knowledge Gap %",
                data: [statsA.knowledge_gap?.pct ?? 0, statsB.knowledge_gap?.pct ?? 0],
                backgroundColor: [colorA, colorB],
              },
            ],
          },
          options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
              title: { display: true, text: "Knowledge Gap %" },
              legend: { display: false },
              tooltip: {
                callbacks: {
                  label: (ctx) => {
                    const stats = ctx.dataIndex === 0 ? statsA : statsB;
                    return `${stats.knowledge_gap?.count ?? 0} / ${stats.total_tickets ?? 0} (${ctx.raw}%)`;
                  },
                },
              },
            },
            scales: { y: { beginAtZero: true, max: 100, title: { display: true, text: "%" } } },
          },
        })
      );
    }

    return () => {
      chartInstances.current.forEach((c) => {
        try {
          c.destroy();
        } catch (e) {}
      });
      chartInstances.current = [];
    };
  }, [data, compareA, compareB]);

  return { refTotal, refTier, refEscFixed, refKnowledgeGap };
}

function TeamStatsPageView({ mergedRows, quarterLabel, periodText, quarterOffset, onQuarterChange, onBack }) {
  const { refPct, refTotal, refMedian, refEscPct, tierRefs, tierCharts } = useTeamStatsCharts(mergedRows, quarterLabel);

  return (
    <div className="team-stats-page">
      <div className="team-stats-toolbar">
        <button type="button" className="pill-button" onClick={onBack}>
          Back to dashboard
        </button>
        <label className="team-stats-quarter-label">
          Quarter:{" "}
          <select
            className="tier-filter-select"
            value={quarterOffset}
            onChange={(e) => onQuarterChange(Number(e.target.value))}
          >
            <option value={0}>{getQuarterLabel(0)} (current)</option>
            <option value={-1}>{getQuarterLabel(-1)}</option>
            <option value={-2}>{getQuarterLabel(-2)}</option>
            <option value={-3}>{getQuarterLabel(-3)}</option>
            <option value={-4}>{getQuarterLabel(-4)}</option>
          </select>
        </label>
        <span className="team-stats-period">{periodText}</span>
      </div>
      <p className="team-stats-attribution">
        Metrics use each ticket&apos;s <strong>current</strong> Jira assignee (same as the dashboard). Reassignments move counts between people.
      </p>
      <div className="team-stats-grid">
        <div className="team-stats-chart-wrap">
          <canvas ref={refPct} />
        </div>
        <div className="team-stats-chart-wrap">
          <canvas ref={refTotal} />
        </div>
        <div className="team-stats-chart-wrap">
          <canvas ref={refMedian} />
        </div>
        <div className="team-stats-chart-wrap">
          <canvas ref={refEscPct} />
        </div>
        {tierCharts.map((tier) => (
          <div key={tier} className="team-stats-chart-wrap team-stats-chart-wrap-wide">
            <canvas
              ref={(el) => {
                tierRefs.current[tier] = el;
              }}
            />
          </div>
        ))}
      </div>
    </div>
  );
}

const COMPARE_QUARTERS = [0, -1, -2, -3, -4];

const COMPARE_METRICS = [
  { key: "total_tickets", label: "Total tickets", get: (s) => s.total_tickets, format: (v) => String(v ?? 0), direction: "neutral" },
  { key: "median_resolution_hours", label: "Median resolution (hours)", get: (s) => s.median_resolution_hours, format: (v) => v == null ? "—" : v.toFixed(1), direction: "lower" },
  { key: "knowledge_gap_pct", label: "Knowledge Gap %", get: (s) => s.knowledge_gap?.pct, format: (v) => v == null ? "—" : `${v}%`, direction: "lower" },
  { key: "knowledge_gap_count", label: "Knowledge Gap (count)", get: (s) => s.knowledge_gap?.count, format: (v) => String(v ?? 0), direction: "lower" },
  { key: "reporter_responsibility_pct", label: "Poor escalation %", get: (s) => s.reporter_responsibility?.pct, format: (v) => v == null ? "—" : `${v}%`, direction: "lower" },
  { key: "reporter_responsibility_count", label: "Poor escalation (count)", get: (s) => s.reporter_responsibility?.count, format: (v) => String(v ?? 0), direction: "lower" },
  { key: "known_issue_pct", label: "Known issue %", get: (s) => s.known_issue?.pct, format: (v) => v == null ? "—" : `${v}%`, direction: "lower" },
  { key: "known_issue_count", label: "Known issue (count)", get: (s) => s.known_issue?.count, format: (v) => String(v ?? 0), direction: "lower" },
  { key: "escalated_count", label: "Escalated tickets", get: (s) => s.escalated_count, format: (v) => String(v ?? 0), direction: "lower" },
  { key: "escalated_fixed_by_code_pct", label: "Escalated · fixed by code %", get: (s) => s.escalated_fixed_by_code_pct, format: (v) => v == null ? "—" : `${v}%`, direction: "higher" },
];

const COMPARE_TIERS = ["Diamond", "Platinum", "Gold", "Silver", "Bronze", "Standard", "No ARR"];

const COMPARE_PRESETS = [
  { id: "qvq", label: "Q vs Q-1", a: [0], b: [-1] },
  { id: "yoy", label: "YoY · Q0 vs Q-4", a: [0], b: [-4] },
  { id: "h1h2", label: "Last 2 vs prior 2", a: [0, -1], b: [-2, -3] },
];

function arraysEqualUnordered(a, b) {
  if (a.length !== b.length) return false;
  const sa = [...a].sort();
  const sb = [...b].sort();
  return sa.every((v, i) => v === sb[i]);
}

const COMPARE_KPI_CARDS = [
  { key: "total_tickets", label: "Total tickets", get: (s) => s.total_tickets, format: (v) => String(v ?? 0), direction: "neutral" },
  { key: "knowledge_gap_pct", label: "Knowledge Gap %", get: (s) => s.knowledge_gap?.pct, format: (v) => v == null ? "—" : `${v}%`, direction: "lower" },
  { key: "median_resolution_hours", label: "Median resolution (h)", get: (s) => s.median_resolution_hours, format: (v) => v == null ? "—" : v.toFixed(1), direction: "lower" },
  { key: "escalated_fixed_by_code_pct", label: "Escalated · fixed by code %", get: (s) => s.escalated_fixed_by_code_pct, format: (v) => v == null ? "—" : `${v}%`, direction: "higher" },
];

function deltaCell(a, b, direction) {
  if (a == null || b == null) return { text: "—", cls: "compare-delta-neutral" };
  const abs = a - b;
  let pctText = "—";
  if (b !== 0) {
    const pct = (abs / Math.abs(b)) * 100;
    pctText = `${pct >= 0 ? "+" : ""}${pct.toFixed(1)}%`;
  } else if (a !== 0) {
    pctText = "+∞";
  } else {
    pctText = "0%";
  }
  let cls = "compare-delta-neutral";
  if (direction !== "neutral" && abs !== 0) {
    const better = direction === "lower" ? abs < 0 : abs > 0;
    cls = better ? "compare-delta-better" : "compare-delta-worse";
  }
  const arrow = abs > 0 ? "▲" : abs < 0 ? "▼" : "·";
  return { text: `${arrow} ${pctText}`, cls };
}

function quartersLabel(quarters) {
  if (!quarters || !quarters.length) return "(none)";
  return quarters.map(getQuarterLabel).join(" + ");
}

function ComparePageView({
  compareA,
  compareB,
  toggleA,
  toggleB,
  applyPreset,
  data,
  loading,
  error,
  onBack,
  onRefresh,
}) {
  const periodA = data?.period_a;
  const periodB = data?.period_b;
  const statsA = periodA?.stats;
  const statsB = periodB?.stats;
  const { refTotal, refTier, refEscFixed, refKnowledgeGap } = useCompareCharts(data, compareA, compareB);

  return (
    <div className="compare-page">
      <div className="compare-toolbar">
        <button type="button" className="pill-button" onClick={onBack}>
          Back to dashboard
        </button>
        <button type="button" className="pill-button" onClick={onRefresh} disabled={loading}>
          ⟳ Refresh
        </button>
        <span className="compare-period-summary">
          {quartersLabel(compareA)}  vs  {quartersLabel(compareB)}
        </span>
      </div>

      <div className="compare-presets">
        <span className="compare-presets-label">Presets:</span>
        {COMPARE_PRESETS.map((p) => {
          const active = arraysEqualUnordered(compareA, p.a) && arraysEqualUnordered(compareB, p.b);
          return (
            <button
              key={p.id}
              type="button"
              className={`compare-preset-btn${active ? " active" : ""}`}
              onClick={() => applyPreset(p.a, p.b)}
            >
              {p.label}
            </button>
          );
        })}
      </div>

      <div className="compare-pickers">
        <div className="compare-picker">
          <div className="compare-picker-title">Period A (recent)</div>
          <div className="compare-picker-options">
            {COMPARE_QUARTERS.map((q) => (
              <label key={q} className={`compare-pill${compareA.includes(q) ? " selected" : ""}`}>
                <input
                  type="checkbox"
                  checked={compareA.includes(q)}
                  onChange={() => toggleA(q)}
                />
                {getQuarterLabel(q)}{q === 0 ? " (current)" : ""}
              </label>
            ))}
          </div>
          <div className="compare-picker-hint">
            {compareA.length === 0 ? "Select 1–4 quarters." : `${compareA.length} selected`}
          </div>
        </div>
        <div className="compare-picker">
          <div className="compare-picker-title">Period B (baseline)</div>
          <div className="compare-picker-options">
            {COMPARE_QUARTERS.map((q) => (
              <label key={q} className={`compare-pill${compareB.includes(q) ? " selected" : ""}`}>
                <input
                  type="checkbox"
                  checked={compareB.includes(q)}
                  onChange={() => toggleB(q)}
                />
                {getQuarterLabel(q)}{q === 0 ? " (current)" : ""}
              </label>
            ))}
          </div>
          <div className="compare-picker-hint">
            {compareB.length === 0 ? "Select 1–4 quarters." : `${compareB.length} selected`}
          </div>
        </div>
      </div>

      {error && <div className="compare-error">{error}</div>}

      {loading && !data ? (
        <div className="compare-skeleton" aria-busy="true" aria-label="Loading comparison">
          <div className="compare-kpi-strip">
            {[0, 1, 2, 3].map((i) => (
              <div key={i} className="compare-kpi-card compare-skeleton-card">
                <div className="skel skel-line skel-line-sm" />
                <div className="skel skel-line skel-line-lg" />
                <div className="skel skel-line skel-line-xs" />
              </div>
            ))}
          </div>
          <div className="compare-charts-grid">
            <div className="compare-chart-wrap"><div className="skel skel-block" /></div>
            <div className="compare-chart-wrap"><div className="skel skel-block" /></div>
            <div className="compare-chart-wrap"><div className="skel skel-block" /></div>
            <div className="compare-chart-wrap compare-chart-wrap-wide"><div className="skel skel-block" /></div>
          </div>
          <div className="compare-skeleton-hint">Loading comparison… each selected quarter is fetched from Jira.</div>
        </div>
      ) : data && statsA && statsB ? (
        <>
          <div className="compare-kpi-strip">
            {COMPARE_KPI_CARDS.map((m) => {
              const va = m.get(statsA);
              const vb = m.get(statsB);
              const d = deltaCell(va, vb, m.direction);
              return (
                <div key={m.key} className="compare-kpi-card">
                  <div className="compare-kpi-label">{m.label}</div>
                  <div className="compare-kpi-value">{m.format(va)}</div>
                  <div className="compare-kpi-baseline">B · {m.format(vb)}</div>
                  <div className={`compare-kpi-delta ${d.cls}`}>{d.text}</div>
                </div>
              );
            })}
          </div>
          <div className="compare-charts-grid">
            <div className="compare-chart-wrap">
              <canvas ref={refTotal} />
            </div>
            <div className="compare-chart-wrap">
              <canvas ref={refKnowledgeGap} />
            </div>
            <div className="compare-chart-wrap">
              <canvas ref={refEscFixed} />
            </div>
            <div className="compare-chart-wrap compare-chart-wrap-wide">
              <canvas ref={refTier} />
            </div>
          </div>
          <div className="compare-table-wrap">
            <table className="compare-table">
              <thead>
                <tr>
                  <th>Metric</th>
                  <th>Period A — {quartersLabel(compareA)}</th>
                  <th>Period B — {quartersLabel(compareB)}</th>
                  <th>Δ (A vs B)</th>
                </tr>
              </thead>
              <tbody>
                {COMPARE_METRICS.map((m) => {
                  const va = m.get(statsA);
                  const vb = m.get(statsB);
                  const d = deltaCell(va, vb, m.direction);
                  return (
                    <tr key={m.key}>
                      <td>{m.label}</td>
                      <td className="compare-num">{m.format(va)}</td>
                      <td className="compare-num">{m.format(vb)}</td>
                      <td className={`compare-num compare-delta ${d.cls}`}>{d.text}</td>
                    </tr>
                  );
                })}
                {COMPARE_TIERS.map((tier) => {
                  const va = statsA.median_resolution_by_tier?.[tier];
                  const vb = statsB.median_resolution_by_tier?.[tier];
                  const fmt = (v) => (v == null ? "—" : v.toFixed(1));
                  const d = deltaCell(va, vb, "lower");
                  return (
                    <tr key={`tier-${tier}`}>
                      <td>Median resolution · {tier} (hours)</td>
                      <td className="compare-num">{fmt(va)}</td>
                      <td className="compare-num">{fmt(vb)}</td>
                      <td className={`compare-num compare-delta ${d.cls}`}>{d.text}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
          <p className="compare-footnote">
            Δ = A − B, with % relative to B. <span className="compare-delta-better">Green</span> = better,
            {" "}<span className="compare-delta-worse">red</span> = worse, gray = neutral or no change. "Better" is
            lower for resolution time / gap percentages / escalation counts, higher for fixed-by-code share.
          </p>
        </>
      ) : (
        <div className="placeholder-detail">Pick at least one quarter on each side.</div>
      )}
    </div>
  );
}

function JiraCredentialsModal({ initialEmail, onClose, onSaved }) {
  const [email, setEmail] = useState(initialEmail || "");
  const [token, setToken] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);

  async function handleSave() {
    setError(null);
    if (!email.trim() || !token.trim()) {
      setError("Email and API token are required.");
      return;
    }
    setBusy(true);
    try {
      const res = await apiFetch(`${BASE_API_URL}/api/auth/jira-credentials`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email: email.trim(), apiToken: token.trim() }),
      });
      if (!res.ok) {
        let detail;
        try {
          detail = (await res.json()).detail || `HTTP ${res.status}`;
        } catch {
          detail = `HTTP ${res.status}`;
        }
        throw new Error(detail);
      }
      const out = await res.json();
      onSaved && onSaved(out);
    } catch (e) {
      setError(e && e.message ? e.message : String(e));
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal-card" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <h3>Jira API credentials</h3>
          <button type="button" className="modal-close" onClick={onClose} aria-label="Close">✕</button>
        </div>
        <div className="modal-body">
          <p className="modal-description">
            Paste your Atlassian email and an API token. The server will validate against Jira and store
            the token securely in AWS Secrets Manager (one secret per user).
            Get an API token at <a href="https://id.atlassian.com/manage-profile/security/api-tokens" target="_blank" rel="noreferrer">id.atlassian.com → API tokens</a>.
          </p>
          <label className="modal-field">
            <span>Atlassian email</span>
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="you@coralogix.com"
              autoFocus
              disabled={busy}
            />
          </label>
          <label className="modal-field">
            <span>API token</span>
            <input
              type="password"
              value={token}
              onChange={(e) => setToken(e.target.value)}
              placeholder="ATATT3xFfGF0…"
              disabled={busy}
            />
          </label>
          {error && <div className="modal-error">{error}</div>}
        </div>
        <div className="modal-footer">
          <button type="button" className="pill-button" onClick={onClose} disabled={busy}>
            Cancel
          </button>
          <button
            type="button"
            className="pill-button pill-button-primary"
            onClick={handleSave}
            disabled={busy || !email.trim() || !token.trim()}
          >
            {busy ? "Validating…" : "Test & save"}
          </button>
        </div>
      </div>
    </div>
  );
}

function TicketApp() {
  const [tickets, setTickets] = useState([]);
  const [selectedKey, setSelectedKey] = useState(null);
  const [selectedDetail, setSelectedDetail] = useState(null);
  const [tierFilter, setTierFilter] = useState("All");
  const [assigneeFilter, setAssigneeFilter] = useState("");
  const [loadingTickets, setLoadingTickets] = useState(false);
  const [loadingDetail, setLoadingDetail] = useState(false);
  const [updateBusy, setUpdateBusy] = useState(false);
  const [commentDraft, setCommentDraft] = useState("");
  const [commentType, setCommentType] = useState("reply");
  const [commentEditorKey, setCommentEditorKey] = useState(0);

  const hasCommentContent = useMemo(() => {
    const c = commentDraft.trim();
    if (!c) return false;
    if (/<[a-z][\s\S]*>/i.test(c)) {
      const div = document.createElement("div");
      div.innerHTML = c;
      const hasText = !!(div.innerText || div.textContent || "").trim();
      const hasImages = div.querySelectorAll("img").length > 0;
      return hasText || hasImages;
    }
    return true;
  }, [commentDraft]);
  const [newLabelDraft, setNewLabelDraft] = useState("");
  const [visibleCommentsCount, setVisibleCommentsCount] = useState(10);
  const [imageLightboxSrc, setImageLightboxSrc] = useState(null);
  const [closedStatusPrompt, setClosedStatusPrompt] = useState(null);
  const [closedStatusDialogQueue, setClosedStatusDialogQueue] = useState([]);
  const [closedStatusPendingLabels, setClosedStatusPendingLabels] = useState([]);
  const closedDialogAnswersRef = useRef({});
  const [users, setUsers] = useState([]);
  const [projectLabels, setProjectLabels] = useState([]);
  const [currentUserId, setCurrentUserId] = useState("");
  const [userName, setUserName] = useState("");
  const [userPrivilege, setUserPrivilege] = useState("");
  const [userEmailFromAuth, setUserEmailFromAuth] = useState("");
  const [jiraCredsStatus, setJiraCredsStatus] = useState(null); // null | { applicable, configured, privilege }
  const [jiraCredsModalOpen, setJiraCredsModalOpen] = useState(false);
  const [jiraBaseUrl, setJiraBaseUrl] = useState("");
  const [statusFilter, setStatusFilter] = useState([]);
  const [stats, setStats] = useState(null);
  const [loadingStats, setLoadingStats] = useState(false);
  const [quarterOffset, setQuarterOffset] = useState(0);
  const [searchQuery, setSearchQuery] = useState("");
  const [searchSuggestions, setSearchSuggestions] = useState([]);
  const [searchMode, setSearchMode] = useState(null);
  const [searchResults, setSearchResults] = useState([]);
  const [searchSuggestionsOpen, setSearchSuggestionsOpen] = useState(false);
  const [statsFilterView, setStatsFilterView] = useState(null);
  const [teamStatsPage, setTeamStatsPage] = useState(false);
  const [teamStatsRaw, setTeamStatsRaw] = useState(null);
  const [loadingTeamStats, setLoadingTeamStats] = useState(false);
  const [comparePage, setComparePage] = useState(false);
  const [compareA, setCompareA] = useState([0]);
  const [compareB, setCompareB] = useState([-1]);
  const [compareData, setCompareData] = useState(null);
  const [loadingCompare, setLoadingCompare] = useState(false);
  const [compareError, setCompareError] = useState(null);
  const [highlights, setHighlights] = useState(null);
  const [loadingHighlights, setLoadingHighlights] = useState(false);
  const [geminiApiKey, setGeminiApiKey] = useState(() => typeof localStorage !== "undefined" ? localStorage.getItem("geminiApiKey") || "" : "");
  const [aiInsights, setAiInsights] = useState(null);
  const [loadingAiInsights, setLoadingAiInsights] = useState(false);
  const searchInputRef = useRef(null);
  const searchSuggestionsRef = useRef(null);
  const [coeAssigneeAccountIds, setCoeAssigneeAccountIds] = useState(null);
  const [coeAssigneeDisplayNames, setCoeAssigneeDisplayNames] = useState(null);
  const [cognitoAuthEnabled, setCognitoAuthEnabled] = useState(false);
  const [authRequiredBlock, setAuthRequiredBlock] = useState(false);
  const [authConfigError, setAuthConfigError] = useState(null);

  useEffect(() => {
    if (!teamStatsPage && !comparePage) fetchTickets();
  }, [quarterOffset, teamStatsPage, comparePage]);

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const quarterParam = params.get("quarter");
    const quarterNum =
      quarterParam != null && !Number.isNaN(parseInt(quarterParam, 10))
        ? parseInt(quarterParam, 10)
        : null;

    if (params.get("team_stats") === "1") {
      setQuarterOffset(quarterNum !== null ? quarterNum : 0);
      setTeamStatsPage(true);
      return;
    }

    if (params.get("compare") === "1") {
      const parseList = (raw) => {
        if (!raw) return null;
        const out = raw
          .split(",")
          .map((p) => parseInt(p, 10))
          .filter((n) => !Number.isNaN(n) && n <= 0 && n >= -16);
        return out.length >= 1 && out.length <= 4 ? out : null;
      };
      const a = parseList(params.get("a")) || [0];
      const b = parseList(params.get("b")) || [-1];
      setCompareA(a);
      setCompareB(b);
      setComparePage(true);
      return;
    }

    if (quarterNum === null || Number.isNaN(quarterNum)) return;

    const resolutionTier = params.get("resolution_tier");
    const validResolutionTiers = [
      "Diamond", "Platinum", "Gold", "Silver", "Bronze", "Standard", "No ARR",
    ];
    if (resolutionTier && validResolutionTiers.includes(resolutionTier)) {
      setQuarterOffset(quarterNum);
      setStatsFilterView({
        resolutionTier,
        label: `Median resolution · ${resolutionTier} (longest first)`,
        tickets: null,
      });
      (async () => {
        try {
          const res = await apiFetch(
            `${BASE_API_URL}/api/stats/resolution-by-tier?quarter=${quarterNum}&tier=${encodeURIComponent(resolutionTier)}`
          );
          if (!res.ok) throw new Error(await res.text());
          const data = await res.json();
          setStatsFilterView((prev) => (prev ? { ...prev, tickets: data } : null));
        } catch (e) {
          console.error(e);
          setStatsFilterView((prev) => (prev ? { ...prev, tickets: [] } : null));
        }
      })();
      return;
    }

    const filter = params.get("filter");
    if (!filter) return;
    const filterMap = {
      knowledge_gap: "Knowledge Gap tickets",
      "reporter-responsibility": "Poor escalation tickets",
      "known-issue": "Known issue tickets",
      escalated: "Escalated tickets",
    };
    const label = filterMap[filter];
    if (!label) return;
    setQuarterOffset(quarterNum);
    setStatsFilterView({ filter, label, tickets: null });
    (async () => {
      try {
        const res = await apiFetch(
          `${BASE_API_URL}/api/stats/tickets?quarter=${quarterNum}&filter_type=${encodeURIComponent(filter)}`
        );
        if (!res.ok) throw new Error(await res.text());
        const data = await res.json();
        setStatsFilterView((prev) => (prev ? { ...prev, tickets: data } : null));
      } catch (e) {
        console.error(e);
        setStatsFilterView((prev) => (prev ? { ...prev, tickets: [] } : null));
      }
    })();
  }, []);

  useEffect(() => {
    if (!teamStatsPage) return;
    let cancelled = false;
    (async () => {
      setLoadingTeamStats(true);
      try {
        const res = await apiFetch(`${BASE_API_URL}/api/stats/by-assignee?quarter=${quarterOffset}`);
        if (!res.ok) throw new Error(await res.text());
        const data = await res.json();
        if (!cancelled) setTeamStatsRaw(data);
      } catch (e) {
        console.warn("Failed to load teammate stats", e);
        if (!cancelled) setTeamStatsRaw(null);
      } finally {
        if (!cancelled) setLoadingTeamStats(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [teamStatsPage, quarterOffset]);

  useEffect(() => {
    if (!teamStatsPage) return;
    const u = new URL(window.location.href);
    u.searchParams.set("team_stats", "1");
    u.searchParams.set("quarter", String(quarterOffset));
    const qs = u.searchParams.toString();
    window.history.replaceState({}, "", u.pathname + (qs ? `?${qs}` : ""));
  }, [teamStatsPage, quarterOffset]);

  useEffect(() => {
    if (!comparePage) return;
    const u = new URL(window.location.href);
    u.searchParams.set("compare", "1");
    u.searchParams.set("a", compareA.join(","));
    u.searchParams.set("b", compareB.join(","));
    u.searchParams.delete("team_stats");
    u.searchParams.delete("quarter");
    u.searchParams.delete("resolution_tier");
    u.searchParams.delete("filter");
    const qs = u.searchParams.toString();
    window.history.replaceState({}, "", u.pathname + (qs ? `?${qs}` : ""));
  }, [comparePage, compareA, compareB]);

  useEffect(() => {
    if (!comparePage) return;
    if (compareA.length === 0 || compareB.length === 0) return;
    let cancelled = false;
    setLoadingCompare(true);
    setCompareError(null);
    (async () => {
      try {
        const url = `${BASE_API_URL}/api/stats/comparison?a=${compareA.join(",")}&b=${compareB.join(",")}`;
        const res = await apiFetch(url);
        if (!res.ok) {
          const txt = await res.text();
          throw new Error(txt || `HTTP ${res.status}`);
        }
        const data = await res.json();
        if (!cancelled) setCompareData(data);
      } catch (e) {
        console.warn("compare fetch", e);
        if (!cancelled) {
          setCompareData(null);
          setCompareError(e && e.message ? e.message : String(e));
        }
      } finally {
        if (!cancelled) setLoadingCompare(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [comparePage, compareA, compareB]);

  useEffect(() => {
    if (searchQuery.trim().length < 3) {
      setSearchSuggestions([]);
      setSearchSuggestionsOpen(false);
      return;
    }
    const t = setTimeout(async () => {
      try {
        const res = await apiFetch(
          `${BASE_API_URL}/api/tickets/search?q=${encodeURIComponent(searchQuery.trim())}&limit=10`
        );
        if (res.ok) {
          const data = await res.json();
          setSearchSuggestions(data);
          setSearchSuggestionsOpen(true);
        }
      } catch (e) {
        setSearchSuggestions([]);
      }
    }, 300);
    return () => clearTimeout(t);
  }, [searchQuery]);

  useEffect(() => {
    function handleClickOutside(e) {
      if (
        searchSuggestionsRef.current &&
        !searchSuggestionsRef.current.contains(e.target) &&
        searchInputRef.current &&
        !searchInputRef.current.contains(e.target)
      ) {
        setSearchSuggestionsOpen(false);
      }
    }
    document.addEventListener("mousedown", handleClickOutside);
    return () => document.removeEventListener("mousedown", handleClickOutside);
  }, []);

  async function handleSearchAll() {
    if (searchQuery.trim().length < 3) return;
    try {
      const res = await apiFetch(
        `${BASE_API_URL}/api/tickets/search?q=${encodeURIComponent(searchQuery.trim())}&all_results=true`
      );
      if (!res.ok) throw new Error(await res.text());
      const data = await res.json();
      setSearchResults(data);
      setSearchMode("all");
      setSearchSuggestionsOpen(false);
      const allStatuses = Array.from(new Set(data.map((t) => t.status).filter(Boolean)));
      setStatusFilter(allStatuses);
    } catch (e) {
      console.error(e);
      alert("Search failed.");
    }
  }

  function handleSearchSuggestionClick(ticket) {
    setSearchResults([ticket]);
    setSearchMode("single");
    setSearchSuggestionsOpen(false);
    setSearchQuery("");
    setSearchSuggestions([]);
    handleSelectTicket(ticket.key);
  }

  function clearSearchMode() {
    setSearchMode(null);
    setSearchResults([]);
    setSearchQuery("");
    setSearchSuggestions([]);
    setSearchSuggestionsOpen(false);
    fetchTickets();
  }

  useEffect(() => {
    (async () => {
      setLoadingStats(true);
      try {
        const res = await apiFetch(`${BASE_API_URL}/api/stats?quarter=${quarterOffset}`);
        if (res.ok) {
          const data = await res.json();
          setStats(data);
        }
      } catch (e) {
        console.warn("Failed to load quarter stats", e);
      } finally {
        setLoadingStats(false);
      }
    })();
  }, [quarterOffset]);

  async function fetchHighlights() {
    setLoadingHighlights(true);
    try {
      const res = await apiFetch(`${BASE_API_URL}/api/highlights`);
      if (res.ok) {
        const data = await res.json();
        setHighlights(data);
      }
    } catch (e) {
      console.warn("Failed to load highlights", e);
    } finally {
      setLoadingHighlights(false);
    }
  }

  useEffect(() => {
    fetchHighlights();
    const interval = setInterval(fetchHighlights, 60 * 60 * 1000);
    return () => clearInterval(interval);
  }, []);

  function saveGeminiKey(key) {
    setGeminiApiKey(key);
    try {
      if (key) localStorage.setItem("geminiApiKey", key);
      else localStorage.removeItem("geminiApiKey");
    } catch (e) {}
  }

  async function generateAiInsights() {
    if (!highlights?.by_assignee) return;
    const summaryParts = [];
    for (const name of highlights.assignee_order || []) {
      const b = highlights.by_assignee[name];
      if (!b) continue;
      const lines = [];
      const fmt = (tickets, reason) => (tickets || []).map((t) => `${t.key}: "${(t.title || "").slice(0, 80)}${(t.title || "").length > 80 ? "…" : ""}" (stuck: ${reason})`);
      lines.push(...fmt(b.unassigned_high_tier, "Unassigned"));
      lines.push(...fmt(b.high_tier_backlog, "In Backlog"));
      lines.push(...fmt(b.high_tier_waiting_3d, "Waiting for customer 3+ days"));
      lines.push(...fmt(b.high_tier_wip_5d, "WIP 5+ days"));
      if (lines.length) summaryParts.push(`${name}:\n${lines.map((l) => `  - ${l}`).join("\n")}`);
    }
    const summary = summaryParts.join("\n\n") || "No highlights.";
    setLoadingAiInsights(true);
    setAiInsights(null);
    try {
      const res = await apiFetch(`${BASE_API_URL}/api/ai/insights`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          highlights_summary: summary,
          gemini_api_key: geminiApiKey || undefined,
        }),
      });
      if (!res.ok) {
        const err = await res.json().catch(() => ({ detail: res.statusText }));
        throw new Error(err.detail || "AI insights failed");
      }
      const data = await res.json();
      setAiInsights(data.insights);
    } catch (e) {
      setAiInsights(`Error: ${e.message}`);
    } finally {
      setLoadingAiInsights(false);
    }
  }

  useEffect(() => {
    // Public bootstrap, then config/users/labels (skipped until Cognito login when required)
    (async () => {
      setAuthConfigError(null);
      let pub = { cognitoAuthEnabled: false };
      try {
        const pr = await fetch(`${BASE_API_URL}/api/auth/public-config`);
        if (pr.ok) pub = await pr.json();
      } catch (e) {
        console.warn("public-config", e);
      }
      setCognitoAuthEnabled(!!pub.cognitoAuthEnabled);

      if (pub.cognitoAuthEnabled && (pub.cognitoHostedUiDomain || pub.cognitoAppClientId)) {
        window.JIRA_INTERFACE_AUTH = {
          ...(window.JIRA_INTERFACE_AUTH || {}),
          useCognito: true,
          ...(pub.cognitoHostedUiDomain ? { cognitoDomain: pub.cognitoHostedUiDomain } : {}),
          ...(pub.cognitoAppClientId ? { clientId: pub.cognitoAppClientId } : {}),
        };
      }

      const hasToken =
        typeof sessionStorage !== "undefined" && !!sessionStorage.getItem("cognito_access_token");
      if (pub.cognitoAuthEnabled && !hasToken) {
        if (!window.JIRA_INTERFACE_AUTH || !window.JIRA_INTERFACE_AUTH.useCognito) {
          setAuthConfigError(
            "The API requires Cognito login. Add auth-config.js (see auth-config.example.js) with useCognito and your Hosted UI domain."
          );
          return;
        }
        const ac = window.JIRA_INTERFACE_AUTH;
        const dom = String(ac.cognitoDomain || "")
          .replace(/^https?:\/\//, "")
          .trim();
        const cid = String(ac.clientId || "").trim();
        if (!dom || !cid) {
          setAuthConfigError(
            "Cognito needs a Hosted UI domain and app client id. On the API host set COGNITO_HOSTED_UI_DOMAIN (e.g. my-prefix.auth.eu-west-1.amazoncognito.com from App integration → Domain) and COGNITO_APP_CLIENT_ID, or set cognitoDomain and clientId in auth-config.js. The domain is not the User Pool ID — if it is wrong, Cognito shows “Login pages unavailable”."
          );
          return;
        }
        setAuthRequiredBlock(true);
        return;
      }
      setAuthRequiredBlock(false);

      try {
        const [cfgRes, usersRes, labelsRes] = await Promise.all([
          apiFetch(`${BASE_API_URL}/api/config`),
          apiFetch(`${BASE_API_URL}/api/users`),
          apiFetch(`${BASE_API_URL}/api/labels`),
        ]);
        let cfg = null;
        if (cfgRes.ok) {
          cfg = await cfgRes.json();
          if (cfg.jiraBaseUrl) setJiraBaseUrl(cfg.jiraBaseUrl);
          if (cfg.myAccountId) {
            setCurrentUserId(cfg.myAccountId);
            setAssigneeFilter((prev) => prev || cfg.myAccountId);
          }
          const ids = cfg.teamStatsAssigneeAccountIds;
          const names = cfg.teamStatsAssigneeDisplayNames;
          setCoeAssigneeAccountIds(Array.isArray(ids) && ids.length ? ids : null);
          setCoeAssigneeDisplayNames(Array.isArray(names) && names.length ? names : null);
        }
        if (usersRes.ok) {
          const list = await usersRes.json();
          setUsers(list);
          if (cfg && cfg.myAccountId && !userName) {
            const me = list.find((u) => u.account_id === cfg.myAccountId);
            if (me && me.display_name) {
              setUserName(me.display_name);
            }
          }
        }
        if (labelsRes.ok) {
          const labels = await labelsRes.json();
          setProjectLabels(labels);
        }
        if (pub.cognitoAuthEnabled) {
          const mr = await apiFetch(`${BASE_API_URL}/api/auth/me`);
          if (mr.ok) {
            const m = await mr.json();
            if (m.privilege) setUserPrivilege(m.privilege);
            const looksLikeEmail = typeof m.email === "string" && m.email.includes("@");
            if (looksLikeEmail) setUserEmailFromAuth(m.email);
            // Only fall back to email/sub if Jira didn't already give us a real display name.
            // Cognito JWTs without an email claim end up returning the raw sub UUID here.
            setUserName((cur) => {
              if (cur) return cur;
              if (looksLikeEmail) return m.email;
              return cur;
            });
          }
          try {
            const sr = await apiFetch(`${BASE_API_URL}/api/auth/jira-credentials/status`);
            if (sr.ok) setJiraCredsStatus(await sr.json());
          } catch (e) {
            console.warn("jira-credentials status", e);
          }
        }
      } catch (e) {
        console.warn("Failed to load config/users for filters", e);
      }
    })();
  }, [userName]);

  async function fetchTickets() {
    setLoadingTickets(true);
    try {
      const res = await apiFetch(`${BASE_API_URL}/api/tickets?quarter=${quarterOffset}`);
      if (!res.ok) {
        const text = await res.text();
        throw new Error(text || "Failed to load tickets");
      }
      const data = await res.json();
      setTickets(data);

      // Initialize status filter if empty: include all statuses except Closed / Canceled
      if (!statusFilter.length && Array.isArray(data)) {
        const allStatuses = Array.from(
          new Set(data.map((t) => t.status).filter(Boolean))
        );
        const defaultStatuses = allStatuses.filter(
          (s) =>
            s !== "Closed" &&
            s !== "Canceled" &&
            s !== "Cancelled" &&
            s !== "Done"
        );
        setStatusFilter(defaultStatuses);
      }
    } catch (e) {
      console.error(e);
      alert("Failed to load tickets from Jira. Check your config and server.");
    } finally {
      setLoadingTickets(false);
    }
  }

  async function fetchTicketDetail(key) {
    setLoadingDetail(true);
    setSelectedDetail(null);
    setVisibleCommentsCount(10);
    try {
      const res = await apiFetch(
        `${BASE_API_URL}/api/tickets/${encodeURIComponent(key)}`
      );
      if (!res.ok) {
        const text = await res.text();
        throw new Error(text || "Failed to load ticket details");
      }
      const data = await res.json();
      setSelectedDetail(data);
    } catch (e) {
      console.error(e);
      alert("Failed to load ticket details.");
    } finally {
      setLoadingDetail(false);
    }
  }

  function handleSelectTicket(key) {
    setSelectedKey(key);
    fetchTicketDetail(key);
  }

  const displayTickets = searchMode ? searchResults : tickets;

  const assigneeUiUsers = useMemo(
    () => resolveAssigneeUiUsers(users, coeAssigneeAccountIds, coeAssigneeDisplayNames),
    [users, coeAssigneeAccountIds, coeAssigneeDisplayNames]
  );

  const mergedTeamStatsRows = useMemo(() => {
    if (!teamStatsPage) return [];
    const rows = teamStatsRaw?.rows;
    if (!rows) return [];
    if (!users.length) {
      return [...rows].sort((a, b) =>
        (a.display_name || "").localeCompare(b.display_name || "", undefined, { sensitivity: "base" })
      );
    }
    if (assigneeUiUsers.coeSubset) {
      return mergeTeamStatsForCoeSubset(rows, assigneeUiUsers.list);
    }
    return mergeTeamStatsWithUsers(rows, users);
  }, [teamStatsPage, teamStatsRaw, users, assigneeUiUsers]);

  function exitTeamStats() {
    setTeamStatsPage(false);
    setTeamStatsRaw(null);
    const u = new URL(window.location.href);
    u.searchParams.delete("team_stats");
    const qs = u.searchParams.toString();
    window.history.replaceState({}, "", u.pathname + (qs ? `?${qs}` : ""));
  }

  function exitComparePage() {
    setComparePage(false);
    setCompareData(null);
    setCompareError(null);
    const u = new URL(window.location.href);
    u.searchParams.delete("compare");
    u.searchParams.delete("a");
    u.searchParams.delete("b");
    const qs = u.searchParams.toString();
    window.history.replaceState({}, "", u.pathname + (qs ? `?${qs}` : ""));
  }

  function enterComparePage() {
    setSelectedKey(null);
    setSelectedDetail(null);
    setStatsFilterView(null);
    if (teamStatsPage) {
      setTeamStatsPage(false);
      setTeamStatsRaw(null);
    }
    setComparePage(true);
  }

  function toggleCompareSet(setter, current, q) {
    const has = current.includes(q);
    if (has) {
      if (current.length === 1) return;
      setter(current.filter((x) => x !== q));
    } else {
      if (current.length >= 4) return;
      setter([...current, q].sort((x, y) => y - x));
    }
  }

  function refreshCompare() {
    if (!comparePage) return;
    setCompareA((arr) => [...arr]);
  }

  const filteredTickets = useMemo(() => {
    let base = displayTickets;
    if (searchMode === "single") return base;
    if (searchMode === "all") {
      base = base.filter((t) => isTicketInQuarter(t, quarterOffset));
    }
    if (tierFilter !== "All") {
      base = base.filter((t) => t.tier === tierFilter);
    }

    // Status filter: if any statuses are selected, keep only those
    if (statusFilter.length) {
      base = base.filter((t) => statusFilter.includes(t.status));
    }

    // Assignee filter:
    // - empty or unknown => no extra filter
    // - "ALL" => no extra filter
    // - explicit accountId => only tickets assigned to that account
    if (assigneeFilter && assigneeFilter !== "ALL" && assigneeFilter !== "UNASSIGNED") {
      base = base.filter((t) => t.assignee_id === assigneeFilter);
    } else if (assigneeFilter === "UNASSIGNED") {
      base = base.filter((t) => t.is_unassigned);
    }

    return base;
  }, [displayTickets, searchMode, quarterOffset, tierFilter, assigneeFilter, statusFilter]);

  const unassignedTickets = useMemo(() => {
    let base = displayTickets.filter((t) => t.is_unassigned);
    if (searchMode === "all") base = base.filter((t) => isTicketInQuarter(t, quarterOffset));
    if (statusFilter.length) base = base.filter((t) => statusFilter.includes(t.status));
    return base;
  }, [displayTickets, searchMode, quarterOffset, statusFilter]);
  const yourActiveTickets = filteredTickets.filter((t) => {
    const isAssignedToFilterUser =
      assigneeFilter === "ALL"
        ? true
        : assigneeFilter && assigneeFilter !== "UNASSIGNED"
        ? t.assignee_id === assigneeFilter
        : assigneeFilter === "UNASSIGNED"
        ? t.is_unassigned
        : currentUserId
        ? t.assignee_id === currentUserId
        : t.is_mine;
    return (
      isAssignedToFilterUser &&
      (t.status === STATUS_BACKLOG ||
        t.status === STATUS_WIP ||
        t.status === "Waiting for support")
    );
  });

  const escalatedTickets = filteredTickets.filter((t) => {
    const isAssignedToFilterUser =
      assigneeFilter === "ALL"
        ? true
        : assigneeFilter && assigneeFilter !== "UNASSIGNED"
        ? t.assignee_id === assigneeFilter
        : assigneeFilter === "UNASSIGNED"
        ? t.is_unassigned
        : currentUserId
        ? t.assignee_id === currentUserId
        : t.is_mine;
    return isAssignedToFilterUser && t.status === STATUS_ESCALATED;
  });

  const waitingForCustomerTickets = filteredTickets.filter((t) => {
    const isAssignedToFilterUser =
      assigneeFilter === "ALL"
        ? true
        : assigneeFilter && assigneeFilter !== "UNASSIGNED"
        ? t.assignee_id === assigneeFilter
        : assigneeFilter === "UNASSIGNED"
        ? t.is_unassigned
        : currentUserId
        ? t.assignee_id === currentUserId
        : t.is_mine;
    return isAssignedToFilterUser && t.status === STATUS_WAITING_FOR_CUSTOMER;
  });

  const closedDoneCanceledTickets = useMemo(() => {
    let base = displayTickets;
    if (searchMode === "all") base = base.filter((t) => isTicketInQuarter(t, quarterOffset));
    if (tierFilter !== "All") base = base.filter((t) => t.tier === tierFilter);
    if (assigneeFilter && assigneeFilter !== "ALL" && assigneeFilter !== "UNASSIGNED") {
      base = base.filter((t) => t.assignee_id === assigneeFilter);
    } else if (assigneeFilter === "UNASSIGNED") {
      base = base.filter((t) => t.is_unassigned);
    }
    base = base.filter((t) =>
      ["Closed", "Done", "Canceled", "Cancelled", "Solution Provided"].includes(t.status)
    );
    if (statusFilter.length) {
      base = base.filter((t) => statusFilter.includes(t.status));
    }
    return base;
  }, [displayTickets, searchMode, quarterOffset, tierFilter, assigneeFilter, statusFilter]);

  const tierCounts = useMemo(() => {
    let base = displayTickets;
    if (searchMode === "all") base = base.filter((t) => isTicketInQuarter(t, quarterOffset));
    base = base.filter((t) => {
      if (statusFilter.length && !statusFilter.includes(t.status)) return false;
      if (assigneeFilter && assigneeFilter !== "ALL") {
        if (assigneeFilter === "UNASSIGNED") return t.is_unassigned;
        return t.assignee_id === assigneeFilter;
      }
      return true;
    });
    const counts = { All: base.length };
    for (const t of base) {
      const tier = t.tier || "No ARR";
      counts[tier] = (counts[tier] || 0) + 1;
    }
    return counts;
  }, [displayTickets, searchMode, quarterOffset, statusFilter, assigneeFilter]);

  const assigneeCounts = useMemo(() => {
    let base = displayTickets;
    if (searchMode === "all") base = base.filter((t) => isTicketInQuarter(t, quarterOffset));
    base = base.filter((t) => {
      if (tierFilter !== "All" && t.tier !== tierFilter) return false;
      if (statusFilter.length && !statusFilter.includes(t.status)) return false;
      return true;
    });
    const counts = { ALL: base.length };
    if (currentUserId) {
      counts[currentUserId] = base.filter((t) => t.assignee_id === currentUserId).length;
    }
    counts.UNASSIGNED = base.filter((t) => t.is_unassigned).length;
    for (const u of assigneeUiUsers.list) {
      counts[u.account_id] = base.filter((t) => t.assignee_id === u.account_id).length;
    }
    return counts;
  }, [displayTickets, searchMode, quarterOffset, tierFilter, statusFilter, assigneeUiUsers, currentUserId]);

  const statusCounts = useMemo(() => {
    let base = displayTickets;
    if (searchMode === "all") base = base.filter((t) => isTicketInQuarter(t, quarterOffset));
    base = base.filter((t) => {
      if (tierFilter !== "All" && t.tier !== tierFilter) return false;
      if (assigneeFilter && assigneeFilter !== "ALL") {
        if (assigneeFilter === "UNASSIGNED") return t.is_unassigned;
        return t.assignee_id === assigneeFilter;
      }
      return true;
    });
    const counts = {};
    for (const t of base) {
      const s = t.status || "Unknown";
      counts[s] = (counts[s] || 0) + 1;
    }
    return counts;
  }, [displayTickets, searchMode, quarterOffset, tierFilter, assigneeFilter]);

  const totalArrMine = useMemo(
    () =>
      tickets
        .filter((t) => t.is_mine && t.arr != null)
        .reduce((sum, t) => sum + Number(t.arr || 0), 0),
    [tickets]
  );

  async function updateTicket(partial, opts = {}) {
    if (!selectedKey) return;
    const { preserveCommentDraft = false } = opts;
    setUpdateBusy(true);
    try {
      const res = await apiFetch(
        `${BASE_API_URL}/api/tickets/${encodeURIComponent(selectedKey)}`,
        {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(partial),
        }
      );
      if (!res.ok) {
        const text = await res.text();
        throw new Error(text || "Failed to update ticket");
      }
      await fetchTickets();
      await fetchTicketDetail(selectedKey);
      if (!preserveCommentDraft) {
        setCommentDraft("");
        setNewLabelDraft("");
        setCommentType("reply");
        setCommentEditorKey((k) => k + 1);
      }
    } catch (e) {
      console.error(e);
      alert("Failed to update ticket in Jira.");
    } finally {
      setUpdateBusy(false);
    }
  }

  async function handleStatusChange(e) {
    const newStatus = e.target.value;
    if (newStatus !== "Closed") {
      updateTicket({ status: newStatus });
      return;
    }
    const comments = selectedDetail?.comments || [];
    const allCommentText = comments.map((c) => (c.body || "")).join("\n");
    const hasKnowledgeGap = /#knowledge_gap/i.test(allCommentText);
    const hasReporterResponsibility = /#reporter-responsibility/i.test(allCommentText);
    const hasKnownIssue = /#known-issue/i.test(allCommentText);
    const hasCoeGap = /#coe_gap/i.test(allCommentText);

    const currentLabels = selectedDetail?.labels || [];
    const labelsToAdd = [];
    if (hasKnowledgeGap && !currentLabels.includes("knowledge_gap")) labelsToAdd.push("knowledge_gap");
    if (hasReporterResponsibility && !currentLabels.includes("reporter-responsibility")) labelsToAdd.push("reporter-responsibility");
    if (hasKnownIssue && !currentLabels.includes("known-issue")) labelsToAdd.push("known-issue");

    const remembered = closedDialogAnswersRef.current[selectedKey] || {};
    const wasAnswered = (id) => remembered[id] === "skip" || remembered[id] === "no" || remembered[id] === "yes";

    const dialogsToShow = [];
    if (!hasKnowledgeGap && !wasAnswered("knowledge_gap")) dialogsToShow.push("knowledge_gap");
    if (!hasReporterResponsibility && !wasAnswered("escalation")) dialogsToShow.push("escalation");
    if (!hasKnownIssue && !wasAnswered("known_issue")) dialogsToShow.push("known_issue");
    if (!wasAnswered("solution_confirmed")) dialogsToShow.push("solution_confirmed");
    if (!wasAnswered("no_response")) dialogsToShow.push("no_response");
    if (selectedDetail?.was_ever_escalated) {
      if (!wasAnswered("fixed_by_code")) dialogsToShow.push("fixed_by_code");
      if (!hasCoeGap && !wasAnswered("coe_gap")) dialogsToShow.push("coe_gap");
      if (!wasAnswered("still_open_on_RnD")) dialogsToShow.push("still_open_on_RnD");
    }

    const finalLabels = [...currentLabels, ...labelsToAdd];

    if (labelsToAdd.length > 0) {
      await updateTicket({ labels: finalLabels });
    }

    if (dialogsToShow.length === 0) {
      updateTicket({ status: "Closed" });
      return;
    }

    setClosedStatusPendingLabels(finalLabels);
    setClosedStatusDialogQueue(dialogsToShow);
    setClosedStatusPrompt(dialogsToShow[0]);
  }

  const COMMENT_DIALOGS = ["knowledge_gap", "escalation", "known_issue"];
  const LABEL_DIALOG_MAP = {
    solution_confirmed: "solution_confirmed",
    no_response: "no_response",
    fixed_by_code: "fixed_by_code",
    coe_gap: "coe_gap",
    still_open_on_RnD: "still_open_on_RnD",
  };

  function recordClosedAnswer(dialogId, answer) {
    if (!selectedKey) return;
    closedDialogAnswersRef.current[selectedKey] = closedDialogAnswersRef.current[selectedKey] || {};
    closedDialogAnswersRef.current[selectedKey][dialogId] = answer;
  }

  function clearClosedAnswersForTicket() {
    if (selectedKey) delete closedDialogAnswersRef.current[selectedKey];
  }

  function handleClosedPromptYes() {
    if (COMMENT_DIALOGS.includes(closedStatusPrompt)) {
      recordClosedAnswer(closedStatusPrompt, "yes");
      const templateMap = { knowledge_gap: "knowledgeGap", escalation: "escalationQuality", known_issue: "knownIssue" };
      const key = templateMap[closedStatusPrompt];
      setCommentType("internal");
      setCommentDraft(QUICK_NOTE_TEMPLATES[key] || "");
      setCommentEditorKey((k) => k + 1);
      setClosedStatusPrompt(null);
      setClosedStatusDialogQueue([]);
    } else if (closedStatusPrompt === "coe_gap") {
      recordClosedAnswer("coe_gap", "yes");
      updateTicket({ labels: [...closedStatusPendingLabels, "coe_gap"] }, { preserveCommentDraft: true });
      setCommentType("internal");
      setCommentDraft(QUICK_NOTE_TEMPLATES.coeGap || "");
      setCommentEditorKey((k) => k + 1);
      setClosedStatusPrompt(null);
      setClosedStatusDialogQueue([]);
      setClosedStatusPendingLabels([]);
    } else {
      recordClosedAnswer(closedStatusPrompt, "yes");
      const label = LABEL_DIALOG_MAP[closedStatusPrompt];
      if (label) {
        updateTicket({ status: "Closed", labels: [...closedStatusPendingLabels, label] });
      } else {
        updateTicket({ status: "Closed" });
      }
      setClosedStatusPrompt(null);
      setClosedStatusDialogQueue([]);
      setClosedStatusPendingLabels([]);
      clearClosedAnswersForTicket();
    }
  }

  function handleClosedPromptSkipOrNo() {
    const answer = COMMENT_DIALOGS.includes(closedStatusPrompt) ? "skip" : "no";
    recordClosedAnswer(closedStatusPrompt, answer);
    const rest = closedStatusDialogQueue.slice(1);
    setClosedStatusDialogQueue(rest);
    setClosedStatusPrompt(rest[0] || null);
    if (rest.length === 0) {
      updateTicket({ status: "Closed" });
      setClosedStatusPendingLabels([]);
      clearClosedAnswersForTicket();
    }
  }

  function handleArrBlur(e) {
    const raw = e.target.value;
    if (!raw) {
      return;
    }
    const value = Number(raw.replace(/,/g, ""));
    if (Number.isNaN(value)) {
      alert("ARR must be a number.");
      return;
    }
    updateTicket({ arr: value });
  }

  function handleIntercomBlur(e) {
    const value = e.target.value || null;
    updateTicket({ intercom_url: value });
  }

  function handleTeamUrlBlur(e) {
    const value = e.target.value || null;
    updateTicket({ team_url: value });
  }

  function handleAssigneeChange(e) {
    const accountId = e.target.value || null;
    updateTicket({ assignee_id: accountId });
  }

  function handleCustomerChange(e) {
    const value = e.target.value || "";
    updateTicket({ customer: value || null });
  }

  function handleAddLabel(label) {
    if (!label.trim()) return;
    const current = selectedDetail?.labels || [];
    if (current.includes(label.trim())) return;
    updateTicket({ labels: [...current, label.trim()] });
  }

  function handleRemoveLabel(label) {
    const current = selectedDetail?.labels || [];
    updateTicket({ labels: current.filter((l) => l !== label) });
  }

  async function handleDeleteAttachment(attachmentId) {
    if (!selectedKey || !confirm("Delete this attachment?")) return;
    setUpdateBusy(true);
    try {
      const res = await apiFetch(
        `${BASE_API_URL}/api/tickets/${encodeURIComponent(selectedKey)}/attachments/${encodeURIComponent(attachmentId)}`,
        { method: "DELETE" }
      );
      if (!res.ok) throw new Error(await res.text());
      await fetchTicketDetail(selectedKey);
    } catch (e) {
      console.error(e);
      alert("Failed to delete attachment.");
    } finally {
      setUpdateBusy(false);
    }
  }

  function handleAddComment() {
    const content = commentDraft.trim();
    if (!content) return;
    const hasHtml = /<[a-z][\s\S]*>/i.test(content);
    const payload = { comment_internal: commentType === "internal" };
    if (hasHtml) {
      try {
        payload.comment_adf = htmlToAdf(content, selectedKey);
      } catch (e) {
        const div = document.createElement("div");
        div.innerHTML = content;
        payload.comment = (div.innerText || div.textContent || "").trim();
      }
    } else {
      payload.comment = content;
    }
    updateTicket(payload);
  }

  function handleDataLossQuickAdd() {
    const adam = users.find((u) => {
      const name = (u.display_name || "").toLowerCase();
      return name.includes("adam") && name.includes("pikulik");
    });
    const accountId = adam?.account_id || currentUserId;
    if (!accountId) {
      alert("Could not find Adam Pikulik in users. Please add the comment manually.");
      return;
    }
    const adf = {
      version: 1,
      type: "doc",
      content: [{
        type: "paragraph",
        content: [
          { type: "mention", attrs: { id: accountId, text: "@Adam Pikulik" } },
          { type: "text", text: " Data loss" },
        ],
      }],
    };
    updateTicket({ comment_internal: true, comment_adf: adf });
  }

  const QUICK_NOTE_TEMPLATES = {
    knowledgeGap: `<p>#knowledge_gap</p>
<p>Describe why you think it is the knowledge gap issue. Is it covered by a documentation? Is it something very well known?</p>
<p>Example:</p>
<p>#knowledge_gap</p>
<p>The reporter should know that dots are being changed to underscores. It is a part of this training: &lt;link to the training&gt;</p>`,
    coeGap: `<p>#coe_gap</p>
<p>What R&amp;D did to solve the problem? What knowledge/tools you missed to solve it on your own?</p>`,
    escalationQuality: `<p>#reporter-responsibility</p>
<p>Describe why you think it is the reporter should do more before escalating to COE. Is it covered by documentation or the reporter should do more research before escalating? What kind of research? Does TSE or TAM should know the answer? Should he ask first on cs-internal?</p>
<p>Example:</p>
<p>#reporter-responsibility</p>
<p>The reporter should check with <a href="https://kapa.ai" target="_blank" rel="noopener">kapa.ai</a> first as I did and I found the answer there.</p>`,
    knownIssue: `<p>#known-issue</p>
<p>Ticket is covered by known issue - Slack, BUGV2 is already open. Put an internal note with the link.</p>
<p>Example:</p>
<p>#known-issue</p>
<p>Reported in BUGV2-1234.</p>`,
  };

  function handleQuickNoteTemplate(templateKey) {
    setCommentType("internal");
    setCommentDraft(QUICK_NOTE_TEMPLATES[templateKey] || "");
    setCommentEditorKey((k) => k + 1);
  }

  const visibleComments =
    selectedDetail && selectedDetail.comments
      ? [...selectedDetail.comments]
          .slice(Math.max(0, selectedDetail.comments.length - visibleCommentsCount))
          .reverse()
      : [];

  const canLoadMoreComments =
    selectedDetail &&
    selectedDetail.comments &&
    selectedDetail.comments.length > visibleCommentsCount;

  if (authConfigError) {
    return (
      <div className="auth-gate-screen">
        <h1>Configuration required</h1>
        <p>{authConfigError}</p>
      </div>
    );
  }

  if (authRequiredBlock) {
    return (
      <div className="auth-gate-screen">
        <h1>Sign in</h1>
        <p>This deployment uses Amazon Cognito. Use your <strong>@coralogix.com</strong> account and complete MFA (TOTP) when prompted.</p>
        <button type="button" className="pill-button primary-button" onClick={() => window.CognitoPkce && window.CognitoPkce.startLogin()}>
          Sign in with Cognito
        </button>
      </div>
    );
  }

  return (
    <div className="app-shell">
      <header className="app-header">
        <div className="app-title">
          <div className="app-logo-pill">
            <span>J</span>
          </div>
          <div className="app-heading-text">
            <h1>Jira Service Desk Companion</h1>
            <span>Local dashboard for your COE queue</span>
          </div>
        </div>
        <div className="app-search-container" ref={searchInputRef}>
          <input
            type="text"
            className="app-search-input"
            placeholder="Search tickets (min 3 chars)..."
            value={searchQuery}
            onChange={(e) => setSearchQuery(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === "Enter") handleSearchAll();
            }}
          />
          <button
            type="button"
            className="pill-button pill-button-primary app-search-btn"
            onClick={handleSearchAll}
            disabled={searchQuery.trim().length < 3}
          >
            Search
          </button>
          {searchSuggestionsOpen && searchSuggestions.length > 0 && (
            <div className="app-search-suggestions" ref={searchSuggestionsRef}>
              {searchSuggestions.map((t) => (
                <button
                  key={t.key}
                  type="button"
                  className="app-search-suggestion-item"
                  onClick={() => handleSearchSuggestionClick(t)}
                >
                  <span className="app-search-suggestion-key">{t.key}</span>
                  <span className="app-search-suggestion-title">{t.title}</span>
                </button>
              ))}
            </div>
          )}
        </div>
        <div className="app-header-controls">
          {userName && (
            <div className="user-chip" title={userName}>
              <span className="user-chip-avatar">{(userName || "?").trim().charAt(0).toUpperCase()}</span>
              <span className="user-chip-text">
                <span className="user-chip-name">{userName}</span>
                {userPrivilege && (
                  <span className={`user-chip-role user-chip-role-${userPrivilege}`}>{userPrivilege}</span>
                )}
              </span>
            </div>
          )}
          {teamStatsPage && (
            <button type="button" className="pill-button" onClick={exitTeamStats}>
              Back to dashboard
            </button>
          )}
          {comparePage && (
            <button type="button" className="pill-button" onClick={exitComparePage}>
              Back to dashboard
            </button>
          )}
          {statsFilterView && (
            <button
              className="pill-button"
              onClick={() => {
                setStatsFilterView(null);
                window.history.replaceState({}, "", window.location.pathname);
              }}
            >
              Back to main
            </button>
          )}
          {searchMode && !statsFilterView && !teamStatsPage && !comparePage && (
            <button className="pill-button" onClick={clearSearchMode}>
              Clear search
            </button>
          )}
          <button
            type="button"
            className="pill-button"
            onClick={() => {
              setSelectedKey(null);
              setSelectedDetail(null);
              if (teamStatsPage) exitTeamStats();
              if (comparePage) exitComparePage();
            }}
          >
            Main
          </button>
          {jiraCredsStatus?.applicable && (
            <button
              type="button"
              className="pill-button"
              onClick={() => setJiraCredsModalOpen(true)}
              title="Add or rotate your personal Jira API token"
            >
              Jira credentials
            </button>
          )}
          {cognitoAuthEnabled && (
            <button
              type="button"
              className="pill-button"
              onClick={() => {
                if (window.CognitoPkce) window.CognitoPkce.logout();
                // logout() navigates to Cognito's /logout (which then redirects back),
                // so we don't trigger a local reload here — it would race the navigation.
              }}
            >
              Sign out
            </button>
          )}
          <button
            type="button"
            className="pill-button"
            onClick={async () => {
              if (teamStatsPage) {
                setLoadingTeamStats(true);
                try {
                  const res = await apiFetch(`${BASE_API_URL}/api/stats/by-assignee?quarter=${quarterOffset}`);
                  if (res.ok) setTeamStatsRaw(await res.json());
                } catch (e) {
                  console.warn(e);
                } finally {
                  setLoadingTeamStats(false);
                }
              } else if (searchMode) {
                clearSearchMode();
              } else {
                fetchTickets();
              }
            }}
          >
            <span>⟳</span> Refresh
          </button>
        </div>
      </header>

      {jiraCredsStatus?.applicable && jiraCredsStatus?.configured === false && (
        <div className="jira-creds-banner">
          <div className="jira-creds-banner-text">
            <strong>Jira API token required.</strong>{" "}
            Your account is in the <code>{jiraCredsStatus.privilege}</code> group, which uses your personal Jira PAT.
            Add it once and the dashboard will load your tickets, comments, and edits.
          </div>
          <button
            type="button"
            className="pill-button pill-button-primary"
            onClick={() => setJiraCredsModalOpen(true)}
          >
            Add Jira token
          </button>
        </div>
      )}

      {jiraCredsModalOpen && (
        <JiraCredentialsModal
          initialEmail={userEmailFromAuth || (userName.includes("@") ? userName : "")}
          onClose={() => setJiraCredsModalOpen(false)}
          onSaved={() => {
            setJiraCredsModalOpen(false);
            setJiraCredsStatus({ ...(jiraCredsStatus || {}), applicable: true, configured: true });
            // Re-fetch the data that previously 503'd because the secret was missing.
            fetchTickets();
          }}
        />
      )}

      {!statsFilterView && !teamStatsPage && !comparePage && (
      <div className="filters-row">
        <span>Quarter:</span>
        <select
          className="tier-filter-select"
          value={quarterOffset}
          onChange={(e) => setQuarterOffset(Number(e.target.value))}
        >
          <option value={0}>{getQuarterLabel(0)} (current)</option>
          <option value={-1}>{getQuarterLabel(-1)}</option>
          <option value={-2}>{getQuarterLabel(-2)}</option>
          <option value={-3}>{getQuarterLabel(-3)}</option>
          <option value={-4}>{getQuarterLabel(-4)}</option>
        </select>
        <span>Customer tier:</span>
        <select
          className="tier-filter-select"
          value={tierFilter}
          onChange={(e) => setTierFilter(e.target.value)}
        >
          <option value="All">All tiers ({tierCounts.All ?? 0})</option>
          <option value="Diamond">Diamond ({tierCounts.Diamond ?? 0})</option>
          <option value="Platinum">Platinum ({tierCounts.Platinum ?? 0})</option>
          <option value="Gold">Gold ({tierCounts.Gold ?? 0})</option>
          <option value="Silver">Silver ({tierCounts.Silver ?? 0})</option>
          <option value="Bronze">Bronze ({tierCounts.Bronze ?? 0})</option>
          <option value="Standard">Standard ({tierCounts.Standard ?? 0})</option>
          <option value="No ARR">No ARR ({tierCounts["No ARR"] ?? 0})</option>
        </select>
        <span>Assignee:</span>
        <select
          className="tier-filter-select"
          value={assigneeFilter || "ALL"}
          onChange={(e) => setAssigneeFilter(e.target.value)}
        >
          {currentUserId && (
            <option value={currentUserId}>Me ({assigneeCounts[currentUserId] ?? 0})</option>
          )}
          <option value="ALL">All ({assigneeCounts.ALL ?? 0})</option>
          <option value="UNASSIGNED">Unassigned only ({assigneeCounts.UNASSIGNED ?? 0})</option>
          {assigneeUiUsers.list.map((u) => (
            <option key={u.account_id} value={u.account_id}>
              {u.display_name} ({assigneeCounts[u.account_id] ?? 0})
            </option>
          ))}
        </select>
        <span>Status:</span>
        <span>
          {Array.from(new Set(displayTickets.map((t) => t.status).filter(Boolean))).map(
            (status) => (
              <label key={status} style={{ marginRight: 8 }}>
                <input
                  type="checkbox"
                  checked={statusFilter.includes(status)}
                  onChange={(e) => {
                    setStatusFilter((prev) => {
                      if (e.target.checked) {
                        return Array.from(new Set([...prev, status]));
                      }
                      return prev.filter((s) => s !== status);
                    });
                  }}
                />{" "}
                {status} ({statusCounts[status] ?? 0})
              </label>
            )
          )}
        </span>
      </div>
      )}
      <div className="filters-summary">
        {comparePage
          ? loadingCompare && !compareData
            ? "Loading comparison…"
            : "Quarter comparison — pick 1–4 quarters on each side"
          : teamStatsPage
          ? loadingTeamStats && !teamStatsRaw
            ? "Loading teammate statistics…"
            : "Teammate statistics — same quarter rules as Quarter statistics"
          : statsFilterView
          ? statsFilterView.tickets === null
            ? "Loading tickets…"
            : `${statsFilterView.label}: ${statsFilterView.tickets.length} ticket${statsFilterView.tickets.length !== 1 ? "s" : ""}`
          : loadingTickets
            ? "Loading tickets…"
            : searchMode
              ? `Search results: ${filteredTickets.length} ticket${filteredTickets.length !== 1 ? "s" : ""}`
              : `Displayed ${filteredTickets.length} tickets`}
      </div>

      <div className={`layout-main${teamStatsPage ? " layout-main-team-stats" : ""}${comparePage ? " layout-main-compare" : ""}`}>
        {comparePage ? (
          <ComparePageView
            compareA={compareA}
            compareB={compareB}
            toggleA={(q) => toggleCompareSet(setCompareA, compareA, q)}
            toggleB={(q) => toggleCompareSet(setCompareB, compareB, q)}
            applyPreset={(a, b) => {
              setCompareA(a);
              setCompareB(b);
            }}
            data={compareData}
            loading={loadingCompare}
            error={compareError}
            onBack={exitComparePage}
            onRefresh={refreshCompare}
          />
        ) : teamStatsPage ? (
          loadingTeamStats && !teamStatsRaw ? (
            <div className="team-stats-page team-stats-loading">
              <div className="placeholder-detail">Loading teammate statistics…</div>
            </div>
          ) : !teamStatsRaw ? (
            <div className="team-stats-page team-stats-loading">
              <div className="placeholder-detail">Unable to load teammate statistics. Check the server and use Refresh or go back.</div>
              <button type="button" className="pill-button team-stats-error-back" onClick={exitTeamStats}>
                Back to dashboard
              </button>
            </div>
          ) : (
            <TeamStatsPageView
              mergedRows={mergedTeamStatsRows}
              quarterLabel={getQuarterLabel(quarterOffset)}
              periodText={teamStatsRaw ? `${teamStatsRaw.quarter_start} — ${teamStatsRaw.quarter_end}` : ""}
              quarterOffset={quarterOffset}
              onQuarterChange={setQuarterOffset}
              onBack={exitTeamStats}
            />
          )
        ) : (
        <>
        <div
          className={
            selectedKey ? "tickets-panel compact" : "tickets-panel full"
          }
        >
          {statsFilterView ? (
            <>
              <div className="tickets-section-header">
                <div className="tickets-section-title">
                  <span>{statsFilterView.label}</span>
                </div>
                <span className="tickets-section-pill">
                  {statsFilterView.tickets === null
                    ? "…"
                    : `${statsFilterView.tickets.length} ticket${statsFilterView.tickets.length !== 1 ? "s" : ""}`}
                </span>
              </div>
              <div className="tickets-scroll">
                {statsFilterView.tickets === null ? (
                  <div className="empty-state">Loading tickets…</div>
                ) : statsFilterView.tickets.length === 0 ? (
                  <div className="empty-state">No tickets for this filter.</div>
                ) : (
                  renderTierGroups(
                    statsFilterView.tickets,
                    selectedKey,
                    handleSelectTicket,
                    { showUnassignedTimer: false, jiraBaseUrl }
                  )
                )}
              </div>
            </>
          ) : (
            <>
          <div className="tickets-section-header">
            <div className="tickets-section-title">
              <span>Unassigned tickets</span>
            </div>
            <span className="tickets-section-pill">
              {unassignedTickets.length} open
            </span>
          </div>
          <div className="tickets-scroll">
            {unassignedTickets.length === 0 && (
              <div className="empty-state">No unassigned tickets.</div>
            )}
            {renderTierGroups(unassignedTickets, selectedKey, handleSelectTicket, { showUnassignedTimer: true, jiraBaseUrl })}
          </div>

          <div className="tickets-section-header">
            <div className="tickets-section-title">
              <span>Your active tickets</span>
            </div>
            <span className="tickets-section-pill">
              {yourActiveTickets.length} active
            </span>
          </div>
          <div className="tickets-scroll">
            {yourActiveTickets.length === 0 && (
              <div className="empty-state">No active tickets assigned to you.</div>
            )}
            {renderTierGroups(
              yourActiveTickets,
              selectedKey,
              handleSelectTicket,
              { jiraBaseUrl }
            )}
          </div>

          <div className="tickets-section-header">
            <div className="tickets-section-title">
              <span>Escalated tickets</span>
            </div>
            <span className="tickets-section-pill">
              {escalatedTickets.length} escalated
            </span>
          </div>
          <div className="tickets-scroll">
            {escalatedTickets.length === 0 && (
              <div className="empty-state">
                No escalated tickets assigned to you.
              </div>
            )}
            {renderTierGroups(
              escalatedTickets,
              selectedKey,
              handleSelectTicket,
              { jiraBaseUrl }
            )}
          </div>

          <div className="tickets-section-header">
            <div className="tickets-section-title">
              <span>Waiting for customer</span>
            </div>
            <span className="tickets-section-pill">
              {waitingForCustomerTickets.length} waiting
            </span>
          </div>
          <div className="tickets-scroll">
            {waitingForCustomerTickets.length === 0 && (
              <div className="empty-state">
                No tickets waiting for customer for this assignee.
              </div>
            )}
            {renderTierGroups(
              waitingForCustomerTickets,
              selectedKey,
              handleSelectTicket,
              { jiraBaseUrl }
            )}
          </div>

          <div className="tickets-section-header">
            <div className="tickets-section-title">
              <span>Closed / Done / Canceled / Solution Provided</span>
            </div>
            <span className="tickets-section-pill">
              {closedDoneCanceledTickets.length} resolved
            </span>
          </div>
          <div className="tickets-scroll">
            {closedDoneCanceledTickets.length === 0 && (
              <div className="empty-state">
                No closed/done/canceled/solution provided tickets for this filter.
              </div>
            )}
            {renderTierGroups(
              closedDoneCanceledTickets,
              selectedKey,
              handleSelectTicket,
              { jiraBaseUrl }
            )}
          </div>
            </>
          )}
        </div>

        {selectedKey ? (
          <div className="detail-panel">
            {loadingDetail || !selectedDetail ? (
              <div className="placeholder-detail">
                Loading ticket details from Jira…
              </div>
            ) : (
              <>
                <div className="detail-header">
                  <div className="detail-title-block">
                    <div className="detail-title">
                      {selectedDetail.key} · {selectedDetail.title}
                    </div>
                  </div>
                </div>

                <div className="detail-body">
                  <div className="detail-description-row">
                    <div className="detail-description-col">
                      <div className="section-label">Description</div>
                      <div className="description-box">
                        {selectedDetail.description || "No description."}
                      </div>
                      <div className="section-label" style={{ marginTop: 12 }}>Attachments</div>
                      <div className="attachments-list">
                        {(selectedDetail.attachments || []).length === 0 ? (
                          <div className="attachments-empty">No attachments</div>
                        ) : (
                          (selectedDetail.attachments || []).map((att) => {
                            const viewUrl = `${BASE_API_URL}/api/tickets/${encodeURIComponent(selectedKey)}/attachments/${encodeURIComponent(att.id)}/content`;
                            const canViewInBrowser = /\.(png|jpg|jpeg|gif|webp|svg|yaml|yml|txt|json)$/i.test(att.filename || "") ||
                              /^(image\/|text\/|application\/json)/.test(att.mimeType || "");
                            return (
                              <div key={att.id} className="attachment-row">
                                <a
                                  href={viewUrl}
                                  target="_blank"
                                  rel="noopener noreferrer"
                                  className="attachment-link"
                                  title={canViewInBrowser ? "Open in browser" : "Download"}
                                >
                                  {att.filename}
                                </a>
                                <button
                                  type="button"
                                  className="attachment-delete-btn"
                                  onClick={() => handleDeleteAttachment(att.id)}
                                  disabled={updateBusy}
                                  title="Delete attachment"
                                  aria-label="Delete attachment"
                                >
                                  ×
                                </button>
                              </div>
                            );
                          })
                        )}
                      </div>
                    </div>
                    <div className="detail-metadata-col">
                      <div className="detail-metadata-grid">
                        <div className="detail-metadata-item">
                          <span className="detail-metadata-label">Reporter</span>
                          <span className="detail-metadata-value">{selectedDetail.reporter || "—"}</span>
                        </div>
                        <div className="detail-metadata-item">
                          <span className="detail-metadata-label">Assignee</span>
                          <select
                            className="pill-select"
                            value={selectedDetail.assignee_id || ""}
                            onChange={handleAssigneeChange}
                            disabled={updateBusy}
                            style={{ width: "100%", minWidth: 140 }}
                          >
                            <option value="">Unassigned</option>
                            {users.map((u) => (
                              <option key={u.account_id} value={u.account_id}>
                                {u.display_name}
                              </option>
                            ))}
                          </select>
                        </div>
                        <div className="detail-metadata-item">
                          <span className="detail-metadata-label">Status</span>
                          <select
                            className="pill-select"
                            value={selectedDetail.status}
                            onChange={handleStatusChange}
                            disabled={
                              updateBusy ||
                              ["Closed", "Done", "Canceled", "Cancelled"].includes(
                                selectedDetail.status
                              )
                            }
                            style={{ width: "100%", minWidth: 140 }}
                            title={
                              ["Closed", "Done", "Canceled", "Cancelled"].includes(
                                selectedDetail.status
                              )
                                ? "Status cannot be changed for closed tickets"
                                : undefined
                            }
                          >
                            {(selectedDetail.available_statuses || [selectedDetail.status]).map(
                              (s) => (
                                <option key={s} value={s}>{s}</option>
                              )
                            )}
                          </select>
                        </div>
                        <div className="detail-metadata-item">
                          <span className="detail-metadata-label">ARR</span>
                          <input
                            className="pill-input"
                            defaultValue={
                              selectedDetail.arr != null
                                ? String(Math.round(selectedDetail.arr))
                                : ""
                            }
                            onBlur={handleArrBlur}
                            style={{ width: "100%" }}
                          />
                        </div>
                        <div className="detail-metadata-item">
                          <span className="detail-metadata-label">Customer</span>
                          <select
                            className="pill-select"
                            value={selectedDetail.customer_name || ""}
                            onChange={handleCustomerChange}
                            disabled={updateBusy}
                            style={{ width: "100%", minWidth: 140 }}
                          >
                            <option value="">—</option>
                            {(selectedDetail.customer_options || []).map((o) => (
                              <option key={o.id || o.value} value={o.value}>{o.value}</option>
                            ))}
                            {!selectedDetail.customer_options?.length && selectedDetail.customer_name && (
                              <option value={selectedDetail.customer_name}>{selectedDetail.customer_name}</option>
                            )}
                          </select>
                        </div>
                        <div className="detail-metadata-item">
                          <span className="detail-metadata-label">Team URL</span>
                          <div className="link-edit-row">
                            <input
                              className="pill-input"
                              defaultValue={selectedDetail.team_url || ""}
                              onBlur={handleTeamUrlBlur}
                              placeholder="—"
                              style={{ flex: 1 }}
                            />
                            {selectedDetail.team_url && (
                              <a
                                href={selectedDetail.team_url.startsWith("http") ? selectedDetail.team_url : `https://${selectedDetail.team_url}`}
                                target="_blank"
                                rel="noopener noreferrer"
                                className="link-open-btn"
                                title="Open in new tab"
                              >
                                ↗
                              </a>
                            )}
                          </div>
                        </div>
                        <div className="detail-metadata-item">
                          <span className="detail-metadata-label">Intercom / Customer Chat Link</span>
                          <div className="link-edit-row">
                            <input
                              className="pill-input"
                              defaultValue={selectedDetail.intercom_url || ""}
                              onBlur={handleIntercomBlur}
                              placeholder="—"
                              style={{ flex: 1 }}
                            />
                            {selectedDetail.intercom_url && (
                              <a
                                href={selectedDetail.intercom_url.startsWith("http") ? selectedDetail.intercom_url : `https://${selectedDetail.intercom_url}`}
                                target="_blank"
                                rel="noopener noreferrer"
                                className="link-open-btn"
                                title="Open in new tab"
                              >
                                ↗
                              </a>
                            )}
                          </div>
                        </div>
                        <div className="detail-metadata-item detail-metadata-item-full">
                          <span className="detail-metadata-label">Labels</span>
                          <div className="labels-editor">
                            <div className="labels-list">
                              {(selectedDetail.labels || []).map((label) => (
                                <span key={label} className="label-chip">
                                  {label}
                                  <button
                                    type="button"
                                    className="label-remove"
                                    onClick={() => handleRemoveLabel(label)}
                                    disabled={updateBusy}
                                    aria-label={`Remove ${label}`}
                                  >
                                    ×
                                  </button>
                                </span>
                              ))}
                            </div>
                            <div className="labels-add">
                              <input
                                className="pill-input"
                                list="label-suggestions"
                                placeholder="Add label…"
                                value={newLabelDraft}
                                onChange={(e) => setNewLabelDraft(e.target.value)}
                                onKeyDown={(e) => {
                                  if (e.key === "Enter") {
                                    handleAddLabel(newLabelDraft);
                                    setNewLabelDraft("");
                                  }
                                }}
                                style={{ flex: 1, minWidth: 80 }}
                              />
                              <datalist id="label-suggestions">
                                {projectLabels
                                  .filter((l) => !(selectedDetail?.labels || []).includes(l))
                                  .map((l) => (
                                    <option key={l} value={l} />
                                  ))}
                              </datalist>
                              <button
                                type="button"
                                className="pill-button small"
                                onClick={() => {
                                  handleAddLabel(newLabelDraft);
                                  setNewLabelDraft("");
                                }}
                                disabled={updateBusy || !newLabelDraft.trim()}
                              >
                                Add
                              </button>
                            </div>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>

                  <div className="detail-column comments-section">
                    <div className="section-label">Comments</div>
                    <div className="quick-action-buttons">
                      <span className="quick-action-label">Comment Templates: </span>
                      <button
                        type="button"
                        className="quick-action-btn quick-action-data-loss"
                        onClick={handleDataLossQuickAdd}
                        disabled={updateBusy}
                        title="Add internal comment: @Adam Pikulik Data loss"
                      >
                        Data loss
                      </button>
                      <button
                        type="button"
                        className="quick-action-btn quick-action-knowledge-gap"
                        onClick={() => handleQuickNoteTemplate("knowledgeGap")}
                        title="Add Knowledge Gap template to internal note"
                      >
                        Knowledge Gap
                      </button>
                      <button
                        type="button"
                        className="quick-action-btn quick-action-escalation"
                        onClick={() => handleQuickNoteTemplate("escalationQuality")}
                        title="Add Escalation quality template to internal note"
                      >
                        Escalation quality
                      </button>
                      <button
                        type="button"
                        className="quick-action-btn quick-action-known-issue"
                        onClick={() => handleQuickNoteTemplate("knownIssue")}
                        title="Add Known issue template to internal note"
                      >
                        Known issue
                      </button>
                    </div>
                    <div className="comments-type-toggle">
                      <button
                        type="button"
                        className={`comment-type-btn ${commentType === "internal" ? "active" : ""}`}
                        onClick={() => setCommentType("internal")}
                      >
                        Add internal note
                      </button>
                      <span className="comment-type-sep">/</span>
                      <button
                        type="button"
                        className={`comment-type-btn ${commentType === "reply" ? "active" : ""}`}
                        onClick={() => setCommentType("reply")}
                      >
                        Reply to customer
                      </button>
                    </div>
                    <div className="comments-input-block">
                      <RichTextEditor
                        key={commentEditorKey}
                        value={commentDraft}
                        onChange={setCommentDraft}
                        placeholder={commentType === "internal" ? "Add internal note…" : "Reply to customer…"}
                        minRows={5}
                        issueKey={selectedKey}
                        users={users}
                      />
                      <button
                        className="primary-button comments-post-btn"
                        disabled={updateBusy || !hasCommentContent}
                        onClick={handleAddComment}
                      >
                        Post
                      </button>
                    </div>
                    <div className="comments-list">
                      {canLoadMoreComments && (
                        <button
                          className="ghost-button"
                          onClick={() => setVisibleCommentsCount((c) => c + 10)}
                        >
                          Load 10 more
                        </button>
                      )}
                      {visibleComments.length === 0 && (
                        <div className="empty-state">No comments yet for this ticket.</div>
                      )}
                      {visibleComments.map((c) => (
                        <CommentItem
                          key={c.id}
                          comment={c}
                          selectedKey={selectedKey}
                          updateTicket={updateTicket}
                          fetchTicketDetail={() => fetchTicketDetail(selectedKey)}
                          setUpdateBusy={setUpdateBusy}
                          users={users}
                          attachments={selectedDetail?.attachments || []}
                          onImageClick={setImageLightboxSrc}
                        />
                      ))}
                    </div>
                  </div>
                </div>
              </>
            )}
          </div>
        ) : (
          <div className="detail-panel stats-panel">
            <div className="stats-header">
              <h2 className="stats-title">Quarter statistics</h2>
              <div className="stats-header-actions">
                <button
                  type="button"
                  className="pill-button stats-compare-teammates-btn"
                  onClick={() => {
                    setSelectedKey(null);
                    setSelectedDetail(null);
                    setTeamStatsPage(true);
                    const u = new URL(window.location.href);
                    u.searchParams.set("team_stats", "1");
                    u.searchParams.set("quarter", String(quarterOffset));
                    window.history.pushState({}, "", u.pathname + "?" + u.searchParams.toString());
                  }}
                >
                  Teammates Stats
                </button>
                <button
                  type="button"
                  className="pill-button stats-compare-teammates-btn"
                  onClick={enterComparePage}
                >
                  Compare Quarters
                </button>
                {stats && (
                  <span className="stats-period">
                    {stats.quarter_start} — {stats.quarter_end}
                  </span>
                )}
              </div>
            </div>
            {loadingStats ? (
              <div className="placeholder-detail">Loading statistics…</div>
            ) : stats ? (
              <>
                <div className="stats-grid">
                  <div className="stat-card">
                    <div className="stat-label">Total tickets</div>
                    <div className="stat-value">{stats.total_tickets}</div>
                  </div>
                  <div className="stat-card stat-card-clickable">
                    <div className="stat-label">Knowledge Gap tickets</div>
                    <button
                      type="button"
                      className="stat-value stat-value-btn"
                      onClick={() => {
                        const url = `${window.location.origin}${window.location.pathname}?filter=knowledge_gap&quarter=${quarterOffset}`;
                        window.open(url, "_blank", "noopener");
                      }}
                    >
                      {stats.knowledge_gap.count}
                      <span className="stat-pct"> ({stats.knowledge_gap.pct}%)</span>
                    </button>
                  </div>
                  <div className="stat-card stat-card-clickable">
                    <div className="stat-label">Poor escalation tickets</div>
                    <button
                      type="button"
                      className="stat-value stat-value-btn"
                      onClick={() => {
                        const url = `${window.location.origin}${window.location.pathname}?filter=reporter-responsibility&quarter=${quarterOffset}`;
                        window.open(url, "_blank", "noopener");
                      }}
                    >
                      {stats.reporter_responsibility.count}
                      <span className="stat-pct"> ({stats.reporter_responsibility.pct}%)</span>
                    </button>
                  </div>
                  <div className="stat-card stat-card-clickable">
                    <div className="stat-label">Known issue tickets</div>
                    <button
                      type="button"
                      className="stat-value stat-value-btn"
                      onClick={() => {
                        const url = `${window.location.origin}${window.location.pathname}?filter=known-issue&quarter=${quarterOffset}`;
                        window.open(url, "_blank", "noopener");
                      }}
                    >
                      {stats.known_issue.count}
                      <span className="stat-pct"> ({stats.known_issue.pct}%)</span>
                    </button>
                  </div>
                  <div className="stat-card">
                    <div className="stat-label">Median Resolution Time</div>
                    <div className="stat-value">{formatResolutionHours(stats.median_resolution_hours)}</div>
                  </div>
                  <div className="stat-card stat-card-clickable">
                    <div className="stat-label">Escalated tickets</div>
                    <button
                      type="button"
                      className="stat-value stat-value-btn"
                      onClick={() => {
                        const url = `${window.location.origin}${window.location.pathname}?filter=escalated&quarter=${quarterOffset}`;
                        window.open(url, "_blank", "noopener");
                      }}
                    >
                      {stats.escalated_count}
                    </button>
                  </div>
                </div>
                {stats.median_resolution_by_tier && Object.keys(stats.median_resolution_by_tier).length > 0 && (
                  <div className="stats-tier-section">
                    <div className="section-label section-label-with-info">
                      Median Resolution Time by Customer Tier
                      <span className="info-icon-wrapper">
                        <span className="info-icon">ⓘ</span>
                        <span className="info-tooltip">
                          Diamond, Platinum, Gold: green &lt; 8 d, orange 8–10 d, red ≥ 10 d
                          <br />Silver, Bronze: green &lt; 12 d, orange 12–15 d, red ≥ 15 d
                          <br />Standard, No ARR: always black (default)
                          <br />Click a value to open resolved tickets for that tier (longest first).
                        </span>
                      </span>
                    </div>
                    <div className="stats-tier-grid">
                      {["Diamond", "Platinum", "Gold", "Silver", "Bronze", "Standard", "No ARR"].map(
                        (tier) =>
                          stats.median_resolution_by_tier[tier] != null && (
                            <div key={tier} className="stat-tier-row">
                              <span className="stat-tier-name">{tier}</span>
                              <button
                                type="button"
                                className={`stat-tier-value stat-tier-value-btn ${getTierValueColorClass(tier, stats.median_resolution_by_tier[tier])}`}
                                onClick={() => {
                                  const url = `${window.location.origin}${window.location.pathname}?resolution_tier=${encodeURIComponent(tier)}&quarter=${quarterOffset}`;
                                  window.open(url, "_blank", "noopener");
                                }}
                                title="Open tickets used for this median (longest resolution first)"
                              >
                                {formatResolutionHours(stats.median_resolution_by_tier[tier])}
                              </button>
                            </div>
                          )
                      )}
                    </div>
                  </div>
                )}
                {stats.escalated_count != null && (
                  <EscalationPieChart
                    escalatedCount={stats.escalated_count}
                    fixedByCodeCount={stats.escalated_fixed_by_code_count ?? 0}
                  />
                )}
                <div className="stats-tier-section highlights-section">
                  <div className="section-label section-label-with-info">
                    Highlights (by assignee)
                    <span className="info-icon-wrapper">
                      <span className="info-icon">ⓘ</span>
                      <span className="info-tooltip">
                        Gold/Platinum/Diamond tickets: unassigned, in Backlog, Waiting for customer 3+ days, or WIP 5+ days.
                        <br />Rule-based analysis (no AI). Updates every hour.
                        <br />Optional: add your Gemini API key for AI prioritization suggestions.
                      </span>
                    </span>
                  </div>
                  {loadingHighlights ? (
                    <div className="highlights-loading">Loading highlights…</div>
                  ) : highlights?.by_assignee ? (
                    <div className="highlights-by-assignee">
                      {(highlights.assignee_order || Object.keys(highlights.by_assignee)).map((assigneeName) => {
                        const bucket = highlights.by_assignee[assigneeName];
                        if (!bucket) return null;
                        const total = (bucket.unassigned_high_tier?.length ?? 0) +
                          (bucket.high_tier_backlog?.length ?? 0) +
                          (bucket.high_tier_waiting_3d?.length ?? 0) +
                          (bucket.high_tier_wip_5d?.length ?? 0);
                        if (total === 0) return null;
                        const cats = [
                          { key: "unassigned_high_tier", label: "Unassigned high-tier", tickets: bucket.unassigned_high_tier || [] },
                          { key: "high_tier_backlog", label: "In Backlog", tickets: bucket.high_tier_backlog || [] },
                          { key: "high_tier_waiting_3d", label: "Waiting 3+ days", tickets: bucket.high_tier_waiting_3d || [] },
                          { key: "high_tier_wip_5d", label: "WIP 5+ days", tickets: bucket.high_tier_wip_5d || [] },
                        ];
                        return (
                          <div key={assigneeName} className="highlight-assignee-block">
                            <div className="highlight-assignee-header">
                              <span className="highlight-assignee-name">{assigneeName}</span>
                              <span className="highlight-assignee-total">{total} ticket{total !== 1 ? "s" : ""}</span>
                            </div>
                            <div className="highlight-assignee-cats">
                              {cats.filter((c) => c.tickets.length > 0).map((cat) => (
                                <div key={cat.key} className="highlight-cat">
                                  <div className="highlight-cat-title">
                                    {cat.label}
                                    <span className="highlight-count">{cat.tickets.length}</span>
                                  </div>
                                  <ul className="highlight-list">
                                    {cat.tickets.slice(0, 3).map((t) => (
                                      <li key={t.key}>
                                        <a
                                          href={jiraBaseUrl ? `${jiraBaseUrl}/browse/${t.key}` : "#"}
                                          target="_blank"
                                          rel="noopener noreferrer"
                                          className="highlight-link"
                                          onClick={(e) => { if (!jiraBaseUrl) { e.preventDefault(); handleSelectTicket(t.key); } }}
                                        >
                                          {t.key} · {t.title}
                                        </a>
                                        <span className="highlight-meta">{t.tier} · {t.status}</span>
                                      </li>
                                    ))}
                                    {cat.tickets.length > 3 && (
                                      <li className="highlight-more">+{cat.tickets.length - 3} more</li>
                                    )}
                                  </ul>
                                </div>
                              ))}
                            </div>
                          </div>
                        );
                      })}
                    </div>
                  ) : (
                    <div className="highlights-loading">Unable to load highlights.</div>
                  )}
                  <div className="highlights-ai-section">
                    <div className="highlights-ai-header">
                      <span className="section-label">AI insights (Gemini)</span>
                      <input
                        type="password"
                        className="highlights-gemini-input"
                        placeholder="Your Gemini API key (optional, per user)"
                        value={geminiApiKey}
                        onChange={(e) => saveGeminiKey(e.target.value)}
                        title="Store your own key for AI insights. Get one at aistudio.google.com/app/apikey"
                      />
                      <button
                        type="button"
                        className="pill-button"
                        onClick={generateAiInsights}
                        disabled={loadingAiInsights || !highlights?.by_assignee}
                      >
                        {loadingAiInsights ? "Generating…" : "Generate AI insights"}
                      </button>
                    </div>
                    {aiInsights && (
                      <div className={`highlights-ai-output ${aiInsights.startsWith("Error") ? "highlights-ai-error" : ""}`}>
                        {aiInsights}
                      </div>
                    )}
                  </div>
                </div>
              </>
            ) : (
              <div className="placeholder-detail">Unable to load quarter statistics.</div>
            )}
          </div>
        )}
        </>
        )}
      </div>
      {imageLightboxSrc && (
        <div
          className="image-lightbox"
          onClick={() => setImageLightboxSrc(null)}
          role="button"
          tabIndex={0}
          onKeyDown={(e) => e.key === "Escape" && setImageLightboxSrc(null)}
          aria-label="Close fullscreen image"
        >
          <div className="image-lightbox-backdrop" aria-hidden />
          <img
            src={imageLightboxSrc}
            alt=""
            onClick={(e) => e.stopPropagation()}
            className="image-lightbox-img"
          />
          <button
            type="button"
            className="image-lightbox-close"
            onClick={(e) => { e.stopPropagation(); setImageLightboxSrc(null); }}
            aria-label="Close"
          >
            ×
          </button>
        </div>
      )}
      {closedStatusPrompt && (
        <div className="closed-prompt-overlay" role="dialog" aria-modal="true" aria-labelledby="closed-prompt-title">
          <div className="closed-prompt-backdrop" />
          <div className="closed-prompt-dialog">
            <h3 id="closed-prompt-title">
              {closedStatusPrompt === "knowledge_gap" && "Do you want to add Knowledge Gap comment?"}
              {closedStatusPrompt === "escalation" && "Do you want to add Escalation quality comment?"}
              {closedStatusPrompt === "known_issue" && "Do you want to add Known issue comment?"}
              {closedStatusPrompt === "solution_confirmed" && "Did the customer confirm the provided solution?"}
              {closedStatusPrompt === "no_response" && "Do you close it due to no response from the customer?"}
              {closedStatusPrompt === "fixed_by_code" && "Did you escalate this issue to R&D and it will be fixed by code changes?"}
              {closedStatusPrompt === "coe_gap" && "Did you escalate this issue to R&D and they were able to provide the answer (no code change was needed)?"}
              {closedStatusPrompt === "still_open_on_RnD" && "Did you escalate this issue to R&D and the issue is still not concluded by R&D?"}
            </h3>
            <div className="closed-prompt-actions">
              <button type="button" className="primary-button" onClick={handleClosedPromptYes}>
                Yes
              </button>
              <button type="button" className="ghost-button" onClick={handleClosedPromptSkipOrNo}>
                {COMMENT_DIALOGS.includes(closedStatusPrompt) ? "Skip" : "No"}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function renderTierGroups(tickets, selectedKey, onSelect, opts = {}) {
  const { showUnassignedTimer = false, jiraBaseUrl = "" } = opts;
  const byTier = tickets.reduce((map, t) => {
    const tier = t.tier || "No ARR";
    if (!map[tier]) map[tier] = [];
    map[tier].push(t);
    return map;
  }, {});

  const tierOrder = [
    "Diamond",
    "Platinum",
    "Gold",
    "Silver",
    "Bronze",
    "Standard",
    "No ARR",
  ];

  return tierOrder
    .filter((tier) => byTier[tier] && byTier[tier].length > 0)
    .map((tier) => (
      <div key={tier} className="tier-group">
        <div className="tier-group-header">
          <span
            className={`tier-dot ${tier.toLowerCase().replace(/\s+/g, "-")}`}
            aria-hidden="true"
          />
          <span>{tier}</span>
          <span className="tickets-section-pill">
            {byTier[tier].length} ticket
            {byTier[tier].length === 1 ? "" : "s"}
          </span>
        </div>
        {byTier[tier].map((t) => (
          <article
            key={t.key}
            className={
              "ticket-card" +
              (selectedKey === t.key ? " selected" : "") +
              (t.is_unassigned ? " unassigned" : "")
            }
            onClick={() => onSelect(t.key)}
          >
            <div className="ticket-title-row">
              <div className="ticket-title">
                {t.key} · {t.title}
              </div>
              {jiraBaseUrl && (
                <a
                  href={`${jiraBaseUrl}/browse/${t.key}`}
                  target="_blank"
                  rel="noopener noreferrer"
                  className="ticket-open-external"
                  onClick={(e) => e.stopPropagation()}
                  title="Open in Jira"
                  aria-label={`Open ${t.key} in Jira`}
                >
                  ↗
                </a>
              )}
            </div>
            <div className="ticket-meta-row">
              <div className="ticket-meta-left">
                <span
                  className={
                    "status-chip " +
                    (t.status === STATUS_ESCALATED
                      ? "status-escalated"
                      : t.status === STATUS_WIP
                      ? "status-wip"
                      : t.status === STATUS_BACKLOG
                      ? "status-backlog"
                      : "status-done")
                  }
                >
                  {t.status}
                </span>
                <span className="ticket-arr">{formatCurrency(t.arr)}</span>
                {t.resolution_hours != null && (
                  <span className="ticket-resolution-time" title="Time from created to resolved">
                    {formatResolutionHours(t.resolution_hours)}
                  </span>
                )}
              </div>
              <div className="ticket-meta-right">
                {t.last_updated &&
                  !["Closed", "Done", "Canceled", "Cancelled", "Solution Provided"].includes(t.status) &&
                  Date.now() - new Date(t.last_updated).getTime() >
                    5 * 24 * 60 * 60 * 1000 && (
                    <span className="ticket-stale-flag">
                      No update for 5 days
                    </span>
                  )}
                <span className="ticket-assignee">
                  {t.assignee_display || "Unassigned"}
                </span>
                {showUnassignedTimer && t.is_unassigned && t.created && (
                  <span className="ticket-unassigned-timer" title={`Unassigned since ${new Date(t.created).toLocaleString()}`}>
                    {formatDurationSince(t.created)}
                  </span>
                )}
              </div>
            </div>
          </article>
        ))}
      </div>
    ));
}

ReactDOM.createRoot(document.getElementById("root")).render(<TicketApp />);

