import { createCssVariablesTheme, createHighlighterCore, } from "shiki/core";
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
const THEME_NAME = "methanium-css";
const CACHE_LIMIT = 200;
export const shikiPreClassName = "shiki m-0 w-max min-w-full bg-[var(--markdown-code-background,var(--markdown-code-background-default))]! font-mono text-[0.82rem]/[1.7] text-[var(--markdown-code-foreground)]! [tab-size:2]";
export const shikiLineClassNames = "line -mx-4 inline-block min-w-full px-4 group-data-[line-numbers]/code-block:before:inline-block group-data-[line-numbers]/code-block:before:w-10 group-data-[line-numbers]/code-block:before:pe-4 group-data-[line-numbers]/code-block:before:text-end group-data-[line-numbers]/code-block:before:text-muted-foreground group-data-[line-numbers]/code-block:before:select-none group-data-[line-numbers]/code-block:before:content-[attr(data-line)]";
export const highlightedLineClassNames = "bg-primary/12 shadow-[inset_0.18rem_0_var(--primary-foreground-alt)]";
const aliases = {
    "c++": "cpp",
    cjs: "javascript",
    gql: "graphql",
    html: "html",
    js: "javascript",
    javascript: "javascript",
    jsx: "jsx",
    markdown: "markdown",
    md: "markdown",
    mjs: "javascript",
    nix: "nix",
    py: "python",
    rs: "rust",
    sh: "shellscript",
    shell: "shellscript",
    bash: "shellscript",
    ts: "typescript",
    tsx: "tsx",
    typescript: "typescript",
    yml: "yaml",
};
const lazyLanguages = {
    c: () => import("@shikijs/langs/c"),
    cpp: () => import("@shikijs/langs/cpp"),
    diff: () => import("@shikijs/langs/diff"),
    dockerfile: () => import("@shikijs/langs/dockerfile"),
    go: () => import("@shikijs/langs/go"),
    graphql: () => import("@shikijs/langs/graphql"),
    java: () => import("@shikijs/langs/java"),
    kotlin: () => import("@shikijs/langs/kotlin"),
    lua: () => import("@shikijs/langs/lua"),
    python: () => import("@shikijs/langs/python"),
    rust: () => import("@shikijs/langs/rust"),
    sql: () => import("@shikijs/langs/sql"),
};
const supportedLanguages = new Set([
    ...Object.values(aliases),
    ...Object.keys(lazyLanguages),
    "css",
    "json",
    "toml",
    "yaml",
]);
const cache = new Map();
const loadingLanguages = new Map();
let highlighterPromise;
function getHighlighter() {
    highlighterPromise ??= createHighlighterCore({
        engine: createJavaScriptRegexEngine({ target: "auto" }),
        themes: [
            createCssVariablesTheme({
                name: THEME_NAME,
                variablePrefix: "--markdown-code-",
                variableDefaults: {
                    background: "transparent",
                    foreground: "var(--foreground)",
                },
                fontStyle: true,
            }),
        ],
        langs: [
            import("@shikijs/langs/nix"),
            import("@shikijs/langs/javascript"),
            import("@shikijs/langs/typescript"),
            import("@shikijs/langs/jsx"),
            import("@shikijs/langs/tsx"),
            import("@shikijs/langs/json"),
            import("@shikijs/langs/shellscript"),
            import("@shikijs/langs/html"),
            import("@shikijs/langs/css"),
            import("@shikijs/langs/markdown"),
            import("@shikijs/langs/yaml"),
            import("@shikijs/langs/toml"),
        ],
    });
    return highlighterPromise;
}
function normalizeLanguage(language) {
    const normalized = language?.trim().toLowerCase();
    if (!normalized)
        return undefined;
    return aliases[normalized] ?? normalized;
}
async function ensureLanguage(highlighter, language) {
    if (highlighter.getLoadedLanguages().includes(language))
        return;
    const loader = lazyLanguages[language];
    if (!loader)
        return;
    let pending = loadingLanguages.get(language);
    if (!pending) {
        pending = highlighter.loadLanguage(loader).then(() => undefined);
        loadingLanguages.set(language, pending);
    }
    await pending;
}
function addClassNames(node, classNames) {
    const current = node.properties.className ?? node.properties.class;
    const classes = Array.isArray(current)
        ? current.map(String)
        : current === undefined
            ? []
            : [String(current)];
    node.properties.className = [...classes, ...classNames.split(" ")];
}
function decorateTree(tree, highlightedLines) {
    const pre = tree.children.find((node) => node.type === "element" && node.tagName === "pre");
    if (pre)
        addClassNames(pre, shikiPreClassName);
    const code = pre?.children.find((node) => node.type === "element" && node.tagName === "code");
    let line = 0;
    for (const node of code?.children ?? []) {
        if (node.type !== "element")
            continue;
        const className = node.properties.className ?? node.properties.class;
        const classes = Array.isArray(className)
            ? className.map(String)
            : className === undefined
                ? []
                : [String(className)];
        if (!classes.includes("line"))
            continue;
        line += 1;
        node.properties["data-line"] = String(line);
        addClassNames(node, shikiLineClassNames);
        if (highlightedLines.has(line))
            addClassNames(node, highlightedLineClassNames);
    }
}
export function isSupportedLanguage(language) {
    const normalized = normalizeLanguage(language);
    return normalized !== undefined && supportedLanguages.has(normalized);
}
export async function highlightCodeTokens(code, language) {
    const normalized = normalizeLanguage(language);
    if (!normalized || !supportedLanguages.has(normalized))
        return null;
    const highlighter = await getHighlighter();
    await ensureLanguage(highlighter, normalized);
    return highlighter
        .codeToTokens(code, { lang: normalized, theme: THEME_NAME })
        .tokens.flatMap((line) => line.map(({ color, content, fontStyle, offset }) => ({
        color,
        content,
        fontStyle,
        offset,
    })));
}
export async function highlightCode(code, language, highlightedLines) {
    const normalized = normalizeLanguage(language);
    if (!normalized || !supportedLanguages.has(normalized))
        return null;
    const highlighted = [...highlightedLines].sort((left, right) => left - right);
    const key = JSON.stringify([code, normalized, highlighted]);
    const cached = cache.get(key);
    if (cached) {
        cache.delete(key);
        cache.set(key, cached);
        return cached;
    }
    const highlighter = await getHighlighter();
    await ensureLanguage(highlighter, normalized);
    const tree = highlighter.codeToHast(code, {
        lang: normalized,
        theme: THEME_NAME,
    });
    decorateTree(tree, highlightedLines);
    cache.set(key, tree);
    if (cache.size > CACHE_LIMIT) {
        const oldest = cache.keys().next().value;
        if (oldest !== undefined)
            cache.delete(oldest);
    }
    return tree;
}
