import { Plugin, PluginKey } from 'prosemirror-state';
// @ts-ignore // TODO: fix cjs build
import { findChildrenByType } from 'prosemirror-utils';
import { Decoration, DecorationSet } from 'prosemirror-view';
import { capitalize } from "../../../../lodash.js";
import { globalLogger } from "../../../../logger.js";
import { CodeBlockNodeAttr, codeBlockNodeName, codeBlockType } from "../CodeBlockSpecs/index.js";
import { codeLangSelectTooltipViewCreator } from "./TooltipPlugin/index.js";
const key = new PluginKey('code_block_highlight');
export const CodeBlockHighlight = (builder, opts) => {
    let langs;
    let lowlight;
    let hljs;
    const loadModules = async () => {
        try {
            hljs = (await import('highlight.js/lib/core')).default;
            const low = await import('lowlight');
            const all = low.all;
            const create = low.createLowlight;
            langs = { ...all, ...opts.langs };
            lowlight = create(langs);
            return true;
        }
        catch (e) {
            globalLogger.info('Skip code_block highlighting');
            builder.logger.log('Skip code_block highlighting');
            return false;
        }
    };
    builder.addPlugin(() => {
        let modulesLoaded = false;
        const selectItems = [];
        const mapping = {};
        // TODO: add TAB key handler
        // TODO: Remove constant selection of block
        return new Plugin({
            key,
            state: {
                init: (_, state) => {
                    loadModules().then((loaded) => {
                        modulesLoaded = loaded;
                        if (modulesLoaded) {
                            for (const lang of Object.keys(langs)) {
                                const defs = langs[lang](hljs);
                                selectItems.push({
                                    value: lang,
                                    content: defs.name || capitalize(lang),
                                });
                                if (defs.aliases) {
                                    for (const alias of defs.aliases) {
                                        mapping[alias] = lang;
                                    }
                                }
                            }
                        }
                    });
                    return getDecorations(state.doc);
                },
                apply: (tr, decos, oldState, newState) => {
                    if (!modulesLoaded) {
                        return DecorationSet.empty;
                    }
                    if (tr.docChanged) {
                        const oldNodeName = oldState.selection.$head.parent.type.name;
                        const newNodeName = newState.selection.$head.parent.type.name;
                        // Apply decorations if:
                        if (oldNodeName === codeBlockNodeName ||
                            newNodeName === codeBlockNodeName) {
                            // selection includes codeblock node,
                            return getDecorations(tr.doc);
                        }
                        else {
                            const oldNodes = findChildrenByType(oldState.doc, codeBlockType(oldState.schema));
                            const newNodes = findChildrenByType(newState.doc, codeBlockType(newState.schema));
                            if (
                            // OR transaction adds/removes codeblock nodes,
                            newNodes.length !== oldNodes.length ||
                                // OR transaction has changes that completely encapsulate a node
                                // (for example, a transaction that affects the entire document).
                                // Such transactions can happen during collab syncing via y-prosemirror, for example.
                                tr.steps.some((step) => {
                                    return (stepHasFromTo(step) &&
                                        oldNodes.some((node) => node.pos >= step.from &&
                                            node.pos + node.node.nodeSize <= step.to));
                                })) {
                                return getDecorations(tr.doc);
                            }
                        }
                    }
                    return decos.map(tr.mapping, tr.doc);
                },
            },
            view: (view) => codeLangSelectTooltipViewCreator(view, selectItems, mapping),
            props: {
                decorations: (state) => {
                    return key.getState(state);
                },
                nodeViews: {
                    [codeBlockNodeName]: (node) => {
                        let prevLang = node.attrs[CodeBlockNodeAttr.Lang];
                        const dom = document.createElement('pre');
                        const contentDOM = document.createElement('code');
                        contentDOM.classList.add('hljs');
                        if (prevLang) {
                            dom.setAttribute(CodeBlockNodeAttr.Lang, prevLang);
                            contentDOM.classList.add(prevLang);
                        }
                        dom.append(contentDOM);
                        return {
                            dom,
                            contentDOM,
                            update(newNode) {
                                if (node.type !== newNode.type)
                                    return false;
                                const newLang = newNode.attrs[CodeBlockNodeAttr.Lang];
                                if (prevLang !== newLang) {
                                    contentDOM.className = 'hljs';
                                    if (newLang) {
                                        dom.setAttribute(CodeBlockNodeAttr.Lang, newLang);
                                        contentDOM.classList.add(newLang);
                                    }
                                    else {
                                        dom.removeAttribute(CodeBlockNodeAttr.Lang);
                                    }
                                    prevLang = newLang;
                                }
                                return true;
                            },
                        };
                    },
                },
            },
        });
    });
    function getDecorations(doc) {
        const decos = [];
        if (!lowlight) {
            return DecorationSet.empty;
        }
        for (const { node, pos } of findChildrenByType(doc, codeBlockType(doc.type.schema), true)) {
            let from = pos + 1;
            let nodes;
            const lang = node.attrs[CodeBlockNodeAttr.Lang];
            if (lang && lowlight.registered(lang)) {
                nodes = lowlight.highlight(lang, node.textContent).children;
            }
            else {
                continue;
            }
            for (const { text, classes } of parseNodes(nodes)) {
                const to = from + text.length;
                if (classes.length) {
                    decos.push(Decoration.inline(from, to, {
                        class: classes.join(' '),
                    }));
                }
                from = to;
            }
        }
        return DecorationSet.create(doc, decos);
    }
};
function parseNodes(nodes, className = []) {
    return nodes
        .map((node) => {
        let classes = className;
        if (node.type === 'element') {
            classes = classes.concat(node.properties.className ?? []);
            return parseNodes(node.children, classes);
        }
        return {
            text: node.type === 'comment' || node.type === 'text' ? node.value : '',
            classes,
        };
    })
        .flat();
}
function stepHasFromTo(step) {
    // @ts-expect-error
    return typeof step.from === 'number' && typeof step.to === 'number';
}
//# sourceMappingURL=CodeBlockHighlight.js.map
