import type { PluginManifest } from "obsidian";
import { describe, expect, test, vi } from "vitest";

vi.mock("obsidian-dev-utils/obsidian/plugin/plugin-settings-manager-base", () => {
    return {
        PluginSettingsManagerBase: class PluginSettingsManagerBase {
            public app: unknown;
            public defaultSettings: Record<string, unknown>;
            public plugin: { app: unknown; loadData: () => Promise<Record<string, unknown> | null | undefined>; };
            public settingsWrapper: { settings: Record<string, unknown>; };

            public constructor(
                plugin: { app: unknown; loadData: () => Promise<Record<string, unknown> | null | undefined>; }
            ) {
                this.app = plugin.app;
                this.defaultSettings = this.createDefaultSettings();
                this.plugin = plugin;
                this.settingsWrapper = { settings: { ...this.defaultSettings } };
                this.registerValidators();
            }

            public async loadFromFile(_isInitialLoad: boolean): Promise<void> {
                const data = await this.plugin.loadData();
                const rawRecord = JSON.parse(JSON.stringify(data ?? {})) as Record<string, unknown>;

                await this.onLoadRecord(rawRecord);

                this.settingsWrapper = { settings: { ...this.defaultSettings, ...rawRecord } };
            }

            public async saveToFile(): Promise<void> {}
            public registerValidators(): void {}

            protected createDefaultSettings(): Record<string, unknown> {
                return {};
            }

            protected async onLoadRecord(_rawRecord: Record<string, unknown>): Promise<void> {}
            protected async onSavingRecord(_rawRecord: Record<string, unknown>): Promise<void> {}
            protected registerValidator(_key: string, _validator: (value: any) => string | void): void {}
        }
    };
});

vi.mock("obsidian-dev-utils/obsidian/plugin/plugin-base", () => ({
    PluginBase: class PluginBase {
        public app: unknown;
        public manifest: unknown;

        public constructor(app?: unknown, manifest?: unknown) {
            this.app = app;
            this.manifest = manifest;
        }

        public registerEvent(): void {}
        public registerMarkdownCodeBlockProcessor(): void {}
        public addCommand(): void {}
        public addRibbonIcon(): void {}
        public async onloadImpl(): Promise<void> {}
        public async onunloadImpl(): Promise<void> {}
    }
}));

vi.mock("obsidian-dev-utils/obsidian/plugin/plugin-context", () => ({ initPluginContext: vi.fn() }));

vi.mock("../src/settings/SettingsTab", () => ({ SettingTab: class SettingTab {} }));

vi.mock(
    "../src/svelte",
    () => ({
        CodeBlockComponent: class CodeBlockComponent {},
        LocalSettingsModal: class LocalSettingsModal {
            public async open(): Promise<void> {}
        },
        MarkdownComponentMounter: class MarkdownComponentMounter {}
    })
);

import InstaTocPlugin from "../src/Plugin";
import { injectGlobals } from "../src/globals/globalFuncs";
import type { PluginSettingsManager } from "../src/settings/PluginSettingManager";

injectGlobals();

function createPluginWithPersistedData(
    data: Record<string, unknown>
): InstaTocPlugin & { loadData: ReturnType<typeof vi.fn>; saveData: ReturnType<typeof vi.fn>; } {
    return Object.assign(Object.create(InstaTocPlugin.prototype), {
        app: {},
        loadData: vi
            .fn(async () => JSON.parse(JSON.stringify(data)) as Record<string, unknown>),
        manifest: {
            author: "Nick C.",
            description: "Simultaneously generate, update, and maintain a table of contents for your notes.",
            id: "insta-toc",
            minAppVersion: "0.15.0",
            name: "Insta TOC",
            version: "7.0.0"
        } satisfies PluginManifest,
        saveData: vi.fn(async () => undefined)
    });
}

function createSettingsManager(plugin: InstaTocPlugin): PluginSettingsManager {
    const createSettingsManager = Reflect.get(InstaTocPlugin.prototype as object, "createSettingsManager") as (
        this: InstaTocPlugin
    ) => PluginSettingsManager;

    return createSettingsManager.call(plugin);
}

describe("InstaTocPlugin settings initialization", () => {
    test("loads persisted ui state through the settings manager without requiring onloadImpl", async () => {
        // Arrange
        const persistedData = {
            settings: {
                excludedChars: [ "*", "_", "#" ],
                excludedHeadingLevels: [ 2 ],
                excludedHeadingText: [ "Draft" ],
                tocTitle: "Contents",
                tocTitleCentered: false,
                tocTitleLevel: 2,
                updateDelay: 1000
            },
            tocBlockCollapseState: { "folder/note.md": true },
            tocFoldState: { "folder/note.md::1": true, "folder/note.md::2": false }
        };
        const plugin = createPluginWithPersistedData(persistedData);
        const settingsManager = createSettingsManager(plugin);

        // Act
        await settingsManager.loadFromFile(true);
        await settingsManager.savePersistedData();

        // Assert
        expect(plugin.uiStateManager.getPersistedUiState()).toEqual({
            tocBlockCollapseState: new Map([ [ "folder/note.md", true ] ]),
            tocFoldState: new Map([ [ "folder/note.md::1", true ], [ "folder/note.md::2", false ] ])
        });
        expect(settingsManager.settingsWrapper.settings.tocTitle).toBe("Contents");
        expect(plugin.saveData).toHaveBeenCalledWith({
            settings: persistedData.settings,
            tocBlockCollapseState: { "folder/note.md": true },
            tocFoldState: { "folder/note.md::1": true, "folder/note.md::2": false }
        });
    });
});
