#!/usr/bin/env node
"use strict";

const assert = require("node:assert");
const { chromium } = require("playwright");
const {
  BASE_URL,
  cleanupDocuments,
  createTempDocument,
  ensureAuthenticated,
  openDocument,
  openSectionView,
  renameDocument,
  requestJson,
  UI_TIMEOUT_MS,
} = require("./tree-shell-smoke-helpers");

async function main() {
  const suffix = Date.now().toString(36);
  const originalTitle = `TEST-HERMES-AI-title-original-${suffix}`;
  const nextTitle = `TEST-HERMES-AI-title-updated-${suffix}`;
  const createdIds = [];
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({ viewport: { width: 1280, height: 900 } });
  const page = await context.newPage();

  try {
    await ensureAuthenticated(page, context.request);
    const target = await createTempDocument(context.request);
    createdIds.push(target.documentId);
    await renameDocument(context.request, target.workspaceId, target.documentId, originalTitle);

    const common = {
      workspaceId: target.workspaceId,
      documentId: target.documentId,
      actorId: "smoke-user",
      sessionId: `mnote_smoke_title_${suffix}`,
      runId: `run_smoke_title_${suffix}`,
      traceId: `trace_smoke_title_${suffix}`,
      capabilityScope: ["page.write"],
      dryRun: false,
    };

    const dryRunTitle = `TEST-HERMES-AI-title-dry-${suffix}`;
    const titleDryRun = await requestJson(context.request, "/api/hermes/tools/mnote/call", {
      method: "POST",
      headers: { "x-mnote-actor-id": "smoke-user" },
      data: {
        ...common,
        dryRun: true,
        toolName: "mnote.page.update_title",
        toolCallId: `call_title_dry_${suffix}`,
        idempotencyKey: `idem_title_dry_${suffix}`,
        args: { title: dryRunTitle },
      },
    });
    assert.equal(titleDryRun.result.dryRun, true, "标题 dryRun 不应写入");
    const metaAfterDryRun = await requestJson(
      context.request,
      `/api/documents/meta?documentId=${encodeURIComponent(target.documentId)}&workspaceId=${encodeURIComponent(target.workspaceId)}`,
      { method: "GET" },
    );
    assert(!JSON.stringify(metaAfterDryRun).includes(dryRunTitle), "标题 dryRun 后 meta 不应变化");

    const titleResult = await requestJson(context.request, "/api/hermes/tools/mnote/call", {
      method: "POST",
      headers: { "x-mnote-actor-id": "smoke-user" },
      data: {
        ...common,
        toolName: "mnote.page.update_title",
        toolCallId: `call_title_${suffix}`,
        idempotencyKey: `idem_title_${suffix}`,
        args: { title: nextTitle },
      },
    });
    assert.equal(titleResult.result.commandName, "page.head.updateTitle", "标题必须走 page.head.updateTitle");
    assert.equal(titleResult.audit.commandId, titleResult.result.commandId, "标题 audit commandId 必须指向 Rust commandId");
    const titleRetry = await requestJson(context.request, "/api/hermes/tools/mnote/call", {
      method: "POST",
      headers: { "x-mnote-actor-id": "smoke-user" },
      data: {
        ...common,
        toolName: "mnote.page.update_title",
        toolCallId: `call_title_${suffix}`,
        idempotencyKey: `idem_title_${suffix}`,
        args: { title: nextTitle },
      },
    });
    assert.equal(
      titleRetry.result.commandId,
      titleResult.result.commandId,
      "标题同一 idempotencyKey 重试必须返回同一个 commandId",
    );

    const optionsResult = await requestJson(context.request, "/api/hermes/tools/mnote/call", {
      method: "POST",
      headers: { "x-mnote-actor-id": "smoke-user" },
      data: {
        ...common,
        toolName: "mnote.page.update_options",
        toolCallId: `call_options_${suffix}`,
        idempotencyKey: `idem_options_${suffix}`,
        args: { options: { wideLayout: true, smallText: true, pageFont: "serif" } },
      },
    });
    assert.equal(
      optionsResult.result.commandName,
      "page.layout.updateOptions",
      "页面设置必须走 page.layout.updateOptions",
    );
    assert.equal(
      optionsResult.audit.commandId,
      optionsResult.result.commandId,
      "页面设置 audit commandId 必须指向 Rust commandId",
    );
    assert(
      Array.isArray(optionsResult.result.ignoredOptions) &&
        optionsResult.result.ignoredOptions.includes("pageFont"),
      "planned/ui_only 页面设置字段必须明确返回 ignoredOptions",
    );
    assert(
      Array.isArray(optionsResult.result.warnings) &&
        optionsResult.result.warnings.some((warning) => warning && warning.code === "page_option_not_wired"),
      "planned/ui_only 页面设置字段必须明确返回 warning",
    );

    const meta = await requestJson(
      context.request,
      `/api/documents/meta?documentId=${encodeURIComponent(target.documentId)}&workspaceId=${encodeURIComponent(target.workspaceId)}`,
      { method: "GET" },
    );
    assert(JSON.stringify(meta).includes(nextTitle), "meta 未读到 AI 更新后的标题");
    assert(JSON.stringify(meta).includes("wide"), "meta 未读到页面设置更新结果");

    await openDocument(page, target.workspaceId, target.documentId);
    await openSectionView(page);
    await page.waitForFunction(
      (title) => {
        const input = document.querySelector('[data-page-title-input="true"]');
        const current = document.querySelector('[data-page-title-current="true"]');
        return (input && input.value === title) || (current && current.textContent.includes(title));
      },
      nextTitle,
      { timeout: UI_TIMEOUT_MS },
    );
    await page.waitForFunction(
      ({ documentId, title }) => {
        const selectors = [
          `#sidebar-tree-root .tree-row[data-node-id="${CSS.escape(documentId)}"] .tree-link-title`,
          `.tree-row[data-shell-mode="page"][data-node-id="${CSS.escape(documentId)}"] > .tree-link > .tree-link-title`,
        ];
        return selectors.some((selector) => {
          const node = document.querySelector(selector);
          return node && (node.textContent || "").includes(title);
        });
      },
      { documentId: target.documentId, title: nextTitle },
      { timeout: UI_TIMEOUT_MS },
    );
    const uiState = await page.evaluate((documentId) => {
      const shell = document.querySelector(".document-shell");
      const sessionKey = document.documentElement.getAttribute("data-mnote-page-ai-session-key") || "";
      return {
        titleInput: document.querySelector('[data-page-title-input="true"]')?.value || "",
        currentTitle: document.querySelector('[data-page-title-current="true"]')?.textContent || "",
        sidebarTitle:
          document.querySelector(`#sidebar-tree-root .tree-row[data-node-id="${CSS.escape(documentId)}"] .tree-link-title`)
            ?.textContent || "",
        wideLayout: shell?.getAttribute("data-page-wide-layout") || "",
        smallText: shell?.getAttribute("data-page-small-text") || "",
        pageAiSessionOwner: document.documentElement.getAttribute("data-mnote-page-ai-session-owner") || "",
        pageAiSessionKey: sessionKey,
        pageAiSessionStorage: sessionKey && window.localStorage ? window.localStorage.getItem(sessionKey) || "" : "",
      };
    }, target.documentId);
    assert(
      uiState.titleInput === nextTitle || uiState.currentTitle.includes(nextTitle),
      `刷新后页面 UI 未显示 AI 更新标题: ${JSON.stringify(uiState)}`,
    );
    assert(uiState.sidebarTitle.includes(nextTitle), `sidebar 未显示 AI 更新标题: ${JSON.stringify(uiState)}`);
    assert.equal(uiState.wideLayout, "true", "刷新后 document-shell 未应用 wideLayout");
    assert.equal(uiState.smallText, "true", "刷新后 document-shell 未应用 smallText");

    await page.locator('[data-testid="wolai-floating-ai"]').click({ timeout: UI_TIMEOUT_MS });
    await page.waitForFunction(
      () =>
        document.documentElement.getAttribute("data-mnote-page-ai-session-owner") === "hermes" &&
        Boolean(document.documentElement.getAttribute("data-mnote-page-ai-session-key")),
      undefined,
      { timeout: UI_TIMEOUT_MS },
    );
    const aiSessionState = await page.evaluate(() => {
      const key = document.documentElement.getAttribute("data-mnote-page-ai-session-key") || "";
      return {
        owner: document.documentElement.getAttribute("data-mnote-page-ai-session-owner") || "",
        key,
        stored: key && window.localStorage ? window.localStorage.getItem(key) || "" : "",
        pageTitle:
          document.querySelector('[data-page-title-input="true"]')?.value ||
          document.querySelector('[data-page-title-current="true"]')?.textContent ||
          "",
      };
    });
    assert.equal(aiSessionState.owner, "hermes", "页面 AI session owner 必须是 Hermes");
    const storedSessionState = JSON.parse(aiSessionState.stored || "{}");
    assert(storedSessionState.activeSessionId, "mnote 本地只应保存 Hermes activeSessionId");
    assert(!aiSessionState.stored.includes(nextTitle), "mnote 本地 AI session 状态不应保存或驱动页面标题真相");
    assert(aiSessionState.pageTitle.includes(nextTitle), "页面标题真相必须仍来自页面 UI / Page Aggregate");

    console.log(
      JSON.stringify(
        {
          ok: true,
          baseUrl: BASE_URL,
          documentId: target.documentId,
          workspaceId: target.workspaceId,
          title: nextTitle,
          titleCommand: titleResult.result.commandName,
          optionsCommand: optionsResult.result.commandName,
          ignoredOptions: optionsResult.result.ignoredOptions,
          warningCodes: optionsResult.result.warnings.map((warning) => warning.code),
          uiState,
          aiSessionState,
        },
        null,
        2,
      ),
    );
  } finally {
    await cleanupDocuments(context.request, createdIds).catch(() => undefined);
    await context.close().catch(() => undefined);
    await browser.close().catch(() => undefined);
  }
}

main().catch((error) => {
  console.error(error instanceof Error ? error.stack || error.message : String(error));
  process.exit(1);
});
