import { act, useEffect } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createRoot, type Root } from "react-dom/client";
import { useSidebarTreeStream } from "./use-sidebar-tree-stream";
import type { SidebarInitialData } from "@/components/sidebar/types";

type MockEventListener = (event: MessageEvent<string>) => void;

class MockEventSource {
  static instances: MockEventSource[] = [];

  readonly url: string;
  readonly withCredentials: boolean;
  readonly listeners = new Map<string, Set<MockEventListener>>();
  onmessage: MockEventListener | null = null;
  onerror: (() => void) | null = null;
  closed = false;

  constructor(url: string, options?: EventSourceInit) {
    this.url = url;
    this.withCredentials = Boolean(options?.withCredentials);
    MockEventSource.instances.push(this);
  }

  addEventListener(type: string, listener: EventListener) {
    const typedListener = listener as unknown as MockEventListener;
    const next = this.listeners.get(type) ?? new Set<MockEventListener>();
    next.add(typedListener);
    this.listeners.set(type, next);
  }

  removeEventListener(type: string, listener: EventListener) {
    const typedListener = listener as unknown as MockEventListener;
    this.listeners.get(type)?.delete(typedListener);
  }

  close() {
    this.closed = true;
  }

  emit(type: string, payload: unknown) {
    const event = {
      type,
      data: JSON.stringify(payload),
    } as MessageEvent<string>;
    this.listeners.get(type)?.forEach((listener) => listener(event));
    if (type === "message" && this.onmessage) {
      this.onmessage(event);
    }
  }
}

declare global {
  // eslint-disable-next-line no-var
  var EventSource: typeof MockEventSource;
}

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

function buildInitialData(): SidebarInitialData {
  return {
    activeWorkspaceId: "ws_1",
    workspaces: [],
    documents: [],
    kernelSidebarProjection: {
      projectionId: "kernel_projection:sidebar_tree:workspace_root",
      projection: "sidebar_tree",
      rootNodeId: null,
      items: [],
      edges: [],
    },
    kernelSidebarTree: [],
    trashedDocuments: [],
    trashedMediaAssets: [],
    trashedMindmapAssets: [],
    trashedTableAssets: [],
    tableAssets: [],
    mindmapDocs: [],
    mindmapAssets: [],
    mindmapAssetChildren: {},
    mediaAssets: [],
  };
}

function Harness({ onState }: { onState: (state: ReturnType<typeof useSidebarTreeStream>) => void }) {
  const state = useSidebarTreeStream(buildInitialData());

  useEffect(() => {
    onState(state);
  }, [onState, state]);

  return null;
}

describe("useSidebarTreeStream", () => {
  let container: HTMLDivElement;
  let root: Root;
  const onState = vi.fn();

  beforeEach(() => {
    (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
      true;
    vi.stubGlobal("window", {
      ...window,
      location: {
        ...window.location,
        origin: "http://127.0.0.1:3000",
      },
    });
    MockEventSource.instances = [];
    globalThis.EventSource = MockEventSource as unknown as typeof EventSource;
    container = document.createElement("div");
    document.body.appendChild(container);
    root = createRoot(container);
    onState.mockClear();
  });

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

  it("收到带 cursor 的 snapshot 后不应重建 EventSource", async () => {
    await act(async () => {
      root.render(<Harness onState={onState} />);
      await flush();
      await flush();
    });

    expect(MockEventSource.instances).toHaveLength(1);

    await act(async () => {
      MockEventSource.instances[0]?.emit("snapshot", {
        stream: "workspace",
        workspaceId: "ws_1",
        cursor: "evt_2",
        projection: "sidebar_tree",
        data: {
          active_workspace_id: "ws_1",
          workspaces: [],
          documents: [],
          kernel_sidebar_projection: {
            projectionId: "kernel_projection:sidebar_tree:workspace_root",
            projection: "sidebar_tree",
            rootNodeId: null,
            items: [],
            edges: [],
          },
          trashed_documents: [],
          media_assets: [],
          trashed_media_assets: [],
          mindmap_assets: [],
          trashed_mindmap_assets: [],
          table_assets: [],
          trashed_table_assets: [],
          mindmap_docs: [],
          mindmap_asset_children: {},
        },
      });
      await flush();
      await flush();
    });

    expect(MockEventSource.instances).toHaveLength(1);
    expect(onState).toHaveBeenLastCalledWith(
      expect.objectContaining({
        cursor: "evt_2",
        status: "live",
      }),
    );
  });
});
