import MarkdownKatex from "@traptitech/markdown-it-katex";
import MarkdownSpoilers from "@traptitech/markdown-it-spoiler";
import "katex/dist/katex.min.css";
import MarkdownIt from "markdown-it";
// @ts-ignore
import MarkdownEmoji from "markdown-it-emoji/dist/markdown-it-emoji-bare";
// @ts-ignore
import MarkdownSub from "markdown-it-sub";
// @ts-ignore
import MarkdownSup from "markdown-it-sup";
import Prism from "prismjs";
import "prismjs/themes/prism-tomorrow.css";
import { RE_MENTIONS } from "revolt.js";

import styles from "./Markdown.module.scss";
import { useContext } from "preact/hooks";

import { internalEmit } from "../../lib/eventEmitter";

import { AppContext } from "../../context/revoltjs/RevoltClient";

import { generateEmoji } from "../common/Emoji";

import { emojiDictionary } from "../../assets/emojis";
import { MarkdownProps } from "./Markdown";

// TODO: global.d.ts file for defining globals
declare global {
    interface Window {
        copycode: (element: HTMLDivElement) => void;
    }
}

// Handler for code block copy.
if (typeof window !== "undefined") {
    window.copycode = function (element: HTMLDivElement) {
        try {
            let code = element.parentElement?.parentElement?.children[1];
            if (code) {
                navigator.clipboard.writeText(code.textContent?.trim() ?? "");
            }
        } catch (e) {}
    };
}

export const md: MarkdownIt = MarkdownIt({
    breaks: true,
    linkify: true,
    highlight: (str, lang) => {
        let v = Prism.languages[lang];
        if (v) {
            let out = Prism.highlight(str, v, lang);
            return `<pre class="code"><div class="lang"><div onclick="copycode(this)">${lang}</div></div><code class="language-${lang}">${out}</code></pre>`;
        }

        return `<pre class="code"><code>${md.utils.escapeHtml(
            str,
        )}</code></pre>`;
    },
})
    .disable("image")
    .use(MarkdownEmoji, { defs: emojiDictionary })
    .use(MarkdownSpoilers)
    .use(MarkdownSup)
    .use(MarkdownSub)
    .use(MarkdownKatex, {
        throwOnError: false,
        maxExpand: 0,
    });

// ? Force links to open _blank.
// From: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
const defaultRender =
    md.renderer.rules.link_open ||
    function (tokens, idx, options, _env, self) {
        return self.renderToken(tokens, idx, options);
    };

// TODO: global.d.ts file for defining globals
declare global {
    interface Window {
        internalHandleURL: (element: HTMLAnchorElement) => void;
    }
}

// Handler for internal links, pushes events to React using magic.
if (typeof window !== "undefined") {
    window.internalHandleURL = function (element: HTMLAnchorElement) {
        const url = new URL(element.href, location.href);
        const pathname = url.pathname;

        if (pathname.startsWith("/@")) {
            internalEmit("Intermediate", "openProfile", pathname.substr(2));
        } else {
            internalEmit("Intermediate", "navigate", pathname);
        }
    };
}

md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
    let internal;
    const hIndex = tokens[idx].attrIndex("href");
    if (hIndex >= 0) {
        try {
            // For internal links, we should use our own handler to use react-router history.
            // @ts-ignore
            const href = tokens[idx].attrs[hIndex][1];
            const url = new URL(href, location.href);

            if (url.hostname === location.hostname) {
                internal = true;
                // I'm sorry.
                tokens[idx].attrPush([
                    "onclick",
                    "internalHandleURL(this); return false",
                ]);

                if (url.pathname.startsWith("/@")) {
                    tokens[idx].attrPush(["data-type", "mention"]);
                }
            }
        } catch (err) {
            // Ignore the error, treat as normal link.
        }
    }

    if (!internal) {
        // Add target=_blank for external links.
        const aIndex = tokens[idx].attrIndex("target");

        if (aIndex < 0) {
            tokens[idx].attrPush(["target", "_blank"]);
        } else {
            try {
                // @ts-ignore
                tokens[idx].attrs[aIndex][1] = "_blank";
            } catch (_) {}
        }
    }

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

md.renderer.rules.emoji = function (token, idx) {
    return generateEmoji(token[idx].content);
};

const RE_TWEMOJI = /:(\w+):/g;

export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
    const client = useContext(AppContext);
    if (typeof content === "undefined") return null;
    if (content.length === 0) return null;

    // We replace the message with the mention at the time of render.
    // We don't care if the mention changes.
    let newContent = content.replace(
        RE_MENTIONS,
        (sub: string, ...args: any[]) => {
            const id = args[0],
                user = client.users.get(id);

            if (user) {
                return `[@${user.username}](/@${id})`;
            }

            return sub;
        },
    );

    const useLargeEmojis = disallowBigEmoji
        ? false
        : content.replace(RE_TWEMOJI, "").trim().length === 0;

    return (
        <span
            className={styles.markdown}
            dangerouslySetInnerHTML={{
                __html: md.render(newContent),
            }}
            data-large-emojis={useLargeEmojis}
            onClick={(ev) => {
                if (ev.target) {
                    let element = ev.currentTarget;
                    if (element.classList.contains("spoiler")) {
                        element.classList.add("shown");
                    }
                }
            }}
        />
    );
}