// SPEC: PJ2026-010401 Web工作台 draft-2026-06-18-r1; PJ2026-0104010803 唯一投影 draft-2026-06-19-p2-terminal-sealed-final-response.
// Responsibility: Workbench diagnostic visibility regression, including separation from sealed final response.

import type { Locator } from "@playwright/test";
import { expect, gotoWorkbench, saveScreenshot, test } from "../fixtures/test";
import { selectors, sessionTab } from "../fixtures/selectors";

test.describe("projection diagnostics visibility", () => {
  test.use({ scenarioId: "projection-degraded-diagnostics" });

  test("turn and trace projection blockers are visible without changing lifecycle", async ({ page }, testInfo) => {
    await gotoWorkbench(page, "/workbench/sessions/ses_projection_degraded");
    await expect(page.locator(sessionTab("ses_projection_degraded"))).toHaveAttribute("data-status", "running");
    await expect(page.locator(sessionTab("ses_projection_degraded"))).toHaveAttribute("data-projection-health", "degraded");
    const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`).last();
    await expect(card).toContainText("状态更新超时");
    const diagnostic = card.locator(".projection-diagnostic");
    await expectCompactDiagnostic(diagnostic, card, "trc_projection_degraded");
    await expect(diagnostic).not.toContainText("valuesPrinted");
    await expandDiagnostic(diagnostic);
    await expect(diagnostic).toContainText("agentrun_result_timeout");
    await expect(diagnostic).toContainText("trace_id");
    await expect(diagnostic).toContainText("trc_projection_degraded");
    await expect(diagnostic).toContainText("valuesPrinted");
    await expect(diagnostic).toContainText("false");
    await expect(card.locator(selectors.traceTimeline)).toHaveAttribute("data-projection-health", "degraded");
    await expect(card.locator(".trace-empty")).toContainText("状态更新超时");
    await expect(page.locator(selectors.commandSend)).toHaveAttribute("data-action", "cancel");
    await saveScreenshot(page, testInfo, "projection-degraded-visible");
  });
});

test.describe("SSE projection errors", () => {
  test.use({ scenarioId: "projection-sse-error" });

  test("workbench.error binds to the current message diagnostic", async ({ page }, testInfo) => {
    await gotoWorkbench(page, "/workbench/sessions/ses_projection_sse_error");
    const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`).last();
    await expect(card).toContainText("SSE 投影事件异常");
    const diagnostic = card.locator(".projection-diagnostic");
    await expectCompactDiagnostic(diagnostic, card, "trc_projection_sse_error");
    await expect(diagnostic).not.toContainText("req_e2e_workbench_sse_projection_error");
    await expandDiagnostic(diagnostic);
    await expect(diagnostic).toContainText("workbench_sse_projection_error");
    await expect(diagnostic).toContainText("req_e2e_workbench_sse_projection_error");
    await expect(diagnostic).toContainText("/v1/workbench/events");
    await expect(page.locator(sessionTab("ses_projection_sse_error"))).toHaveAttribute("data-projection-health", "degraded");
    await saveScreenshot(page, testInfo, "projection-sse-error-visible");
  });
});

test.describe("REST gap hydration errors", () => {
  test.use({ scenarioId: "trace-hydration-timeout-diagnostic" });

  test("trace hydration timeout is visible without terminal inference", async ({ page }, testInfo) => {
    await gotoWorkbench(page, "/workbench/sessions/ses_trace_hydration_timeout");
    const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="running"]`).last();
    await expect(card).toContainText("Trace 更新超时");
    const diagnostic = card.locator(".projection-diagnostic");
    await expectCompactDiagnostic(diagnostic, card, "33333333333333333333333333333333");
    await expect(diagnostic).not.toContainText("req_e2e_trace_hydration_timeout");
    await expect(diagnostic).not.toContainText("/v1/workbench/traces/trc_trace_hydration_timeout/events");
    await expandDiagnostic(diagnostic);
    await expect(diagnostic).toContainText("trace_hydration_timeout");
    await expect(diagnostic).toContainText("33333333333333333333333333333333");
    await expect(diagnostic).toContainText("req_e2e_trace_hydration_timeout");
    await expect(diagnostic).toContainText("/v1/workbench/traces/trc_trace_hydration_timeout/events");
    await expect(card).toHaveAttribute("data-status", "running");
    await expect(card.locator(selectors.traceTimeline)).toHaveAttribute("data-projection-health", "degraded");
    await expect(page.locator(selectors.commandSend)).toHaveAttribute("data-action", "cancel");
    await saveScreenshot(page, testInfo, "trace-hydration-timeout-visible");
  });
});

test.describe("sealed final response diagnostics separation", () => {
  test.use({ scenarioId: "sealed-final-response-diagnostics" });

  test("keeps completed final response visible without surfacing transient hydration diagnostics", async ({ page }, testInfo) => {
    await gotoWorkbench(page, "/workbench/sessions/ses_sealed_final_diag");
    const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`).last();
    const finalResponse = card.locator(".message-text").filter({ hasText: "已完成的 sealed final response 不应被诊断覆盖。" });
    await expect(finalResponse).toBeVisible();
    await expect(finalResponse).not.toContainText("Trace 更新超时");
    await expect(card.locator(".projection-diagnostic")).toHaveCount(0);
    await saveScreenshot(page, testInfo, "sealed-final-response-diagnostic-separate");
  });
});

async function expectCompactDiagnostic(diagnostic: Locator, container: Locator, traceId: string): Promise<void> {
  await expect(diagnostic).toContainText(`trace_id=${traceId}`);
  const toggle = diagnostic.getByRole("button", { name: "诊断详情" });
  await expect(toggle).toHaveAttribute("aria-expanded", "false");
  await expect(toggle).toBeVisible();
  const diagnosticBox = await diagnostic.boundingBox();
  const containerBox = await container.boundingBox();
  expect(diagnosticBox?.width ?? 0).toBeGreaterThan(0);
  if (containerBox && diagnosticBox) expect(diagnosticBox.width).toBeLessThan(containerBox.width - 12);
}

async function expandDiagnostic(diagnostic: Locator): Promise<void> {
  const toggle = diagnostic.getByRole("button", { name: "诊断详情" });
  await toggle.click();
  await expect(toggle).toHaveAttribute("aria-expanded", "true");
}

test.describe("trace detail final response separation", () => {
  test.use({ scenarioId: "trace-final-response-not-message-authority" });

  test("keeps trace finalResponse out of the main message projection", async ({ page }, testInfo) => {
    await gotoWorkbench(page, "/workbench/sessions/ses_trace_final_conflict");
    const card = page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`).last();
    const messageText = card.locator(".message-markdown.message-text");
    await expect(messageText).toContainText("messageProjection 的 sealed final response 才是主消息正文。");
    const traceResponse = await page.request.get("/v1/workbench/traces/trc_trace_final_conflict/events?limit=20");
    expect(traceResponse.status()).toBe(200);
    const tracePayload = await traceResponse.json();
    expect(tracePayload.finalResponse.text).toBe("TRACE_DETAIL_CONFLICT_SHOULD_NOT_REPLACE_MESSAGE");
    await page.waitForTimeout(500);
    await expect(messageText).toContainText("messageProjection 的 sealed final response 才是主消息正文。");
    await expect(messageText).not.toContainText("TRACE_DETAIL_CONFLICT_SHOULD_NOT_REPLACE_MESSAGE");
    await saveScreenshot(page, testInfo, "trace-final-response-not-message-authority");
  });
});
