import {
    Component,
    MarkdownPostProcessorContext,
    Notice,
    setIcon
} from "obsidian";
import { Admonition } from "src/@types";
import ObsidianAdmonition from "src/main";
import { CalloutSuggest } from "../suggest/suggest";

type Heights = Partial<{
    height: string;
    "padding-top": string;
    "padding-bottom": string;
    "margin-top": string;
    "margin-bottom": string;
}>;

export default class CalloutManager extends Component {
    /* ruleMap: Map<string, number> = new Map(); */
    constructor(public plugin: ObsidianAdmonition) {
        super();
    }

    onload() {
        //build sheet for custom admonitions

        document.head.appendChild(this.style);

        for (const admonition of Object.values(
            this.plugin.data.userAdmonitions
        )) {
            this.addAdmonition(admonition);
        }
        this.setUseSnippet();

        this.plugin.registerEditorSuggest(new CalloutSuggest(this.plugin));

        this.plugin.registerMarkdownPostProcessor(
            this.calloutProcessor.bind(this)
        );
    }
    heights: Array<keyof Heights> = [
        "height",
        "padding-top",
        "padding-bottom",
        "margin-top",
        "margin-bottom"
    ];
    heightMap: WeakMap<HTMLDivElement, Heights> = new WeakMap();
    calloutProcessor(el: HTMLElement, ctx: MarkdownPostProcessorContext) {
        const callout = el?.querySelector<HTMLDivElement>(".callout");
        if (!callout) return;
        //apply metadata

        const type = callout.dataset.callout;
        const admonition = this.plugin.admonitions[type];
        if (!admonition) return;

        const titleEl = callout.querySelector<HTMLDivElement>(".callout-title");
        const content =
            callout.querySelector<HTMLDivElement>(".callout-content");

        const section = ctx.getSectionInfo(el);
        if (section) {
            const { text, lineStart, lineEnd } = section;
            const definition = text.split("\n")[lineStart];

            const [, metadata] = definition.match(/> \[!.+\|(.*)]/) ?? [];
            if (metadata) {
                callout.dataset.calloutMetadata = metadata;
            }

            if (
                this.plugin.admonitions[type].copy ??
                this.plugin.data.copyButton
            ) {
                let copy = content.createDiv("admonition-content-copy");
                setIcon(copy, "copy");
                copy.addEventListener("click", () => {
                    navigator.clipboard
                        .writeText(
                            text
                                .split("\n")
                                .slice(lineStart + 1, lineEnd + 1)
                                .join("\n")
                                .replace(/^> /gm, "")
                        )
                        .then(async () => {
                            new Notice("Callout content copied to clipboard.");
                        });
                });
            }
        }

        if (admonition.noTitle && !callout.dataset.calloutFold) {
            titleEl.addClass("no-title");
        }
        if (
            !admonition.noTitle &&
            this.plugin.data.autoCollapse &&
            !callout.dataset.calloutFold
        ) {
            this.setCollapsible(callout);
        }

        if (
            admonition.title &&
            titleEl.textContent ==
                type[0].toUpperCase() + type.slice(1).toLowerCase()
        ) {
            const titleContentEl = titleEl.querySelector<HTMLDivElement>(
                ".callout-title-inner"
            );
            if (titleContentEl) {
                titleContentEl.setText(admonition.title);
            }
        }
        if (this.plugin.data.dropShadow) {
            callout.addClass("drop-shadow");
        }
    }

    setCollapsible(callout: HTMLElement) {
        const titleEl = callout.querySelector<HTMLDivElement>(".callout-title");
        const content =
            callout.querySelector<HTMLDivElement>(".callout-content");

        if (!content) return;
        callout.addClass("is-collapsible");
        if (this.plugin.data.defaultCollapseType == "closed") {
            callout.dataset.calloutFold = "-";
            callout.addClass("is-collapsed");
        } else {
            callout.dataset.calloutFold = "+";
        }

        const iconEl = titleEl.createDiv("callout-fold");

        setIcon(iconEl, "chevron-down");

        let collapsed = callout.hasClass("is-collapsed");

        this.getComputedHeights(content);

        if (collapsed) {
            for (const prop of this.heights) {
                content.style.setProperty(prop, "0px");
            }
        }
        titleEl.onclick = (event: MouseEvent) => {
            event.preventDefault();

            function transitionEnd(evt: TransitionEvent) {
                content.removeEventListener("transitionend", transitionEnd);
                content.style.removeProperty("transition");
            }
            content.addEventListener("transitionend", transitionEnd);
            content.style.setProperty(
                "transition",
                "all 100ms cubic-bezier(.02, .01, .47, 1)"
            );
            collapsed = callout.hasClass("is-collapsed");
            if (event.button == 0) {
                for (const prop of this.heights) {
                    const heights = this.getComputedHeights(content);
                    content.style.setProperty(
                        prop,
                        collapsed ? heights[prop] : "0px"
                    );
                }

                callout.toggleClass("is-collapsed", !collapsed);
            }
        };
    }

    getComputedHeights(el: HTMLDivElement): Heights {
        if (this.heightMap.has(el)) {
            return this.heightMap.get(el);
        }
        const style = getComputedStyle(el);
        const heights: Heights = {};
        for (const key of this.heights) {
            heights[key] = style.getPropertyValue(key);
        }
        this.heightMap.set(el, heights);
        return heights;
    }
    generateCssString() {
        const sheet = [
            `/* This snippet was auto-generated by the Admonitions plugin on ${new Date().toLocaleString()} */\n\n`
        ];
        for (const rule of Array.from(this.sheet.cssRules)) {
            sheet.push(rule.cssText);
        }
        return sheet.join("\n\n");
    }
    addAdmonition(admonition: Admonition) {
        if (!admonition.icon) return;
        let rule: string;
        const color = admonition.injectColor ?? this.plugin.data.injectColor ? `--callout-color: ${admonition.color};` : '';
        if (admonition.icon.type == "obsidian") {
            rule = `.callout[data-callout="${admonition.type}"] {
    ${color}
    --callout-icon: ${admonition.icon.name};  /* Icon name from the Obsidian Icon Set */
}`;
        } else {
            rule = `.callout[data-callout="${admonition.type}"] {
       ${color}
        --callout-icon: '${(
            this.plugin.iconManager.getIconNode(admonition.icon)?.outerHTML ??
            ""
        ).replace(/(width|height)=(\\?"|')\d+(\\?"|')/g, "")}';
    }`;
        }
        if (this.indexing.contains(admonition.type)) {
            this.sheet.deleteRule(this.indexing.indexOf(admonition.type));
        }
        this.indexing = [
            ...this.indexing.filter((type) => type != admonition.type),
            admonition.type
        ];
        this.sheet.insertRule(rule, this.sheet.cssRules.length);
        this.updateSnippet();
    }
    indexing: string[] = [];
    removeAdmonition(admonition: Admonition) {
        if (!this.indexing.contains(admonition.type)) return;
        const index = this.indexing.indexOf(admonition.type);
        this.sheet.deleteRule(index);
        this.indexing.splice(index, 1);
        this.updateSnippet();
    }
    style = document.head.createEl("style", {
        attr: { id: "ADMONITIONS_CUSTOM_STYLE_SHEET" }
    });
    get sheet() {
        return this.style.sheet;
    }

    unload() {
        this.style.detach();
    }

    get snippetPath() {
        return this.plugin.app.customCss.getSnippetPath(
            this.plugin.data.snippetPath
        );
    }
    setUseSnippet() {
        if (this.plugin.data.useSnippet) {
            this.updateSnippet();
        }
    }
    async updateSnippet() {
        if (!this.plugin.data.useSnippet) return;
        if (await this.plugin.app.vault.adapter.exists(this.snippetPath)) {
            await this.plugin.app.vault.adapter.write(
                this.snippetPath,
                this.generateCssString()
            );
        } else {
            await this.plugin.app.vault.create(
                this.snippetPath,
                this.generateCssString()
            );
        }
        this.plugin.app.customCss.setCssEnabledStatus(
            this.plugin.data.snippetPath,
            true
        );
        this.plugin.app.customCss.readCssFolders();
    }
}