import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import React, { type ReactNode } from "react";
import { MoveEmbedPickerDialog } from "./move-embed-picker-dialog";

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

const sidebarData = {
  activeWorkspaceId: "ws_test",
  workspaces: [],
  documents: [],
  kernelSidebarProjection: {
    projectionId: "kernel_projection:sidebar_tree:workspace_root",
    projection: "sidebar_tree",
    rootNodeId: null,
    items: [],
    edges: [],
  },
  kernelSidebarTree: [],
  trashedDocuments: [],
};

function buildSidebarNode(input: {
  id: string;
  title: string;
  children?: Array<ReturnType<typeof buildSidebarNode>>;
}) {
  return {
    access_scope: "private",
    id: input.id,
    workspace_id: "ws_test",
    title: input.title,
    parent_id: null,
    sort_order: 0,
    is_starred: false,
    is_template: false,
    created_at: "2026-04-24T00:00:00Z",
    updated_at: "2026-04-24T00:00:00Z",
    children: input.children ?? [],
    kernel: {
      nodeType: "page" as const,
      depth: 0,
      position: 0,
      childCount: input.children?.length ?? 0,
      expandedByDefault: true,
    },
  };
}

vi.mock("@tanstack/react-query", () => ({
  useQuery: ({ queryKey }: { queryKey: unknown[] }) => {
    const key = Array.isArray(queryKey) ? queryKey[0] : queryKey;
    if (key === "move-embed-picker-sidebar") {
      return {
        data: sidebarData,
        isLoading: false,
        error: null,
      };
    }
    return {
      data: null,
      isLoading: false,
      error: null,
    };
  },
}));

const mockUseDocumentSearch = vi.fn(() => ({
  data: null,
  isLoading: false,
  error: null,
}));

vi.mock("@/hooks/use-document-search", () => ({
  useDocumentSearch: (...args: unknown[]) => mockUseDocumentSearch(...args),
}));

vi.mock("@/components/ui/dialog", () => ({
  Dialog: ({ open, children }: { open: boolean; children: ReactNode }) => (open ? <div>{children}</div> : null),
  DialogContent: ({ children, className }: { children: ReactNode; className?: string }) => (
    <div className={className}>{children}</div>
  ),
  DialogTitle: ({ children, className }: { children: ReactNode; className?: string }) => (
    <div className={className}>{children}</div>
  ),
}));

vi.mock("@/components/ui/input", () => ({
  Input: React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>((props, ref) => (
    <input ref={ref} {...props} />
  )),
}));

vi.mock("@/components/ui/tabs", () => ({
  Tabs: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  TabsList: ({ children, className }: { children: ReactNode; className?: string }) => (
    <div className={className}>{children}</div>
  ),
  TabsTrigger: ({
    children,
    className,
    value,
  }: {
    children: ReactNode;
    className?: string;
    value: string;
  }) => (
    <button type="button" className={className} data-value={value}>
      {children}
    </button>
  ),
  TabsContent: ({ children, className }: { children: ReactNode; className?: string }) => (
    <div className={className}>{children}</div>
  ),
}));

describe("MoveEmbedPickerDialog", () => {
  let container: HTMLDivElement;
  let root: Root;

  beforeEach(() => {
    container = document.createElement("div");
    document.body.appendChild(container);
    root = createRoot(container);
    window.__MNOTE_RUNTIME_CONFIG__ = {
      treeRendererFamily: "react",
    };
  });

  afterEach(() => {
    act(() => {
      root.unmount();
    });
    container.remove();
    vi.restoreAllMocks();
    vi.clearAllMocks();
    mockUseDocumentSearch.mockReset();
    mockUseDocumentSearch.mockReturnValue({
      data: null,
      isLoading: false,
      error: null,
    });
    sidebarData.kernelSidebarTree = [];
    delete window.__MNOTE_RUNTIME_CONFIG__;
  });

  it("空查询时会使用统一 picker surface 并透传根目录选择", async () => {
    const onPick = vi.fn(async () => undefined);
    const onOpenChange = vi.fn();

    await act(async () => {
      root.render(
        <MoveEmbedPickerDialog
          open
          onOpenChange={onOpenChange}
          workspaceId="ws_test"
          defaultMode="move"
          allowRoot
          excludeIds={["doc_hidden"]}
          onPick={onPick}
        />,
      );
    });

    const shellPickButton = container.querySelector('[data-testid="tree-picker-root"]');
    expect(shellPickButton).not.toBeNull();

    await act(async () => {
      shellPickButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    });

    expect(onPick).toHaveBeenCalledTimes(1);
    expect(onPick).toHaveBeenCalledWith("move", null);
    expect(onOpenChange).toHaveBeenCalledWith(false);
  });

  it("空查询时应保留根节点并过滤 excludeIds", async () => {
    sidebarData.kernelSidebarTree = [
      buildSidebarNode({ id: "doc_hidden", title: "隐藏页面" }),
      buildSidebarNode({ id: "doc_visible", title: "保留页面" }),
    ];

    await act(async () => {
      root.render(
        <MoveEmbedPickerDialog
          open
          onOpenChange={vi.fn()}
          workspaceId="ws_test"
          defaultMode="move"
          allowRoot
          excludeIds={["doc_hidden"]}
          onPick={vi.fn(async () => undefined)}
        />,
      );
    });

    expect(container.querySelector('[data-testid="tree-picker-root"]')).not.toBeNull();
    expect(container.querySelector('[data-testid="tree-picker-row"][data-node-id="doc_hidden"]')).toBeNull();
    expect(container.querySelector('[data-testid="tree-picker-row"][data-node-id="doc_visible"]')).not.toBeNull();
  });

  it("空查询态应支持键盘高亮切换并按当前高亮项选中", async () => {
    sidebarData.kernelSidebarTree = [
      buildSidebarNode({ id: "doc_first", title: "第一页" }),
      buildSidebarNode({ id: "doc_second", title: "第二页" }),
    ];

    const onPick = vi.fn(async () => undefined);
    const onOpenChange = vi.fn();

    await act(async () => {
      root.render(
        <MoveEmbedPickerDialog
          open
          onOpenChange={onOpenChange}
          workspaceId="ws_test"
          defaultMode="move"
          allowRoot
          excludeIds={[]}
          onPick={onPick}
        />,
      );
    });

    const input = container.querySelector("input");
    const pickerRows = container.querySelectorAll('[data-testid="tree-picker-row"]');
    expect(input).not.toBeNull();
    expect(pickerRows).toHaveLength(2);

    await act(async () => {
      input?.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }));
    });

    expect(pickerRows[0]?.className ?? "").toContain("bg-[#e3ecff]");

    await act(async () => {
      input?.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
    });

    expect(onPick).toHaveBeenCalledWith("move", "doc_first");
    expect(onOpenChange).toHaveBeenCalledWith(false);
  });

  it("搜索结果也应继续复用统一 picker surface", async () => {
    mockUseDocumentSearch.mockReturnValue({
      data: {
        results: [
          {
            id: "doc_target",
            title: "目标页面",
            matchField: "title",
          },
        ],
      },
      isLoading: false,
      error: null,
    });

    const onPick = vi.fn(async () => undefined);
    const onOpenChange = vi.fn();

    await act(async () => {
      root.render(
        <MoveEmbedPickerDialog
          open
          onOpenChange={onOpenChange}
          workspaceId="ws_test"
          defaultMode="move"
          allowRoot
          excludeIds={[]}
          onPick={onPick}
        />,
      );
    });

    const input = container.querySelector("input");
    expect(input).not.toBeNull();

    await act(async () => {
      input?.dispatchEvent(new Event("input", { bubbles: true }));
      Object.defineProperty(input as HTMLInputElement, "value", {
        configurable: true,
        value: "目标",
      });
      input?.dispatchEvent(new Event("change", { bubbles: true }));
    });

    const pickerSurface = container.querySelector('[data-testid="tree-picker-surface"]');
    const pickerRows = container.querySelectorAll('[data-testid="tree-picker-row"]');
    expect(pickerSurface).not.toBeNull();
    expect(pickerRows).toHaveLength(1);

    await act(async () => {
      pickerRows[0]?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    });

    expect(onPick).toHaveBeenCalledWith("move", "doc_target");
    expect(onOpenChange).toHaveBeenCalledWith(false);
  });

  it("搜索结果态应支持高亮切换并按当前高亮项选中", async () => {
    mockUseDocumentSearch.mockReturnValue({
      data: {
        results: [
          {
            id: "doc_first",
            title: "第一页",
            matchField: "title",
          },
          {
            id: "doc_second",
            title: "第二页",
            matchField: "title",
          },
        ],
      },
      isLoading: false,
      error: null,
    });

    const onPick = vi.fn(async () => undefined);
    const onOpenChange = vi.fn();

    await act(async () => {
      root.render(
        <MoveEmbedPickerDialog
          open
          onOpenChange={onOpenChange}
          workspaceId="ws_test"
          defaultMode="move"
          allowRoot
          excludeIds={[]}
          onPick={onPick}
        />,
      );
    });

    const input = container.querySelector("input");
    expect(input).not.toBeNull();

    await act(async () => {
      Object.defineProperty(input as HTMLInputElement, "value", {
        configurable: true,
        value: "第",
      });
      input?.dispatchEvent(new Event("input", { bubbles: true }));
      input?.dispatchEvent(new Event("change", { bubbles: true }));
    });

    const pickerRows = container.querySelectorAll('[data-testid="tree-picker-row"]');
    expect(pickerRows).toHaveLength(2);
    await act(async () => {
      pickerRows[1]?.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
    });

    expect(pickerRows[1]?.className ?? "").toContain("bg-[#e3ecff]");

    await act(async () => {
      pickerRows[1]?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    });

    expect(onPick).toHaveBeenCalledWith("move", "doc_second");
    expect(onOpenChange).toHaveBeenCalledWith(false);
  });

  it("搜索结果态不应再注入根节点，且仍应支持多次 ArrowDown 后选中后续结果", async () => {
    mockUseDocumentSearch.mockReturnValue({
      data: {
        results: [
          {
            id: "doc_first",
            title: "第一页",
            matchField: "title",
          },
          {
            id: "doc_second",
            title: "第二页",
            matchField: "title",
          },
        ],
      },
      isLoading: false,
      error: null,
    });

    const onPick = vi.fn(async () => undefined);
    const onOpenChange = vi.fn();

    await act(async () => {
      root.render(
        <MoveEmbedPickerDialog
          open
          onOpenChange={onOpenChange}
          workspaceId="ws_test"
          defaultMode="move"
          allowRoot
          excludeIds={[]}
          onPick={onPick}
        />,
      );
    });

    const input = container.querySelector("input");
    expect(input).not.toBeNull();

    await act(async () => {
      Object.defineProperty(input as HTMLInputElement, "value", {
        configurable: true,
        value: "第",
      });
      input?.dispatchEvent(new Event("input", { bubbles: true }));
      input?.dispatchEvent(new Event("change", { bubbles: true }));
    });

    const pickerRows = container.querySelectorAll('[data-testid="tree-picker-row"]');
    expect(container.querySelector('[data-testid="tree-picker-root"]')).toBeNull();
    expect(pickerRows).toHaveLength(2);

    await act(async () => {
      input?.focus();
      input?.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }));
    });

    await act(async () => {
      input?.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }));
    });

    await act(async () => {
      input?.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
    });

    expect(onPick).toHaveBeenCalledWith("move", "doc_second");
    expect(onOpenChange).toHaveBeenCalledWith(false);
  });

  it("rust_family 配置下，picker 空态与结果态都应进入统一 host", async () => {
    window.__MNOTE_RUNTIME_CONFIG__ = {
      treeRendererFamily: "rust_family",
    };

    const onPick = vi.fn(async () => undefined);
    const onOpenChange = vi.fn();

    await act(async () => {
      root.render(
        <MoveEmbedPickerDialog
          open
          onOpenChange={onOpenChange}
          workspaceId="ws_test"
          defaultMode="move"
          allowRoot
          excludeIds={[]}
          onPick={onPick}
        />,
      );
    });

    const emptySurface = container.querySelector('[data-testid="tree-picker-surface"]');
    expect(emptySurface?.getAttribute("data-tree-host-kind")).toBe("rust_family");
    expect(emptySurface?.getAttribute("data-tree-host-implementation")).toBe("rust_inline_compat_host");
    expect(
      container.querySelector('[data-testid="tree-picker-surface-rust-host"]'),
    ).not.toBeNull();
    expect(
      container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]'),
    ).not.toBeNull();

    mockUseDocumentSearch.mockReturnValue({
      data: {
        results: [
          {
            id: "doc_target",
            title: "目标页面",
            matchField: "title",
          },
        ],
      },
      isLoading: false,
      error: null,
    });

    const input = container.querySelector("input");
    expect(input).not.toBeNull();

    await act(async () => {
      Object.defineProperty(input as HTMLInputElement, "value", {
        configurable: true,
        value: "目标",
      });
      input?.dispatchEvent(new Event("input", { bubbles: true }));
      input?.dispatchEvent(new Event("change", { bubbles: true }));
    });

    const resultSurface = container.querySelector('[data-testid="tree-picker-surface"]');
    expect(resultSurface?.getAttribute("data-tree-host-kind")).toBe("rust_family");
    expect(resultSurface?.getAttribute("data-tree-host-implementation")).toBe("rust_inline_compat_host");
    expect(
      container.querySelector('[data-testid="tree-picker-surface-rust-host"]'),
    ).not.toBeNull();
    expect(
      container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]'),
    ).not.toBeNull();
  });

  it("rust_family 配置下，输入框键盘命令应转发给 iframe，并用焦点回传更新 shell 状态", async () => {
    window.__MNOTE_RUNTIME_CONFIG__ = {
      treeRendererFamily: "rust_family",
    };
    mockUseDocumentSearch.mockReturnValue({
      data: {
        results: [
          {
            id: "doc_first",
            title: "第一页",
            matchField: "title",
          },
          {
            id: "doc_second",
            title: "第二页",
            matchField: "title",
          },
        ],
      },
      isLoading: false,
      error: null,
    });
    vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(
        '<!doctype html><html><body><script id="tree-shell-state" type="application/json">{}</script></body></html>',
        { status: 200, headers: { "content-type": "text/html" } },
      ),
    );

    await act(async () => {
      root.render(
        <MoveEmbedPickerDialog
          open
          onOpenChange={vi.fn()}
          workspaceId="ws_test"
          defaultMode="move"
          allowRoot
          excludeIds={[]}
          onPick={vi.fn(async () => undefined)}
        />,
      );
    });

    const input = container.querySelector("input");
    expect(input).not.toBeNull();

    await act(async () => {
      Object.defineProperty(input as HTMLInputElement, "value", {
        configurable: true,
        value: "第",
      });
      input?.dispatchEvent(new Event("input", { bubbles: true }));
      input?.dispatchEvent(new Event("change", { bubbles: true }));
    });

    const iframe = container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]');
    expect(iframe).not.toBeNull();
    Object.defineProperty(iframe, "contentWindow", {
      configurable: true,
      value: window,
    });
    const postMessageMock = vi.spyOn(window, "postMessage").mockImplementation(() => undefined);
    postMessageMock.mockClear();

    await act(async () => {
      input?.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }));
    });

    expect(postMessageMock).toHaveBeenCalledWith(
      expect.objectContaining({
        channel: "tree-picker-surface",
        type: "tree.picker.command",
        command: "next",
      }),
      "*",
    );

    postMessageMock.mockClear();

    await act(async () => {
      window.dispatchEvent(
        new MessageEvent("message", {
          data: {
            channel: "tree-picker-surface",
            type: "tree.picker.focus.changed",
            documentId: "doc_second",
            itemKey: "doc_second",
          },
          source: window,
        }),
      );
    });

    expect(postMessageMock).toHaveBeenCalledWith(
      expect.objectContaining({
        channel: "tree-picker-surface",
        type: "tree.shell.state.patch",
        activeDocumentId: "doc_second",
        activePickerItemKey: "doc_second",
      }),
      "*",
    );

    postMessageMock.mockClear();

    await act(async () => {
      input?.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
    });

    expect(postMessageMock).toHaveBeenCalledWith(
      expect.objectContaining({
        channel: "tree-picker-surface",
        type: "tree.picker.command",
        command: "pick",
      }),
      "*",
    );
  });

  it("rust_family 配置下，无根目录且无结果时也应保持 same-origin host 空态", async () => {
    window.__MNOTE_RUNTIME_CONFIG__ = {
      treeRendererFamily: "rust_family",
    };
    vi.spyOn(globalThis, "fetch").mockResolvedValue(
      new Response(
        '<!doctype html><html><body><script id="tree-shell-state" type="application/json">{}</script></body></html>',
        { status: 200, headers: { "content-type": "text/html" } },
      ),
    );

    const onPick = vi.fn(async () => undefined);
    const onOpenChange = vi.fn();

    await act(async () => {
      root.render(
        <MoveEmbedPickerDialog
          open
          onOpenChange={onOpenChange}
          workspaceId="ws_test"
          defaultMode="move"
          allowRoot={false}
          excludeIds={[]}
          onPick={onPick}
        />,
      );
    });

    await act(async () => {
      await Promise.resolve();
    });

    const surface = container.querySelector('[data-testid="tree-picker-surface"]');
    const iframe = container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]');

    expect(surface?.getAttribute("data-tree-host-kind")).toBe("rust_family");
    expect(surface?.getAttribute("data-tree-host-implementation")).toBe("rust_inline_compat_host");
    expect(iframe).not.toBeNull();
    expect(iframe?.getAttribute("data-tree-shell-inline")).toBe("1");
  });

});
