import MarkdownIt from "markdown-it";
import type Token from "markdown-it/lib/token";
import { readonly } from "vue";

/**
 * Adds a rule to open all links in a new page.
 * https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
 */
function addRuleOpenLinksInNewPage(engine: MarkdownIt) {
    const defaultRender =
        engine.renderer.rules.link_open ||
        function (tokens, idx, options, _env, self) {
            return self.renderToken(tokens, idx, options);
        };

    engine.renderer.rules.link_open = function (tokens, idx, options, env, self) {
        const token = tokens[idx];

        if (token) {
            const aIndex = token.attrIndex("target");

            if (aIndex && aIndex < 0) {
                token.attrPush(["target", "_blank"]);
            } else {
                token.attrs![aIndex]![1] = "_blank";
            }
        }

        return defaultRender(tokens, idx, options, env, self);
    };
}

function addRuleHeadingIncreaseLevel(engine: MarkdownIt, increaseBy: number) {
    const defaultOpen =
        engine.renderer.rules.heading_open ||
        function (tokens, idx, options, _env, self) {
            return self.renderToken(tokens, idx, options);
        };

    const defaultClose =
        engine.renderer.rules.heading_close ||
        function (tokens, idx, options, _env, self) {
            return self.renderToken(tokens, idx, options);
        };

    const increaseHeadingLevel = (token: Token) => {
        const level = parseInt(token.tag[1] ?? "1");
        token.tag = `h${level + increaseBy}`;
    };

    engine.renderer.rules.heading_open = function (tokens, idx, options, env, self) {
        const token = tokens[idx];

        if (token) {
            increaseHeadingLevel(token);
        }

        return defaultOpen(tokens, idx, options, env, self);
    };

    engine.renderer.rules.heading_close = function (tokens, idx, options, env, self) {
        const token = tokens[idx];

        if (token) {
            increaseHeadingLevel(token);
        }

        return defaultClose(tokens, idx, options, env, self);
    };
}

function addRuleNoMargin(engine: MarkdownIt) {
    engine.renderer.rules.paragraph_open = function (tokens, idx, options, env, self) {
        const token = tokens[idx];
        if (token) {
            token.attrPush(["style", "margin:0"]);
        }
        return self.renderToken(tokens, idx, options);
    };
}

/**
 * Add a rule that removes newlines after list items.
 */
function addRuleRemoveNewlinesAfterList(engine: MarkdownIt) {
    const defaultRenderListItemOpen =
        engine.renderer.rules.list_item_open ||
        function (tokens, idx, options, _env, self) {
            return self.renderToken(tokens, idx, options);
        };

    const defaultRenderListItemClose =
        engine.renderer.rules.list_item_close ||
        function (tokens, idx, options, _env, self) {
            return self.renderToken(tokens, idx, options);
        };

    const defaultRenderOrderedListOpen =
        engine.renderer.rules.ordered_list_open ||
        function (tokens, idx, options, _env, self) {
            return self.renderToken(tokens, idx, options);
        };

    const defaultRenderOrderedListClose =
        engine.renderer.rules.ordered_list_close ||
        function (tokens, idx, options, _env, self) {
            return self.renderToken(tokens, idx, options);
        };

    const defaultRenderBulletListOpen =
        engine.renderer.rules.bullet_list_open ||
        function (tokens, idx, options, _env, self) {
            return self.renderToken(tokens, idx, options);
        };

    const defaultRenderBulletListClose =
        engine.renderer.rules.bullet_list_close ||
        function (tokens, idx, options, _env, self) {
            return self.renderToken(tokens, idx, options);
        };

    engine.renderer.rules.list_item_open = function (tokens, idx, options, env, self) {
        return defaultRenderListItemOpen(tokens, idx, options, env, self).replace(/\n+$/, "");
    };

    engine.renderer.rules.list_item_close = function (tokens, idx, options, env, self) {
        return defaultRenderListItemClose(tokens, idx, options, env, self).replace(/\n+$/, "");
    };

    engine.renderer.rules.ordered_list_open = function (tokens, idx, options, env, self) {
        return defaultRenderOrderedListOpen(tokens, idx, options, env, self).replace(/\n+$/, "");
    };

    engine.renderer.rules.ordered_list_close = function (tokens, idx, options, env, self) {
        return defaultRenderOrderedListClose(tokens, idx, options, env, self).replace(/\n+$/, "");
    };

    engine.renderer.rules.bullet_list_open = function (tokens, idx, options, env, self) {
        return defaultRenderBulletListOpen(tokens, idx, options, env, self).replace(/\n+$/, "");
    };

    engine.renderer.rules.bullet_list_close = function (tokens, idx, options, env, self) {
        return defaultRenderBulletListClose(tokens, idx, options, env, self).replace(/\n+$/, "");
    };
}

interface UseMarkdownOptions {
    openLinksInNewPage?: boolean;
    increaseHeadingLevelBy?: number;
    removeNewlinesAfterList?: boolean;
    noMargin?: boolean;
}

type RawMarkdown = string;
type HTMLString = string;

/** Composable for rendering Markdown strings.  */
export function useMarkdown(options: UseMarkdownOptions = {}) {
    const mdEngine = MarkdownIt();

    if (options.openLinksInNewPage) {
        addRuleOpenLinksInNewPage(mdEngine);
    }

    if (options.increaseHeadingLevelBy) {
        addRuleHeadingIncreaseLevel(mdEngine, options.increaseHeadingLevelBy);
    }

    if (options.noMargin) {
        addRuleNoMargin(mdEngine);
    }

    if (options.removeNewlinesAfterList) {
        addRuleRemoveNewlinesAfterList(mdEngine);
    }

    function renderMarkdown(markdown: RawMarkdown): HTMLString {
        return mdEngine.render(markdown);
    }

    return {
        /** Render markdown string into html. */
        renderMarkdown,
        /** The full Markdown parser/renderer engine for advanced use cases. */
        markdownEngine: readonly(mdEngine),
    };
}
