'use strict';

var rangeParser = require('parse-numeric-range');

const isArrayOfStrings = (value) => Array.isArray(value) && value.every((v) => typeof v === 'string');
let tokenizePool;
const mdxCodeFormatter = () => {
    return async function transformer(tree) {
        var _a, _b, _c;
        const [{ visit, SKIP }, { htmlEscape }, { Tinypool }] = await Promise.all([
            import('unist-util-visit'),
            import('escape-goat'),
            import('tinypool'),
        ]);
        // using TinyThread because shiki has some gnarly memory leaks
        // so we stick it in a worker to keep it isolated
        // and configure it so it will be destroyed if it remains unused for a while
        tokenizePool =
            tokenizePool ||
                new Tinypool({
                    filename: require.resolve('@rowinbot/mdx-code-formatter/dist/worker.js'),
                    minThreads: 0,
                    idleTimeout: 60,
                });
        const nodesToTokenize = [];
        visit(tree, 'element', (preNode) => {
            var _a, _b, _c, _d, _e;
            if (preNode.tagName !== 'pre' || !preNode.children) {
                return;
            }
            const codeNode = preNode.children[0];
            if (!codeNode ||
                codeNode.type !== 'element' ||
                codeNode.tagName !== 'code') {
                return;
            }
            const meta = (_b = (_a = codeNode.data) === null || _a === void 0 ? void 0 : _a.meta) !== null && _b !== void 0 ? _b : '';
            if (typeof meta !== 'string')
                return;
            const className = (_c = codeNode.properties) === null || _c === void 0 ? void 0 : _c.className;
            if (!isArrayOfStrings(className))
                return;
            const language = (_e = (_d = className
                .find((c) => c.startsWith('language-'))) === null || _d === void 0 ? void 0 : _d.slice('language-'.length)) !== null && _e !== void 0 ? _e : 'txt';
            if (codeNode.children.length > 1) {
                console.warn(`@kentcdodds/md-temp - Unexpected: ${codeNode.children.length} children`);
                return;
            }
            const [firstChild] = codeNode.children;
            if (firstChild.type !== 'text') {
                console.warn(`@kentcdodds/md-temp - Unexpected: firstChild type is not "text": ${firstChild.type}`);
                return;
            }
            const codeString = firstChild.value.trimEnd();
            nodesToTokenize.push({ preNode, codeString, language, meta });
            return SKIP;
        });
        for (const nodeStuff of nodesToTokenize) {
            const { preNode, codeString, language, meta } = nodeStuff;
            let metaParams = new URLSearchParams();
            if (meta) {
                metaParams = new URLSearchParams(meta.split(/\s+/).join('&'));
            }
            // TODO: review this (?? '') because URLSearchParams entries might be null and we're not handling that
            const addedLines = parseLineRange((_a = metaParams.get('add')) !== null && _a !== void 0 ? _a : '');
            const removedLines = parseLineRange((_b = metaParams.get('remove')) !== null && _b !== void 0 ? _b : '');
            const highlightLines = parseLineRange((_c = metaParams.get('lines')) !== null && _c !== void 0 ? _c : '');
            const startValNum = metaParams.has('start')
                ? Number(metaParams.get('start'))
                : 1;
            const startingLineNumber = Number.isFinite(startValNum) ? startValNum : 1;
            const numbers = !metaParams.has('nonumber');
            const { tokens, fgColor, bgColor } = (await tokenizePool.run({
                code: codeString,
                language,
            }));
            const isDiff = addedLines.length > 0 || removedLines.length > 0;
            let diffLineNumber = startingLineNumber - 1;
            const children = tokens.map((lineTokens, zeroBasedLineNumber) => {
                const children = lineTokens.map((token) => {
                    const color = convertFakeHexToCustomProp(token.color || '');
                    const content = {
                        type: 'text',
                        // Do not escape the _actual_ content
                        value: token.content,
                    };
                    return color && color !== fgColor
                        ? {
                            type: 'element',
                            tagName: 'span',
                            properties: {
                                style: `color: ${htmlEscape(color)}`,
                            },
                            children: [content],
                        }
                        : content;
                });
                children.push({
                    type: 'text',
                    value: '\n',
                });
                const lineNumber = zeroBasedLineNumber + startingLineNumber;
                const highlightLine = highlightLines.includes(lineNumber);
                const removeLine = removedLines.includes(lineNumber);
                const addLine = addedLines.includes(lineNumber);
                if (!removeLine) {
                    diffLineNumber++;
                }
                return {
                    type: 'element',
                    tagName: 'span',
                    properties: {
                        className: 'codeblock-line',
                        dataHighlight: highlightLine ? 'true' : undefined,
                        dataLineNumber: numbers ? lineNumber : undefined,
                        dataAdd: isDiff ? addLine : undefined,
                        dataRemove: isDiff ? removeLine : undefined,
                        dataDiffLineNumber: isDiff ? diffLineNumber : undefined,
                    },
                    children,
                };
            });
            const metaProps = {};
            metaParams.forEach((val, key) => {
                if (key === 'lines')
                    return;
                metaProps[`data-${key}`] = val;
            });
            Object.assign(preNode, {
                type: 'element',
                tagName: 'pre',
                properties: {
                    ...metaProps,
                    dataLineNumbers: numbers ? 'true' : 'false',
                    // Make pre focusable so that it can be scrolled with the keyboard
                    tabIndex: '0',
                    dataLang: htmlEscape(language),
                    style: `color: ${htmlEscape(fgColor)};background-color: ${htmlEscape(bgColor)}`,
                },
                children: [
                    {
                        type: 'element',
                        tagName: 'code',
                        children,
                    },
                ],
            });
        }
    };
};
////////////////////////////////////////////////////////////////////////////////
const parseLineRange = (param) => {
    if (!param)
        return [];
    return rangeParser(param);
};
// The theme actually stores #FFFF${base-16-color-id} because vscode-textmate
// requires colors to be valid hex codes, if they aren't, it changes them to a
// default, so this is a mega hack to trick it.
function convertFakeHexToCustomProp(color) {
    return color.replace(/^#FFFF(.+)/, 'var(--base$1)');
}

exports.mdxCodeFormatter = mdxCodeFormatter;
