// @vitest-environment jsdom
import { act, renderHook, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { useContextDrawerData } from "./useContextDrawer";

// 后端客户端与 Toast 是 hook 的外部副作用边界，单测一律 mock。
const get = vi.fn();
const toast = vi.fn();
vi.mock("@/lib/api/client", () => ({
  api: { GET: (...a: unknown[]) => get(...a) },
}));
vi.mock("@/components/Toast", () => ({ useToast: () => toast }));

const CODEX_PATH = "/projects/{project_id}/world_entities";
const OUTLINE_PATH = "/projects/{project_id}/outline";

// 按路径分流 codex / outline 两个端点的 mock 响应。
function routeGet(
  responses: {
    codex?: unknown;
    outline?: unknown;
    codexReject?: boolean;
    outlineReject?: boolean;
  } = {},
): void {
  get.mockImplementation((path: string) => {
    if (path === CODEX_PATH) {
      if (responses.codexReject) return Promise.reject(new Error("net"));
      return Promise.resolve(responses.codex ?? { data: null, error: null });
    }
    if (path === OUTLINE_PATH) {
      if (responses.outlineReject) return Promise.reject(new Error("net"));
      return Promise.resolve(responses.outline ?? { data: null, error: null });
    }
    return Promise.resolve({ data: null, error: null });
  });
}

describe("useContextDrawerData", () => {
  beforeEach(() => {
    get.mockReset();
    toast.mockReset();
  });
  afterEach(() => vi.clearAllMocks());

  it("抽屉关闭时不发请求，两资源保持 idle", () => {
    routeGet();
    const { result } = renderHook(() =>
      useContextDrawerData("p1", "codex", false),
    );
    expect(get).not.toHaveBeenCalled();
    expect(result.current.codex.status).toBe("idle");
    expect(result.current.outline.status).toBe("idle");
  });

  it("打开且在设定库 Tab：拉取并归组到 ready", async () => {
    routeGet({
      codex: {
        data: {
          world_entities: [
            { type: "势力", name: "雾港议会", rules: [] },
            { type: "地理", name: "北境", rules: [] },
          ],
        },
        error: null,
      },
    });
    const { result } = renderHook(() =>
      useContextDrawerData("p1", "codex", true),
    );
    await waitFor(() => expect(result.current.codex.status).toBe("ready"));
    expect(result.current.codex.data.map((g) => g.type)).toEqual([
      "势力",
      "地理",
    ]);
    expect(get).toHaveBeenCalledTimes(1);
    expect(toast).not.toHaveBeenCalled();
  });

  it("打开且在大纲 Tab：拉取并按章号升序整形", async () => {
    routeGet({
      outline: {
        data: {
          chapters: [
            { no: 2, volume: 1, beats: ["决战"], foreshadow_windows: [] },
            { no: 1, volume: 1, beats: ["开场"], foreshadow_windows: [] },
          ],
        },
        error: null,
      },
    });
    const { result } = renderHook(() =>
      useContextDrawerData("p1", "outline", true),
    );
    await waitFor(() => expect(result.current.outline.status).toBe("ready"));
    expect(result.current.outline.data.map((r) => r.no)).toEqual([1, 2]);
  });

  it("data 为空时 ready 且为空数组", async () => {
    routeGet({ codex: { data: { world_entities: null }, error: null } });
    const { result } = renderHook(() =>
      useContextDrawerData("p1", "codex", true),
    );
    await waitFor(() => expect(result.current.codex.status).toBe("ready"));
    expect(result.current.codex.data).toEqual([]);
  });

  it("后端返回 error：error 态 + 错误 toast", async () => {
    routeGet({ codex: { data: null, error: { detail: "boom" } } });
    const { result } = renderHook(() =>
      useContextDrawerData("p1", "codex", true),
    );
    await waitFor(() => expect(result.current.codex.status).toBe("error"));
    expect(toast).toHaveBeenCalledWith(expect.any(String), "error");
  });

  it("请求抛异常：error 态 + 网络异常 toast", async () => {
    routeGet({ codexReject: true });
    const { result } = renderHook(() =>
      useContextDrawerData("p1", "codex", true),
    );
    await waitFor(() => expect(result.current.codex.status).toBe("error"));
    expect(toast).toHaveBeenCalledWith(
      "设定库加载异常，请检查网络。",
      "error",
    );
  });

  it("已加载后切走再切回不重复拉取（缓存）", async () => {
    routeGet({
      codex: {
        data: { world_entities: [{ type: "势力", name: "甲", rules: [] }] },
        error: null,
      },
    });
    const { result, rerender } = renderHook(
      ({ tab }: { tab: "codex" | "outline" }) =>
        useContextDrawerData("p1", tab, true),
      { initialProps: { tab: "codex" } as { tab: "codex" | "outline" } },
    );
    await waitFor(() => expect(result.current.codex.status).toBe("ready"));
    rerender({ tab: "outline" });
    rerender({ tab: "codex" });
    expect(get.mock.calls.filter((c) => c[0] === CODEX_PATH)).toHaveLength(1);
  });

  it("reloadCodex 在出错后可重试拉取", async () => {
    routeGet({ codex: { data: null, error: { detail: "boom" } } });
    const { result } = renderHook(() =>
      useContextDrawerData("p1", "codex", true),
    );
    await waitFor(() => expect(result.current.codex.status).toBe("error"));

    routeGet({
      codex: {
        data: { world_entities: [{ type: "势力", name: "甲", rules: [] }] },
        error: null,
      },
    });
    act(() => result.current.reloadCodex());
    await waitFor(() => expect(result.current.codex.status).toBe("ready"));
    expect(result.current.codex.data).toHaveLength(1);
  });
});
