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

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

const TASK = "task168-mindmap-put-validator-smoke";
const OUTPUT_DIR = path.join(process.cwd(), "tmp", TASK);
const RESULT_PATH = path.join(OUTPUT_DIR, "result.json");
const BAD_TEXT_PATTERN = /ArgumentValidationError|domainEventHint|domainEventPlan|streamDeltaHint|command_failed|502 Bad Gateway/i;

async function writeResult(payload) {
  await fs.mkdir(OUTPUT_DIR, { recursive: true });
  await fs.writeFile(RESULT_PATH, `${JSON.stringify({ ...payload, resultPath: RESULT_PATH }, null, 2)}\n`, "utf8");
}

async function screenshot(page, name) {
  const file = path.join(OUTPUT_DIR, `${name}.png`);
  await page.screenshot({ path: file, fullPage: true });
  return file;
}

function attachMindmapNetworkCapture(page, label, records) {
  page.on("response", async (response) => {
    const url = response.url();
    if (!url.includes("/api/mindmap/") && !url.includes("/mindmap/") && !url.includes("/api/documents/save")) {
      return;
    }
    const request = response.request();
    const method = request.method();
    const status = response.status();
    let responseText = null;
    if (method !== "GET" || status >= 400) {
      responseText = await response.text().catch((error) => `<<read_response_failed:${error.message}>>`);
    }
    records.push({
      type: "response",
      label,
      method,
      url,
      status,
      statusText: response.statusText(),
      requestBody: request.postData() || null,
      responseText: responseText ? responseText.slice(0, 3000) : null,
    });
  });
  page.on("requestfailed", (request) => {
    const url = request.url();
    if (url.includes("/api/mindmap/") || url.includes("/mindmap/") || url.includes("/api/documents/save")) {
      records.push({
        type: "requestfailed",
        label,
        method: request.method(),
        url,
        failure: request.failure()?.errorText || null,
        requestBody: request.postData() || null,
      });
    }
  });
  page.on("console", (message) => {
    if (message.type() === "error") {
      const text = message.text();
      if (/mindmap|ArgumentValidationError|domainEvent|502|command_failed/i.test(text)) {
        records.push({
          type: "console",
          label,
          level: message.type(),
          text: text.slice(0, 2000),
        });
      }
    }
  });
}

async function insertMindmapThroughSlash(page) {
  const editor = page
    .locator('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror[contenteditable="true"]')
    .first();
  await editor.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await editor.click({ timeout: UI_TIMEOUT_MS });
  await page.keyboard.type("/");
  const item = page.getByTestId("slash-item-mindmap").first();
  await item.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await item.click({ timeout: UI_TIMEOUT_MS });
}

async function readMindmapState(page) {
  return page.evaluate(() => {
    const root = document.querySelector('[data-testid="mnote-mindmap-editor-root"]');
    const scene = document.querySelector('[data-testid="leptos-mindmap-island"]');
    const runtime = document.querySelector('[data-testid="simple-mind-map-runtime"]');
    const error = document.querySelector('[data-testid="leptos-mindmap-error"]');
    return {
      mindmapId: root instanceof HTMLElement ? root.dataset.mnoteMindmapId || null : null,
      runtimeReady: runtime instanceof HTMLElement ? runtime.dataset.runtimeReady === "true" : false,
      commandStatus: scene instanceof HTMLElement ? scene.dataset.commandStatus || null : null,
      lastSchemaAction: scene instanceof HTMLElement ? scene.dataset.lastSchemaAction || null : null,
      commandRuntimeError: scene instanceof HTMLElement ? scene.dataset.commandRuntimeError || null : null,
      commandRuntimeMessage: scene instanceof HTMLElement ? scene.dataset.commandRuntimeMessage || null : null,
      errorStage: error instanceof HTMLElement ? error.dataset.stage || null : null,
      errorText: error instanceof HTMLElement ? (error.textContent || "").slice(0, 3000) : null,
      bodyText: (document.body?.innerText || "").slice(0, 5000),
    };
  });
}

async function assertNoValidatorLeak(page, records, stage) {
  await page.waitForTimeout(500);
  const state = await readMindmapState(page);
  const badRecord = records.find((record) => {
    if (record.type === "response" && record.status >= 500) return true;
    return BAD_TEXT_PATTERN.test(`${record.responseText || ""}\n${record.text || ""}\n${record.failure || ""}`);
  });
  if (badRecord || BAD_TEXT_PATTERN.test(`${state.errorText || ""}\n${state.bodyText || ""}`)) {
    throw new Error(
      `${stage}:mindmap_validator_or_502_leak:${JSON.stringify(
        {
          state,
          badRecord,
          recentMindmapNetwork: records.slice(-12),
        },
        null,
        2,
      )}`,
    );
  }
  return state;
}

async function waitForMindmapReady(page, stage) {
  await page
    .waitForFunction(
      () => {
        const root = document.querySelector('[data-testid="mnote-mindmap-editor-root"]');
        const runtime = document.querySelector('[data-testid="simple-mind-map-runtime"]');
        const mindmapId = root instanceof HTMLElement ? root.dataset.mnoteMindmapId || null : null;
        const registry = window.__MNOTE_LEPTOS_MINDMAP_BRIDGES__ || {};
        return (
          root instanceof HTMLElement &&
          runtime instanceof HTMLElement &&
          runtime.dataset.runtimeReady === "true" &&
          Boolean(mindmapId) &&
          Boolean(registry[mindmapId])
        );
      },
      null,
      { timeout: UI_TIMEOUT_MS },
    )
    .catch((error) => {
      throw new Error(`${stage}:mindmap_not_ready:${error.message}`);
    });
}

async function waitForDocumentContentToIncludeMindmap(requestContext, documentId, workspaceId, mindmapId) {
  let lastText = "";
  for (let index = 0; index < 45; index += 1) {
    const response = await requestContext.fetch(
      `${BASE_URL}/api/documents/content?documentId=${encodeURIComponent(documentId)}&workspaceId=${encodeURIComponent(workspaceId)}`,
      { method: "GET", timeout: 10_000 },
    );
    lastText = await response.text();
    if (response.ok() && lastText.includes(mindmapId)) {
      try {
        return JSON.parse(lastText);
      } catch {
        return { raw: lastText.slice(0, 3000) };
      }
    }
    await new Promise((resolve) => setTimeout(resolve, 500));
  }
  throw new Error(
    `document_content_missing_mindmap:${JSON.stringify({
      documentId,
      workspaceId,
      mindmapId,
      lastText: lastText.slice(0, 5000),
    })}`,
  );
}

async function fetchMindmapProjection(requestContext, documentId, mindmapId) {
  const response = await requestContext.fetch(
    `${BASE_URL}/api/mindmap/${encodeURIComponent(documentId)}/${encodeURIComponent(mindmapId)}?view=simple_mind_map_scene&queryName=mindmap.simple_mind_map_scene.get`,
    { method: "GET", timeout: 10_000 },
  );
  const text = await response.text();
  assert(response.ok(), `mindmap_projection_fetch_failed:${response.status()}:${text.slice(0, 2000)}`);
  const payload = JSON.parse(text);
  return payload.result ?? payload;
}

function collectMindmapTexts(root) {
  const texts = [];
  const visit = (node) => {
    if (!node || typeof node !== "object") return;
    const data = node.data && typeof node.data === "object" ? node.data : {};
    if (typeof data.text === "string") texts.push(data.text);
    if (Array.isArray(node.children)) node.children.forEach(visit);
  };
  visit(root);
  return texts;
}

async function waitForProjectionText(requestContext, documentId, mindmapId, expectedText) {
  let lastProjection = null;
  for (let index = 0; index < 45; index += 1) {
    lastProjection = await fetchMindmapProjection(requestContext, documentId, mindmapId);
    const texts = collectMindmapTexts(lastProjection.root);
    if (texts.includes(expectedText)) {
      return { projection: lastProjection, texts };
    }
    await new Promise((resolve) => setTimeout(resolve, 500));
  }
  throw new Error(
    `mindmap_projection_missing_edited_text:${JSON.stringify({
      expectedText,
      texts: collectMindmapTexts(lastProjection?.root),
      projection: lastProjection,
    }).slice(0, 5000)}`,
  );
}

async function editTopicTextThroughRuntime(page, mindmapId, text) {
  const result = await page.evaluate(
    ({ mindmapId, text }) => {
      const scene = document.querySelector('[data-testid="leptos-mindmap-island"]');
      if (scene instanceof HTMLElement) {
        scene.dataset.lastDataChangeRefreshTriggered = "";
        scene.dataset.lastDataChangeDiffCommandCount = "";
        scene.dataset.commandStatus = "smoke-waiting-topic-edit";
      }
      const registry = window.__MNOTE_LEPTOS_MINDMAP_BRIDGES__ || {};
      const bridge = registry[mindmapId];
      const instance = bridge?.instance;
      const topicNode = instance?.renderer?.findNodeByUid?.("topic") || null;
      if (!topicNode || typeof topicNode !== "object") {
        return { ok: false, reason: "topic_node_missing" };
      }
      if (typeof topicNode.setData === "function") {
        topicNode.setData({ text });
      } else if (topicNode.data && typeof topicNode.data === "object") {
        topicNode.data.text = text;
      } else {
        return { ok: false, reason: "topic_node_not_mutable" };
      }
      const snapshot = typeof bridge.getSnapshot === "function" ? bridge.getSnapshot() : instance?.getData?.(true);
      return {
        ok: true,
        snapshotText: snapshot?.children?.[0]?.data?.text ?? null,
        commandStatus: scene instanceof HTMLElement ? scene.dataset.commandStatus || null : null,
        diffCommandCount: scene instanceof HTMLElement ? scene.dataset.lastDataChangeDiffCommandCount || null : null,
      };
    },
    { mindmapId, text },
  );
  assert(result.ok, `topic_runtime_edit_failed:${JSON.stringify(result)}`);
  await page.waitForFunction(
    ({ text }) => {
      const bodyText = document.body?.innerText || "";
      const scene = document.querySelector('[data-testid="leptos-mindmap-island"]');
      return (
        bodyText.includes(text) ||
        (scene instanceof HTMLElement && scene.dataset.commandStatus !== "smoke-waiting-topic-edit")
      );
    },
    { text },
    { timeout: UI_TIMEOUT_MS },
  );
  return result;
}

async function clickInsertChild(page) {
  const button = page.getByTestId("mindmap-schema-toolbar-action-insertChild");
  await button.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  const disabled = await button.evaluate((node) => node instanceof HTMLButtonElement && node.disabled);
  if (disabled) {
    await page.locator('[data-testid="simple-mind-map-runtime"] .smm-node').first().click({ timeout: UI_TIMEOUT_MS });
    await page.waitForFunction(() => {
      const button = document.querySelector('[data-testid="mindmap-schema-toolbar-action-insertChild"]');
      return button instanceof HTMLButtonElement && !button.disabled;
    }, null, { timeout: UI_TIMEOUT_MS });
  }
  await page.evaluate(() => {
    const scene = document.querySelector('[data-testid="leptos-mindmap-island"]');
    if (scene instanceof HTMLElement) {
      scene.dataset.commandStatus = "smoke-waiting";
      delete scene.dataset.lastSchemaAction;
    }
  });
  await button.click({ timeout: UI_TIMEOUT_MS });
  await page.waitForFunction(() => {
    const scene = document.querySelector('[data-testid="leptos-mindmap-island"]');
    return scene instanceof HTMLElement && scene.dataset.lastSchemaAction === "insertChild" && scene.dataset.commandStatus === "success";
  }, null, { timeout: UI_TIMEOUT_MS });
}

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

  const browser = await chromium.launch({ headless: true });
  const contextA = await browser.newContext({ viewport: { width: 1440, height: 960 }, locale: "zh-CN" });
  const pageA = await contextA.newPage();
  const records = [];
  attachMindmapNetworkCapture(pageA, "browser-a", records);
  const screenshots = [];
  const createdIds = [];
  const result = {
    ok: false,
    task: TASK,
    baseUrl: BASE_URL,
    documentId: null,
    workspaceId: null,
    mindmapId: null,
    network: records,
    screenshots,
  };

  let contextB = null;
  let pageB = null;
  try {
    await ensureAuthenticated(pageA, contextA.request);
    const doc = await createTempDocument(contextA.request, null);
    createdIds.push(doc.documentId);
    result.documentId = doc.documentId;
    result.workspaceId = doc.workspaceId;
    await renameDocument(contextA.request, doc.workspaceId, doc.documentId, `task168-mindmap-${Date.now().toString().slice(-6)}`);

    await openDocument(pageA, doc.workspaceId, doc.documentId);
    await insertMindmapThroughSlash(pageA);
    await waitForMindmapReady(pageA, "browser-a-initial");
    const initialState = await assertNoValidatorLeak(pageA, records, "browser-a-initial");
    result.mindmapId = initialState.mindmapId;

    await clickInsertChild(pageA);
    const afterCommand = await assertNoValidatorLeak(pageA, records, "browser-a-insert-child");
    assert(afterCommand.commandStatus === "success", `insert_child_not_success:${JSON.stringify(afterCommand)}`);
    screenshots.push(await screenshot(pageA, "01-after-insert-child"));
    result.documentContentAfterInsert = await waitForDocumentContentToIncludeMindmap(
      contextA.request,
      doc.documentId,
      doc.workspaceId,
      result.mindmapId,
    );

    const editedTopicText = `二级节点-SMOKE-${Date.now().toString().slice(-6)}`;
    result.topicEditAttempt = await editTopicTextThroughRuntime(pageA, result.mindmapId, editedTopicText);
    const afterTopicEdit = await assertNoValidatorLeak(pageA, records, "browser-a-topic-edit");
    result.afterTopicEdit = afterTopicEdit;
    result.topicProjectionAfterEdit = await waitForProjectionText(
      contextA.request,
      doc.documentId,
      result.mindmapId,
      editedTopicText,
    );
    screenshots.push(await screenshot(pageA, "02-after-topic-edit"));

    contextB = await browser.newContext({ viewport: { width: 1440, height: 960 }, locale: "zh-CN" });
    pageB = await contextB.newPage();
    attachMindmapNetworkCapture(pageB, "browser-b", records);
    await ensureAuthenticated(pageB, contextB.request);
    await openDocument(pageB, doc.workspaceId, doc.documentId);
    await waitForMindmapReady(pageB, "browser-b-reopen");
    const secondState = await assertNoValidatorLeak(pageB, records, "browser-b-reopen");
    assert(
      secondState.bodyText.includes(editedTopicText),
      `second_browser_missing_edited_topic_text:${JSON.stringify({
        expected: editedTopicText,
        secondState,
      })}`,
    );
    assert(
      secondState.mindmapId === result.mindmapId,
      `second_browser_mindmap_id_mismatch:${JSON.stringify({ expected: result.mindmapId, actual: secondState.mindmapId })}`,
    );
    screenshots.push(await screenshot(pageB, "03-second-browser-visible"));

    result.ok = true;
    result.initialState = initialState;
    result.afterCommand = afterCommand;
    result.secondState = secondState;
    await writeResult(result);
  } catch (error) {
    result.error = error instanceof Error ? error.stack || error.message : String(error);
    screenshots.push(await screenshot(pageA, "99-failure").catch(() => null));
    if (pageB) {
      screenshots.push(await screenshot(pageB, "99-failure-browser-b").catch(() => null));
    }
    await writeResult(result);
    throw error;
  } finally {
    if (contextB) {
      await contextB.close().catch(() => undefined);
    }
    await cleanupDocuments(contextA.request, createdIds).catch(() => undefined);
    await contextA.close().catch(() => undefined);
    await browser.close();
  }
}

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