import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import type { FileKey, FoldKey } from "../src/types";
import { createUiStateManagerMock } from "./mocks/pluginClassMocks";

describe("UiStateManager", () => {
    const persistedUiStateSaveDelayMs = 500;

    beforeEach(() => {
        vi.useFakeTimers();
        vi.stubGlobal("app", { plugins: { getPlugin: vi.fn(() => ({ settings: { updateDelay: 1000 } })) } });
    });

    afterEach(() => {
        vi.restoreAllMocks();
        vi.unstubAllGlobals();
        vi.useRealTimers();
    });

    test("returns cloned persisted ui state snapshots", () => {
        // Arrange
        const foldKey: FoldKey = "note.md::1";
        const fileKey: FileKey = "note.md";
        const { uiStateManager } = createUiStateManagerMock({
            tocBlockCollapseState: { [fileKey]: true },
            tocFoldState: { [foldKey]: true }
        });

        // Act
        const snapshot = uiStateManager.getPersistedUiState();
        snapshot.tocFoldState.set(foldKey, false);
        snapshot.tocBlockCollapseState.set(fileKey, false);

        // Assert
        expect(uiStateManager.getTocFoldState(foldKey)).toBe(true);
        expect(uiStateManager.getTocBlockCollapsed(fileKey)).toBe(true);
    });

    test("persists fold state changes only when the value changes", () => {
        // Arrange
        const foldKey: FoldKey = "note.md::1";
        const { savePersistedDataSpy, uiStateManager } = createUiStateManagerMock();

        // Act
        uiStateManager.setTocFoldState(foldKey, true);
        uiStateManager.setTocFoldState(foldKey, true);

        // Assert
        expect(uiStateManager.getTocFoldState(foldKey)).toBe(true);
        expect(savePersistedDataSpy).not.toHaveBeenCalled();
    });

    test("batches rapid fold state updates into a single debounced save", async () => {
        // Arrange
        const { savePersistedDataSpy, uiStateManager } = createUiStateManagerMock();

        // Act
        uiStateManager.setTocFoldState("note.md::1", true);
        uiStateManager.setTocFoldState("note.md::2", true);
        await vi.advanceTimersByTimeAsync(persistedUiStateSaveDelayMs);

        // Assert
        expect(savePersistedDataSpy).toHaveBeenCalledTimes(1);
    });

    test("persists block collapse changes only when the value changes", () => {
        // Arrange
        const fileKey = "note.md" as FileKey;
        const { savePersistedDataSpy, uiStateManager } = createUiStateManagerMock();

        // Act
        uiStateManager.setTocBlockCollapsed(fileKey, true);
        uiStateManager.setTocBlockCollapsed(fileKey, true);

        // Assert
        expect(uiStateManager.getTocBlockCollapsed(fileKey)).toBe(true);
        expect(savePersistedDataSpy).not.toHaveBeenCalled();
    });

    test("flushPersistedData saves immediately and cancels a pending debounce", async () => {
        // Arrange
        const foldKey: FoldKey = "note.md::1";
        const { savePersistedDataSpy, uiStateManager } = createUiStateManagerMock();

        // Act
        uiStateManager.setTocFoldState(foldKey, true);
        await uiStateManager.flushPersistedData();
        await vi.runAllTimersAsync();

        // Assert
        expect(savePersistedDataSpy).toHaveBeenCalledTimes(1);
    });

    test("prunes inactive fold keys for a file path and keeps active modern keys", async () => {
        // Arrange
        const activeFoldKey: FoldKey = "note.md::2";
        const { savePersistedDataSpy, uiStateManager } = createUiStateManagerMock({
            tocFoldState: new Map([ [ "note.md::1", true ], [ activeFoldKey, false ], [ "other.md::1", true ] ])
        });

        // Act
        uiStateManager.pruneTocFoldStateForPath("note.md", { activeModernFoldKeys: new Set([ activeFoldKey ]) });

        // Assert
        expect(uiStateManager.getTocFoldState("note.md::1" as FoldKey)).toBeUndefined();
        expect(uiStateManager.getTocFoldState(activeFoldKey)).toBe(false);
        expect(uiStateManager.getTocFoldState("other.md::1" as FoldKey)).toBe(true);
        await vi.advanceTimersByTimeAsync(persistedUiStateSaveDelayMs);
        expect(savePersistedDataSpy).toHaveBeenCalledTimes(1);
    });

    test("moves fold state to the replacement file on rename", async () => {
        // Arrange
        const { savePersistedDataSpy, uiStateManager } = createUiStateManagerMock({
            tocFoldState: new Map([ [ "old-note.md::1", true ], [ "old-note.md::2", false ] ])
        });

        // Act
        uiStateManager.pruneTocFoldStateForPath("old-note.md", { replacementFile: "new-note.md" });

        // Assert
        expect(uiStateManager.getTocFoldState("old-note.md::1" as FoldKey)).toBeUndefined();
        expect(uiStateManager.getTocFoldState("old-note.md::2" as FoldKey)).toBeUndefined();
        expect(uiStateManager.getTocFoldState("new-note.md::1" as FoldKey)).toBe(true);
        expect(uiStateManager.getTocFoldState("new-note.md::2" as FoldKey)).toBe(false);
        await vi.advanceTimersByTimeAsync(persistedUiStateSaveDelayMs);
        expect(savePersistedDataSpy).toHaveBeenCalledTimes(1);
    });
});
