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 type { Mock } from "vitest";
import type { SidebarTreeNode } from "@/lib/kernel-sidebar";
import { MoveEmbedPickerDialog } from "./move-embed-picker-dialog";

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

type SidebarFixtureNode = SidebarTreeNode;

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

function buildSidebarNode(input: {
  id: string;
  title: string;
  children?: SidebarFixtureNode[];
}): SidebarFixtureNode {
  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: Mock = vi.fn(() => ({
  data: null,
  isLoading: false,
  error: null,
}));

type RuntimeRequest = {
  requestId: string;
  mode: "page" | "fileTree" | "picker";
  environment?: Record<string, unknown>;
  state?: Record<string, unknown>;
  action?: Record<string, unknown>;
};

type TreeShellRuntimeTestGlobal = typeof globalThis & {
  __MNOTE_TREE_SHELL_RUNTIME__?: {
    reduceTreeShellRuntime: Mock;
  };
};

function reducePickerRuntimeForTest(request: RuntimeRequest) {
  const action = request.action ?? {};
  const env = request.environment ?? {};
  const items = Array.isArray(env.items) ? env.items as Array<Record<string, unknown>> : [];
  const state = request.state ?? {};
  const pickable = items.filter((item) => item.pickable !== false);
  const activeItemKey = typeof state.activeItemKey === "string" ? state.activeItemKey : null;
  const activeIndex = Math.max(0, pickable.findIndex((item) => item.itemKey === activeItemKey));
  let nextItemKey = activeItemKey;

  if (action.kind === "focus") {
    nextItemKey = typeof action.itemKey === "string" ? action.itemKey : null;
  } else if (action.kind === "next") {
    nextItemKey = String(pickable[Math.min(pickable.length - 1, activeIndex + 1)]?.itemKey ?? "");
  } else if (action.kind === "previous") {
    nextItemKey = String(pickable[Math.max(0, activeIndex - 1)]?.itemKey ?? "");
  } else if (action.kind === "home") {
    nextItemKey = String(pickable[0]?.itemKey ?? "");
  } else if (action.kind === "end") {
    nextItemKey = String(pickable[pickable.length - 1]?.itemKey ?? "");
  }

  const nextState = { activeItemKey: nextItemKey || null };
  const hostEvents =
    action.kind === "pick"
      ? activeItemKey === "__root__"
        ? [{ kind: "pickerPickRoot" }]
        : activeItemKey
          ? [{ kind: "pickerPickDocument", documentId: activeItemKey }]
          : []
      : [];

  return {
    requestId: request.requestId,
    mode: "picker",
    state: { mode: "picker", state: nextState },
    domPatches: [{ kind: "pickerState", activeItemKey: nextState.activeItemKey, focusDom: false }],
    hostEvents,
    commandEvents: [],
  };
}

function mockTreeRuntimeReducer() {
  const reduceTreeShellRuntime = vi.fn(async (request: RuntimeRequest) =>
    reducePickerRuntimeForTest(request),
  );
  (globalThis as TreeShellRuntimeTestGlobal).__MNOTE_TREE_SHELL_RUNTIME__ = {
    reduceTreeShellRuntime,
  };
  return reduceTreeShellRuntime;
}

async function waitForRuntimeReducer(reducerMock: Mock) {
  for (let index = 0; index < 10; index += 1) {
    if (reducerMock.mock.calls.length > 0) {
      return;
    }
    await act(async () => {
      await Promise.resolve();
    });
  }
}

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 (globalThis as TreeShellRuntimeTestGlobal).__MNOTE_TREE_SHELL_RUNTIME__;
    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",
    };
    mockTreeRuntimeReducer();

    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_wasm_dom_shell_host");
    expect(
      container.querySelector('[data-testid="tree-picker-surface-rust-host"]'),
    ).not.toBeNull();
    expect(
      container.querySelector('[data-testid="tree-picker-surface-dom-host"]'),
    ).not.toBeNull();
    expect(container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]')).toBeNull();
    expect(container.querySelector('[data-tree-browser-bridge="iframe_srcdoc"]')).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_wasm_dom_shell_host");
    expect(
      container.querySelector('[data-testid="tree-picker-surface-rust-host"]'),
    ).not.toBeNull();
    expect(
      container.querySelector('[data-testid="tree-picker-surface-dom-host"]'),
    ).not.toBeNull();
    expect(container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]')).toBeNull();
    expect(container.querySelector('[data-testid="tree-picker-row"][data-node-id="doc_target"]')).not.toBeNull();
  });

  it("rust_family 配置下，输入框键盘命令应驱动 DOM shell 高亮并选中当前项", async () => {
    window.__MNOTE_RUNTIME_CONFIG__ = {
      treeRendererFamily: "rust_family",
    };
    const runtimeReducerMock = mockTreeRuntimeReducer();
    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 }));
    });

    expect(container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]')).toBeNull();
    expect(container.querySelector('[data-testid="tree-picker-surface-dom-host"]')).not.toBeNull();
    expect(
      container.querySelector('[data-testid="tree-picker-row"][data-node-id="doc_first"]')?.getAttribute("data-focused"),
    ).toBe("true");

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

    expect(runtimeReducerMock).toHaveBeenCalledWith(
      expect.objectContaining({
        mode: "picker",
        action: expect.objectContaining({ kind: "next" }),
      }),
    );
    expect(
      container.querySelector('[data-testid="tree-picker-row"][data-node-id="doc_second"]')?.getAttribute("data-focused"),
    ).toBe("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 配置下，无根目录且无结果时也应保持 DOM shell 空态", async () => {
    window.__MNOTE_RUNTIME_CONFIG__ = {
      treeRendererFamily: "rust_family",
    };
    mockTreeRuntimeReducer();

    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 domHost = container.querySelector('[data-testid="tree-picker-surface-dom-host"]');

    expect(surface?.getAttribute("data-tree-host-kind")).toBe("rust_family");
    expect(surface?.getAttribute("data-tree-host-implementation")).toBe("rust_wasm_dom_shell_host");
    expect(domHost).not.toBeNull();
    expect(domHost?.getAttribute("data-tree-browser-bridge")).toBe("dom_wasm");
    expect(container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]')).toBeNull();
    expect(container.textContent).toContain("没有匹配结果");
  });

});
