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

/**
 * Smoke: 保存失败时编辑器保留内容 + 显示可解释错误。
 *
 * 测试路径：
 * 1. 打开当前主文档页（使用 /api/page-body/write 保存链路）
 * 2. 拦截 /api/page-body/write 返回模拟服务端错误
 * 3. 修改正文触发保存 → 请求被拦截 → 服务端返回 error
 * 4. 验证编辑器内容没有被清除
 * 5. 验证 runtime editor status 显示为 "error"
 *
 * 验收条件：
 * - ProseMirror 仍保留用户输入的内容
 * - `data-runtime-editor-status` 为 "error"
 */

const assert = require("node:assert");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");

const { chromium } = require("playwright");

const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000);
const OUTPUT_DIR = path.join(process.cwd(), "tmp", "task486-local-markdown-save-error-editor");
const RESULT_PATH = path.join(OUTPUT_DIR, "result.json");
const CHROMIUM_EXECUTABLE_PATH = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH
  || ["/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/snap/bin/chromium", "/usr/bin/chromium"]
    .find((candidate) => fs.existsSync(candidate));

function fileUrl(localPath) {
  return `file://${localPath}`;
}

function localMdDocumentId(relativePath) {
  return `local-md:${relativePath.replaceAll("/", "~2F")}`;
}

function documentUrl(root, relativePath) {
  const url = new URL(`${BASE_URL}/documents/${encodeURIComponent(localMdDocumentId(relativePath))}`);
  url.searchParams.set("sourceKind", "local_folder");
  url.searchParams.set("rootUri", fileUrl(root));
  return url.toString();
}

async function run() {
  fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task486-save-error-"));
  const relativePath = "save-error-test.md";
  const initialText = "保存失败测试初始内容";
  const editText = "用户新输入的内容——保存失败后应保留";

  // 准备 markdown 文件 + workspace manifest
  writeWorkspaceManifest(root);
  fs.writeFileSync(path.join(root, relativePath), [
    "---",
    "title: Save Error Test",
    "---",
    "",
    initialText,
    "",
  ].join("\n"), "utf8");

  const browser = await chromium.launch({
    headless: true,
    ...(CHROMIUM_EXECUTABLE_PATH ? { executablePath: CHROMIUM_EXECUTABLE_PATH } : {}),
  });
  const context = await browser.newContext({
    viewport: { width: 1280, height: 860 },
    extraHTTPHeaders: {
      "x-mnote-actor-id": "user_real",
      "x-mnote-actor-type": "user",
    },
  });
  const page = await context.newPage();
  const debug = { root, relativePath };

  try {
    // 拦截当前主文档页保存链路，返回模拟服务端错误。
    await page.route("**/api/page-body/write", async (route) => {
      const request = route.request();
      if (request.method() === "POST") {
        debug.interceptedSaveUrl = request.url();
        debug.interceptedSaveBody = request.postData();
        await route.fulfill({
          status: 500,
          headers: { "content-type": "application/json" },
          body: JSON.stringify({
            ok: false,
            code: "simulated_save_error",
            message: "故意的保存失败测试——编辑器不应丢内容",
          }),
        });
      } else {
        await route.continue();
      }
    });

    const url = documentUrl(root, relativePath);
    debug.url = url;
    await page.goto(url, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    const editorRoot = page.locator('[data-testid="mnote-leptos-tiptap-island-editor-root"]').first();
    await editorRoot.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });

    // 验证初始文本已出现
    const editor = page.locator('.document-pane[data-pane-role="primary"] .editor-surface .ProseMirror[contenteditable="true"]').first();
    await editor.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await page.waitForFunction(
      (expected) => {
        const node = document.querySelector('.document-pane[data-pane-role="primary"] .editor-surface .ProseMirror');
        return (node?.textContent || "").includes(expected);
      },
      initialText,
      { timeout: UI_TIMEOUT_MS },
    );
    const initialValue = await editor.innerText({ timeout: UI_TIMEOUT_MS });
    debug.initialValue = initialValue;
    assert(
      initialValue.includes(initialText),
      `编辑器初始内容应包含 "${initialText}"，实际: ${initialValue.substring(0, 100)}`,
    );

    await editor.click({ timeout: UI_TIMEOUT_MS });
    await page.keyboard.press(process.platform === "darwin" ? "Meta+A" : "Control+A");
    await page.keyboard.type(editText, { delay: 8 });
    await page.waitForFunction(
      (expected) => {
        const node = document.querySelector('.document-pane[data-pane-role="primary"] .editor-surface .ProseMirror');
        return (node?.textContent || "").includes(expected);
      },
      editText,
      { timeout: UI_TIMEOUT_MS },
    );
    debug.editedValue = await editor.innerText({ timeout: UI_TIMEOUT_MS });
    assert(debug.editedValue.includes(editText), "编辑器应包含新输入内容");

    // 等待 auto-save 触发 + 错误状态出现
    await page.waitForFunction(
      () => {
        const el = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
        return el?.getAttribute("data-runtime-editor-status") === "error";
      },
      { timeout: UI_TIMEOUT_MS },
    );
    const saveStatusAfterError = await page.evaluate(() => {
      const el = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
      return el ? el.getAttribute("data-runtime-editor-status") : "not-found";
    });
    debug.saveStatusAfterError = saveStatusAfterError;
    assert.strictEqual(saveStatusAfterError, "error", `保存状态应显示 error，实际: "${saveStatusAfterError}"`);

    // 关键断言：编辑器内容没有被错误清除
    const contentAfterError = await editor.innerText({ timeout: UI_TIMEOUT_MS });
    debug.contentAfterError = contentAfterError;
    assert(
      contentAfterError.includes(editText),
      `保存失败后编辑器内容应保留 "${editText}"，实际: "${contentAfterError}"`,
    );

    // 验证拦截确实发生了
    assert(
      debug.interceptedSaveUrl,
      "应触发至少一次 /api/page-body/write 请求",
    );
    assert(
      debug.interceptedSaveBody && debug.interceptedSaveBody.includes(editText),
      "保存请求体应包含编辑后的内容",
    );

    debug.ok = true;
    console.log(JSON.stringify({ status: "passed", debug }));
  } catch (error) {
    debug.error = error instanceof Error ? error.message : String(error);
    debug.ok = false;
    console.error(JSON.stringify({ status: "failed", debug }));
    try {
      const statusEl = await page.evaluate(() => {
        const el = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
        return el ? el.getAttribute("data-runtime-editor-status") : "not-found";
      }).catch(() => "evaluate-failed");
      debug.finalSaveStatus = statusEl;
    } catch (_) { /* ignore */ }
  } finally {
    fs.writeFileSync(RESULT_PATH, JSON.stringify(debug, null, 2), "utf8");
    await browser.close();
    fs.rmSync(root, { recursive: true, force: true });
  }

  if (!debug.ok) {
    throw new Error(`smoke failed: ${debug.error || "unknown"}`);
  }
}

function writeWorkspaceManifest(root) {
  const manifestDir = path.join(root, ".mnote");
  fs.mkdirSync(manifestDir, { recursive: true });
  fs.writeFileSync(
    path.join(manifestDir, "workspace.json"),
    JSON.stringify({
      workspaceId: `local-ws:user_real:task486`,
      actorId: "user_real",
      sourceKind: "local_folder",
      ownerId: "user_real",
      capabilities: ["local_files", "markdown_edit"],
    }, null, 2),
    "utf8",
  );
}

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