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

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

const OUT_DIR = path.join(process.cwd(), "tmp", "page-aggregate-options-sync-smoke");

async function saveDocumentContent(requestContext, workspaceId, documentId) {
  await requestJson(requestContext, "/api/documents/save", {
    method: "POST",
    data: {
      workspaceId,
      documentId,
      content: [
        {
          id: "h1",
          type: "heading",
          props: { level: 1 },
          content: [{ type: "text", text: "Page Aggregate Options Smoke" }],
        },
        {
          id: "p1",
          type: "paragraph",
          content: [{ type: "text", text: "Body for page aggregate options smoke." }],
        },
      ],
      blockCount: 2,
      snapshotCapturedAt: new Date().toISOString(),
    },
  });
}

async function fetchPageAggregate(requestContext, workspaceId, documentId) {
  const payload = await requestJson(
    requestContext,
    `/api/page-aggregate/${encodeURIComponent(documentId)}?workspaceId=${encodeURIComponent(workspaceId)}`,
    { method: "GET" },
  );
  assert.equal(payload.schema, "mnote.page_aggregate.v1", "Page Aggregate schema 应保持稳定");
  return payload.result;
}

async function openPageSettingsDialog(page) {
  const trigger = page.getByTestId("wolai-page-settings-trigger");
  await trigger.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await trigger.click({ timeout: UI_TIMEOUT_MS });
  await page.locator('[data-page-option-checkbox="wideLayout"]').waitFor({
    state: "visible",
    timeout: UI_TIMEOUT_MS,
  });
}

async function readRuntimePageOptions(page) {
  return page.evaluate(() => {
    const shell = document.querySelector(".document-shell");
    const editorRoot = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
    const editorSurface = editorRoot?.querySelector(".editor-surface");
    const editor = editorRoot?.querySelector(".ProseMirror");
    const firstParagraph = editorRoot?.querySelector(".ProseMirror p");
    return {
      htmlWide: document.documentElement.getAttribute("data-page-wide-layout"),
      htmlSmall: document.documentElement.getAttribute("data-page-small-text"),
      htmlDensity: document.documentElement.getAttribute("data-layout-density"),
      shellWide: shell instanceof HTMLElement ? shell.getAttribute("data-page-wide-layout") : null,
      shellSmall: shell instanceof HTMLElement ? shell.getAttribute("data-page-small-text") : null,
      shellDensity: shell instanceof HTMLElement ? shell.getAttribute("data-layout-density") : null,
      shellMaxWidth:
        shell instanceof HTMLElement ? shell.style.maxWidth || window.getComputedStyle(shell).maxWidth : null,
      editorRootWide: editorRoot instanceof HTMLElement ? editorRoot.getAttribute("data-page-wide-layout") : null,
      editorRootSmall: editorRoot instanceof HTMLElement ? editorRoot.getAttribute("data-page-small-text") : null,
      editorSurfaceDensity:
        editorSurface instanceof HTMLElement ? editorSurface.getAttribute("data-layout-density") : null,
      editorFontSize: editor instanceof HTMLElement ? window.getComputedStyle(editor).fontSize : null,
      paragraphMarginBottom:
        firstParagraph instanceof HTMLElement ? window.getComputedStyle(firstParagraph).marginBottom : null,
    };
  });
}

async function waitForOptionsResponse(page, documentId, mutate) {
  const responsePromise = page.waitForResponse(
    async (response) => {
      if (!response.url().includes("/api/documents/options") || response.request().method() !== "POST") {
        return false;
      }
      const payload = response.request().postDataJSON();
      return payload?.documentId === documentId && response.ok();
    },
    { timeout: UI_TIMEOUT_MS },
  );
  await mutate();
  const response = await responsePromise;
  const payload = await response.json();
  assert.equal(payload?.meta?.commandName, "page.layout.updateOptions", "页面设置写入必须走 page.layout.updateOptions");
  assert.equal(payload?.meta?.canonicalCommand, "page.layout.updateOptions", "页面设置 canonical command 必须稳定");
  return payload;
}

async function waitForAggregateOption(requestContext, workspaceId, documentId, assertOptions) {
  const deadline = Date.now() + UI_TIMEOUT_MS;
  let aggregate = null;
  while (Date.now() < deadline) {
    aggregate = await fetchPageAggregate(requestContext, workspaceId, documentId);
    const options = aggregate?.layout?.pageOptions ?? {};
    if (assertOptions(options)) {
      return { aggregate, options };
    }
    await new Promise((resolve) => setTimeout(resolve, 300));
  }
  throw new Error(
    `等待 Page Aggregate pageOptions 同步超时：${JSON.stringify({
      documentId,
      lastOptions: aggregate?.layout?.pageOptions ?? null,
    })}`,
  );
}

async function main() {
  await fs.mkdir(OUT_DIR, { recursive: true });
  const suffix = Date.now().toString(36);
  const evidencePath = path.join(OUT_DIR, `${suffix}.json`);
  const screenshotPath = path.join(OUT_DIR, `${suffix}.png`);
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({ viewport: { width: 1440, height: 960 } });
  const page = await context.newPage();
  const createdIds = [];

  try {
    const viewer = await ensureAuthenticated(page, context.request);
    const target = await createTempDocument(context.request, null);
    createdIds.push(target.documentId);
    await saveDocumentContent(context.request, target.workspaceId, target.documentId);
    await openDocument(page, target.workspaceId, target.documentId);
    await page.locator(".ProseMirror").waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await openPageSettingsDialog(page);

    const initialAggregate = await fetchPageAggregate(context.request, target.workspaceId, target.documentId);
    const initialRuntime = await readRuntimePageOptions(page);
    assert.equal(initialAggregate.layout.pageOptions.wideLayout, false, "初始 wideLayout 应为 false");
    assert.equal(initialAggregate.layout.pageOptions.smallText, false, "初始 smallText 应为 false");
    assert.equal(initialAggregate.layout.pageOptions.layoutDensity, "normal", "初始 layoutDensity 应为 normal");
    assert.equal(initialRuntime.htmlWide, "false", "初始 runtime wideLayout 应为 false");
    assert.equal(initialRuntime.htmlSmall, "false", "初始 runtime smallText 应为 false");
    assert.equal(initialRuntime.htmlDensity, "normal", "初始 runtime layoutDensity 应为 normal");

    const wideResponse = await waitForOptionsResponse(page, target.documentId, async () => {
      await page.locator('[data-page-option-checkbox="wideLayout"]').click({ timeout: UI_TIMEOUT_MS });
    });
    const wideAggregate = await waitForAggregateOption(
      context.request,
      target.workspaceId,
      target.documentId,
      (options) => options.wideLayout === true,
    );
    const afterWideRuntime = await readRuntimePageOptions(page);
    assert.equal(afterWideRuntime.htmlWide, "true", "开启宽版后 html wideLayout 应为 true");
    assert.equal(afterWideRuntime.editorRootWide, "true", "开启宽版后 island root wideLayout 应为 true");
    assert.equal(afterWideRuntime.shellMaxWidth, "980px", "开启宽版后主列宽应为 980px");

    const smallResponse = await waitForOptionsResponse(page, target.documentId, async () => {
      await page.locator('[data-page-option-checkbox="smallText"]').click({ timeout: UI_TIMEOUT_MS });
    });
    const smallAggregate = await waitForAggregateOption(
      context.request,
      target.workspaceId,
      target.documentId,
      (options) => options.wideLayout === true && options.smallText === true,
    );
    const afterSmallRuntime = await readRuntimePageOptions(page);
    assert.equal(afterSmallRuntime.htmlSmall, "true", "开启小字体后 html smallText 应为 true");
    assert.equal(afterSmallRuntime.editorRootSmall, "true", "开启小字体后 island root smallText 应为 true");
    assert(
      typeof initialRuntime.editorFontSize === "string" &&
        typeof afterSmallRuntime.editorFontSize === "string" &&
        parseFloat(afterSmallRuntime.editorFontSize) < parseFloat(initialRuntime.editorFontSize),
      `开启小字体后编辑器字号应变小，初始 ${initialRuntime.editorFontSize}，实际 ${afterSmallRuntime.editorFontSize}`,
    );

    const densityResponse = await waitForOptionsResponse(page, target.documentId, async () => {
      await page.locator('[data-page-settings-tab="custom"]').click({ timeout: UI_TIMEOUT_MS });
      await page.locator('[data-page-option-select="layoutDensity"]').selectOption("compact");
    });
    const densityAggregate = await waitForAggregateOption(
      context.request,
      target.workspaceId,
      target.documentId,
      (options) => options.wideLayout === true && options.smallText === true && options.layoutDensity === "compact",
    );
    const afterDensityRuntime = await readRuntimePageOptions(page);
    assert.equal(afterDensityRuntime.htmlDensity, "compact", "切换紧凑后 html density 应为 compact");
    assert.equal(afterDensityRuntime.editorSurfaceDensity, "compact", "切换紧凑后 island surface density 应为 compact");
    assert(
      typeof initialRuntime.paragraphMarginBottom === "string" &&
        typeof afterDensityRuntime.paragraphMarginBottom === "string" &&
        parseFloat(afterDensityRuntime.paragraphMarginBottom) <= parseFloat(initialRuntime.paragraphMarginBottom),
      `切换紧凑后段落间距应不大于初始值，初始 ${initialRuntime.paragraphMarginBottom}，实际 ${afterDensityRuntime.paragraphMarginBottom}`,
    );

    await page.screenshot({ path: screenshotPath, fullPage: false });
    const evidence = {
      ok: true,
      baseUrl: BASE_URL,
      viewerUserId: viewer.userId,
      workspaceId: target.workspaceId,
      documentId: target.documentId,
      initial: {
        aggregate: initialAggregate.layout.pageOptions,
        runtime: initialRuntime,
      },
      afterWide: {
        aggregate: wideAggregate.options,
        runtime: afterWideRuntime,
        commandName: wideResponse.meta.commandName,
      },
      afterSmall: {
        aggregate: smallAggregate.options,
        runtime: afterSmallRuntime,
        commandName: smallResponse.meta.commandName,
      },
      afterDensity: {
        aggregate: densityAggregate.options,
        runtime: afterDensityRuntime,
        commandName: densityResponse.meta.commandName,
      },
      screenshotPath,
      evidencePath,
    };
    await fs.writeFile(evidencePath, JSON.stringify(evidence, null, 2), "utf8");
    console.log(JSON.stringify(evidence, null, 2));
  } finally {
    await cleanupDocuments(context.request, createdIds).catch(() => undefined);
    await page.close().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);
});
