import { parseYaml as parseYml } from "obsidian";
import { deepMerge } from "../Utils";
import type { HeadingLevel, LocalTocSettings, ParseLocalTocSettingsResult, TocBlockTitle } from "../types";
import { getDefaultLocalSettings, type InstaTocSettings } from "./Settings";

type PartialLocalTocSettings = Partial<LocalTocSettings> & Record<string, unknown>;

function isHeadingLevel(value: any): value is HeadingLevel {
    return [ 1, 2, 3, 4, 5, 6 ].includes(value);
}

function sanitizeYaml(value: string): string {
    return value
        .replace(/(\w+):(\S)/g, "$1: $2") // Add space after colon: "min:1" → "min: 1"
        .replace(
            /(?<indent>^[ \t]*)(?<key>[A-Za-z_][A-Za-z0-9_-]*[ \t]*:)[ \t]*(?:(?<value>[^:\n#]+)[ \t]+)?(?<nextKeyWithValue>[A-Za-z_][A-Za-z0-9_-]*[ \t]*:(?:[ \t]*(?:#.*)?|[ \t]+[^#\n]*(?:#.*)?))$/gm,
            "$<indent>$<key> $<value>\n$<indent>$<nextKeyWithValue>"
        ); // Newline before each key: "min: 1 max: 6" → "min: 1\nmax: 6"
}

function parseYaml<T>(yaml: string): T {
    return parseYml(yaml) as T;
}

export function normalizeLocalTocSettings(settings: Partial<LocalTocSettings> | null | undefined): LocalTocSettings {
    return {
        title: {
            name: settings?.title?.name ?? null,
            level: settings?.title?.level ?? null,
            center: settings?.title?.center ?? null
        },
        exclude: settings?.exclude ?? null,
        omit: settings?.omit == null ? null : [ ...settings.omit ],
        levels: { min: settings?.levels?.min ?? null, max: settings?.levels?.max ?? null }
    };
}

export function parseLocalTocSettingsYaml(yaml: string): ParseLocalTocSettingsResult {
    const defaults = getDefaultLocalSettings();

    if (yaml.trim().length === 0) {
        return { settings: defaults, errors: [] };
    }

    let parsed: PartialLocalTocSettings;

    try {
        parsed = parseYaml(sanitizeYaml(yaml));
    }
    catch (error) {
        return { settings: defaults, errors: [ `Invalid YAML in insta-toc settings: ${String(error)}` ] };
    }

    const errors: string[] = [];

    if (parsed.title != null) {
        if (typeof parsed.title !== "object") {
            errors.push("'title' must be an object.");
        }
        else {
            const { name, level, center } = parsed.title;

            if (name != null && typeof name !== "string") {
                parsed.title.name = String(name);
            }

            if (level != null && !isHeadingLevel(level)) {
                errors.push("'title.level' must be an integer between 1 and 6.");
            }

            if (center != null && typeof center !== "boolean") {
                errors.push("'title.center' must be a boolean.");
            }
        }
    }

    if (parsed.exclude != null && typeof parsed.exclude !== "string") {
        parsed.exclude = String(parsed.exclude);
    }

    if (parsed.omit != null) {
        if (!Array.isArray(parsed.omit)) {
            errors.push("'omit' must be an array of strings.");
        }
        else {
            parsed.omit = parsed.omit.map((item) => item == null ? "" : String(item));
        }
    }

    if (parsed.levels != null) {
        if (typeof parsed.levels !== "object") {
            errors.push("'levels' must be an object.");
        }
        else {
            const { min, max } = parsed.levels;

            if (min != null && !isHeadingLevel(min)) {
                errors.push("'levels.min' must be an integer between 1 and 6.");
            }

            if (max != null && !isHeadingLevel(max)) {
                errors.push("'levels.max' must be an integer between 1 and 6.");
            }

            if (min != null && max != null && min > max) {
                errors.push("'levels.min' cannot be greater than 'levels.max'.");
            }
        }
    }

    if (errors.length > 0) {
        return { settings: defaults, errors };
    }

    return {
        settings: normalizeLocalTocSettings(deepMerge<LocalTocSettings>(defaults, parsed, { dedupArrays: true })),
        errors: []
    };
}

export function resolveTocTitle(
    localTocSettings: Pick<LocalTocSettings, "title">,
    settings: Pick<InstaTocSettings, "tocTitle" | "tocTitleLevel" | "tocTitleCentered">
): TocBlockTitle | null {
    const text = localTocSettings.title.name ?? settings.tocTitle;

    if (text.trim().length === 0) {
        return null;
    }

    return {
        text,
        level: localTocSettings.title.level ?? settings.tocTitleLevel,
        centerOverride: localTocSettings.title.center
    };
}
