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

import { useAutosave } from "./useAutosave";
import { AUTOSAVE_DELAY_MS } from "./autosave";

// 后端客户端与 Toast 是 hook 的副作用边界，单测一律 mock；防抖逻辑（autosave.ts）保留真实。
const put = vi.fn();
const toast = vi.fn();
vi.mock("@/lib/api/client", () => ({ api: { PUT: (...a: unknown[]) => put(...a) } }));
vi.mock("@/components/Toast", () => ({ useToast: () => toast }));

describe("useAutosave", () => {
  beforeEach(() => {
    vi.useFakeTimers();
    put.mockReset();
    toast.mockReset();
    put.mockResolvedValue({ error: null });
  });
  afterEach(() => {
    vi.useRealTimers();
    vi.clearAllMocks();
  });

  it("无初始草稿时初始为 idle、无标签", () => {
    // Arrange + Act
    const { result } = renderHook(() => useAutosave("p1", 1));
    // Assert
    expect(result.current.status).toBe("idle");
    expect(result.current.savedLabel).toBeNull();
  });

  it("种入已存草稿时初始即为 saved 并显示「已保存」", () => {
    const { result } = renderHook(() => useAutosave("p1", 1, "旧草稿"));
    expect(result.current.status).toBe("saved");
    expect(result.current.savedLabel).toBe("已保存");
  });

  it("防抖合并：快速多次改动只在静默后保存一次", async () => {
    // Arrange
    const { result } = renderHook(() => useAutosave("p1", 2));

    // Act：连续三次输入，未到延迟前不应保存。
    act(() => {
      result.current.onChange("a");
      result.current.onChange("ab");
      result.current.onChange("abc");
    });
    expect(put).not.toHaveBeenCalled();

    await act(async () => {
      await vi.advanceTimersByTimeAsync(AUTOSAVE_DELAY_MS);
    });

    // Assert：只发一次 PUT，且是最后一次文本；状态落到 saved + 时间标签。
    expect(put).toHaveBeenCalledTimes(1);
    expect(put).toHaveBeenCalledWith(
      "/projects/{project_id}/chapters/{chapter_no}/draft",
      {
        params: { path: { project_id: "p1", chapter_no: 2 } },
        body: { text: "abc" },
      },
    );
    expect(result.current.status).toBe("saved");
    expect(result.current.savedLabel).toMatch(/^保存于 \d{2}:\d{2}$/);
  });

  it("flush 立即触发待保存项，无需等待延迟", async () => {
    const { result } = renderHook(() => useAutosave("p1", 3));
    act(() => result.current.onChange("即时"));
    await act(async () => {
      result.current.flush();
      await Promise.resolve();
    });
    expect(put).toHaveBeenCalledTimes(1);
    expect(result.current.status).toBe("saved");
  });

  it("文本与已存基线相同时早退、不发请求", async () => {
    const { result } = renderHook(() => useAutosave("p1", 4, "基线"));
    act(() => result.current.onChange("基线"));
    await act(async () => {
      await vi.advanceTimersByTimeAsync(AUTOSAVE_DELAY_MS);
    });
    expect(put).not.toHaveBeenCalled();
    expect(result.current.status).toBe("saved");
  });

  it("后端返回 error：状态回滚为 error 并弹 toast", async () => {
    put.mockResolvedValue({ error: { detail: "boom" } });
    const { result } = renderHook(() => useAutosave("p1", 5));
    act(() => result.current.onChange("变更"));
    await act(async () => {
      await vi.advanceTimersByTimeAsync(AUTOSAVE_DELAY_MS);
    });
    expect(result.current.status).toBe("error");
    expect(toast).toHaveBeenCalledWith(expect.any(String), "error");
  });

  it("卸载时取消待保存定时器，不再发请求", async () => {
    const { result, unmount } = renderHook(() => useAutosave("p1", 6));
    act(() => result.current.onChange("待保存"));
    unmount();
    await act(async () => {
      await vi.advanceTimersByTimeAsync(AUTOSAVE_DELAY_MS * 2);
    });
    expect(put).not.toHaveBeenCalled();
  });
});
