"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CodeBlockHighlight = void 0;
const prosemirror_state_1 = require("prosemirror-state");
// @ts-ignore // TODO: fix cjs build
const prosemirror_utils_1 = require("prosemirror-utils");
const prosemirror_view_1 = require("prosemirror-view");
const CodeBlockSpecs_1 = require("../CodeBlockSpecs/index.js");
const CodeBlockNodeView_1 = require("./CodeBlockNodeView.js");
const TooltipPlugin_1 = require("./TooltipPlugin/index.js");
const codeBlockLangsPlugin_1 = require("./plugins/codeBlockLangsPlugin.js");
const codeBlockLineNumbersPlugin_1 = require("./plugins/codeBlockLineNumbersPlugin.js");
const codeBlockLineWrappingPlugin_1 = require("./plugins/codeBlockLineWrappingPlugin.js");
const utils_1 = require("./utils.js");
require("./CodeBlockHighlight.css");
const pluginKey = new prosemirror_state_1.PluginKey('code_block_highlight');
const CodeBlockHighlight = (builder, opts) => {
    if (opts.lineWrapping?.enabled)
        builder.addPlugin(codeBlockLineWrappingPlugin_1.codeBlockLineWrappingPlugin);
    if (opts.lineNumbers?.enabled)
        builder.addPlugin(codeBlockLineNumbersPlugin_1.codeBlockLineNumbersPlugin);
    builder.addPlugin(() => (0, codeBlockLangsPlugin_1.codeBlockLangsPlugin)(opts.langs, builder.logger));
    builder.addPlugin(() => {
        // TODO: add TAB key handler
        // TODO: Remove constant selection of block
        return new prosemirror_state_1.Plugin({
            key: pluginKey,
            state: {
                init: (_config, _state) => {
                    const cache = new WeakMap();
                    return { cache, decoSet: prosemirror_view_1.DecorationSet.empty };
                },
                apply: (tr, { cache, decoSet }, _oldState, newState) => {
                    const langsUpdate = tr.getMeta(codeBlockLangsPlugin_1.codeBlockLangsPluginKey);
                    if (langsUpdate?.loaded && langsUpdate.lowlight) {
                        return {
                            cache,
                            decoSet: getDecorations(tr.doc, cache, langsUpdate.lowlight),
                        };
                    }
                    const { lowlight } = (0, codeBlockLangsPlugin_1.getCodeBlockLangsState)(newState);
                    if (!lowlight) {
                        return { cache, decoSet: prosemirror_view_1.DecorationSet.empty };
                    }
                    if (!tr.docChanged)
                        return { cache, decoSet };
                    decoSet = (0, utils_1.processChangedCodeBlocks)(tr, decoSet, (node, pos, decoSet) => {
                        const lang = node.attrs[CodeBlockSpecs_1.CodeBlockNodeAttr.Lang];
                        if (!lang || !lowlight.registered(lang)) {
                            return decoSet.remove(decoSet.find(pos, pos + node.nodeSize));
                        }
                        const cached = cache.get(node);
                        if (cached) {
                            // node is in cache, but decorations may be missing (for example, after undo)
                            if (!decoSet.find(pos, pos + node.nodeSize).length) {
                                return decoSet.add(tr.doc, renderTree(cached, pos + 1));
                            }
                            return decoSet;
                        }
                        decoSet = decoSet.remove(decoSet.find(pos, pos + node.nodeSize));
                        const ast = lowlight.highlight(lang, node.textContent);
                        const parsed = parseNodes(ast.children);
                        cache.set(node, parsed);
                        return decoSet.add(tr.doc, renderTree(parsed, pos + 1));
                    });
                    return { cache, decoSet };
                },
            },
            view: (view) => {
                return (0, TooltipPlugin_1.codeLangSelectTooltipViewCreator)(view, {
                    showCodeWrapping: Boolean(opts.lineWrapping?.enabled),
                    showLineNumbers: Boolean(opts.lineNumbers?.enabled),
                });
            },
            props: {
                decorations(state) {
                    return pluginKey.getState(state)?.decoSet;
                },
                nodeViews: {
                    [CodeBlockSpecs_1.codeBlockNodeName]: CodeBlockNodeView_1.CodeBlockNodeView.withOpts(opts),
                },
            },
        });
    });
    function getDecorations(doc, cache, lowlight) {
        const decos = [];
        for (const { node, pos } of (0, prosemirror_utils_1.findChildrenByType)(doc, (0, CodeBlockSpecs_1.codeBlockType)(doc.type.schema), true)) {
            const lang = node.attrs[CodeBlockSpecs_1.CodeBlockNodeAttr.Lang];
            if (!lang || !lowlight.registered(lang)) {
                continue;
            }
            // Try to get parsed result from cache using node as key
            let parsedNodes = cache.get(node);
            if (!parsedNodes) {
                // Compute, parse and cache using the node itself as key
                const nodes = lowlight.highlight(lang, node.textContent).children;
                parsedNodes = parseNodes(nodes);
                cache.set(node, parsedNodes);
            }
            decos.push(...renderTree(parsedNodes, pos + 1));
        }
        return prosemirror_view_1.DecorationSet.create(doc, decos);
    }
};
exports.CodeBlockHighlight = CodeBlockHighlight;
function renderTree(parsedNodes, from) {
    const decos = [];
    for (const { text, classes } of parsedNodes) {
        const to = from + text.length;
        if (classes.length) {
            decos.push(prosemirror_view_1.Decoration.inline(from, to, {
                class: classes.join(' '),
            }));
        }
        from = to;
    }
    return decos;
}
function parseNodes(nodes, className = []) {
    const result = [];
    collectNodes(nodes, className, result);
    return result;
}
function collectNodes(nodes, className, result) {
    for (const node of nodes) {
        if (node.type === 'element') {
            const classes = className.concat(node.properties.className ?? []);
            collectNodes(node.children, classes, result);
        }
        else {
            result.push({
                text: node.type === 'comment' || node.type === 'text' ? node.value : '',
                classes: className,
            });
        }
    }
}
//# sourceMappingURL=CodeBlockHighlight.js.map
