// Atlas · Step 5 — Draft review with inline Google-Docs-style comments
const {
  useState: useStateDr,
  useEffect: useEffectDr,
  useMemo: useMemoDr,
  useRef: useRefDr,
  useCallback: useCallbackDr,
} = React;

const NOWRAP_THRESHOLD = 25;

function getCellsForColumn(table, colIdx) {
  const cells = [];
  Array.from(table.rows).forEach(tr => {
    let cursor = 0;
    for (const cell of tr.cells) {
      const span = parseInt(cell.getAttribute("colspan") || "1", 10);
      if (cursor === colIdx) { cells.push(cell); break; }
      if (cursor > colIdx) break;
      cursor += span;
    }
  });
  return cells;
}

function applyTableColumnWidths(html) {
  if (!html || !html.includes("<table")) return html;
  if (typeof DOMParser === "undefined") return html;
  const doc = new DOMParser().parseFromString(`<body>${html}</body>`, "text/html");
  doc.querySelectorAll("table").forEach(table => {
    const numCols = Math.max(
      ...Array.from(table.rows).map(tr =>
        Array.from(tr.cells).reduce((sum, cell) =>
          sum + parseInt(cell.getAttribute("colspan") || "1", 10), 0)
      ),
      0
    );
    for (let colIdx = 0; colIdx < numCols; colIdx++) {
      const cells = getCellsForColumn(table, colIdx);
      const maxLen = Math.max(...cells.map(c => c.textContent.trim().length), 0);
      if (maxLen > 0 && maxLen <= NOWRAP_THRESHOLD) {
        cells.forEach(c => {
          const existing = c.getAttribute("style") || "";
          c.setAttribute("style", existing ? `${existing}; white-space: nowrap` : "white-space: nowrap");
        });
      }
    }
    const wrapper = doc.createElement("div");
    wrapper.setAttribute("style", "overflow-x: auto; margin: 0 0 20px;");
    table.parentNode.insertBefore(wrapper, table);
    wrapper.appendChild(table);
  });
  return doc.body.innerHTML;
}

function renderWithIds(markdown) {
  if (!markdown) return "";
  if (typeof marked === "undefined") return `<pre style='white-space:pre-wrap'>${markdown}</pre>`;
  try {
    let html = marked.parse(markdown);
    html = applyTableColumnWidths(html);
    const slugs = {};
    html = html.replace(/<h([1-3])([^>]*)>([\s\S]*?)<\/h[1-3]>/gi, (match, level, attrs, inner) => {
      const plain = inner.replace(/<[^>]+>/g, "");
      const slug = plain.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
      slugs[slug] = (slugs[slug] || 0) + 1;
      const id = slugs[slug] > 1 ? `${slug}-${slugs[slug]}` : slug;
      return `<h${level} id="${id}"${attrs}>${inner}</h${level}>`;
    });
    return html;
  } catch (e) {
    return `<pre style='white-space:pre-wrap'>${markdown}</pre>`;
  }
}


// Highlight all text nodes that fall within a Range using <mark> elements.
// Works for multi-paragraph / multi-element selections.
function applyRangeHighlight(range, commentId) {
  const marks = [];
  const container = range.commonAncestorContainer;
  const root = container.nodeType === Node.TEXT_NODE ? container.parentNode : container;

  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
  const textNodes = [];
  let node;
  while ((node = walker.nextNode())) {
    const cmp1 = range.comparePoint(node, 0);
    const cmp2 = range.comparePoint(node, node.nodeValue.length);
    // Node overlaps range if start is before end of range and end is after start of range
    if (cmp1 <= 0 && cmp2 >= 0) textNodes.push(node);
    else if (cmp1 < 0 && cmp2 > 0) textNodes.push(node);
    else if (cmp1 === 0 || cmp2 === 0) textNodes.push(node);
    // simpler: any node inside or touching
  }

  // Re-filter: keep only nodes within the range
  const inRange = textNodes.filter(n => {
    try {
      const nr = document.createRange();
      nr.selectNode(n);
      return range.compareBoundaryPoints(Range.END_TO_START, nr) < 0 &&
             range.compareBoundaryPoints(Range.START_TO_END, nr) > 0;
    } catch { return false; }
  });

  // Clip to single table cell: if the first node is inside a td/th, discard any
  // nodes that belong to a different cell to avoid breaking table structure.
  const getCell = n => n.parentNode?.closest("td, th") || null;
  const firstCell = inRange.length > 0 ? getCell(inRange[0]) : null;
  const safeNodes = firstCell
    ? inRange.filter(n => getCell(n) === firstCell)
    : inRange;
  if (safeNodes.length === 0) return false;

  safeNodes.forEach(n => {
    let start = 0;
    let end = n.nodeValue.length;
    if (n === range.startContainer) start = range.startOffset;
    if (n === range.endContainer) end = range.endOffset;
    if (start >= end) return;

    const before = n.nodeValue.slice(0, start);
    const mid    = n.nodeValue.slice(start, end);
    const after  = n.nodeValue.slice(end);

    const mark = document.createElement("mark");
    mark.className = "inline-comment-mark";
    mark.dataset.commentId = commentId;
    mark.textContent = mid;
    marks.push(mark);

    const parent = n.parentNode;
    if (before) parent.insertBefore(document.createTextNode(before), n);
    parent.insertBefore(mark, n);
    if (after) parent.insertBefore(document.createTextNode(after), n);
    parent.removeChild(n);
  });

  return marks.length > 0;
}

function removeHighlight(commentId) {
  document.querySelectorAll(`.inline-comment-mark[data-comment-id="${commentId}"]`).forEach(mark => {
    mark.replaceWith(document.createTextNode(mark.textContent));
    mark.parentNode && mark.parentNode.normalize();
  });
}

function diffParagraphs(oldMd, newMd) {
  const oldPs = oldMd.split(/\n{2,}/);
  const newPs = newMd.split(/\n{2,}/);
  const m = oldPs.length, n = newPs.length;
  const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
  for (let i = 1; i <= m; i++)
    for (let j = 1; j <= n; j++)
      dp[i][j] = oldPs[i-1] === newPs[j-1] ? dp[i-1][j-1] + 1 : Math.max(dp[i-1][j], dp[i][j-1]);
  const result = [];
  let i = m, j = n;
  while (i > 0 || j > 0) {
    if (i > 0 && j > 0 && oldPs[i-1] === newPs[j-1]) {
      result.unshift({ type: "equal", text: newPs[j-1] }); i--; j--;
    } else if (j > 0 && (i === 0 || dp[i][j-1] >= dp[i-1][j])) {
      result.unshift({ type: "add", text: newPs[j-1] }); j--;
    } else {
      result.unshift({ type: "remove", text: oldPs[i-1] }); i--;
    }
  }
  return result;
}

function StepDraft({ reportId, draft, setDraft, goBack, goNext, onRevisionComplete, loading }) {
  const [comments, setComments] = useStateDr([]);
  const [revisionHistory, setRevisionHistory] = useStateDr([]);
  // pendingQuote: {quote, docY, id} — set at mouseup, cleared on add/cancel
  const [pendingQuote, setPendingQuote] = useStateDr(null);
  const [commentInput, setCommentInput] = useStateDr("");
  const [triggerBtnY, setTriggerBtnY] = useStateDr(null); // viewport Y for pill
  const [applyLoading, setApplyLoading] = useStateDr(false);
  const [applyDone, setApplyDone] = useStateDr(false);
  const [revising, setRevising] = useStateDr(false);
  const [revisionLog, setRevisionLog] = useStateDr([]);
  const [revisionPhase, setRevisionPhase] = useStateDr(null); // "classifying"|"researching"|"writing"|"done"
  const [revisionIteration, setRevisionIteration] = useStateDr(0);
  const [appliedIds, setAppliedIds] = useStateDr(new Set());
  const [unmatchedIds, setUnmatchedIds] = useStateDr(new Set());
  const [rejectedIds, setRejectedIds] = useStateDr(new Set());
  const [writingRejectionReasons, setWritingRejectionReasons] = useStateDr({});
  const [researchBlockedInfo, setResearchBlockedInfo] = useStateDr(null);
  const [revisionStuck, setRevisionStuck] = useStateDr(false);
  const [previousDraft, setPreviousDraft] = useStateDr(null);
  const [showDiff, setShowDiff] = useStateDr(false);
  const [reportPreview, setReportPreview] = useStateDr(false);
  const draftRef = useRefDr(null);
  const rightPanelRef = useRefDr(null);
  const logRef = useRefDr(null);
  const abortControllerRef = useRefDr(null);
  const lastRevisionDataRef = useRefDr(null);
  const panelTopY = useRefDr(0);   // page-absolute top of right panel
  const inputRef = useRefDr(null);

  const renderedHtml = useMemoDr(() => renderWithIds(draft), [draft]);
  const diffSegments = useMemoDr(() => {
    if (!showDiff || !previousDraft) return null;
    return diffParagraphs(previousDraft, draft);
  }, [showDiff, previousDraft, draft]);

  // After HTML re-renders (draft changed after Apply), re-apply all highlights
  useEffectDr(() => {
    const container = draftRef.current;
    if (!container || !comments.length) return;
    // Remove any stale marks then re-apply (fallback text search for post-Apply)
    container.querySelectorAll(".inline-comment-mark").forEach(m => {
      m.replaceWith(document.createTextNode(m.textContent));
    });
    container.normalize();
    comments.forEach(c => {
      if (!c.quote) return;
      // Walk text nodes looking for the first occurrence of the quote
      const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null, false);
      let node;
      while ((node = walker.nextNode())) {
        const idx = node.nodeValue.indexOf(c.quote.slice(0, 40));
        if (idx !== -1) {
          // Best-effort single-node highlight for post-Apply re-renders
          const r = document.createRange();
          r.setStart(node, idx);
          r.setEnd(node, Math.min(idx + c.quote.length, node.nodeValue.length));
          applyRangeHighlight(r, c.id);
          break;
        }
      }
    });
  }, [renderedHtml]); // only runs when draft HTML changes (Apply)

  // Record panel top for positioning
  useEffectDr(() => {
    if (rightPanelRef.current) {
      panelTopY.current = rightPanelRef.current.getBoundingClientRect().top + window.scrollY;
    }
  }, [renderedHtml]);

  // Auto-scroll revision log to bottom as new lines arrive
  useEffectDr(() => {
    if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
  }, [revisionLog]);

  const handleMouseUp = useCallbackDr(() => {
    const sel = window.getSelection();
    if (!sel || sel.isCollapsed || !sel.toString().trim()) return;
    const container = draftRef.current;
    if (!container) return;

    // Only react to selections inside the draft content
    if (!container.contains(sel.anchorNode) && !container.contains(sel.focusNode)) return;

    const range = sel.getRangeAt(0).cloneRange();
    const rect = range.getBoundingClientRect();
    const viewY = rect.top + rect.height / 2;
    const clampedY = Math.max(60, Math.min(viewY, window.innerHeight - 60));
    const docY = rect.top + window.scrollY; // page-absolute

    // Assign ID now so we can immediately highlight
    const id = `c${Date.now()}`;

    // Capture quote text BEFORE DOM mutation (applyRangeHighlight replaces text nodes)
    const quoteText = range.toString().trim();

    // Apply highlight immediately — works for multi-node selections
    const ok = applyRangeHighlight(range, id);

    sel.removeAllRanges();
    setTriggerBtnY(clampedY);
    setPendingQuote({ quote: quoteText, docY, id });
    setApplyDone(false);
  }, []);

  // Dismiss pill on click-away (not on panel or pill)
  useEffectDr(() => {
    const onDown = (e) => {
      if (e.target.closest(".comment-trigger-btn") || e.target.closest(".comment-right-panel")) return;
      if (!commentInput && pendingQuote) {
        // Cancel — remove the pending highlight
        removeHighlight(pendingQuote.id);
        setTriggerBtnY(null);
        setPendingQuote(null);
      }
    };
    document.addEventListener("mousedown", onDown);
    return () => document.removeEventListener("mousedown", onDown);
  }, [commentInput, pendingQuote]);

  const openInput = () => {
    setCommentInput("");
    setTriggerBtnY(null); // hide pill, show input in panel
    setTimeout(() => inputRef.current?.focus(), 50);
  };

  const submitComment = () => {
    if (!commentInput.trim() || !pendingQuote) return;
    const newComment = {
      id: pendingQuote.id,
      quote: pendingQuote.quote,
      docY: pendingQuote.docY,
      body: commentInput.trim(),
    };
    setComments(prev => [...prev, newComment].sort((a, b) => a.docY - b.docY));
    setCommentInput("");
    setPendingQuote(null);
    setApplyDone(false);
  };

  const cancelInput = () => {
    if (pendingQuote) removeHighlight(pendingQuote.id);
    setCommentInput("");
    setPendingQuote(null);
    setTriggerBtnY(null);
  };

  const removeComment = (id) => {
    removeHighlight(id);
    setComments(prev => prev.filter(c => c.id !== id));
    setUnmatchedIds(prev => { const s = new Set(prev); s.delete(id); return s; });
    setRejectedIds(prev => { const s = new Set(prev); s.delete(id); return s; });
  };

  const applyChanges = async () => {
    if (!reportId) return;

    const allComments = comments.filter(c => !rejectedIds.has(c.id));
    if (!allComments.length) return;

    // Pre-flight: validate quotes still exist in draft
    try {
      const vRes = await fetch(`/api/report/${reportId}/revise/validate`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ comments: allComments }),
      });
      if (vRes.ok) {
        const vData = await vRes.json();
        if (vData.unmatched && vData.unmatched.length > 0) {
          setUnmatchedIds(new Set(vData.unmatched));
          rightPanelRef.current?.scrollTo({ top: 0, behavior: "smooth" });
        } else {
          setUnmatchedIds(new Set());
        }
      }
    } catch (_) { /* non-blocking */ }

    setRevising(true);
    setRevisionStuck(false);
    setRevisionLog([]);
    setRevisionPhase("classifying");
    setApplyDone(false);
    setAppliedIds(new Set());
    setResearchBlockedInfo(null);
    setShowDiff(false);

    // Stuck detection: fire if no data received for 5 minutes
    lastRevisionDataRef.current = Date.now();
    const stuckIntervalId = setInterval(() => {
      if (lastRevisionDataRef.current && Date.now() - lastRevisionDataRef.current > 300000) {
        setRevisionStuck(true);
      }
    }, 30000);

    const controller = new AbortController();
    abortControllerRef.current = controller;

    try {
      const res = await fetch(`/api/report/${reportId}/revise/stream`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ comments: allComments }),
        signal: controller.signal,
      });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buf = "";
      let localRejectedIds = new Set();
      let localWritingRejectedIds = new Set();

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        lastRevisionDataRef.current = Date.now();
        setRevisionStuck(false);
        buf += decoder.decode(value, { stream: true });
        const parts = buf.split("\n\n");
        buf = parts.pop();
        for (const part of parts) {
          if (!part.startsWith("data:")) continue;
          let data;
          try { data = JSON.parse(part.slice(5).trim()); } catch { continue; }
          if (data.text !== undefined) {
            setRevisionLog(prev => [...prev, data.text]);
            const t = data.text;
            if (/classif/i.test(t)) setRevisionPhase("classifying");
            else if (/research/i.test(t)) setRevisionPhase("researching");
            else if (/writ/i.test(t)) setRevisionPhase("writing");
          }
          if (data.applied_ids && data.applied_ids.length > 0) {
            setAppliedIds(prev => new Set([...prev, ...data.applied_ids]));
          }
          if (data.writing_rejected) {
            const { id, reason } = data.writing_rejected;
            console.log("[writing_rejected]", id, reason?.slice(0, 80));
            localRejectedIds.add(id);
            localWritingRejectedIds.add(id);
            removeHighlight(id);
            setRejectedIds(prev => new Set([...prev, id]));
            setWritingRejectionReasons(prev => ({ ...prev, [id]: reason }));
          }
          if (data.rejected_ids && data.rejected_ids.length > 0) {
            data.rejected_ids.forEach(id => localRejectedIds.add(id));
            setRejectedIds(prev => new Set([...prev, ...data.rejected_ids]));
          }
          if (data.research_blocked) {
            const { comment_ids, explanation } = data.research_blocked;
            (comment_ids || []).forEach(id => {
              localRejectedIds.add(id);
              removeHighlight(id);
              setRejectedIds(prev => new Set([...prev, id]));
              setWritingRejectionReasons(prev => ({ ...prev, [id]: explanation }));
            });
            setResearchBlockedInfo(null);
          }
          if (data.done) {
            if (data.markdown && setDraft && data.markdown !== draft) {
              setPreviousDraft(draft);
              setDraft(data.markdown);
            }
            setComments(prev => prev.filter(c => localRejectedIds.has(c.id)));
            setAppliedIds(new Set());
            setUnmatchedIds(new Set());
            setApplyDone(data.markdown !== draft && localWritingRejectedIds.size === 0);
            setRevisionIteration(n => n + 1);
            setRevisionPhase("done");
            setRevising(false);
            // Refresh revision history and re-export if already approved
            fetch(`/api/report/${reportId}/revisions`)
              .then(r => r.json())
              .then(d => setRevisionHistory(d.revisions || []))
              .catch(() => {});
            onRevisionComplete?.();
          }
        }
      }
    } catch (e) {
      if (e.name !== "AbortError") {
        console.error(e);
        setRevisionLog(prev => [...prev, `Error: ${e.message}`]);
      }
      setRevising(false);
    } finally {
      clearInterval(stuckIntervalId);
      lastRevisionDataRef.current = null;
      abortControllerRef.current = null;
    }
  };


  // Position input card and comment cards relative to right panel top
  const CARD_HEIGHT_EST = 108;
  const CARD_GAP = 8;

  // Input card position: align with selection Y
  const inputCardTop = pendingQuote && triggerBtnY === null
    ? Math.max(28, pendingQuote.docY - panelTopY.current)
    : null;

  // Comment card positions: each aligned to its docY, pushed down if it would overlap
  const cardTops = (() => {
    let floor = 0;
    return comments.map(c => {
      const ideal = Math.max(0, c.docY - panelTopY.current);
      const top = Math.max(floor, ideal);
      floor = top + CARD_HEIGHT_EST + CARD_GAP;
      return top;
    });
  })();

  // Height of the comments container (enough to hold all cards)
  const containerHeight = comments.length
    ? (cardTops[cardTops.length - 1] || 0) + CARD_HEIGHT_EST + 48
    : 0;

  if (!draft) {
    return (
      <div className="canvas-inner" style={{ display: "flex", alignItems: "center", justifyContent: "center", minHeight: 400 }}>
        <div style={{ textAlign: "center", color: "var(--ink-3)" }}>
          <div style={{ fontFamily: "var(--mono)", fontSize: 12, marginBottom: 8 }}>No draft available</div>
          <div style={{ fontSize: 13 }}>Complete the research phase first.</div>
          <button className="btn ghost" style={{ marginTop: 16 }} onClick={goBack}>← Back</button>
        </div>
      </div>
    );
  }

  return (
    <div className="canvas-inner" style={{ maxWidth: 1400, position: "relative" }}>

      {/* Fixed pill — sits at right edge at selection Y */}
      {triggerBtnY !== null && (
        <button
          className="comment-trigger-btn"
          onClick={openInput}
          style={{
            position: "fixed", right: 24, top: triggerBtnY,
            transform: "translateY(-50%)", zIndex: 200,
            background: "var(--ink)", color: "#fff",
            border: "none", borderRadius: 20, padding: "6px 14px",
            fontSize: 12, fontFamily: "var(--mono)", cursor: "pointer",
            boxShadow: "0 2px 10px rgba(0,0,0,0.22)",
            display: "flex", alignItems: "center", gap: 6, whiteSpace: "nowrap",
          }}
        >
          <Icon name="plus" size={11} /> Add comment
        </button>
      )}

      <div style={{ display: "grid", gridTemplateColumns: reportPreview ? "1fr" : "1fr 276px", gap: 32, alignItems: "start", background: reportPreview ? "#e8e8e8" : "transparent", padding: reportPreview ? "32px 24px" : 0, borderRadius: reportPreview ? 8 : 0 }}>

        {/* Document */}
        <div style={reportPreview ? { maxWidth: 680, margin: "0 auto", width: "100%", overflowX: "hidden" } : undefined}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 16 }}>
            <div style={{ fontSize: 11, fontFamily: "var(--mono)", color: "var(--ink-4)" }}>
              {new Date().toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })}
            </div>
            <div style={{ display: "flex", gap: 8 }}>
              {previousDraft && (
                <button
                  onClick={() => setShowDiff(v => !v)}
                  style={{
                    padding: "3px 10px", fontSize: 11, fontFamily: "var(--mono)",
                    border: "1px solid var(--line-2)", borderRadius: 12, cursor: "pointer",
                    background: showDiff ? "#d4edda" : "var(--bg-2)", color: "var(--ink-2)",
                  }}
                >
                  {showDiff ? "Hide diff" : "Show diff"}
                </button>
              )}
              <button
                onClick={() => setReportPreview(v => !v)}
                style={{
                  padding: "3px 10px", fontSize: 11, fontFamily: "var(--mono)",
                  border: "1px solid var(--line-2)", borderRadius: 12, cursor: "pointer",
                  background: reportPreview ? "#cce5ff" : "var(--bg-2)", color: "var(--ink-2)",
                }}
              >
                {reportPreview ? "Exit preview" : "Preview as report"}
              </button>
            </div>
          </div>


          {diffSegments ? (
            <div
              ref={draftRef}
              className={reportPreview ? "draft-content report-preview" : "draft-content"}
              onMouseUp={handleMouseUp}
              style={reportPreview
                ? { background: "white", boxShadow: "0 4px 24px rgba(0,0,0,0.18)", padding: "48px 56px", overflowX: "hidden", userSelect: "text", cursor: "text" }
                : { fontFamily: "var(--serif)", fontSize: 15, lineHeight: 1.75, color: "var(--ink)", userSelect: "text", cursor: "text" }}
            >
              {diffSegments.map((seg, idx) => (
                <div
                  key={idx}
                  className={seg.type === "add" ? "diff-add" : seg.type === "remove" ? "diff-remove" : undefined}
                  dangerouslySetInnerHTML={{ __html: renderWithIds(seg.text) }}
                />
              ))}
            </div>
          ) : (
            <div
              ref={draftRef}
              className={reportPreview ? "draft-content report-preview" : "draft-content"}
              dangerouslySetInnerHTML={{ __html: renderedHtml }}
              onMouseUp={handleMouseUp}
              style={reportPreview
                ? { background: "white", boxShadow: "0 4px 24px rgba(0,0,0,0.18)", padding: "48px 56px", overflowX: "hidden", userSelect: "text", cursor: "text" }
                : { fontFamily: "var(--serif)", fontSize: 15, lineHeight: 1.75, color: "var(--ink)", userSelect: "text", cursor: "text" }}
            />
          )}

          <div style={{ marginTop: 32, paddingTop: 16, borderTop: "1px solid var(--line)", fontSize: 12, color: "var(--ink-4)", fontFamily: "var(--mono)" }}>
            Highlight text to request a change or ask for deeper research.
          </div>
        </div>

        {/* Right panel — comment input + cards, absolutely positioned by docY */}
        <div className="comment-right-panel" ref={rightPanelRef} style={{ position: "relative", minHeight: Math.max(200, containerHeight + 60), display: reportPreview ? "none" : undefined }}>

          {/* Revision progress overlay — replaces comment list while agents work */}
          {revising ? (() => {
            const phases = [
              { key: "classifying", label: "Classifying comments" },
              { key: "researching", label: "Researching" },
              { key: "writing",     label: "Writing draft" },
            ];
            const phaseOrder = ["classifying", "researching", "writing", "done"];
            const currentIdx = phaseOrder.indexOf(revisionPhase);
            return (
              <div style={{ fontFamily: "var(--mono)", fontSize: 12 }}>
                <div style={{ fontSize: 10, letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 14 }}>
                  Revision in progress
                </div>
                <div style={{ marginBottom: 16 }}>
                  {phases.map((p, i) => {
                    const pIdx = phaseOrder.indexOf(p.key);
                    const isDone = currentIdx > pIdx;
                    const isActive = currentIdx === pIdx;
                    return (
                      <div key={p.key} style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8, color: isDone ? "var(--success)" : isActive ? "var(--ink)" : "var(--ink-4)" }}>
                        <span className={isActive ? "phase-dot-active" : ""} style={{
                          width: 8, height: 8, borderRadius: "50%", flexShrink: 0,
                          background: isDone ? "var(--success)" : isActive ? "var(--ink)" : "var(--line-2)",
                        }} />
                        <span style={{ fontSize: 12 }}>{isDone ? "✓ " : ""}{p.label}</span>
                      </div>
                    );
                  })}
                </div>
                {revisionStuck && (
                  <div style={{
                    marginBottom: 12, padding: "10px 12px",
                    background: "var(--warm-tint)", border: "1px solid var(--warm-line)",
                    borderRadius: "var(--radius)", fontSize: 12, color: "var(--warm)",
                  }}>
                    <div style={{ marginBottom: 8 }}>⚠️ Revision appears stuck.</div>
                    <div style={{ display: "flex", gap: 8 }}>
                      <button className="btn ghost sm" onClick={() => {
                        abortControllerRef.current?.abort();
                        setRevising(false);
                        setRevisionStuck(false);
                      }}>Cancel this revision</button>
                      <button className="btn primary sm" onClick={() => {
                        abortControllerRef.current?.abort();
                        setRevisionStuck(false);
                        setTimeout(() => applyChanges(), 100);
                      }}>Retry this revision</button>
                    </div>
                  </div>
                )}
                <div ref={logRef} style={{ borderLeft: "2px solid var(--line-2)", paddingLeft: 10, maxHeight: 220, overflowY: "auto" }}>
                  {revisionLog.map((logLine, i) => (
                    <div key={i} style={{
                      fontSize: 11, lineHeight: 1.6,
                      color: logLine.startsWith("  ✓") ? "var(--success)"
                           : logLine.startsWith("━") ? "var(--ink-4)"
                           : "var(--ink-3)",
                      whiteSpace: "pre-wrap",
                    }}>
                      {logLine || " "}
                    </div>
                  ))}
                </div>
              </div>
            );
          })() : (<>

          {revisionIteration > 0 && (
            <div style={{ fontSize: 10, fontFamily: "var(--mono)", color: "var(--ink-4)", marginBottom: 8 }}>
              Revision {revisionIteration} applied
            </div>
          )}



          {applyDone && (
            <div style={{ marginBottom: 16, padding: "8px 12px", background: "var(--success-tint)", border: "1px solid var(--success-line)", borderRadius: "var(--radius)", fontSize: 12, color: "var(--success)", fontFamily: "var(--mono)" }}>
              ✓ Changes applied to draft.
            </div>
          )}

          {revisionHistory.length > 0 && (
            <details style={{ marginBottom: 16 }}>
              <summary style={{ fontSize: 11, fontFamily: "var(--mono)", letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--ink-4)", cursor: "pointer", userSelect: "none" }}>
                Revision log ({revisionHistory.length})
              </summary>
              <div style={{ marginTop: 8, display: "flex", flexDirection: "column", gap: 8 }}>
                {revisionHistory.map((rev, i) => (
                  <div key={i} style={{ padding: "8px 10px", background: "var(--surface-2)", borderRadius: "var(--radius)", fontSize: 11.5, fontFamily: "var(--mono)", color: "var(--ink-3)" }}>
                    <div style={{ color: "var(--ink-4)", marginBottom: 4 }}>
                      {new Date(rev.ts).toLocaleString()}
                    </div>
                    {rev.comments.map((c, j) => (
                      <div key={j} style={{ marginTop: 2 }}>
                        {c.quote ? <span style={{ color: "var(--ink-4)" }}>«{c.quote.slice(0, 60)}{c.quote.length > 60 ? "…" : ""}» — </span> : null}
                        {c.body}
                      </div>
                    ))}
                  </div>
                ))}
              </div>
            </details>
          )}

          <div style={{ fontSize: 10, fontFamily: "var(--mono)", letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 12 }}>
            {comments.length === 0 ? "Inline comments" : `${comments.length} inline comment${comments.length !== 1 ? "s" : ""}`}
          </div>

          {comments.length === 0 && inputCardTop === null && (
            <div style={{
              padding: "12px 14px", borderRadius: "var(--radius-lg)",
              background: "var(--bg-2)", border: "1px solid var(--line)",
              fontSize: 13, color: "var(--ink-3)", lineHeight: 1.6,
            }}>
              <div style={{ fontWeight: 500, color: "var(--ink-2)", marginBottom: 4 }}>Refine this draft</div>
              Highlight any text and click <strong>Add comment</strong> to request a change. You can ask for edits, rewrites, or deeper research on any section. The report structure is set at this stage, but the content within each section can be fully revised.
            </div>
          )}

          {/* New comment input — positioned at selection Y */}
          {inputCardTop !== null && pendingQuote && (
            <div style={{
              position: "absolute", top: inputCardTop, left: 0, right: 0,
              background: "var(--surface)", border: "1px solid var(--accent)",
              borderRadius: "var(--radius-lg)", padding: 14, zIndex: 10,
              boxShadow: "0 2px 14px rgba(0,0,0,0.10)",
            }}>
              <div style={{ fontSize: 11, fontFamily: "var(--mono)", color: "var(--ink-3)", marginBottom: 8, lineHeight: 1.4, borderLeft: "2px solid #c9a84c", paddingLeft: 8, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                «{pendingQuote.quote.slice(0, 60)}{pendingQuote.quote.length > 60 ? "…" : ""}»
              </div>
              <textarea
                ref={inputRef}
                value={commentInput}
                onChange={e => setCommentInput(e.target.value)}
                onKeyDown={e => {
                  if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) submitComment();
                  if (e.key === "Escape") cancelInput();
                }}
                placeholder="Describe the change or ask for deeper research on this section. (⌘↵ to submit)"
                rows={3}
                style={{
                  width: "100%", resize: "none", boxSizing: "border-box",
                  border: "1px solid var(--line-2)", borderRadius: "var(--radius)",
                  padding: "8px 10px", fontSize: 13, fontFamily: "var(--sans)",
                  background: "var(--bg)", color: "var(--ink)",
                }}
              />
              <div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 8 }}>
                <button className="btn ghost sm" onClick={cancelInput}>Cancel</button>
                <button className="btn primary sm" disabled={!commentInput.trim()} onClick={submitComment}>Add</button>
              </div>
            </div>
          )}

          {/* Comment cards — each at its docY offset */}
          {comments.map((c, i) => {
            const isApplied = appliedIds.has(c.id);
            const isStale = unmatchedIds.has(c.id);
            const isRejected = rejectedIds.has(c.id);
            return (
              <div key={c.id} style={{
                position: "absolute", top: cardTops[i], left: 0, right: 0,
                background: isApplied ? "var(--bg)" : "var(--bg-2)",
                border: `1px solid ${isRejected ? "#e0a0a0" : "var(--line)"}`,
                borderRadius: "var(--radius-lg)", padding: "12px 14px",
                opacity: isApplied ? 0.55 : 1,
                transition: "opacity 0.2s",
              }}>
                <div style={{ display: "flex", alignItems: "flex-start", gap: 8, marginBottom: 6 }}>
                  <div style={{ fontSize: 11, fontFamily: "var(--mono)", color: "var(--ink-3)", lineHeight: 1.4, borderLeft: "2px solid #c9a84c", paddingLeft: 8, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1 }}>
                    «{c.quote.slice(0, 72)}{c.quote.length > 72 ? "…" : ""}»
                  </div>
                  {isApplied && <span style={{ fontSize: 11, color: "#28a745", fontFamily: "var(--mono)", flexShrink: 0 }}>✓ applied</span>}
                  {isRejected && <span style={{ fontSize: 11, color: "#c0392b", fontFamily: "var(--mono)", flexShrink: 0 }}>✗ not applied</span>}
                </div>
                {isStale && !isApplied && !isRejected && (
                  <div style={{ fontSize: 11, color: "#c9a84c", fontFamily: "var(--mono)", marginBottom: 6 }}>
                    ⚠ quoted text not found in current draft
                  </div>
                )}
                {isRejected && writingRejectionReasons[c.id] && (
                  <div style={{ fontSize: 12, color: "#92400e", lineHeight: 1.5, marginBottom: 8, padding: "8px 10px", background: "#fffbeb", borderRadius: "var(--radius)", border: "1px solid #fbbf24" }}>
                    <strong style={{ display: "block", marginBottom: 4 }}>Change not applied</strong>
                    {writingRejectionReasons[c.id]}
                  </div>
                )}
                {isRejected && !writingRejectionReasons[c.id] && (
                  <div style={{ fontSize: 12, color: "#c0392b", lineHeight: 1.5, marginBottom: 8, padding: "8px 10px", background: "#fdf0f0", borderRadius: "var(--radius)", border: "1px solid #e0a0a0" }}>
                    New sections cannot be added at this stage. The report structure was set during the outline. To go deeper on an existing section, highlight text within it and describe what to expand.
                  </div>
                )}
                {!isRejected && <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.5, marginBottom: 8 }}>{c.body}</div>}
                <button onClick={() => removeComment(c.id)} disabled={revising} style={{ background: "none", border: "none", cursor: revising ? "default" : "pointer", fontSize: 11, color: "var(--ink-4)", padding: 0, fontFamily: "var(--mono)" }}>
                  Remove
                </button>
              </div>
            );
          })}

          {/* Apply — sits just below the last comment card; only shown when there are non-rejected comments */}
          {(() => { const pending = comments.filter(c => !rejectedIds.has(c.id)); return pending.length > 0 && (
            <div style={{ position: "absolute", top: containerHeight, left: 0, right: 0, paddingTop: 12 }}>
              <button className="btn primary" style={{ width: "100%", justifyContent: "center" }} disabled={revising} onClick={applyChanges}>
                {`Apply ${pending.length} change${pending.length !== 1 ? "s" : ""}`}
              </button>
            </div>
          ); })()}

          </>)}
        </div>
      </div>

      <style>{`
        .draft-content { max-width: 820px; overflow-wrap: break-word; word-break: break-word; }
        .draft-content h1 { font-family: var(--serif); font-size: 26px; font-weight: 500; margin: 0 0 8px; letter-spacing: -0.015em; scroll-margin-top: 80px; }
        .draft-content h2 { font-family: var(--serif); font-size: 19px; font-weight: 500; margin: 40px 0 12px; padding-top: 28px; border-top: 1px solid var(--line); letter-spacing: -0.01em; scroll-margin-top: 80px; }
        .draft-content h3 { font-family: var(--serif); font-size: 16px; font-weight: 600; margin: 24px 0 8px; scroll-margin-top: 80px; }
        .draft-content p { margin: 0 0 14px; }
        .draft-content ul, .draft-content ol { padding-left: 20px; margin: 0 0 14px; }
        .draft-content li { margin-bottom: 4px; }
        .draft-content strong { color: var(--ink); font-weight: 600; }
        .draft-content em { font-style: italic; color: var(--ink-2); }
        .draft-content blockquote { border-left: 3px solid var(--line-2); margin: 0 0 14px; padding: 4px 16px; color: var(--ink-3); font-style: italic; }
        .draft-content code { font-family: var(--mono); font-size: 12px; background: var(--bg-2); padding: 1px 5px; border-radius: 3px; }
        .draft-content table { border-collapse: collapse; font-size: 13px; display: block; overflow-x: auto; }
        .draft-content th { background: var(--bg-2); padding: 8px 12px; text-align: left; border: 1px solid var(--line-2); font-weight: 600; white-space: nowrap; word-break: normal; }
        .draft-content td { padding: 8px 12px; border: 1px solid var(--line); white-space: nowrap; word-break: normal; }
        .draft-content tr:nth-child(even) td { background: var(--bg); }
        mark.inline-comment-mark { background: rgba(201,168,76,0.30); color: inherit; border-radius: 2px; padding: 0; display: inline; cursor: default; }
        .toc-btn:hover { color: var(--ink-2) !important; }
        @keyframes phasePulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
        .phase-dot-active { animation: phasePulse 1.2s ease-in-out infinite; }
        .diff-add { background: #d4edda; border-left: 3px solid #28a745; padding: 4px 10px; margin: 4px 0; border-radius: 0 4px 4px 0; }
        .diff-remove { background: #f8d7da; border-left: 3px solid #dc3545; padding: 4px 10px; margin: 4px 0; border-radius: 0 4px 4px 0; opacity: 0.8; }
        .diff-remove * { text-decoration: line-through; }
        .report-preview { font-family: Georgia, 'Times New Roman', serif !important; color: #1a1a1a; max-width: 680px; overflow-wrap: break-word; word-break: break-word; }
        .report-preview h1 { font-family: Georgia, 'Times New Roman', serif; font-size: 2rem; font-weight: 700; text-align: center; margin: 2.5rem 0 0.5rem; border-bottom: 2px solid #333; padding-bottom: 0.5rem; page-break-before: always; }
        .report-preview h1:first-child, .report-preview > *:first-child h1 { page-break-before: avoid; }
        .report-preview h2 { font-family: Georgia, 'Times New Roman', serif; font-size: 1.35rem; font-weight: 600; color: #1a1a1a; margin: 2rem 0 0.6rem; border-top: none; border-bottom: 1px solid #ccc; padding-bottom: 0.25rem; }
        .report-preview h3 { font-family: Georgia, 'Times New Roman', serif; font-size: 1.05rem; font-weight: 600; font-style: italic; margin: 1.5rem 0 0.4rem; color: #2a2a2a; }
        .report-preview p { font-family: Georgia, 'Times New Roman', serif; line-height: 1.8; margin: 0.9rem 0; text-align: justify; }
        .report-preview table { border-collapse: collapse; font-size: 0.88rem; display: block; overflow-x: auto; }
        .report-preview th { background: #1a1a1a; color: #fff; padding: 9px 13px; text-align: left; font-weight: 600; border: none; white-space: nowrap; word-break: normal; }
        .report-preview td { padding: 8px 13px; border-bottom: 1px solid #dde4ec; white-space: nowrap; word-break: normal; }
        .report-preview tr:nth-child(even) td { background: #f5f5f5; }
        .report-preview blockquote { border-left: 4px solid #999; margin: 1.2rem 0; padding: 0.6rem 1.2rem; color: #555; font-style: italic; background: #f7f7f7; }
        .report-preview hr { border: none; border-top: 1px solid #d0d7e0; margin: 2rem 0; }
        .report-preview ul, .report-preview ol { padding-left: 1.6rem; margin: 0.8rem 0 1rem; }
        .report-preview li { font-family: Georgia, 'Times New Roman', serif; line-height: 1.7; margin-bottom: 0.3rem; font-size: 0.93rem; }
        .report-preview a { color: #1a1a1a; }
        .report-preview code { font-family: var(--mono); font-size: 0.85em; background: #eef1f5; padding: 1px 5px; border-radius: 3px; }
      `}</style>

      <div className="actionbar">
        <div className="left">
          {revising ? "Agents are revising your draft…"
            : applyDone ? "Draft updated. Review changes above."
            : comments.filter(c => !rejectedIds.has(c.id)).length > 0 ? `${comments.filter(c => !rejectedIds.has(c.id)).length} comment${comments.filter(c => !rejectedIds.has(c.id)).length > 1 ? "s" : ""} pending: apply before approving.`
            : "Highlight text to refine the draft, or approve to generate your final files."}
        </div>
        <div className="right">
          <button className="btn ghost" onClick={goBack}><Icon name="arrowLeft" size={14} /> Back</button>
          <button className="btn primary lg" disabled={loading || revising || comments.filter(c => !rejectedIds.has(c.id)).length > 0} onClick={() => { setPreviousDraft(null); setShowDiff(false); goNext(); }}
            title={revising ? "Revision in progress…" : comments.length > 0 ? "Apply all comments before approving" : ""}>
            {loading ? "Exporting files…" : "Approve & export"}
            {!loading && <Icon name="arrowRight" size={14} />}
          </button>
        </div>
      </div>
    </div>
  );
}

window.StepDraft = StepDraft;
