"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Placeholder = exports.createPlaceholder = 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 classname_1 = require("../../../classname.js");
const lodash_1 = require("../../../lodash.js");
const nodes_1 = require("../../../utils/nodes.js");
const placeholder_1 = require("../../../utils/placeholder.js");
const selection_1 = require("../../../utils/selection.js");
require("./index.css");
const getPlaceholderPluginKeys = (schema) => {
    const pluginKeys = [];
    for (const node in schema.nodes) {
        if (schema.nodes[node]) {
            const spec = schema.nodes[node].spec;
            if (spec.placeholder?.customPlugin) {
                pluginKeys.push(spec.placeholder.customPlugin);
            }
        }
    }
    return pluginKeys;
};
const b = (0, classname_1.cn)('placeholder');
const createPlaceholder = (node, parent, focus) => {
    const content = (0, placeholder_1.getPlaceholderContent)(node, parent);
    if (!content)
        return null;
    const placeholder = document.createElement('div');
    placeholder.className = b({ focus });
    const placeholderCursor = document.createElement('span');
    placeholderCursor.className = b('cursor');
    const placeholderText = document.createElement('span');
    placeholderText.className = b('text');
    placeholderText.textContent = content;
    placeholder.append(placeholderCursor, placeholderText);
    return placeholder;
};
exports.createPlaceholder = createPlaceholder;
const placeholderNeeded = (node) => {
    const childrenWithPlaceholderVisible = (0, prosemirror_utils_1.findChildren)(node, (n) => Boolean(n.type.spec.placeholder?.alwaysVisible));
    return ((0, nodes_1.isNodeEmpty)(node) &&
        // If there are child nodes with constant placeholder - give them the priority
        !childrenWithPlaceholderVisible.length);
};
const addDecoration = (widgetsMap, node, pos, parent, cursorPos, globalState) => {
    const placeholderSpec = node.type.spec.placeholder;
    const decorationPosition = pos + node.childCount + 1;
    // We do not add decoration if there is already a placeholder at this position
    if (!placeholderSpec || widgetsMap[decorationPosition])
        return;
    if (placeholderSpec.customPlugin) {
        widgetsMap[decorationPosition] = placeholderSpec.customPlugin;
        return;
    }
    const focus = decorationPosition === cursorPos;
    const placeholderDOM = (0, exports.createPlaceholder)(node, parent, focus);
    if (!placeholderDOM)
        return;
    if (focus)
        globalState.hasFocus = true;
    widgetsMap[decorationPosition] = {
        pos: decorationPosition,
        toDOM: placeholderDOM,
        spec: { focus },
    };
};
const pluginKey = new prosemirror_state_1.PluginKey('placeholder_plugin');
const Placeholder = (builder, opts) => {
    builder.context.set('placeholder', opts);
    builder.addPlugin(() => new prosemirror_state_1.Plugin({
        key: pluginKey,
        props: {
            attributes(state) {
                const attrs = {};
                if (pluginKey.getState(state).hasFocus) {
                    // hide native cursor
                    attrs.class = 'g-md-editor-hidecursor';
                }
                return attrs;
            },
            decorations(state) {
                return pluginKey.getState(state)?.decorationSet;
            },
        },
        state: {
            init: (_config, state) => initState(state),
            apply: applyState,
        },
    }), builder.Priority.VeryHigh);
};
exports.Placeholder = Placeholder;
function getPlaceholderWidgetSpecs(state) {
    const globalState = { hasFocus: false };
    const widgetsMap = {};
    const { selection } = state;
    const cursorPos = (0, selection_1.isTextSelection)(selection) ? selection.$cursor?.pos : null;
    getPlaceholderPluginKeys(state.schema).forEach((f) => {
        // We use find because it can be used to iterate over the DecorationSet.
        f.getState(state)?.find(undefined, undefined, (spec) => {
            widgetsMap[spec.pos] = f;
            return false;
        });
    });
    // Fraw placeholder for all nodes where placeholder is alwaysVisible
    const decorate = (node, pos, parent) => {
        const placeholderSpec = node.type.spec.placeholder;
        if (placeholderSpec && placeholderSpec.alwaysVisible && placeholderNeeded(node)) {
            addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);
        }
    };
    state.doc.descendants(decorate);
    const parentNode = (0, prosemirror_utils_1.findParentNodeClosestToPos)(state.selection.$from, (node) => {
        return Boolean(node.type.spec.placeholder);
    });
    const placeholderSpec = parentNode?.node.type.spec.placeholder;
    // Draw placeholder if it needs to be draw in the place of cursor
    if (parentNode &&
        placeholderNeeded(parentNode.node) &&
        placeholderSpec &&
        !placeholderSpec.alwaysVisible) {
        const { node, pos, depth } = parentNode;
        const parent = depth > 0 ? state.selection.$from.node(depth - 1) : null;
        addDecoration(widgetsMap, node, pos, parent, cursorPos, globalState);
    }
    const widgetSpecs = Object.values(widgetsMap).filter((decoration) => !(decoration instanceof prosemirror_state_1.PluginKey));
    return { widgetSpecs, hasFocus: globalState.hasFocus };
}
function initState(state) {
    const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(state);
    const decorationSet = prosemirror_view_1.DecorationSet.create(state.doc, widgetSpecs.map((widget) => prosemirror_view_1.Decoration.widget(widget.pos, widget.toDOM, widget.spec)));
    return { decorationSet, hasFocus };
}
function applyState(tr, oldPluginState, _oldState, newState) {
    const { widgetSpecs, hasFocus } = getPlaceholderWidgetSpecs(newState);
    const { decorationSet } = oldPluginState;
    const oldMappedSet = decorationSet.map(tr.mapping, tr.doc);
    // Find all decorations that are present in old and new set
    const decorationsThatDidNotChange = widgetSpecs.reduce((a, { pos, spec }) => {
        const deco = oldMappedSet.find(pos, pos);
        if (deco.length && (0, lodash_1.isEqual)(deco[0].spec, spec))
            a.push(...deco);
        return a;
    }, []);
    // Those are decorations that are presenr only in new set
    const newAddedDecorations = widgetSpecs.filter(({ pos }) => !decorationsThatDidNotChange.map(({ from }) => from).includes(pos));
    // That is a set with decorations that are present in old set and absent in new set
    const notRelevantDecorations = oldMappedSet.remove(decorationsThatDidNotChange);
    let newSet = oldMappedSet;
    // Remove decorations that are not present in new set
    if (notRelevantDecorations.find().length)
        newSet = newSet.remove(notRelevantDecorations.find());
    // Add new decorations
    if (newAddedDecorations.length)
        newSet = newSet.add(tr.doc, newAddedDecorations.map((widget) => prosemirror_view_1.Decoration.widget(widget.pos, widget.toDOM, widget.spec)));
    return { decorationSet: newSet, hasFocus };
}
//# sourceMappingURL=index.js.map
