import { describe, expect, it, vi } from "vitest";
import type { Command } from "@/deps";
import {
    REPLICATION_PROGRESS_PRESENTATIONS,
    USER_INITIATED_REPLICATION_AUTHORITY,
} from "@vrtmrz/livesync-commonlib/replication";
import { ModuleBasicMenu } from "./ModuleBasicMenu";

type RegisteredCommand = Command & {
    checkCallback?: (checking: boolean) => boolean | void;
};

function createFixture() {
    const commands: RegisteredCommand[] = [];
    const settings = {
        liveSync: false,
        useAdvancedMode: false,
        enableDebugTools: false,
    };
    const services = {
        API: {
            addLog: vi.fn(),
            addCommand: vi.fn((command: RegisteredCommand) => {
                commands.push(command);
                return command;
            }),
            registerWindow: vi.fn(),
            addRibbonIcon: vi.fn(),
            registerProtocolHandler: vi.fn(),
        },
        replication: {
            replicateUserInitiated: vi.fn(async () => ({ status: "completed" as const })),
            stopActiveTransfer: vi.fn(async () => ({ status: "completed" as const })),
        },
        vault: {
            getActiveFilePath: vi.fn((): string | null => "note.md"),
            scanVault: vi.fn(async () => undefined),
        },
        control: {
            applySettings: vi.fn(async () => undefined),
        },
        setting: {
            saveSettingData: vi.fn(async () => undefined),
        },
        appLifecycle: {
            isSuspended: vi.fn(() => false),
            setSuspended: vi.fn(),
        },
        fileProcessing: {
            commitPendingFileEvents: vi.fn(async () => true),
        },
        UI: {
            promptCopyToClipboard: vi.fn(async (_title: string, _value: string) => true),
        },
        path: {
            path2id: vi.fn(async () => "f:note"),
        },
    };
    const core = {
        settings,
        _services: services,
        services,
        localDatabase: {
            getDBEntry: vi.fn(async () => false),
            localDatabase: {
                get: vi.fn(async () => ({
                    _id: "f:note",
                    _rev: "2-current",
                    _conflicts: [],
                    path: "note.md",
                    ctime: 100,
                    mtime: 200,
                    size: 12,
                    type: "plain",
                    children: ["h:private-chunk-id"],
                    eden: {},
                })),
            },
            getDBEntryMeta: vi.fn(async () => ({
                _id: "f:note",
                _rev: "2-current",
                _conflicts: [],
                path: "note.md",
                ctime: 100,
                mtime: 200,
                size: 12,
                type: "plain",
                datatype: "plain",
                data: "",
                children: ["h:private-chunk-id"],
                eden: {},
            })),
            allDocsRaw: vi.fn(async () => ({
                rows: [{ id: "h:private-chunk-id", key: "h:private-chunk-id", value: { rev: "1-chunk" } }],
            })),
        },
        storageAccess: {
            isExistsIncludeHidden: vi.fn(async () => true),
            statHidden: vi.fn(async () => ({ ctime: 100, mtime: 200, size: 12, type: "file" })),
        },
        replicator: {
            terminateSync: vi.fn(),
        },
    };
    const module = new ModuleBasicMenu(core as never);

    return {
        commands,
        core,
        module,
        services,
        settings,
        getCommand(id: string) {
            const command = commands.find((candidate) => candidate.id === id);
            expect(command, `command ${id}`).toBeDefined();
            return command!;
        },
    };
}

describe("ModuleBasicMenu command palette", () => {
    it("uses clear user-facing names without changing the established command IDs", async () => {
        const fixture = createFixture();

        await fixture.module._everyOnloadStart();

        expect(fixture.getCommand("livesync-replicate").name).toBe("Sync now");
        expect(fixture.getCommand("livesync-runbatch").name).toBe("Apply pending changes now");
    });

    it("keeps Sync now progress quiet while retaining failure-recovery authority", async () => {
        const fixture = createFixture();

        await fixture.module._everyOnloadStart();
        await fixture.getCommand("livesync-replicate").callback?.();

        expect(fixture.services.replication.replicateUserInitiated).toHaveBeenCalledWith({
            trigger: "manual",
            progressPresentation: REPLICATION_PROGRESS_PRESENTATIONS.QUIET,
            interaction: USER_INITIATED_REPLICATION_AUTHORITY,
        });
    });

    it("keeps maintenance commands out of the normal palette", async () => {
        const fixture = createFixture();

        await fixture.module._everyOnloadStart();

        expect(fixture.getCommand("livesync-scan-files").checkCallback?.(true)).toBe(false);
        expect(fixture.getCommand("livesync-abortsync").checkCallback?.(true)).toBe(false);

        fixture.settings.useAdvancedMode = true;
        expect(fixture.getCommand("livesync-scan-files").checkCallback?.(true)).toBe(true);
        expect(fixture.getCommand("livesync-abortsync").checkCallback?.(true)).toBe(true);
    });

    it("routes an explicit stop through the active provider capability", async () => {
        const fixture = createFixture();
        fixture.settings.useAdvancedMode = true;

        await fixture.module._everyOnloadStart();

        expect(fixture.getCommand("livesync-abortsync").checkCallback?.(false)).toBe(true);
        await vi.waitFor(() => {
            expect(fixture.services.replication.stopActiveTransfer).toHaveBeenCalledOnce();
        });
        expect(fixture.core.replicator.terminateSync).not.toHaveBeenCalled();
    });

    it("keeps active-file database information available and opens it in a copy dialogue", async () => {
        const fixture = createFixture();

        await fixture.module._everyOnloadStart();

        const command = fixture.getCommand("livesync-dump");
        expect(command.name).toBe("Copy database information for the active file");
        expect(command.checkCallback?.(true)).toBe(true);

        command.checkCallback?.(false);

        await vi.waitFor(() => {
            expect(fixture.services.UI.promptCopyToClipboard).toHaveBeenCalledOnce();
        });
        const [title, report] = fixture.services.UI.promptCopyToClipboard.mock.calls[0];
        expect(title).toBe("Database information for note.md");
        expect(report).toContain("note.md");
        expect(report).toContain("2-current");
        expect(report).toContain("h:private-chunk-id");
        expect(report).toContain("1-chunk");
        expect(fixture.core.localDatabase.getDBEntry).not.toHaveBeenCalled();
    });

    it("hides the active-file database report when no file is active", async () => {
        const fixture = createFixture();
        fixture.services.vault.getActiveFilePath.mockReturnValue(null);

        await fixture.module._everyOnloadStart();

        expect(fixture.getCommand("livesync-dump").checkCallback?.(true)).toBe(false);
    });
});
