import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
  TreeShellIframeHost,
  buildTreeShellIframeSrc,
  buildTreeShellInlinePickerItems,
  injectTreeShellInlineOverrides,
} from "./tree-shell-iframe-host";

(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;

describe("tree-shell-iframe-host", () => {
  let container: HTMLDivElement;
  let root: Root;

  beforeEach(() => {
    container = document.createElement("div");
    document.body.appendChild(container);
    root = createRoot(container);
  });

  afterEach(() => {
    act(() => {
      root.unmount();
    });
    container.remove();
  });

  it("应构造同源 tree shell iframe 地址，并透传 picker 所需 query", () => {
    const src = buildTreeShellIframeSrc({
      mode: "picker",
      workspaceId: "ws_picker",
      activeDocumentId: "doc_active",
      allowRootPick: true,
      excludeIds: ["doc_hidden", "doc_other"],
      channel: "tree-picker-surface",
      host: "tree-picker-surface",
    });
    const url = new URL(src, "http://127.0.0.1:3000");

    expect(url.pathname).toBe("/api/tree/shell");
    expect(url.searchParams.get("workspaceId")).toBe("ws_picker");
    expect(url.searchParams.get("mode")).toBe("picker");
    expect(url.searchParams.get("activeDocumentId")).toBe("doc_active");
    expect(url.searchParams.get("allowRootPick")).toBe("1");
    expect(url.searchParams.get("excludeIds")).toBe("doc_hidden,doc_other");
    expect(url.searchParams.get("channel")).toBe("tree-picker-surface");
    expect(url.searchParams.get("host")).toBe("tree-picker-surface");
  });

  it("应能把 picker 搜索结果转换为 inline shell items 并注入到 HTML", () => {
    const pickerItems = buildTreeShellInlinePickerItems([
      { kind: "doc", id: "doc_target", title: "目标页面", depth: 0 },
      { kind: "doc", id: "doc_recent", title: "最近</script>打开", depth: 0 },
    ]);

    expect(pickerItems).toEqual([
      expect.objectContaining({
        nodeId: "doc_target",
        parentNodeId: null,
        title: "目标页面",
        depth: 0,
        childCount: 0,
      }),
      expect.objectContaining({
        nodeId: "doc_recent",
        parentNodeId: null,
        title: "最近</script>打开",
        depth: 0,
        childCount: 0,
      }),
    ]);

    const html = injectTreeShellInlineOverrides(
      '<!doctype html><html><body><script id="tree-shell-state" type="application/json">{}</script></body></html>',
      { items: pickerItems },
    );

    expect(html).toContain("__MNOTE_TREE_SHELL_OVERRIDE__");
    expect(html).toContain("\\u003c/script\\u003e");
    expect(html.indexOf("__MNOTE_TREE_SHELL_OVERRIDE__")).toBeGreaterThan(-1);
    expect(html.indexOf("__MNOTE_TREE_SHELL_OVERRIDE__")).toBeLessThan(
      html.indexOf('id="tree-shell-state"'),
    );
  });

  it("应把 iframe postMessage 桥接回宿主回调，并忽略错误 channel", async () => {
    const onNavigate = vi.fn();
    const onPageContextMenu = vi.fn();
    const onPick = vi.fn();
    const onFileTreeContextMenu = vi.fn();
    const onFileTreeSelectionChange = vi.fn();
    const onAssetOpen = vi.fn();
    const onTreeMutation = vi.fn();
    const onInternalDrop = vi.fn();
    const onDropFiles = vi.fn();
    const targetRow = {
      kind: "doc",
      rowId: "doc:doc_target",
      depth: 0,
      docId: "doc_target",
      parentDocId: null,
      node: {
        _id: "doc_target",
        id: "doc_target",
        title: "目标页面",
      },
      hasChildren: false,
      isExpanded: false,
    };
    const droppedFile = new File(["hello"], "hello.txt", { type: "text/plain" });

    await act(async () => {
      root.render(
        <TreeShellIframeHost
          mode="filetree"
          surfaceTestId="sidebar-file-tree-shell"
          workspaceId="ws_1"
          activeDocumentId="doc_1"
          channel="sidebar-file-tree-shell"
          host="sidebar-file-tree-shell"
          onNavigate={onNavigate}
          onPageContextMenu={onPageContextMenu}
          onPick={onPick}
          onFileTreeContextMenu={onFileTreeContextMenu}
          onFileTreeSelectionChange={onFileTreeSelectionChange}
          onAssetOpen={onAssetOpen}
          onTreeMutation={onTreeMutation}
          onInternalDrop={onInternalDrop}
          onDropFiles={onDropFiles}
        />,
      );
    });

    const iframe = container.querySelector("iframe");
    expect(iframe).not.toBeNull();
    Object.defineProperty(iframe, "contentWindow", {
      configurable: true,
      value: window,
    });

    await act(async () => {
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "wrong-channel",
            type: "tree.navigate",
            documentId: "doc_wrong",
          },
          source: window,
        }),
      );
    });

    expect(onNavigate).not.toHaveBeenCalled();

    await act(async () => {
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "sidebar-file-tree-shell",
            type: "tree.navigate",
            documentId: "doc_2",
          },
          source: window,
        }),
      );
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "sidebar-file-tree-shell",
            type: "tree.page.context-menu",
            documentId: "doc_2",
            x: 12,
            y: 34,
          },
          source: window,
        }),
      );
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "sidebar-file-tree-shell",
            type: "tree.pick.root",
            documentId: null,
          },
          source: window,
        }),
      );
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "sidebar-file-tree-shell",
            type: "tree.filetree.context-menu",
            documentId: "doc_2",
            assetId: "asset_2",
            rowId: "asset:asset_2",
            rowKind: "asset",
            x: 56,
            y: 78,
          },
          source: window,
        }),
      );
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "sidebar-file-tree-shell",
            type: "tree.filetree.selection.changed",
            selectedRowIds: ["doc:doc_2", "index:doc_2"],
            anchorRowId: "doc:doc_2",
            focusedRowId: "index:doc_2",
          },
          source: window,
        }),
      );
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "sidebar-file-tree-shell",
            type: "tree.asset.open",
            documentId: "doc_2",
            assetId: "asset_2",
          },
          source: window,
        }),
      );
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "sidebar-file-tree-shell",
            type: "tree.node.renamed",
            documentId: "doc_2",
          },
          source: window,
        }),
      );
    });

    expect(onNavigate).toHaveBeenCalledWith("doc_2");
    expect(onPageContextMenu).toHaveBeenCalledWith({
      documentId: "doc_2",
      x: 12,
      y: 34,
    });
    expect(onPick).toHaveBeenCalledWith(null);
    expect(onFileTreeContextMenu).toHaveBeenCalledWith({
      documentId: "doc_2",
      assetId: "asset_2",
      rowId: "asset:asset_2",
      rowKind: "asset",
      x: 56,
      y: 78,
    });
    expect(onFileTreeSelectionChange).toHaveBeenCalledWith({
      selectedRowIds: ["doc:doc_2", "index:doc_2"],
      anchorRowId: "doc:doc_2",
      focusedRowId: "index:doc_2",
    });
    expect(onAssetOpen).toHaveBeenCalledWith({
      documentId: "doc_2",
      assetId: "asset_2",
    });
    expect(onTreeMutation).toHaveBeenCalledWith({
      type: "tree.node.renamed",
      documentId: "doc_2",
    });

    await act(async () => {
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "sidebar-file-tree-shell",
            type: "tree.filetree.internal-drop",
            rowIds: ["doc:doc_source", "asset:asset_source"],
            copy: true,
            targetRow,
          },
          source: window,
        }),
      );
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "sidebar-file-tree-shell",
            type: "tree.filetree.drop-files",
            documentId: "doc_target",
            targetRow,
            files: [droppedFile],
          },
          source: window,
        }),
      );
    });

    expect(onInternalDrop).toHaveBeenCalledWith({
      targetRow,
      rowIds: ["doc:doc_source", "asset:asset_source"],
      copy: true,
    });
    expect(onDropFiles).toHaveBeenCalledWith("doc_target", [droppedFile], targetRow);
  });
});
