import assert from "node:assert/strict";
import { test } from "bun:test";

import { shouldAutoReplayTrace } from "./ConversationPanel";
import type { ChatMessage } from "../../types/domain";

const baseAgentMessage: ChatMessage = {
  id: "msg_agent",
  role: "agent",
  title: "Code Agent 回复",
  text: "done",
  status: "idle" as ChatMessage["status"],
  createdAt: "2026-06-04T12:13:54.661Z",
  traceId: "trc_issue849",
  runnerTrace: {
    traceId: "trc_issue849",
    status: "completed",
    eventCount: 49,
    eventsCompacted: false,
    fullTraceLoaded: false
  }
};

test("conversation panel auto replays compact historical traces", () => {
  assert.equal(shouldAutoReplayTrace(baseAgentMessage), true);
  assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, runnerTrace: { ...baseAgentMessage.runnerTrace, fullTraceLoaded: true, events: [] } }), true);
});

test("conversation panel does not auto replay loaded, empty, or running traces", () => {
  assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, runnerTrace: { ...baseAgentMessage.runnerTrace, events: [{ seq: 1, label: "assistant:message" }] } }), false);
  assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, runnerTrace: { ...baseAgentMessage.runnerTrace, eventCount: 0 } }), false);
  assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, status: "running" }), false);
  assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, traceReplayStatus: "完整 trace 已回放：49 个原始 event" }), false);
  assert.equal(shouldAutoReplayTrace({ ...baseAgentMessage, runnerTrace: { ...baseAgentMessage.runnerTrace, fullTraceLoaded: true, events: [{ seq: 1, label: "assistant:message" }] } }), false);
});

test("conversation panel does not delay markdown rendering behind first-screen fallbacks", async () => {
  const source = await Bun.file(new URL("./ConversationPanel.tsx", import.meta.url)).text();

  assert.doesNotMatch(source, /MessageMarkdown upgradeDelayMs=/u);
  assert.doesNotMatch(source, /markdownUpgradeDelayMs/u);
});

test("conversation panel asks trace panels to collapse after final response appears", async () => {
  const source = await Bun.file(new URL("./ConversationPanel.tsx", import.meta.url)).text();

  assert.match(source, /collapseWhenFinalResponse=\{hasFinalResponse\}/u);
});

test("conversation panel does not consume replay opportunity before timer fires", async () => {
  const source = await Bun.file(new URL("./ConversationPanel.tsx", import.meta.url)).text();
  const effectStart = source.indexOf("const replayRequestedTraceRef = useRef<string | null>(null);");
  const timerStart = source.indexOf("const timer = window.setTimeout", effectStart);
  const markRequested = source.indexOf("replayRequestedTraceRef.current = traceId", effectStart);
  const replayCall = source.indexOf("replayHandlerRef.current();", effectStart);

  assert.ok(effectStart > 0, "replay guard ref exists");
  assert.ok(timerStart > effectStart, "auto replay uses delayed timer");
  assert.ok(markRequested > timerStart, "route changes that cancel the timer must not mark replay as already requested");
  assert.ok(replayCall > markRequested, "replay is marked requested only immediately before dispatching replay");
});

test("conversation panel keeps replay timer stable across parent rerenders", async () => {
  const source = await Bun.file(new URL("./ConversationPanel.tsx", import.meta.url)).text();
  const handlerRef = source.indexOf("const replayHandlerRef = useRef(onReplayTrace);");
  const effectStart = source.indexOf("const timer = window.setTimeout", handlerRef);
  const effectEnd = source.indexOf("if (message.role === \"agent\")", effectStart);
  const effectSource = source.slice(effectStart, effectEnd);

  assert.ok(handlerRef > 0, "onReplayTrace is kept behind a stable ref");
  assert.match(effectSource, /replayHandlerRef\.current\(\);/u);
  assert.match(effectSource, /\}, \[shouldReplayTrace, traceId\]\);/u);
  assert.doesNotMatch(effectSource, /\bonReplayTrace\b/u);
});
