import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { Mock } from "vitest";
import type { KernelFileTreeProjectionItem } from "@/lib/kernel-file-tree";
import { SidebarTreeSurface, TreePickerSurface, type TreeRendererFamily } from "./tree-shell-surface";

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

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 reduceTreeRuntimeForTest(request: RuntimeRequest) {
  if (request.mode === "fileTree") {
    const state = request.state ?? {};
    const action = request.action ?? {};
    const env = request.environment ?? {};
    const selection = (state.selection ?? {}) as Record<string, unknown>;
    const rows = Array.isArray(env.rows) ? env.rows as Array<Record<string, unknown>> : [];
    const rowId = typeof action.targetRowId === "string" ? action.targetRowId : null;
    const targetRow = rows.find((row) => row.rowId === rowId) ?? {};
    const target = {
      kind: targetRow.rowKind ?? "doc",
      documentId: targetRow.documentId ?? null,
      assetId: targetRow.assetId ?? null,
    };
    const hostEvents =
      action.kind === "dispatchInternalDrop"
        ? [{
            kind: "fileTreeInternalDrop",
            targetRowId: rowId,
            target,
            rowIds: Array.isArray(action.rowIds) ? action.rowIds : [],
            copy: action.copy === true,
          }]
        : action.kind === "dispatchExternalDrop"
          ? [{
              kind: "fileTreeExternalDrop",
              targetRowId: rowId,
              target,
              fileCount: action.fileCount ?? 0,
          }]
        : [];
    const selectedRowId = typeof action.rowId === "string" ? action.rowId : null;
    const nextSelection =
      action.kind === "selectRow" && selectedRowId
        ? { selectedRowIds: [selectedRowId], anchorRowId: selectedRowId, focusedRowId: selectedRowId }
        : action.kind === "clearSelection"
          ? { selectedRowIds: [], anchorRowId: null, focusedRowId: null }
          : selection;
    return {
      requestId: request.requestId,
      mode: "fileTree",
      state: {
        mode: "fileTree",
        state: {
          activeRowId: (state as Record<string, unknown>).activeRowId ?? null,
          selection: nextSelection,
          dragRowIds: (state as Record<string, unknown>).dragRowIds ?? [],
          dragEffect: (state as Record<string, unknown>).dragEffect ?? null,
          dropTargetRowId: (state as Record<string, unknown>).dropTargetRowId ?? null,
        },
      },
      domPatches: [{ kind: "fileTreeState" }],
      hostEvents,
      commandEvents: [],
    };
  }

  return {
    requestId: request.requestId,
    mode: request.mode,
    state: { mode: request.mode, state: request.state ?? {} },
    domPatches: [],
    hostEvents: [],
    commandEvents: [],
  };
}

function mockTreeRuntimeReducer() {
  const reduceTreeShellRuntime = vi.fn(async (request: RuntimeRequest) =>
    reduceTreeRuntimeForTest(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();
    });
  }
}

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

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

  afterEach(() => {
    act(() => {
      root.unmount();
    });
    container.remove();
    delete (globalThis as TreeShellRuntimeTestGlobal).__MNOTE_TREE_SHELL_RUNTIME__;
  });

  function renderPageSurface(rendererFamily: TreeRendererFamily, focusedDocumentId?: string) {
    act(() => {
      root.render(
        <SidebarTreeSurface
          mode="page"
          rendererFamily={rendererFamily}
          workspaceId="ws_1"
          treeShellEnabled
          rows={[]}
          expanded={new Set<string>()}
          activeId=""
          focusedDocumentId={focusedDocumentId}
          onToggleExpand={() => undefined}
          onMove={() => undefined}
          onCreateChild={() => undefined}
          onContextMenu={() => undefined}
        />,
      );
    });
  }

  it("page tree surface 在 rust_family 下默认切到 Rust/WASM DOM shell host", () => {
    renderPageSurface("rust_family");

    const surface = container.querySelector('[data-testid="sidebar-page-tree-shell"]');
    const rustHost = container.querySelector(
      '[data-testid="sidebar-page-tree-shell-rust-host"]',
    );
    const domHost = container.querySelector('[data-testid="sidebar-page-tree-shell-dom-host"]');

    expect(surface?.getAttribute("data-renderer-family")).toBe("rust_family");
    expect(surface?.getAttribute("data-tree-host-kind")).toBe("rust_family");
    expect(surface?.getAttribute("data-tree-host-implementation")).toBe("rust_wasm_dom_shell_host");
    expect(surface?.getAttribute("data-tree-renderer-contract")).toBe("rust_renderer_input_v1");
    expect(rustHost?.getAttribute("data-tree-renderer-contract")).toBe("rust_renderer_input_v1");
    expect(rustHost).not.toBeNull();
    expect(domHost).not.toBeNull();
    expect(domHost?.getAttribute("data-tree-runtime-artifact-host")).toBe("rust_tree_shell_runtime_artifact_v1");
    expect(domHost?.getAttribute("data-tree-browser-bridge")).toBe("dom_wasm");
    expect(container.querySelector('[data-testid="sidebar-page-tree-shell-rust-iframe"]')).toBeNull();
    expect(container.querySelector('[data-tree-browser-bridge="iframe_srcdoc"]')).toBeNull();
    expect(container.querySelector('[data-testid="page-tree-renderer-removed"]')).toBeNull();
  });

  it("page tree surface 在 rust_family 下应把 focusedDocumentId 暴露到宿主 DOM 状态", () => {
    renderPageSurface("rust_family", "doc_focus");

    const surface = container.querySelector('[data-testid="sidebar-page-tree-shell"]');
    const domHost = container.querySelector('[data-testid="sidebar-page-tree-shell-dom-host"]');

    expect(surface?.getAttribute("data-page-tree-focused-id")).toBe("doc_focus");
    expect(domHost?.getAttribute("data-tree-browser-bridge")).toBe("dom_wasm");
    expect(container.querySelector('[data-testid="sidebar-page-tree-shell-rust-iframe"]')).toBeNull();
  });

  it("page tree 在 rust_family 下即使 tree shellEnabled=false 也应继续使用默认 DOM shell host", async () => {
    await act(async () => {
      root.render(
        <SidebarTreeSurface
          mode="page"
          rendererFamily="rust_family"
          workspaceId="ws_1"
          treeShellEnabled={false}
          rows={[]}
          expanded={new Set<string>()}
          activeId=""
          onToggleExpand={() => undefined}
          onMove={() => undefined}
          onCreateChild={() => undefined}
          onContextMenu={() => undefined}
        />,
      );
    });

    const surface = container.querySelector('[data-testid="sidebar-page-tree-shell"]');
    expect(surface?.getAttribute("data-tree-host-implementation")).toBe("rust_wasm_dom_shell_host");
    expect(container.querySelector('[data-testid="sidebar-page-tree-shell-dom-host"]')).not.toBeNull();
    expect(container.querySelector('[data-testid="sidebar-page-tree-shell-rust-iframe"]')).toBeNull();
    expect(container.querySelector('[data-testid="page-tree-renderer-removed"]')).toBeNull();
  });

  it("page tree 在缺少 workspaceId 时应显示 Rust 宿主占位，而不是回退旧 React renderer", () => {
    act(() => {
      root.render(
        <SidebarTreeSurface
          mode="page"
          rendererFamily="rust_family"
          workspaceId={null}
          treeShellEnabled
          rows={[]}
          expanded={new Set<string>()}
          activeId=""
          onToggleExpand={() => undefined}
          onMove={() => undefined}
          onCreateChild={() => undefined}
          onContextMenu={() => undefined}
        />,
      );
    });

    const surface = container.querySelector('[data-testid="sidebar-page-tree-shell"]');
    expect(surface?.getAttribute("data-tree-host-implementation")).toBe("page_tree_renderer_removed");
    expect(container.querySelector('[data-testid="page-tree-renderer-removed"]')).not.toBeNull();
  });

  it("file tree surface 在 rust_family 下也应走默认 Rust/WASM DOM shell host", () => {
    act(() => {
      root.render(
        <SidebarTreeSurface
          mode="filetree"
          rendererFamily="rust_family"
          workspaceId="ws_1"
          treeShellEnabled
          rows={[]}
          activeId=""
          onRowClick={() => undefined}
          onRowDoubleClick={() => undefined}
          onRowContextMenu={() => undefined}
          onToggleExpand={() => undefined}
          onCreateChild={() => undefined}
        />,
      );
    });

    const surface = container.querySelector('[data-testid="sidebar-file-tree-shell"]');
    const rustHost = container.querySelector(
      '[data-testid="sidebar-file-tree-shell-rust-host"]',
    );
    const domHost = container.querySelector('[data-testid="sidebar-file-tree-shell-dom-host"]');

    expect(surface?.getAttribute("data-renderer-family")).toBe("rust_family");
    expect(surface?.getAttribute("data-tree-host-kind")).toBe("rust_family");
    expect(surface?.getAttribute("data-tree-host-implementation")).toBe("rust_wasm_dom_shell_host");
    expect(surface?.getAttribute("data-tree-renderer-contract")).toBe("rust_renderer_input_v1");
    expect(rustHost?.getAttribute("data-tree-renderer-contract")).toBe("rust_renderer_input_v1");
    expect(rustHost).not.toBeNull();
    expect(domHost).not.toBeNull();
    expect(domHost?.getAttribute("data-tree-runtime-artifact-host")).toBe("rust_tree_shell_runtime_artifact_v1");
    expect(domHost?.getAttribute("data-tree-browser-bridge")).toBe("dom_wasm");
    expect(container.querySelector('[data-testid="sidebar-file-tree-shell-rust-iframe"]')).toBeNull();
    expect(container.querySelector('[data-tree-browser-bridge="iframe_srcdoc"]')).toBeNull();
    expect(container.querySelector('[data-testid="file-tree-renderer-removed"]')).toBeNull();
  });

  it("file tree 在 rust_family 下即使 tree shellEnabled=false 也应继续使用默认 DOM shell host", async () => {
    await act(async () => {
      root.render(
        <SidebarTreeSurface
          mode="filetree"
          rendererFamily="rust_family"
          workspaceId="ws_1"
          treeShellEnabled={false}
          rows={[]}
          activeId=""
          onRowClick={() => undefined}
          onRowDoubleClick={() => undefined}
          onRowContextMenu={() => undefined}
          onToggleExpand={() => undefined}
          onCreateChild={() => undefined}
        />,
      );
    });

    const surface = container.querySelector('[data-testid="sidebar-file-tree-shell"]');
    expect(surface?.getAttribute("data-tree-host-implementation")).toBe("rust_wasm_dom_shell_host");
    expect(container.querySelector('[data-testid="sidebar-file-tree-shell-dom-host"]')).not.toBeNull();
    expect(container.querySelector('[data-testid="sidebar-file-tree-shell-rust-iframe"]')).toBeNull();
    expect(container.querySelector('[data-testid="file-tree-renderer-removed"]')).toBeNull();
  });

  it("file tree 在缺少 workspaceId 时应显示 Rust 宿主占位，而不是回退旧 React renderer", () => {
    act(() => {
      root.render(
        <SidebarTreeSurface
          mode="filetree"
          rendererFamily="rust_family"
          workspaceId={null}
          treeShellEnabled
          rows={[]}
          activeId=""
          onRowClick={() => undefined}
          onRowDoubleClick={() => undefined}
          onRowContextMenu={() => undefined}
          onToggleExpand={() => undefined}
          onCreateChild={() => undefined}
        />,
      );
    });

    const surface = container.querySelector('[data-testid="sidebar-file-tree-shell"]');
    expect(surface?.getAttribute("data-tree-host-implementation")).toBe("filetree_renderer_removed");
    expect(container.querySelector('[data-testid="file-tree-renderer-removed"]')).not.toBeNull();
  });

  it("file tree surface 应把 DOM shell 内部拖放与外部文件拖放桥接回宿主回调", async () => {
    const runtimeReducerMock = mockTreeRuntimeReducer();
    const onInternalDrop = vi.fn();
    const onDropFiles = vi.fn();
    const droppedFile = new File(["bridge"], "bridge.txt", { type: "text/plain" });
    const dataTransfer = {
      files: [] as File[],
      types: ["application/x-mnote-file-tree", "text/plain"],
      dropEffect: "move",
      getData: (type: string) =>
        type === "application/x-mnote-file-tree" || type === "text/plain"
          ? JSON.stringify({ type: "mnote-file-tree-dnd", version: 1, rowIds: ["doc:doc_source"] })
          : "",
      setData: vi.fn(),
    } as unknown as DataTransfer;
    const externalDataTransfer = {
      files: [droppedFile],
      types: ["Files"],
      dropEffect: "copy",
      getData: () => "",
      setData: vi.fn(),
    } as unknown as DataTransfer;
    const targetProjectionItem: KernelFileTreeProjectionItem = {
      rowId: "doc:doc_target",
      nodeId: "doc_target",
      parentNodeId: null,
      projectionKind: "file_tree",
      title: "目标页面",
      depth: 0,
      position: 0,
      childCount: 0,
      expandable: false,
      expandedByDefault: false,
      nodeType: "page",
      rowKind: "document",
      iconHint: "page",
      capabilities: ["open", "select", "context-menu"],
      resourceMeta: {
        resourceKind: "document",
        documentId: "doc_target",
        workspaceId: "ws_1",
        iconHint: "page",
      },
    };

    await act(async () => {
      root.render(
        <SidebarTreeSurface
          mode="filetree"
          rendererFamily="rust_family"
          workspaceId="ws_1"
          treeShellEnabled
          rows={[]}
          treeShellItems={[targetProjectionItem]}
          activeId=""
          onRowClick={() => undefined}
          onRowDoubleClick={() => undefined}
          onRowContextMenu={() => undefined}
          onToggleExpand={() => undefined}
          onCreateChild={() => undefined}
          onInternalDrop={onInternalDrop}
          onDropFiles={onDropFiles}
        />,
      );
    });

    const targetRow = container.querySelector(
      '[data-testid="filetree-doc-row"][data-document-id="doc_target"]',
    );
    expect(targetRow).not.toBeNull();

    await act(async () => {
      const internalDropEvent = new Event("drop", { bubbles: true, cancelable: true });
      Object.defineProperty(internalDropEvent, "dataTransfer", {
        configurable: true,
        value: dataTransfer,
      });
      targetRow?.dispatchEvent(internalDropEvent);
      const externalDropEvent = new Event("drop", { bubbles: true, cancelable: true });
      Object.defineProperty(externalDropEvent, "dataTransfer", {
        configurable: true,
        value: externalDataTransfer,
      });
      targetRow?.dispatchEvent(externalDropEvent);
    });
    await waitForRuntimeReducer(runtimeReducerMock);

    expect(runtimeReducerMock).toHaveBeenCalledWith(
      expect.objectContaining({
        mode: "fileTree",
        action: expect.objectContaining({ kind: "dispatchInternalDrop" }),
      }),
    );
    expect(onInternalDrop).toHaveBeenCalledWith({
      targetDocumentId: "doc_target",
      targetRowId: "doc:doc_target",
      targetRowKind: "doc",
      targetAssetId: null,
      rowIds: ["doc:doc_source"],
      copy: false,
    });
    expect(onDropFiles).toHaveBeenCalledWith({
      targetDocumentId: "doc_target",
      targetRowId: "doc:doc_target",
      targetRowKind: "doc",
      targetAssetId: null,
      files: [droppedFile],
    });
  });

  it("file tree surface 应支持空白区清空选择", async () => {
    const runtimeReducerMock = mockTreeRuntimeReducer();
    const targetProjectionItem: KernelFileTreeProjectionItem = {
      rowId: "doc:doc_target",
      nodeId: "doc_target",
      parentNodeId: null,
      projectionKind: "file_tree",
      title: "目标页面",
      depth: 0,
      position: 0,
      childCount: 0,
      expandable: false,
      expandedByDefault: false,
      nodeType: "page",
      rowKind: "document",
      iconHint: "page",
      capabilities: ["open", "select", "context-menu"],
      resourceMeta: {
        resourceKind: "document",
        documentId: "doc_target",
        workspaceId: "ws_1",
        iconHint: "page",
      },
    };

    await act(async () => {
      root.render(
        <SidebarTreeSurface
          mode="filetree"
          rendererFamily="rust_family"
          workspaceId="ws_1"
          treeShellEnabled
          rows={[]}
          treeShellItems={[targetProjectionItem]}
          activeId=""
          onRowClick={() => undefined}
          onRowDoubleClick={() => undefined}
          onRowContextMenu={() => undefined}
          onToggleExpand={() => undefined}
          onCreateChild={() => undefined}
        />,
      );
    });

    const targetRow = container.querySelector('[data-testid="filetree-doc-row"][data-row-id="doc:doc_target"]') as HTMLElement | null;
    const treeRoot = container.querySelector('[data-rust-filetree-renderer="dom_v1"]') as HTMLElement | null;
    expect(targetRow).not.toBeNull();
    expect(treeRoot).not.toBeNull();

    await act(async () => {
      targetRow?.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
    });
    await waitForRuntimeReducer(runtimeReducerMock);
    expect(targetRow?.getAttribute("data-selected")).toBe("true");

    await act(async () => {
      treeRoot?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
      await Promise.resolve();
    });

    expect(targetRow?.getAttribute("data-selected")).toBe("false");
  });

  it("picker surface 在 rust_family 下也应挂到默认 Rust/WASM DOM shell host", async () => {
    const onPick = vi.fn();

    await act(async () => {
      root.render(
        <TreePickerSurface
          rendererFamily="rust_family"
          workspaceId="ws_1"
          treeShellEnabled
          items={[{ kind: "doc", id: "doc_1", title: "页面 1", depth: 0 }]}
          highlighted={0}
          onHighlight={() => undefined}
          onPick={onPick}
        />,
      );
    });

    const surface = container.querySelector('[data-testid="tree-picker-surface"]');
    const rustHost = container.querySelector('[data-testid="tree-picker-surface-rust-host"]');
    const domHost = container.querySelector('[data-testid="tree-picker-surface-dom-host"]');

    expect(surface?.getAttribute("data-renderer-family")).toBe("rust_family");
    expect(surface?.getAttribute("data-tree-host-kind")).toBe("rust_family");
    expect(surface?.getAttribute("data-tree-host-implementation")).toBe("rust_wasm_dom_shell_host");
    expect(surface?.getAttribute("data-tree-renderer-contract")).toBe("rust_renderer_input_v1");
    expect(rustHost?.getAttribute("data-tree-renderer-contract")).toBe("rust_renderer_input_v1");
    expect(rustHost).not.toBeNull();
    expect(domHost).not.toBeNull();
    expect(domHost?.getAttribute("data-tree-runtime-artifact-host")).toBe("rust_tree_shell_runtime_artifact_v1");
    expect(domHost?.getAttribute("data-tree-browser-bridge")).toBe("dom_wasm");
    expect(container.querySelector('[data-testid="tree-picker-surface-rust-iframe"]')).toBeNull();
    expect(container.querySelector('[data-tree-browser-bridge="iframe_srcdoc"]')).toBeNull();
    expect(container.querySelector('[data-testid="tree-picker-row"][data-node-id="doc_1"]')).not.toBeNull();
    expect(onPick).not.toHaveBeenCalled();
  });

  it("picker 在 rust_family 下即使 tree shellEnabled=false 也应继续使用默认 DOM shell host", async () => {
    await act(async () => {
      root.render(
        <TreePickerSurface
          rendererFamily="rust_family"
          workspaceId="ws_1"
          treeShellEnabled={false}
          items={[{ kind: "doc", id: "doc_1", title: "页面 1", depth: 0 }]}
          highlighted={0}
          emptyText="没有匹配结果"
          onHighlight={() => undefined}
          onPick={vi.fn()}
        />,
      );
    });

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

    const surface = container.querySelector('[data-testid="tree-picker-surface"]');
    const rustHost = container.querySelector('[data-testid="tree-picker-surface-rust-host"]');
    const domHost = container.querySelector('[data-testid="tree-picker-surface-dom-host"]');

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