// SPEC: PJ2026-0106050514 Workbench实时运行面 draft-2026-06-30-p0-1297-spec-first; PJ2026-0104010803 Workbench唯一投影 draft-2026-06-18-p0-unique-projection.
// Responsibility: Regression tests for pure Workbench trace/message/projection merge helpers before Pinia integration.

import assert from "node:assert/strict";
import test from "node:test";

import type { AgentChatResultResponse, ChatMessage } from "../src/types/index.ts";
import { clearRunnerTraceTransientDiagnostics, mergeTerminalResultTrace, messageHasTerminalResponse, messageStatusPatchForTerminalMerge, messageTimingPatchForMerge, messageTimingSealPatchForProjectionMerge, nonBlockingProjection, normalizeAgentError, projectionFromResult, shouldClearCompletedTurnDiagnostics, terminalAuthorityMessageFromTurnResult, terminalMessagePatchFromTurnResult, terminalMessageTimingPatchForNormalize } from "../src/stores/workbench-message-projection-runtime.ts";

test("terminal result merge preserves runner trace evidence and timing", () => {
  const previous: ChatMessage["runnerTrace"] = {
    traceId: "trc_1",
    sessionId: "ses_1",
    events: [{ id: "evt_old", type: "tool" } as never],
    eventCount: 1,
    projection: { projectionStatus: "blocked", projectionHealth: "degraded", blocker: { code: "old" } },
    updatedAt: "2026-01-01T00:00:00.000Z"
  };
  const result = {
    traceId: "trc_1",
    sessionId: "ses_1",
    status: "completed",
    terminal: true,
    events: [{ id: "evt_new", type: "finish" }],
    eventCount: 1,
    startedAt: "2026-01-01T00:00:00.000Z",
    finishedAt: "2026-01-01T00:00:05.000Z",
    durationMs: 5000,
    projectionStatus: "caught-up",
    projectionHealth: "healthy",
    agentRun: { runId: "run_1", backendProfile: "codex-api" }
  } as unknown as AgentChatResultResponse;

  const merged = mergeTerminalResultTrace(previous, result);

  assert.equal(merged.traceId, "trc_1");
  assert.equal(merged.sessionId, "ses_1");
  assert.equal(merged.eventCount, 1);
  assert.equal(merged.events?.[0]?.id, "evt_old");
  assert.equal(merged.terminalEvidence, undefined);
  assert.equal(merged.timing?.durationMs, 5000);
  assert.equal((merged.agentRun as { runId?: string } | undefined)?.runId, "run_1");
  assert.equal(merged.projectionStatus, "caught-up");
});

test("message projection runtime clears only non-blocking completed diagnostics", () => {
  const error = normalizeAgentError({ message: "provider failed", diagnostic: { code: "provider_error", source: "provider" } });
  assert.equal(error?.message, "provider failed");
  assert.equal(error?.diagnostic?.code, "provider_error");
  assert.equal(shouldClearCompletedTurnDiagnostics("completed", error), false);
  assert.equal(shouldClearCompletedTurnDiagnostics("completed", null), true);

  const projection = projectionFromResult({ projectionStatus: "caught-up", projectionHealth: "healthy" } as AgentChatResultResponse);
  assert.equal(nonBlockingProjection(projection)?.projectionStatus, "caught-up");
  assert.equal(nonBlockingProjection({ projectionStatus: "blocked", projectionHealth: "degraded", blocker: { code: "read_model_gap" } }), null);

  const cleared = clearRunnerTraceTransientDiagnostics({ traceId: "trc_1", error, projection, projectionStatus: "caught-up", projectionHealth: "healthy", staleMs: 10, blocker: { code: "old" } });
  assert.equal(cleared.error, undefined);
  assert.equal(cleared.projection, null);
  assert.equal(cleared.blocker, null);
});

test("message timing and terminal status patches stay independent from store state", () => {
  const message = {
    role: "agent",
    status: "running",
    startedAt: "2026-01-01T00:00:00.000Z",
    timing: { startedAt: "2026-01-01T00:00:00.000Z", valuesRedacted: true }
  } as ChatMessage;
  const result = {
    status: "completed",
    terminal: true,
    startedAt: "2026-01-01T00:00:00.000Z",
    finishedAt: "2026-01-01T00:00:03.500Z"
  };

  const timingPatch = messageTimingPatchForMerge(message, result);
  assert.equal(timingPatch.durationMs, 3500);
  assert.equal(timingPatch.finishedAt, "2026-01-01T00:00:03.500Z");
  assert.deepEqual(messageStatusPatchForTerminalMerge(message, "completed", true), { status: "completed" });

  const terminalPatch = terminalMessageTimingPatchForNormalize(result);
  assert.equal(terminalPatch.durationMs, 3500);
});

test("terminal turn result seals completed final response body", () => {
  const restored = {
    role: "agent",
    status: "running",
    text: "",
    traceAutoLifecycle: "running",
    traceId: "trc_unit_final",
    runnerTrace: { traceId: "trc_unit_final", status: "running" }
  } as ChatMessage;

  const terminal = terminalMessagePatchFromTurnResult(restored, {
    traceId: "trc_unit_final",
    status: "completed",
    terminal: true,
    finalResponse: { text: "final answer" },
    startedAt: "2026-01-01T00:00:00.000Z",
    finishedAt: "2026-01-01T00:00:04.000Z",
    durationMs: 4000
  } as AgentChatResultResponse);

  assert.equal(terminal?.status, "completed");
  assert.equal(terminal?.traceAutoLifecycle, "terminal");
  assert.equal(terminal?.text, "final answer");
  assert.equal((terminal?.finalResponse as { text?: string } | undefined)?.text, "final answer");
});

test("terminal turn authority can project final response without a session message page row", () => {
  const terminal = terminalAuthorityMessageFromTurnResult({
    traceId: "trc_direct_terminal",
    sessionId: "ses_direct_terminal",
    threadId: "thr_direct_terminal",
    status: "completed",
    terminal: true,
    finalResponse: { text: "direct final body" },
    startedAt: "2026-07-02T13:18:40.000Z",
    finishedAt: "2026-07-02T13:18:47.000Z"
  } as AgentChatResultResponse);

  assert.equal(terminal?.id, "msg_trc_direct_terminal_terminal_authority");
  assert.equal(terminal?.status, "completed");
  assert.equal(terminal?.traceAutoLifecycle, "terminal");
  assert.equal(terminal?.text, "direct final body");
  assert.equal(messageHasTerminalResponse(terminal), true);
});

test("terminal turn authority does not synthesize an empty completed message", () => {
  const terminal = terminalAuthorityMessageFromTurnResult({
    traceId: "trc_direct_terminal_empty",
    status: "completed",
    terminal: true,
    terminalSealBlocked: true,
    waitingFor: "final_response"
  } as AgentChatResultResponse);

  assert.equal(terminal, null);
});

test("terminal turn result accepts authoritative assistant trace final", () => {
  const restored = {
    role: "agent",
    status: "running",
    text: "",
    traceId: "trc_trace_final",
    runnerTrace: { traceId: "trc_trace_final", status: "running" }
  } as ChatMessage;

  const terminal = terminalMessagePatchFromTurnResult(restored, {
    traceId: "trc_trace_final",
    status: "completed",
    terminal: true,
    runnerTrace: {
      traceId: "trc_trace_final",
      status: "completed",
      events: [
        { seq: 1, type: "assistant_message", status: "running", message: "progress only" },
        { seq: 2, type: "assistant_message", status: "completed", message: "final from trace", replyAuthority: true, final: true }
      ]
    }
  } as unknown as AgentChatResultResponse);

  assert.equal(terminal?.status, "completed");
  assert.equal(terminal?.text, "final from trace");
  assert.equal((terminal?.finalResponse as { text?: string } | undefined)?.text, "final from trace");
});

test("terminal failure does not synthesize completed final response from progress text", () => {
  const restored = {
    role: "agent",
    status: "running",
    text: "",
    traceId: "trc_failed_progress",
    runnerTrace: { traceId: "trc_failed_progress", status: "running" }
  } as ChatMessage;

  const terminal = terminalMessagePatchFromTurnResult(restored, {
    traceId: "trc_failed_progress",
    status: "failed",
    terminal: true,
    runnerTrace: {
      traceId: "trc_failed_progress",
      status: "failed",
      events: [{ seq: 1, type: "assistant_message", status: "running", message: "progress only" }]
    },
    error: { message: "provider failed" }
  } as unknown as AgentChatResultResponse);

  assert.equal(terminal?.status, "failed");
  assert.equal(terminal?.text, undefined);
  assert.equal(terminal?.finalResponse, undefined);
  assert.equal(terminal?.error?.message, "provider failed");
});

test("terminal seal blocks stale running snapshots after session restore", () => {
  const sealed = {
    role: "agent",
    status: "failed",
    text: "provider stream disconnected",
    traceAutoLifecycle: "terminal",
    runnerTrace: { traceId: "trc_1", status: "failed" },
    timing: { startedAt: "2026-01-01T00:00:00.000Z", finishedAt: "2026-01-01T00:00:05.000Z", durationMs: 5000, valuesRedacted: true }
  } as ChatMessage;

  assert.deepEqual(messageStatusPatchForTerminalMerge(sealed, "running", false), { status: "failed", traceAutoLifecycle: "terminal" });

  const restored = {
    role: "agent",
    status: "running",
    traceAutoLifecycle: "running",
    traceId: "trc_1",
    runnerTrace: { traceId: "trc_1", status: "running" },
    timing: { startedAt: "2026-01-01T00:00:00.000Z", valuesRedacted: true }
  } as ChatMessage;
  const terminal = terminalMessagePatchFromTurnResult(restored, {
    traceId: "trc_1",
    status: "failed",
    terminal: true,
    startedAt: "2026-01-01T00:00:00.000Z",
    finishedAt: "2026-01-01T00:00:04.000Z",
    durationMs: 4000,
    error: { message: "provider stream disconnected" }
  } as AgentChatResultResponse);

  assert.equal(terminal?.status, "failed");
  assert.equal(terminal?.traceAutoLifecycle, "terminal");
  assert.equal(terminal?.durationMs, 4000);
  assert.equal(terminal?.error?.message, "provider stream disconnected");
});

test("terminal projection page seal preserves final response object", () => {
  const previous = {
    id: "msg_terminal_projection_previous",
    role: "agent",
    title: "Code Agent",
    status: "completed",
    createdAt: "2026-01-01T00:00:00.000Z",
    text: "final answer",
    finalResponse: { text: "final answer", status: "completed", sealed: true },
    traceAutoLifecycle: "terminal",
    timing: { startedAt: "2026-01-01T00:00:00.000Z", finishedAt: "2026-01-01T00:00:05.000Z", durationMs: 5000, valuesRedacted: true }
  } as ChatMessage;
  const incoming = {
    id: "msg_terminal_projection_incoming",
    role: "agent",
    title: "Code Agent",
    status: "running",
    createdAt: "2026-01-01T00:00:00.000Z",
    text: "",
    traceAutoLifecycle: "running",
    timing: { startedAt: "2026-01-01T00:00:00.000Z", valuesRedacted: true }
  } as ChatMessage;

  const patch = messageTimingSealPatchForProjectionMerge(incoming, previous);

  assert.equal(patch.status, "completed");
  assert.equal(patch.traceAutoLifecycle, "terminal");
  assert.equal(patch.text, "final answer");
  assert.deepEqual(patch.finalResponse, { text: "final answer", status: "completed", sealed: true });
  assert.equal(patch.durationMs, 5000);
});
