import type { HeadingCache } from "obsidian";
import { describe, expect, test, vi } from "vitest";
import { ManageToc } from "../src/ManageToc";
import { getDefaultLocalSettings } from "../src/settings/Settings";
import { createEditor, createPluginMock, createValidatorMock } from "./mocks/pluginClassMocks";

function getEndPosition(text: string): { line: number; ch: number; } {
    const lines = text.split("\n");

    return { line: lines.length - 1, ch: lines[lines.length - 1]?.length ?? 0 };
}

describe("ManageToc global/local setting precedence", () => {
    test("falls back to global tocTitle when local title.name is null", async () => {
        const localSettings = getDefaultLocalSettings();
        localSettings.title.name = null;
        localSettings.title.level = null;
        localSettings.title.center = null;

        const fileHeadings = [ { heading: "Heading 1", level: 1 }, {
            heading: "Heading 2",
            level: 2
        } ] as HeadingCache[];

        const { validator } = createValidatorMock(localSettings, fileHeadings);
        const pluginMock = createPluginMock({ tocTitle: "Global Table of Contents" });

        await ManageToc.run(pluginMock.plugin.editorService, pluginMock.plugin.settings, validator);

        const generatedTocBlock = pluginMock.getCapturedContent();

        expect(generatedTocBlock).toContain("Global Table of Contents");
        expect(generatedTocBlock).not.toContain("null Global Table of Contents");
        expect(generatedTocBlock).toContain("\n    - Heading 2");
    });

    test("prefers local title settings over global defaults when provided", async () => {
        const localSettings = getDefaultLocalSettings();
        localSettings.title.name = "Local TOC";
        localSettings.title.level = 2;
        localSettings.title.center = true;

        const fileHeadings = [ { heading: "Heading 1", level: 1 } ] as HeadingCache[];

        const { validator } = createValidatorMock(localSettings, fileHeadings);
        const pluginMock = createPluginMock({ tocTitle: "Global Table of Contents" });

        await ManageToc.run(pluginMock.plugin.editorService, pluginMock.plugin.settings, validator);

        const generatedTocBlock = pluginMock.getCapturedContent();

        expect(generatedTocBlock).toContain("## Local TOC");
        expect(generatedTocBlock).not.toContain("Global Table of Contents");
    });

    test("updates only the minimal changed range for an existing toc block", async () => {
        const localSettings = getDefaultLocalSettings();
        const initialHeadings = [ { heading: "Heading 1", level: 1 }, {
            heading: "Old Heading 2",
            level: 1
        } ] as HeadingCache[];
        const updatedHeadings = [ { heading: "Heading 1", level: 1 }, {
            heading: "Heading 2",
            level: 1
        } ] as HeadingCache[];

        const { validator } = createValidatorMock(localSettings, initialHeadings);
        const pluginMock = createPluginMock(undefined, createEditor([]));

        await ManageToc.run(pluginMock.plugin.editorService, pluginMock.plugin.settings, validator);

        const initialTocBlock = pluginMock.getCapturedContent();
        validator.fileHeadings = updatedHeadings;
        validator.tocInsertPos = { from: { line: 0, ch: 0 }, to: getEndPosition(initialTocBlock) };

        const applySpy = vi.spyOn(pluginMock.plugin.editorService, "applyEditorChange");

        await ManageToc.run(pluginMock.plugin.editorService, pluginMock.plugin.settings, validator);

        expect(pluginMock.getCapturedContent()).toContain("- Heading 2");
        expect(pluginMock.getCapturedContent()).not.toContain("- Old Heading 2");
        expect(applySpy).toHaveBeenCalledTimes(1);

        const [ from, to, insert ] = applySpy.mock.calls[0] ?? [];

        expect(from).toEqual({ line: 16, ch: 2 });
        expect(to).toEqual({ line: 16, ch: 6 });
        expect(insert).toBe("");
    });
});
