import { act, useState } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createRoot, type Root } from "react-dom/client";
import { LeptosTiptapIslandEditorHost } from "@/components/editor/leptos-tiptap-island-editor-host";
import type { DocumentEditorHostProps } from "@/components/editor/editor-host-types";

const runtimeModuleUrl = `data:text/javascript;base64,${Buffer.from(`
  const mounts = new Map();
  export default async function init() {
    globalThis.__MNOTE_ISLAND_TEST_STATE__.initCalls += 1;
  }
  export function mount(container, options) {
    const state = globalThis.__MNOTE_ISLAND_TEST_STATE__;
    const mountId = state.nextMountId++;
    const surface = document.createElement("div");
    surface.className = "editor-surface";
    const editor = document.createElement("div");
    editor.className = "ProseMirror";
    editor.setAttribute("contenteditable", "true");
    editor.textContent = options?.title ?? "untitled";
    surface.appendChild(editor);
    container.replaceChildren(surface);
    container.addEventListener("mnote:leptos-tiptap-spike:command", (event) => {
      const detail = event?.detail ?? {};
      state.commandCalls.push({
        command: detail?.payload?.command ?? null,
        payload: detail?.payload ?? null,
      });
    });
    mounts.set(mountId, container);
    state.mountCalls.push({
      mountId,
      title: options?.title ?? null,
      pageOptions: options?.pageOptions ?? null,
    });
    return mountId;
  }
  export function unmount(mountId) {
    const state = globalThis.__MNOTE_ISLAND_TEST_STATE__;
    const container = mounts.get(mountId);
    if (container) {
      container.replaceChildren();
      mounts.delete(mountId);
    }
    state.unmountCalls.push(mountId);
  }
`).toString("base64")}`;

vi.mock("@/components/editor/leptos-tiptap-island-loader", () => ({
  loadLeptosTiptapIslandAssets: vi.fn(async () => ({
    manifest: null,
    entryAssetUrl: runtimeModuleUrl,
    wasmAssetUrl: null,
  })),
}));

type RuntimeTestState = {
  initCalls: number;
  nextMountId: number;
  mountCalls: Array<{ mountId: number; title: string | null; pageOptions?: unknown }>;
  unmountCalls: number[];
  commandCalls: Array<{ command: string | null; payload?: unknown }>;
};

declare global {
  interface Window {
    __MNOTE_ISLAND_TEST_STATE__?: RuntimeTestState;
  }
}

function flushEffects() {
  return new Promise((resolve) => {
    setTimeout(resolve, 0);
  });
}

function buildProps(overrides: Partial<DocumentEditorHostProps> = {}): DocumentEditorHostProps {
  return {
    documentId: "doc-1",
    workspaceId: "ws-1",
    initialContent: [],
    title: "标题 A",
    hostKind: "leptos_tiptap_island",
    initialRevision: 1,
    initialConflictDetectionKey: "doc-1:1",
    pageOptions: {
      wideLayout: false,
      smallText: false,
      showHeadingNumbers: true,
      showToc: false,
      showStructure: false,
      protectEditing: false,
      showWordCount: true,
      collapseBacklinks: false,
      pageFont: "default",
      layoutDensity: "normal",
      hideChildPages: false,
      showBlockRefCount: false,
      embedDefaultBlockId: null,
    },
    readOnly: false,
    onStatsChange: vi.fn(),
    onSnapshot: vi.fn(),
    onCloseToc: vi.fn(),
    onPersistedMetaChange: vi.fn(),
    onHostEvent: vi.fn(),
    onRequestFallback: vi.fn(),
    ...overrides,
  };
}

function HostEchoHarness({
  initialContent,
  onSnapshotSpy,
}: {
  initialContent: DocumentEditorHostProps["initialContent"];
  onSnapshotSpy?: ReturnType<typeof vi.fn>;
}) {
  const [content, setContent] = useState(initialContent);

  return (
    <LeptosTiptapIslandEditorHost
      {...buildProps({
        initialContent: content,
        onSnapshot: (payload) => {
          onSnapshotSpy?.(payload);
          setContent(payload.blocks);
        },
      })}
    />
  );
}

describe("leptos-tiptap-island-editor-host", () => {
  let container: HTMLDivElement;
  let root: Root;

  beforeEach(() => {
    (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
      true;
    window.__MNOTE_ISLAND_TEST_STATE__ = {
      initCalls: 0,
      nextMountId: 1,
      mountCalls: [],
      unmountCalls: [],
      commandCalls: [],
    };
    container = document.createElement("div");
    document.body.appendChild(container);
    root = createRoot(container);
  });

  afterEach(async () => {
    await act(async () => {
      root.unmount();
      await flushEffects();
    });
    container.remove();
    delete window.__MNOTE_ISLAND_TEST_STATE__;
  });

  it("同一文档仅标题变化时不应重复卸载并重挂 island", async () => {
    const initialProps = buildProps();

    await act(async () => {
      root.render(<LeptosTiptapIslandEditorHost {...initialProps} />);
      await flushEffects();
      await flushEffects();
    });

    expect(window.__MNOTE_ISLAND_TEST_STATE__?.mountCalls).toHaveLength(1);
    expect(window.__MNOTE_ISLAND_TEST_STATE__?.unmountCalls).toHaveLength(0);
    expect(
      container.querySelector(
        '[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror[contenteditable="true"]',
      ),
    ).not.toBeNull();

    await act(async () => {
      root.render(
        <LeptosTiptapIslandEditorHost
          {...buildProps({
            title: "标题 B",
          })}
        />,
      );
      await flushEffects();
      await flushEffects();
    });

    expect(window.__MNOTE_ISLAND_TEST_STATE__?.mountCalls).toHaveLength(1);
    expect(window.__MNOTE_ISLAND_TEST_STATE__?.unmountCalls).toHaveLength(0);
  });

  it("页面内 island 宿主不应再额外施加横向 padding", async () => {
    await act(async () => {
      root.render(<LeptosTiptapIslandEditorHost {...buildProps()} />);
      await flushEffects();
      await flushEffects();
    });

    const host = container.querySelector(
      '[data-testid="mnote-leptos-tiptap-island-editor-root"]',
    ) as HTMLDivElement | null;
    expect(host).not.toBeNull();
    expect(host?.className).toContain("min-h-[720px]");
    expect(host?.className).toContain("py-6");
    expect(host?.className).not.toContain("px-8");
  });

  it("mount 时应把 pageOptions 传给 island runtime", async () => {
    await act(async () => {
      root.render(
        <LeptosTiptapIslandEditorHost
          {...buildProps({
            pageOptions: {
              ...buildProps().pageOptions,
              wideLayout: true,
              smallText: true,
              layoutDensity: "compact",
              showHeadingNumbers: false,
              embedDefaultBlockId: "block-anchor-1",
            },
          })}
        />,
      );
      await flushEffects();
      await flushEffects();
    });

    expect(window.__MNOTE_ISLAND_TEST_STATE__?.mountCalls[0]?.pageOptions).toEqual({
      wideLayout: true,
      smallText: true,
      layoutDensity: "compact",
      showHeadingNumbers: false,
      embedDefaultBlockId: "block-anchor-1",
    });
  });

  it("pageOptions 变化时应向 runtime 发送 setPageOptions 命令", async () => {
    await act(async () => {
      root.render(<LeptosTiptapIslandEditorHost {...buildProps()} />);
      await flushEffects();
      await flushEffects();
    });

    await act(async () => {
      root.render(
        <LeptosTiptapIslandEditorHost
          {...buildProps({
            pageOptions: {
              ...buildProps().pageOptions,
              wideLayout: true,
              layoutDensity: "spacious",
            },
          })}
        />,
      );
      await flushEffects();
      await flushEffects();
    });

    const latestPageOptionsCommand = [...(window.__MNOTE_ISLAND_TEST_STATE__?.commandCalls ?? [])]
      .reverse()
      .find((entry) => entry.command === "setPageOptions");

    expect(latestPageOptionsCommand?.payload).toMatchObject({
      command: "setPageOptions",
      pageOptions: {
        wideLayout: true,
        smallText: false,
        layoutDensity: "spacious",
        showHeadingNumbers: true,
        embedDefaultBlockId: null,
      },
    });
  });

  it("runtime 快照回流为同一内容时不应再次发送 replaceContent", async () => {
    const onSnapshot = vi.fn();
    const initialProps = buildProps({
      initialContent: [
        {
          id: "block-1",
          type: "paragraph",
          content: [{ type: "text", text: "hello island" }],
          props: {},
          children: [],
        },
      ],
      onSnapshot,
    });

    await act(async () => {
      root.render(<LeptosTiptapIslandEditorHost {...initialProps} />);
      await flushEffects();
      await flushEffects();
    });

    const host = container.querySelector(
      '[data-testid="mnote-leptos-tiptap-island-editor-root"]',
    ) as HTMLDivElement | null;
    expect(host).not.toBeNull();
    const initialReplaceCount =
      window.__MNOTE_ISLAND_TEST_STATE__?.commandCalls.filter(
        (entry) => entry.command === "replaceContent",
      ).length ?? 0;

    await act(async () => {
      host?.dispatchEvent(
        new CustomEvent("mnote:leptos-tiptap-spike:change", {
          bubbles: true,
          detail: {
            protocol: "mnote.leptos_tiptap.bridge.v1",
            source: "mnote:leptos-tiptap-spike",
            payload: {
              title: "标题 A",
              content: {
                type: "doc",
                content: [
                  {
                    type: "paragraph",
                    content: [{ type: "text", text: "hello island" }],
                  },
                ],
              },
              meta: {
                readOnly: false,
                revision: 1,
                conflictDetectionKey: "doc-1:1",
              },
            },
          },
        }),
      );
      await flushEffects();
      await flushEffects();
    });

    const nextReplaceCount =
      window.__MNOTE_ISLAND_TEST_STATE__?.commandCalls.filter(
        (entry) => entry.command === "replaceContent",
      ).length ?? 0;

    expect(onSnapshot).toHaveBeenCalled();
    expect(nextReplaceCount).toBe(initialReplaceCount);
  });

  it("空文档初始化后收到等价空段落快照时不应再次发送 replaceContent", async () => {
    const onSnapshot = vi.fn();

    await act(async () => {
      root.render(
        <LeptosTiptapIslandEditorHost
          {...buildProps({
            initialContent: [],
            onSnapshot,
          })}
        />,
      );
      await flushEffects();
      await flushEffects();
    });

    const host = container.querySelector(
      '[data-testid="mnote-leptos-tiptap-island-editor-root"]',
    ) as HTMLDivElement | null;
    expect(host).not.toBeNull();

    const initialReplaceCount =
      window.__MNOTE_ISLAND_TEST_STATE__?.commandCalls.filter(
        (entry) => entry.command === "replaceContent",
      ).length ?? 0;

    await act(async () => {
      host?.dispatchEvent(
        new CustomEvent("mnote:leptos-tiptap-spike:change", {
          bubbles: true,
          detail: {
            protocol: "mnote.leptos_tiptap.bridge.v1",
            source: "mnote:leptos-tiptap-spike",
            payload: {
              title: "标题 A",
              content: {
                type: "doc",
                content: [
                  {
                    type: "paragraph",
                    content: [],
                  },
                ],
              },
              meta: {
                readOnly: false,
                revision: 1,
                conflictDetectionKey: "doc-1:1",
              },
            },
          },
        }),
      );
      await flushEffects();
      await flushEffects();
      await flushEffects();
    });

    const nextReplaceCount =
      window.__MNOTE_ISLAND_TEST_STATE__?.commandCalls.filter(
        (entry) => entry.command === "replaceContent",
      ).length ?? 0;

    expect(onSnapshot).toHaveBeenCalledTimes(1);
    expect(nextReplaceCount).toBe(initialReplaceCount);
  });

  it("父组件回灌 runtime 等价 blocks 时不应再次发送 replaceContent", async () => {
    const onSnapshotSpy = vi.fn();

    await act(async () => {
      root.render(
        <HostEchoHarness
          initialContent={[
            {
              id: "block-1",
              type: "paragraph",
              content: [{ type: "text", text: "hello island" }],
              props: {},
              children: [],
            },
          ]}
          onSnapshotSpy={onSnapshotSpy}
        />,
      );
      await flushEffects();
      await flushEffects();
    });

    const host = container.querySelector(
      '[data-testid="mnote-leptos-tiptap-island-editor-root"]',
    ) as HTMLDivElement | null;
    expect(host).not.toBeNull();

    const initialReplaceCount =
      window.__MNOTE_ISLAND_TEST_STATE__?.commandCalls.filter(
        (entry) => entry.command === "replaceContent",
      ).length ?? 0;

    await act(async () => {
      host?.dispatchEvent(
        new CustomEvent("mnote:leptos-tiptap-spike:change", {
          bubbles: true,
          detail: {
            protocol: "mnote.leptos_tiptap.bridge.v1",
            source: "mnote:leptos-tiptap-spike",
            payload: {
              title: "标题 A",
              content: {
                type: "doc",
                content: [
                  {
                    type: "paragraph",
                    content: [{ type: "text", text: "hello island" }],
                  },
                ],
              },
              meta: {
                readOnly: false,
                revision: 1,
                conflictDetectionKey: "doc-1:1",
              },
            },
          },
        }),
      );
      await flushEffects();
      await flushEffects();
      await flushEffects();
    });

    const nextReplaceCount =
      window.__MNOTE_ISLAND_TEST_STATE__?.commandCalls.filter(
        (entry) => entry.command === "replaceContent",
      ).length ?? 0;

    expect(onSnapshotSpy).toHaveBeenCalledTimes(1);
    expect(nextReplaceCount).toBe(initialReplaceCount);
  });

  it("应兼容 runtime 把 tiptap 文档作为 Map 回传", async () => {
    const onSnapshot = vi.fn();

    await act(async () => {
      root.render(
        <LeptosTiptapIslandEditorHost
          {...buildProps({
            initialContent: [],
            onSnapshot,
          })}
        />,
      );
      await flushEffects();
      await flushEffects();
    });

    const host = container.querySelector(
      '[data-testid="mnote-leptos-tiptap-island-editor-root"]',
    ) as HTMLDivElement | null;
    expect(host).not.toBeNull();

    await act(async () => {
      host?.dispatchEvent(
        new CustomEvent("mnote:leptos-tiptap-spike:change", {
          bubbles: true,
          detail: {
            protocol: "mnote.leptos_tiptap.bridge.v1",
            source: "mnote:leptos-tiptap-spike",
            payload: {
              title: "标题 A",
              content: new Map([
                ["type", "doc"],
                [
                  "content",
                  [
                    {
                      type: "paragraph",
                      content: [{ type: "text", text: "hello from map" }],
                    },
                  ],
                ],
              ]),
              meta: {
                readOnly: false,
                revision: 1,
                conflictDetectionKey: "doc-1:1",
              },
            },
          },
        }),
      );
      await flushEffects();
      await flushEffects();
    });

    expect(onSnapshot).toHaveBeenCalledTimes(1);
    expect(onSnapshot.mock.calls[0]?.[0]?.blocks).toEqual([
      {
        id: "block-1",
        type: "paragraph",
        content: "hello from map",
      },
    ]);
  });
});
