// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first; PJ2026-010401080313 Workbench实时权威 draft-2026-07-14-p0-pure-kafka-authority.
// @vitest-environment jsdom

import { beforeEach, describe, expect, it } from "vitest";
import { mount } from "@vue/test-utils";
import { nextTick } from "vue";

import TraceTimeline from "./TraceTimeline.vue";

describe("TraceTimeline sequence authority", () => {
  beforeEach(() => {
    window.HWLAB_CLOUD_WEB_CONFIG = {
      displayTime: { timeZone: "Asia/Shanghai", locale: "zh-CN", label: "北京时间" }
    };
    window.localStorage.clear();
    globalThis.ResizeObserver = FakeResizeObserver as unknown as typeof ResizeObserver;
    Object.defineProperty(HTMLElement.prototype, "scrollTo", { configurable: true, value: () => undefined });
  });

  it("auto-opens a running Trace without persisting programmatic state", async () => {
    const storageKey = "trace-lifecycle.running";
    const wrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: true,
        storageKey,
        trace: liveTrace("running")
      }
    });
    await nextTick();

    expect(wrapper.get<HTMLDetailsElement>(".trace-disclosure").element.open).toBe(true);
    expect(window.localStorage.getItem(storageKey)).toBeNull();
    wrapper.unmount();
  });

  it("keeps an auto-opened Trace expanded when terminal policy has no collapse request", async () => {
    const wrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: true,
        storageKey: "trace-lifecycle.keep-open",
        trace: liveTrace("running")
      }
    });
    await nextTick();
    expect(wrapper.get<HTMLDetailsElement>(".trace-disclosure").element.open).toBe(true);

    await wrapper.setProps({ autoExpanded: null, trace: liveTrace("completed") });
    await nextTick();

    expect(wrapper.get<HTMLDetailsElement>(".trace-disclosure").element.open).toBe(true);
    wrapper.unmount();
  });

  it("collapses on an explicit terminal request while keeping a manual stored value authoritative", async () => {
    const storageKey = "trace-lifecycle.collapse";
    const wrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: true,
        storageKey,
        trace: liveTrace("running")
      }
    });
    await nextTick();
    expect(wrapper.get<HTMLDetailsElement>(".trace-disclosure").element.open).toBe(true);

    await wrapper.setProps({ autoExpanded: false, trace: liveTrace("completed") });
    await nextTick();
    expect(wrapper.get<HTMLDetailsElement>(".trace-disclosure").element.open).toBe(false);
    wrapper.unmount();

    window.localStorage.setItem(storageKey, "1");
    const storedWrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: false,
        storageKey,
        trace: liveTrace("completed")
      }
    });
    await nextTick();

    expect(storedWrapper.get<HTMLDetailsElement>(".trace-disclosure").element.open).toBe(true);
    storedWrapper.unmount();
  });

  it("renders sourceSeq-only live Kafka rows while terminal and does not label them projected", async () => {
    const wrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: true,
        trace: {
          traceId: "trc_live_timeline",
          eventSource: "hwlab-kafka-sse",
          status: "completed",
          eventCount: 2,
          events: [
            { sourceEventId: "evt_live_answer", runId: "run_live", sourceSeq: 4, source: "agentrun.kafka", type: "assistant", label: "agentrun:assistant:message", message: "live answer" },
            { sourceEventId: "evt_live_terminal", runId: "run_live", sourceSeq: 5, source: "agentrun.kafka", type: "result", label: "agentrun:terminal:completed", status: "completed", terminal: true }
          ]
        }
      }
    });
    await nextTick();

    const disclosure = wrapper.get(".trace-disclosure");
    expect(disclosure.attributes("data-event-count")).toBe("2");
    expect(Number(disclosure.attributes("data-readable-row-count"))).toBeGreaterThan(0);
    const rows = wrapper.findAll('[data-testid="trace-render-row"]');
    expect(rows.length).toBeGreaterThan(0);
    expect(rows[0]?.attributes("data-event-seq-authority")).toBe("source");
    expect(rows[0]?.attributes("data-event-seq")).toBe("4");
    expect(rows[0]?.attributes("data-projected-seq")).toBeUndefined();
    expect(wrapper.text()).toContain("live answer");
    expect(wrapper.text()).not.toContain("运行已完成，暂无可读 Trace");
    wrapper.unmount();
  });

  it("keeps durable Trace rows on projected sequence authority", async () => {
    const wrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: true,
        trace: {
          traceId: "trc_projected_timeline",
          eventSource: "trace-api",
          status: "running",
          eventCount: 1,
          events: [{ projectedSeq: 8, type: "assistant", label: "agentrun:assistant:message", message: "projected answer" }]
        }
      }
    });
    await nextTick();

    const row = wrapper.get('[data-testid="trace-render-row"]');
    expect(row.attributes("data-event-seq-authority")).toBe("projected");
    expect(row.attributes("data-event-seq")).toBe("8");
    expect(row.attributes("data-projected-seq")).toBe("8");
    wrapper.unmount();
  });

  it("renders semantic infrastructure retry as a readable row", async () => {
    const wrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: true,
        trace: {
          traceId: "trc_retry_timeline",
          eventSource: "hwlab-kafka-sse",
          status: "running",
          eventCount: 1,
          events: [{
            sourceEventId: "evt_retry_timeline",
            runId: "run_retry",
            sourceSeq: 5,
            source: "agentrun.kafka",
            type: "backend",
            status: "retrying",
            label: "agentrun:backend:runner-startup-retry-scheduled",
            failureDomain: "infrastructure",
            failureComponent: "runner-image-pull",
            failureCode: "runner-image-registry-unreachable",
            summary: "Runner 镜像仓库暂时不可达",
            retryPhase: "retryScheduled",
            retryAttempt: 2,
            retryMaxAttempts: 5,
            retryBackoffMs: 8_000,
            nextRetryAt: "2026-07-20T10:00:09.000Z",
            createdAt: "2026-07-20T10:00:01.000Z"
          }]
        }
      }
    });
    await nextTick();

    expect(wrapper.text()).toContain("基础设施故障");
    const row = wrapper.findAll('[data-testid="trace-render-row"]').find((item) => item.text().includes("基础设施故障"));
    expect(row).toBeDefined();
    if (!row) throw new Error("semantic retry row was not rendered");
    expect(row.attributes("data-event-status")).toBe("warn");
    expect(row.text()).toContain("基础设施故障");
    expect(row.text()).toContain("第 2/5 次重试已安排");
    expect(row.text()).toContain("Runner 镜像仓库暂时不可达");
    expect(row.text()).toContain("runner-image-registry-unreachable");
    wrapper.unmount();
  });

  it("does not classify normal Git source preparation as an infrastructure failure", async () => {
    const wrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: true,
        trace: {
          traceId: "trc_git_fetch_timeline",
          eventSource: "hwlab-kafka-sse",
          status: "completed",
          eventCount: 2,
          events: [{
            sourceEventId: "evt_git_fetch_started",
            runId: "run_git_fetch",
            sourceSeq: 5,
            source: "agentrun.kafka",
            type: "backend",
            status: "running",
            label: "agentrun:backend:runner-source-fetch-started",
            failureDomain: "infrastructure",
            failureComponent: "git-mirror",
            failureCode: "git-mirror-fetch-in-progress",
            summary: "正在从 Git mirror 获取 Runner 源码",
            retryPhase: "initialAttempt",
            retryAttempt: 1,
            retryMaxAttempts: 3,
            createdAt: "2026-07-20T10:00:01.000Z"
          }, {
            sourceEventId: "evt_git_fetch_completed",
            runId: "run_git_fetch",
            sourceSeq: 6,
            source: "agentrun.kafka",
            type: "backend",
            status: "running",
            label: "agentrun:backend:runner-source-fetch-completed",
            failureDomain: "infrastructure",
            failureComponent: "git-mirror",
            failureCode: "git-mirror-fetch-completed",
            summary: "Git mirror 源码获取完成",
            retryPhase: "initialAttempt",
            retryAttempt: 1,
            retryMaxAttempts: 3,
            createdAt: "2026-07-20T10:00:03.000Z"
          }]
        }
      }
    });
    await nextTick();

    expect(wrapper.text()).not.toContain("基础设施故障");
    expect(wrapper.findAll('[data-row-id^="event:semantic-retry:"]')).toHaveLength(0);
    const preparationRows = wrapper.findAll('[data-row-id^="event:runtime-preparation:"]');
    expect(preparationRows).toHaveLength(2);
    expect(preparationRows[0].attributes("data-event-seq")).toBe("5");
    expect(preparationRows[0].text()).toContain("运行环境准备 · 源码获取中");
    expect(preparationRows[0].text()).toContain("第 1/3 次源码获取进行中");
    expect(preparationRows[1].attributes("data-event-seq")).toBe("6");
    expect(preparationRows[1].text()).toContain("运行环境准备 · 源码获取完成");
    expect(preparationRows[1].text()).toContain("正在启动 Runner");
    wrapper.unmount();
  });

  it("keeps a tool row stable while projecting visual and accessibility state", async () => {
    const wrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: true,
        trace: toolTrace("running")
      }
    });
    await nextTick();

    const runningRow = wrapper.get('[data-row-id="tool:tool_bun_test"]');
    const stableElement = runningRow.element;
    const runningDetails = runningRow.get(".trace-tool-details");
    const runningSummary = runningDetails.get("summary");
    expect(runningRow.attributes("data-tool-state")).toBe("running");
    expect(runningRow.attributes("data-event-status")).toBe("warn");
    expect(runningDetails.attributes("aria-busy")).toBe("true");
    expect(runningSummary.attributes("aria-label")).toContain("工具调用中：");
    expect(runningSummary.text()).toContain("bun");
    expect(runningSummary.text()).not.toContain("agentrun:tool");
    expect(runningSummary.attributes("title")).toBe(runningSummary.attributes("aria-label"));
    expect(runningSummary.text()).not.toMatch(/\b(?:ok|fail|run|running)\b|total=|工具调用中/u);

    await wrapper.setProps({ trace: toolTrace("completed") });
    await nextTick();

    const completedRow = wrapper.get('[data-row-id="tool:tool_bun_test"]');
    const completedDetails = completedRow.get(".trace-tool-details");
    const completedSummary = completedDetails.get("summary");
    expect(completedRow.element).toBe(stableElement);
    expect(completedRow.attributes("data-tool-state")).toBe("success");
    expect(completedRow.attributes("data-event-status")).toBe("ok");
    expect(completedDetails.attributes("aria-busy")).toBeUndefined();
    expect(completedSummary.attributes("aria-label")).toContain("工具调用成功：");
    expect(completedSummary.text()).not.toContain("agentrun:tool");
    expect(completedSummary.get("code").text()).toMatch(/^\d{2}:\d{2}:\d{2}$/u);
    expect(completedSummary.get(".trace-tool-command-preview").text()).toBe("bun test tool-state");
    expect(completedSummary.text()).not.toMatch(/\b(?:ok|fail|run|running)\b|total=|duration=|exit=|工具调用成功/u);
    expect(completedDetails.text()).not.toContain("1 test passed");

    completedDetails.element.open = true;
    await completedDetails.trigger("toggle");
    await nextTick();

    expect(completedDetails.text()).toContain("1 test passed");
    wrapper.unmount();
  });

  it("bounds renderer input and DOM rows for a long Kafka Trace", async () => {
    const events = Array.from({ length: 1_000 }, (_, index) => ({
      sourceEventId: `evt_long_${index + 1}`,
      runId: "run_long_trace",
      sourceSeq: index + 1,
      source: "agentrun.kafka",
      type: "assistant",
      label: "agentrun:assistant:message",
      message: `long trace event ${index + 1}`
    }));
    const wrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: true,
        trace: {
          traceId: "trc_long_timeline",
          eventSource: "hwlab-kafka-sse",
          status: "running",
          eventCount: events.length,
          events
        }
      }
    });
    await nextTick();

    const disclosure = wrapper.get(".trace-disclosure");
    expect(disclosure.attributes("data-event-count")).toBe("1000");
    expect(disclosure.attributes("data-render-input-count")).toBe("240");
    expect(disclosure.attributes("data-row-windowed")).toBe("true");
    const rows = wrapper.findAll('[data-testid="trace-render-row"]');
    expect(rows).toHaveLength(120);
    expect(Number(rows[0]?.attributes("data-event-seq"))).toBeGreaterThanOrEqual(881);
    expect(rows.at(-1)?.attributes("data-event-seq")).toBe("1000");
    expect(wrapper.text()).toContain("共 1000 events");
    wrapper.unmount();
  });

  it("does not mount folded tool output bodies for a dense live Trace", async () => {
    const events = Array.from({ length: 120 }, (_, index) => ({
      sourceEventId: `evt_tool_dense_${index + 1}`,
      runId: "run_tool_dense",
      sourceSeq: index + 1,
      source: "agentrun.kafka",
      type: "tool_call",
      label: "agentrun:tool:completed",
      status: "completed",
      toolName: "commandExecution",
      itemId: `tool_dense_${index + 1}`,
      command: `rg dense-${index + 1}`,
      stdoutSummary: `dense output ${index + 1}`,
      exitCode: 0
    }));
    const wrapper = mount(TraceTimeline, {
      props: {
        autoExpanded: true,
        trace: {
          traceId: "trc_tool_dense",
          eventSource: "hwlab-kafka-sse",
          status: "running",
          eventCount: events.length,
          events
        }
      }
    });
    await nextTick();

    const tools = wrapper.findAll(".trace-tool-details");
    expect(tools).toHaveLength(120);
    expect(wrapper.findAll(".trace-tool-details .trace-row-body")).toHaveLength(0);

    const lastTool = tools.at(-1);
    expect(lastTool).toBeDefined();
    if (!lastTool) throw new Error("last tool row was not rendered");
    lastTool.element.open = true;
    await lastTool.trigger("toggle");
    await nextTick();

    expect(wrapper.findAll(".trace-tool-details .trace-row-body")).toHaveLength(1);
    expect(lastTool.text()).toContain("dense output 120");
    wrapper.unmount();
  });
});

function liveTrace(status: "running" | "completed") {
  return {
    traceId: "trc_live_lifecycle",
    eventSource: "hwlab-kafka-sse" as const,
    status,
    eventCount: 1,
    events: [{ sourceEventId: "evt_live_lifecycle", runId: "run_live", sourceSeq: 1, source: "agentrun.kafka", type: "assistant", label: "agentrun:assistant:message", message: "live answer" }]
  };
}

function toolTrace(status: "running" | "completed") {
  const completed = status === "completed";
  return {
    traceId: "trc_tool_state",
    eventSource: "hwlab-kafka-sse" as const,
    status,
    eventCount: 1,
    events: [{
      sourceEventId: completed ? "evt_tool_completed" : "evt_tool_running",
      runId: "run_tool_state",
      sourceSeq: completed ? 2 : 1,
      source: "agentrun.kafka",
      type: "tool_call",
      label: completed ? "agentrun:tool:completed" : "agentrun:tool:started",
      status,
      toolName: "commandExecution",
      itemId: "tool_bun_test",
      command: "bun test tool-state",
      createdAt: completed ? "2026-07-10T11:00:02.000Z" : "2026-07-10T11:00:01.000Z",
      ...(completed ? { durationMs: 1234, exitCode: 0, stdoutSummary: "1 test passed" } : {})
    }]
  };
}

class FakeResizeObserver {
  observe(): void {}
  unobserve(): void {}
  disconnect(): void {}
}
