"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.foldingPlugin = void 0;
const prosemirror_state_1 = require("prosemirror-state");
const prosemirror_transform_1 = require("prosemirror-transform");
// @ts-ignore // TODO: fix cjs build
const prosemirror_utils_1 = require("prosemirror-utils");
const prosemirror_view_1 = require("prosemirror-view");
const const_1 = require("../const.js");
const utils_1 = require("../utils.js");
require("./folding.css");
const CHANGE_META_KEY = 'folding-heading-changed';
const foldingPlugin = () => {
    return new prosemirror_state_1.Plugin({
        state: {
            init: (_config, state) => buildDecosSet(state.doc),
            apply: (tr, prev) => {
                if (tr.getMeta(CHANGE_META_KEY)) {
                    return buildDecosSet(tr.doc);
                }
                if (!tr.docChanged ||
                    // Optimization: ignoring trs, that don't change position of blocks in doc
                    canSafelyIgnoreTr(tr)) {
                    return prev.map(tr.mapping, tr.doc);
                }
                return buildDecosSet(tr.doc);
            },
        },
        props: {
            decorations(state) {
                return this.getState(state);
            },
            handleClickOn(view, _pos, node, nodePos, event, direct) {
                if (direct && (0, utils_1.isFoldingHeading)(node) && isLeftPaddingClick(event)) {
                    view.dispatch(view.state.tr
                        .setNodeAttribute(nodePos, const_1.YfmHeadingAttr.Folding, !node.attrs[const_1.YfmHeadingAttr.Folding])
                        .setMeta(CHANGE_META_KEY, true));
                }
            },
        },
    });
};
exports.foldingPlugin = foldingPlugin;
function isLeftPaddingClick(event) {
    const elem = event.target;
    const leftPadding = parseInt(window.getComputedStyle(elem).paddingLeft, 10);
    if (Number.isNaN(leftPadding))
        return true;
    return event.offsetX < leftPadding;
}
const safeSteps = [prosemirror_transform_1.AddMarkStep, prosemirror_transform_1.AddNodeMarkStep, prosemirror_transform_1.DocAttrStep, prosemirror_transform_1.RemoveMarkStep, prosemirror_transform_1.RemoveNodeMarkStep];
function canSafelyIgnoreTr(tr) {
    if (isInputTr(tr) || isTextBackspaceTr(tr))
        return true;
    if (tr.steps.every((step) => safeSteps.some((SafeStep) => step instanceof SafeStep)))
        return true;
    return false;
}
function isInputTr(tr) {
    if (tr.steps.length !== 1)
        return false;
    const [step] = tr.steps;
    return (step instanceof prosemirror_transform_1.ReplaceStep &&
        step.from === step.to &&
        step.slice.content.childCount === 1 &&
        step.slice.content.child(0).type.name === 'text');
}
function isTextBackspaceTr(tr) {
    if (tr.steps.length !== 1)
        return false;
    const [step] = tr.steps;
    return step instanceof prosemirror_transform_1.ReplaceStep && step.to - step.from === 1 && step.slice.size === 0;
}
// eslint-disable-next-line complexity
function buildDecosSet(doc) {
    const contentDecos = {};
    const separatorDecos = {};
    const headings = (0, prosemirror_utils_1.findChildren)(doc, utils_1.isFoldingHeading, true);
    for (const { node, pos } of headings) {
        // don't add decorations to nested hidden headings and its content
        if (contentDecos[pos]?.hidden)
            continue;
        const isFolded = (0, utils_1.isFoldedHeading)(node);
        const nodeLevel = (0, utils_1.parseLevel)(node);
        const $pos = doc.resolve(pos + 1);
        const depth = $pos.depth - 1;
        const parent = $pos.node(depth);
        let idx = $pos.index(depth);
        let child = null;
        if (isFolded) {
            while ((child = parent.maybeChild(++idx))) {
                if ((0, utils_1.isHeading)(child) && (0, utils_1.parseLevel)(child) <= nodeLevel)
                    break;
                const childPos = $pos.posAtIndex(idx, depth);
                contentDecos[childPos] = {
                    from: childPos,
                    to: childPos + child.nodeSize,
                    hidden: true,
                };
            }
            continue;
        }
        let hLevel = nodeLevel;
        let nextFoldingHeadingFound = false;
        let hidden = false;
        let lastNonHiddenChild = null;
        while ((child = parent.maybeChild(++idx))) {
            const childPos = $pos.posAtIndex(idx, depth);
            if ((0, utils_1.isHeading)(child)) {
                const hasFolding = (0, utils_1.isFoldingHeading)(child);
                if (hasFolding) {
                    nextFoldingHeadingFound = true;
                }
                const level = (0, utils_1.parseLevel)(child);
                if (level <= nodeLevel)
                    break;
                if (!hidden) {
                    lastNonHiddenChild = { node: child, pos: childPos, level: hLevel };
                }
                if (hasFolding) {
                    if ((0, utils_1.isUnfoldedHeading)(child)) {
                        hLevel = level;
                        hidden = false;
                    }
                    else {
                        hidden = true;
                    }
                }
            }
            if (!hidden) {
                lastNonHiddenChild = { node: child, pos: childPos, level: hLevel };
            }
            if (!nextFoldingHeadingFound) {
                contentDecos[childPos] = {
                    from: childPos,
                    to: childPos + child.nodeSize,
                    hidden: false,
                };
            }
        }
        if (lastNonHiddenChild && !separatorDecos[lastNonHiddenChild.pos]) {
            const { pos, node, level } = lastNonHiddenChild;
            separatorDecos[pos] = {
                from: pos,
                to: pos + node.nodeSize,
                value: nodeLevel === level ? 'h' + hLevel : `h${nodeLevel}-h${hLevel}`,
            };
        }
    }
    const decorations = [];
    for (const item of Object.values(contentDecos)) {
        const attrs = item.hidden
            ? { class: 'pm-h-folding-hidden' }
            : {
                class: 'pm-h-folding-content',
                nodeName: 'div',
            };
        decorations.push(prosemirror_view_1.Decoration.node(item.from, item.to, attrs));
    }
    for (const item of Object.values(separatorDecos)) {
        decorations.push(prosemirror_view_1.Decoration.node(item.from, item.to, {
            nodeName: 'div',
            class: 'pm-h-folding-label',
            'data-value': item.value,
        }), prosemirror_view_1.Decoration.node(item.from, item.to, {
            nodeName: 'div',
            class: 'pm-h-folding-separator',
        }));
    }
    return prosemirror_view_1.DecorationSet.create(doc, decorations);
}
//# sourceMappingURL=Folding.js.map
