"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CodeBlockSpecs = exports.codeBlockType = exports.codeBlockNodeName = exports.CodeBlockNodeAttr = void 0;
const schema_1 = require("../../../../utils/schema.js");
exports.CodeBlockNodeAttr = {
    Lang: 'data-language',
    Markup: 'data-markup',
    Line: 'data-line',
    ShowLineNumbers: 'data-show-line-numbers',
};
exports.codeBlockNodeName = 'code_block';
exports.codeBlockType = (0, schema_1.nodeTypeFactory)(exports.codeBlockNodeName);
const getLangOfNode = (node) => {
    let result = node.getAttribute(exports.CodeBlockNodeAttr.Lang) || '';
    if (!result) {
        const firstElementChild = node.firstElementChild;
        if (firstElementChild &&
            firstElementChild.nodeName.toLowerCase() === 'code' &&
            firstElementChild.classList.contains('hljs')) {
            result = firstElementChild.getAttribute('class')?.split(' ')?.[1] || '';
        }
    }
    return result;
};
const CodeBlockSpecs = (builder, opts) => {
    builder.addNode(exports.codeBlockNodeName, () => ({
        view: opts.nodeview,
        spec: {
            attrs: {
                [exports.CodeBlockNodeAttr.Lang]: { default: '' },
                [exports.CodeBlockNodeAttr.Markup]: { default: '```' },
                [exports.CodeBlockNodeAttr.Line]: { default: null },
                ...(opts.lineNumbers?.enabled
                    ? {
                        [exports.CodeBlockNodeAttr.ShowLineNumbers]: {
                            default: opts.lineNumbers.showByDefault ? 'true' : '',
                        },
                    }
                    : {}),
            },
            content: 'text*',
            group: 'block',
            code: true,
            marks: '',
            selectable: true,
            allowSelection: false,
            parseDOM: [
                {
                    tag: 'pre',
                    preserveWhitespace: 'full',
                    getAttrs: (node) => {
                        return {
                            [exports.CodeBlockNodeAttr.Lang]: getLangOfNode(node),
                        };
                    },
                },
            ],
            toDOM({ attrs }) {
                return ['pre', attrs, ['code', 0]];
            },
        },
        fromMd: {
            tokenSpec: {
                name: exports.codeBlockNodeName,
                type: 'block',
                noCloseToken: true,
                getAttrs: (tok) => {
                    return {
                        [exports.CodeBlockNodeAttr.Line]: tok.attrGet('data-line'),
                        [exports.CodeBlockNodeAttr.ShowLineNumbers]: tok.info.includes('showLineNumbers')
                            ? 'true'
                            : '',
                    };
                },
                prepareContent: removeNewLineAtEnd, // content of code blocks contains extra \n at the end
            },
        },
        toMd: (state, node) => {
            const lang = node.attrs[exports.CodeBlockNodeAttr.Lang];
            const showLineNumbers = opts.lineNumbers?.enabled
                ? node.attrs[exports.CodeBlockNodeAttr.ShowLineNumbers]
                : '';
            let info = lang;
            if (showLineNumbers === 'true') {
                info += ' showLineNumbers';
            }
            // The fence must be longer than any run of the same fence character
            // in the content, otherwise such a run would close the block early.
            const fence = getCodeBlockFence(node.attrs[exports.CodeBlockNodeAttr.Markup] || '', node.textContent);
            state.write(fence + info + '\n');
            state.text(node.textContent, false);
            // Add a newline to the current content before adding closing marker
            state.write('\n');
            state.write(fence);
            state.closeBlock(node);
        },
    }));
    builder.addMarkdownTokenParserSpec('fence', () => ({
        name: exports.codeBlockNodeName,
        type: 'block',
        noCloseToken: true,
        getAttrs: (tok) => {
            const attrs = {
                [exports.CodeBlockNodeAttr.Markup]: tok.markup,
                [exports.CodeBlockNodeAttr.Line]: tok.attrGet('data-line'),
            };
            if (tok.info) {
                // like in markdown-it
                // https://github.com/markdown-it/markdown-it/blob/d07d585b6b15aaee2bc8f7a54b994526dad4dbc5/lib/renderer.mjs#L36-L37
                const parts = tok.info.split(/\s+/);
                const isFirstPartForLineNumbers = opts.lineNumbers?.enabled && parts[0] === 'showLineNumbers';
                attrs[exports.CodeBlockNodeAttr.Lang] = isFirstPartForLineNumbers ? '' : parts[0];
            }
            if (opts.lineNumbers?.enabled && tok.info?.includes('showLineNumbers')) {
                attrs[exports.CodeBlockNodeAttr.ShowLineNumbers] = 'true';
            }
            else {
                attrs[exports.CodeBlockNodeAttr.ShowLineNumbers] = '';
            }
            return attrs;
        },
        prepareContent: removeNewLineAtEnd, // content of fence blocks contains extra \n at the end
    }));
    builder.addKeymap(() => ({
        Tab: (state, dispatch) => {
            const { $anchor, $head } = state.selection;
            if ($anchor.sameParent($head) && $anchor.parent.type.name === exports.codeBlockNodeName) {
                dispatch?.(state.tr.replaceSelectionWith(state.schema.text('\t')).scrollIntoView());
                return true;
            }
            return false;
        },
    }));
};
exports.CodeBlockSpecs = CodeBlockSpecs;
function removeNewLineAtEnd(content) {
    return content.endsWith('\n') ? content.slice(0, content.length - 1) : content;
}
/**
 * Builds a fence that is guaranteed not to be closed by its own content.
 *
 * A fenced code block is closed by a line consisting of a run of the fence
 * character that is at least as long as the opening fence. So if the content
 * contains such a run, the fence must be made longer than the longest run.
 */
function getCodeBlockFence(markup, content) {
    markup = markup.trim() || '```';
    const fenceChar = markup.startsWith('~') ? '~' : '`';
    let length = Math.max(markup.length, 3);
    const match = content.match(fenceChar === '~' ? /~{3,}/g : /`{3,}/g);
    if (match) {
        const longest = match.reduce((max, run) => Math.max(max, run.length), 0);
        if (longest >= length) {
            length = longest + 1;
        }
    }
    return fenceChar.repeat(length);
}
//# sourceMappingURL=index.js.map
