"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.collapseListsPlugin = void 0;
exports.collapseAllNestedListItems = collapseAllNestedListItems;
const prosemirror_model_1 = require("prosemirror-model");
const prosemirror_state_1 = require("prosemirror-state");
// @ts-ignore // TODO: fix cjs build
const prosemirror_utils_1 = require("prosemirror-utils");
const utils_1 = require("../utils.js");
const MAX_COLLAPSE_DEPTH = 100;
/**
 * Collapses redundant nested lists inside list items
 *
 * Converts structures where a list item starts with
 * a nested list into a flat list structure
 *
 * IMPORTANT: must be registered AFTER MergeListsPlugin.
 * Collapsing may create adjacent lists that require merging
 */
const collapseListsPlugin = () => new prosemirror_state_1.Plugin({
    appendTransaction(trs, oldState, newState) {
        // early exit if document unchanged
        const docChanged = trs.some((tr) => tr.docChanged);
        if (!docChanged) {
            return null;
        }
        // early exit if not inside a list
        const hasParentList = (0, prosemirror_utils_1.hasParentNode)(utils_1.isListNode)(newState.selection) ||
            (0, prosemirror_utils_1.hasParentNode)(utils_1.isListNode)(oldState.selection);
        if (!hasParentList) {
            return null;
        }
        const { tr } = newState;
        // collapse of redundant nested lists
        const lastCollapsePos = collapseAllNestedListItems(tr);
        // restore cursor position
        if (lastCollapsePos !== null) {
            const clampedPos = Math.min(lastCollapsePos, tr.doc.content.size);
            tr.setSelection(prosemirror_state_1.Selection.near(tr.doc.resolve(clampedPos)));
        }
        return tr.docChanged ? tr : null;
    },
});
exports.collapseListsPlugin = collapseListsPlugin;
/**
 * Finds list items with redundant nesting, collapses them recursively,
 * and applies replacements in reverse document order
 */
function collapseAllNestedListItems(tr) {
    const replacements = [];
    const schema = tr.doc.type.schema;
    // skipUntil prevents re-processing children that were already collapsed
    // by the recursive call in collapseListItemContent
    let skipUntil = -1;
    tr.doc.descendants((node, pos) => {
        if (pos < skipUntil || node.isTextblock) {
            return false;
        }
        if (!(0, utils_1.isListItemNode)(node)) {
            return true;
        }
        const { firstChild } = node;
        if (!firstChild || !(0, utils_1.isListNode)(firstChild)) {
            return true;
        }
        const collapsedContent = collapseListItemContent(node, schema, 0);
        if (collapsedContent === null) {
            return true;
        }
        replacements.push({
            from: pos,
            to: pos + node.nodeSize,
            content: collapsedContent,
        });
        skipUntil = pos + node.nodeSize;
        return false;
    });
    if (replacements.length === 0) {
        return null;
    }
    // apply from end to start so earlier positions stay valid
    for (let i = replacements.length - 1; i >= 0; i--) {
        const { from, to, content } = replacements[i];
        tr.replaceWith(from, to, content);
    }
    // map the original end of the last replacement to its post-edit position
    const last = replacements[replacements.length - 1];
    return tr.mapping.map(last.to);
}
/**
 * Recursively collapses a list item with redundant nesting
 * into a Fragment of flat list items
 *
 * A redundantly nested list item has a list as its first child
 * (instead of a paragraph)
 */
function collapseListItemContent(itemNode, schema, depth) {
    const { firstChild } = itemNode;
    if (!firstChild || !(0, utils_1.isListNode)(firstChild)) {
        return null;
    }
    if (depth >= MAX_COLLAPSE_DEPTH) {
        return null;
    }
    // .slice(1) = all children except firstChild
    const remaining = itemNode.content.content.slice(1);
    let result = prosemirror_model_1.Fragment.empty;
    firstChild.forEach((child) => {
        if ((0, utils_1.isListItemNode)(child)) {
            const collapsed = collapseListItemContent(child, schema, depth + 1);
            result = result.append(collapsed ?? prosemirror_model_1.Fragment.from(child));
        }
        else {
            result = result.append(prosemirror_model_1.Fragment.from(child));
        }
    });
    if (remaining.length) {
        result = result.append(prosemirror_model_1.Fragment.from((0, utils_1.liType)(schema).create(null, remaining)));
    }
    return result;
}
//# sourceMappingURL=CollapseListsPlugin.js.map
