import type { PluginSettingsManager } from "./settings/PluginSettingManager";
import type { FileKey, FoldKey } from "./types";

interface UiState {
    tocFoldState: Map<FoldKey, boolean>;
    tocBlockCollapseState: Map<FileKey, boolean>;
}

export default class UiStateManager implements UiState {
    public tocFoldState: Map<FoldKey, boolean> = new Map<FoldKey, boolean>();
    public tocBlockCollapseState: Map<FileKey, boolean> = new Map<FileKey, boolean>();

    private isPersistedDataDirty = false;
    private saveTimeout: ReturnType<typeof setTimeout> | null = null;
    private saveInFlight: Promise<void> | null = null;

    constructor(private settingsManager: PluginSettingsManager) {}

    public setPersistedUiState(
        tocFoldState: Map<FoldKey, boolean>,
        tocBlockCollapseState: Map<FileKey, boolean>
    ): void {
        this.tocFoldState = new Map(tocFoldState);
        this.tocBlockCollapseState = new Map(tocBlockCollapseState);
    }

    public getPersistedUiState(): UiState {
        return { tocFoldState: new Map(this.tocFoldState), tocBlockCollapseState: new Map(this.tocBlockCollapseState) };
    }

    public getTocFoldState(key: FoldKey): boolean | undefined {
        return this.tocFoldState.get(key);
    }

    public setTocFoldState(key: FoldKey, isCollapsed: boolean): void {
        if (this.tocFoldState.get(key) === isCollapsed) {
            return;
        }

        this.tocFoldState.set(key, isCollapsed);
        this.markPersistedDataDirty();
    }

    public getTocBlockCollapsed(sourcePath: FileKey): boolean {
        return this.tocBlockCollapseState.get(sourcePath) ?? false;
    }

    public setTocBlockCollapsed(sourcePath: FileKey, isCollapsed: boolean): void {
        const current = this.tocBlockCollapseState.get(sourcePath) ?? false;
        if (current === isCollapsed) {
            return;
        }

        this.tocBlockCollapseState.set(sourcePath, isCollapsed);
        this.markPersistedDataDirty();
    }

    public async flushPersistedData(): Promise<void> {
        this.clearScheduledSave();

        while (true) {
            if (this.saveInFlight) {
                await this.saveInFlight;
            }

            if (!this.isPersistedDataDirty) {
                return;
            }

            this.isPersistedDataDirty = false;
            this.saveInFlight = this.settingsManager.savePersistedData();

            try {
                await this.saveInFlight;
            }
            finally {
                this.saveInFlight = null;
            }
        }
    }

    public pruneTocFoldStateForPath(
        sourcePath: string,
        opts: { replacementFile?: FileKey; activeModernFoldKeys?: Set<FoldKey>; }
    ): void {
        let foldStateChanged = false;

        for (const key of Array.from(this.tocFoldState.keys())) {
            if (key.startsWith(`${sourcePath}::`) && (opts.replacementFile || !opts.activeModernFoldKeys?.has(key))) {
                if (opts.replacementFile) {
                    const oldValue = this.getTocFoldState(key) ?? false;
                    const newKey = key.replace(sourcePath, opts.replacementFile) as FoldKey;
                    this.tocFoldState.set(newKey, oldValue);
                }

                this.tocFoldState.delete(key);

                if (!foldStateChanged) foldStateChanged = true;
            }
        }

        if (!foldStateChanged) {
            return;
        }

        this.markPersistedDataDirty();
    }

    private markPersistedDataDirty(): void {
        this.isPersistedDataDirty = true;
        this.schedulePersistedDataSave();
    }

    private schedulePersistedDataSave(): void {
        const timeoutDelay = this.settingsManager.settingsWrapper.settings.updateDelay;
        this.clearScheduledSave();
        this.saveTimeout = setTimeout(async () => {
            console.log(`Timeout: ${timeoutDelay}`);
            await this.flushPersistedData();
        }, timeoutDelay);
    }

    private clearScheduledSave(): void {
        if (this.saveTimeout === null) {
            return;
        }

        clearTimeout(this.saveTimeout);
        this.saveTimeout = null;
    }
}
