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

/**
 * task-editor-delta-channel-smoke.js
 *
 * 验证 Phase B — 编辑器增量 delta channel：
 * - blockDelta 出现在 Hermes 写工具响应中
 * - blockDelta 可被前端拦截并推送给 leptos-tiptap 编辑器
 * - 编辑器通过 CustomEvent 接收到 delta 后可应用（链式调用 ProseMirror）
 *
 * 前提：运行中的 mnote-web (3000)、已登录浏览器、测试文档
 */

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

const OUT_DIR = path.join(process.cwd(), "tmp", "editor-delta-channel-smoke");
const SUFFIX = `edc-${Date.now().toString(36)}`;

async function callTool(request, payload) {
  return requestJson(request, "/api/hermes/tools/mnote/call", {
    method: "POST",
    headers: { "x-mnote-actor-id": payload.actorId || "smoke-user" },
    data: payload,
  });
}

async function main() {
  await fs.mkdir(OUT_DIR, { recursive: true });

  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
  const page = await context.newPage();
  const request = page.request;

  const report = { ok: false, suffix: SUFFIX, checks: [], errors: [] };
  let target = null;

  try {
    // ── 1. 准备测试文档 ──
    target = await createTempDocument(request, SUFFIX, {
      workspaceName: `ws-editor-delta-${SUFFIX}`,
      documentTitle: `测试-Delta-${SUFFIX}`,
      content: [
        { type: "p", children: [{ text: `段落A ${SUFFIX}` }] },
        { type: "p", children: [{ text: `段落B ${SUFFIX}` }] },
        { type: "p", children: [{ text: `段落C ${SUFFIX}` }] },
      ],
    });
    report.documentId = target.documentId;
    report.workspaceId = target.workspaceId;
    console.log(`文档创建: ${target.documentId}`);

    // ── 2. 打开文档页 ──
    await ensureAuthenticated(page);
    await openDocument(page, target.documentId, target.workspaceId);
    await page.waitForTimeout(3000);

    // ── 3. 注入 CustomEvent 监听器 ──
    // 在浏览器中注册 block-delta 监听器，用于验证 blockDelta 推送链
    const receivedDeltas = [];
    await page.exposeFunction("__smoke_record_delta", (json) => {
      try {
        receivedDeltas.push(typeof json === "string" ? JSON.parse(json) : json);
      } catch (e) {
        console.error("delta parse error:", e);
      }
    });
    await page.evaluate(() => {
      window.addEventListener(
        "mnote:editor:block-delta",
        (event) => {
          const detail = event.detail;
          window.__smoke_record_delta(detail);
        },
        { once: false },
      );
    });

    // ── 4. 调用 block.replace，验证响应包含 blockDelta ──
    const fetchRes = await callTool(request, {
      toolName: "mnote.doc.fetch",
      workspaceId: target.workspaceId,
      documentId: target.documentId,
      actorId: "smoke-user",
      sessionId: `sess_fetch_${SUFFIX}`,
      runId: `run_fetch_${SUFFIX}`,
      toolCallId: `call_fetch_${SUFFIX}`,
      traceId: `trace_fetch_${SUFFIX}`,
      capabilityScope: ["page.read"],
      args: { scope: "full", detail: "with_ids", maxBlocks: 20 },
    });
    const blocks = fetchRes.body?.blockDocument?.blocks || [];
    assert.ok(blocks.length >= 3, `预期 ≥3 块，实际 ${blocks.length}`);
    const blockIdB = blocks[1].blockId;

    const replaceRes = await callTool(request, {
      toolName: "mnote.block.replace",
      workspaceId: target.workspaceId,
      documentId: target.documentId,
      actorId: "smoke-user",
      sessionId: `sess_replace_${SUFFIX}`,
      runId: `run_replace_${SUFFIX}`,
      toolCallId: `call_replace_${SUFFIX}`,
      traceId: `trace_replace_${SUFFIX}`,
      idempotencyKey: `idem_replace_${SUFFIX}`,
      capabilityScope: ["page.write", "page.read"],
      args: {
        blockId: blockIdB,
        content: [
          { type: "paragraph", content: [{ type: "text", text: `段落B已替换 ${SUFFIX}` }] },
        ],
        revision: fetchRes.body?.revision,
        conflictDetectionKey: fetchRes.body?.conflictDetectionKey,
        blockRevisionRef: blocks[1].revisionRef,
      },
    });

    console.log("replace 响应 keys:", Object.keys(replaceRes).join(", "));
    const hasBlockDelta = replaceRes.hasOwnProperty("blockDelta");
    report.checks.push({
      name: "replace 响应包含 blockDelta",
      passed: hasBlockDelta,
      details: hasBlockDelta
        ? `blockDelta 包含 ${replaceRes.blockDelta?.operations?.length || 0} 条操作`
        : "响应中无 blockDelta 字段（可能 actor 未启用或未运行 mnote-web）",
    });

    if (hasBlockDelta) {
      const delta = replaceRes.blockDelta;
      report.deltaReceived = delta;

      // 验证 delta 结构
      assert.ok(delta.documentId, "delta 应包含 documentId");
      assert.ok(delta.revision > 0, "delta 应包含 revision");
      assert.ok(
        Array.isArray(delta.operations) && delta.operations.length > 0,
        "delta 应包含至少一条操作",
      );
      report.checks.push({ name: "delta 结构有效", passed: true });

      // ── 5. 在浏览器端主动触发 CustomEvent（模拟真实推送） ──
      await page.evaluate(
        (deltaJson) => {
          const event = new CustomEvent("mnote:editor:block-delta", {
            detail: deltaJson,
            bubbles: true,
          });
          window.dispatchEvent(event);
        },
        delta,
      );
      await page.waitForTimeout(1000);

      report.checks.push({
        name: "CustomEvent 成功分派到 window",
        passed: receivedDeltas.length > 0,
        details: `收到 ${receivedDeltas.length} 条 delta 事件`,
      });

      // ── 6. 验证编辑器内容已更新（通过回读 Convex） ──
      const readbackRes = await callTool(request, {
        toolName: "mnote.doc.fetch",
        workspaceId: target.workspaceId,
        documentId: target.documentId,
        actorId: "smoke-user",
        sessionId: `sess_readback_${SUFFIX}`,
        runId: `run_readback_${SUFFIX}`,
        toolCallId: `call_readback_${SUFFIX}`,
        traceId: `trace_readback_${SUFFIX}`,
        capabilityScope: ["page.read"],
        args: { scope: "full", detail: "with_ids", maxBlocks: 20 },
      });
      const finalBlocks = readbackRes.body?.blockDocument?.blocks || [];
      const hasReplaced = finalBlocks.some((b) => b.text?.includes("段落B已替换"));
      report.checks.push({
        name: "Convex 回读确认替换成功",
        passed: hasReplaced,
        details: finalBlocks.map((b) => b.text).join(" | "),
      });
    }

    report.ok = report.checks.every((c) => c.passed);
    report.passedCount = report.checks.filter((c) => c.passed).length;
    report.totalCount = report.checks.length;

    console.log(
      `\n${report.ok ? "✅" : "⚠️"} Phase B smoke: ${report.passedCount}/${report.totalCount}`,
    );

  } catch (err) {
    report.errors.push({ message: err.message, stack: err.stack });
    console.error("❌ Phase B smoke 失败:", err);
  } finally {
    await fs.writeFile(
      path.join(OUT_DIR, `${SUFFIX}.json`),
      JSON.stringify(report, null, 2),
    );
    console.log(`报告: ${OUT_DIR}/${SUFFIX}.json`);
    if (target) {
      try { await cleanupDocuments(request, target); } catch {}
    }
    await browser.close();
  }
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
