var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
define(["require", "exports", "salve", "wed"], function (require, exports, salve, wed_1) {
    /**
     * Transformation registry for the generic mode.
     * @author Louis-Dominique Dubeau
     * @license MPL 2.0
     * @copyright Mangalam Research Center for Buddhist Languages
     */
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    salve = __importStar(salve);
    const { insertElement, unwrap, wrapInElement } = wed_1.transformation;
    var Transformation = wed_1.transformation.Transformation;
    const { childByClass, indexOf } = wed_1.domutil;
    const { isAttr, isElement } = wed_1.domtypeguards;
    function errFilter(err) {
        const errMsg = err.error.toString();
        return errMsg.lastIndexOf("tag required: ", 0) === 0;
    }
    /**
     * Perform the autoinsertion algorithm on an element.
     *
     * @param el The element that should be subject to the autoinsertion algorithm.
     *
     * @param editor The editor which owns the element.
     */
    function _autoinsert(el, editor) {
        // tslint:disable-next-line:no-constant-condition strict-boolean-expressions
        while (true) {
            let errors = editor.validator.getErrorsFor(el);
            errors = errors.filter(errFilter);
            if (errors.length === 0) {
                break;
            }
            const ename = errors[0].error.getNames()[0];
            const names = ename.toArray();
            // If names is null the pattern is not simple and we cannot autoinsert. If
            // there is more than one option, we also cannot autoinsert.
            if (names === null || names.length > 1) {
                break;
            }
            const name = names[0];
            const locations = editor.validator.possibleWhere(el, new salve.Event("enterStartTag", name.ns, name.name));
            if (locations.length !== 1) {
                break;
            }
            const mode = editor.modeTree.getMode(el);
            const unresolved = mode.getAbsoluteResolver().unresolveName(name.ns, name.name);
            if (unresolved === undefined) {
                throw new Error(`cannot unresolve {${name.ns}}${name.name}`);
            }
            const actions = mode.getContextualActions("insert", unresolved, el, locations[0]);
            // Don't auto insert if it happens that the operation would be ambiguous
            // (ie. if there is more than one way to insert the element).
            if (actions.length !== 1) {
                break;
            }
            // Don't auto insert if the operation needs input from the user.
            if (actions[0].needsInput) {
                break;
            }
            //
            // We move the caret ourselves rather than using moveCaretTo. In this
            // context, it does not matter because autoinsert is meant to be called by a
            // transformation anyway.
            //
            editor.caretManager.setCaret(el, locations[0]);
            actions[0].execute({ name: unresolved });
        }
    }
    function executeInsert(editor, data) {
        const caret = editor.caretManager.getDataCaret();
        if (caret === undefined) {
            throw new Error("inserting without a defined caret!");
        }
        const mode = editor.modeTree.getMode(caret.node);
        const absoluteResolver = mode.getAbsoluteResolver();
        const ename = absoluteResolver.resolveName(data.name);
        if (ename === undefined) {
            throw new Error(`cannot resolve ${data.name}`);
        }
        const unresolved = editor.validator.unresolveNameAt(caret.node, caret.offset, ename.ns, ename.name);
        const el = insertElement(editor.dataUpdater, caret.node, caret.offset, ename.ns, data.name);
        if (unresolved === undefined) {
            // The namespace used by the element has not been defined yet. So we need to
            // define it.
            const prefix = absoluteResolver.prefixFromURI(ename.ns);
            const name = (prefix === "") ? "xmlns" : `xmlns:${prefix}`;
            // The next name is necessarily resolvable so we assert that it is not
            // resolving to undefined.
            const xmlnsURI = absoluteResolver.resolveName("xmlns:q").ns;
            editor.dataUpdater.setAttributeNS(el, xmlnsURI, name, ename.ns);
        }
        let caretNode = el;
        if (mode.getModeOptions().autoinsert) {
            _autoinsert(el, editor);
            // Set el to the deepest first child, so that the caret is put in the right
            // position.
            while (caretNode !== null) {
                const child = caretNode.firstChild;
                if (child === null) {
                    break;
                }
                caretNode = child;
            }
        }
        editor.caretManager.setCaret(caretNode, 0);
    }
    function executeUnwrap(editor, data) {
        const node = data.node;
        if (!isElement(node)) {
            throw new Error("node must be an element");
        }
        const parent = node.parentNode;
        const index = indexOf(parent.childNodes, node);
        unwrap(editor.dataUpdater, node);
        editor.caretManager.setCaret(parent, index);
    }
    function executeWrap(editor, data) {
        const sel = editor.caretManager.sel;
        if (sel == null) {
            throw new Error("wrap transformation called with undefined range");
        }
        if (sel.collapsed) {
            throw new Error("wrap transformation called with collapsed range");
        }
        const [startCaret, endCaret] = sel.mustAsDataCarets();
        const mode = editor.modeTree.getMode(startCaret.node);
        const ename = mode.getAbsoluteResolver().resolveName(data.name);
        if (ename === undefined) {
            throw new Error(`cannot resolve ${data.name}`);
        }
        const el = wrapInElement(editor.dataUpdater, startCaret.node, startCaret.offset, endCaret.node, endCaret.offset, ename.ns, data.name);
        const parent = el.parentNode;
        editor.caretManager.setCaret(startCaret.make(parent, indexOf(parent.childNodes, el) + 1));
    }
    function executeWrapContent(editor, data) {
        const toWrap = data.node;
        if (!isElement(toWrap)) {
            throw new Error("node must be an element");
        }
        const mode = editor.modeTree.getMode(toWrap);
        const ename = mode.getAbsoluteResolver().resolveName(data.name);
        if (ename === undefined) {
            throw new Error(`cannot resolve ${data.name}`);
        }
        wrapInElement(editor.dataUpdater, toWrap, 0, toWrap, toWrap.childNodes.length, ename.ns, data.name);
    }
    function executeDeleteElement(editor, data) {
        const node = data.node;
        if (!isElement(node)) {
            throw new Error("node must be an element");
        }
        const parent = node.parentNode;
        const index = indexOf(parent.childNodes, node);
        const guiLoc = editor.caretManager.fromDataLocation(node, 0);
        // If the node we start with is an Element, then the node in guiLoc is
        // necessarily an Element too.
        if (!guiLoc.node.classList.contains("_readonly")) {
            editor.dataUpdater.removeNode(node);
            editor.caretManager.setCaret(parent, index);
        }
    }
    function executeDeleteParent(editor, data) {
        const node = data.node;
        if (!isElement(node)) {
            throw new Error("node must be an element");
        }
        const parent = node.parentNode;
        const index = indexOf(parent.childNodes, node);
        const guiLoc = editor.caretManager.mustFromDataLocation(node, 0);
        // If the node we start with is an Element, then the node in guiLoc is
        // necessarily an Element too.
        if (!guiLoc.node.classList.contains("_readonly")) {
            editor.dataUpdater.removeNode(node);
            editor.caretManager.setCaret(parent, index);
        }
    }
    function executeAddAttribute(editor, data) {
        const node = data.node;
        if (!isElement(node)) {
            throw new Error("node must be an element");
        }
        const guiLoc = editor.caretManager.mustFromDataLocation(node, 0);
        // If the node we start with is an Element, then the node in guiLoc is
        // necessarily an Element too.
        if (!guiLoc.node.classList.contains("_readonly")) {
            editor.dataUpdater.setAttribute(node, data.name, "");
            const attr = node.getAttributeNode(data.name);
            editor.caretManager.setCaret(attr, 0);
        }
    }
    function executeDeleteAttribute(editor, data) {
        const node = data.node;
        if (node == null || !isAttr(node)) {
            throw new Error("node must be an attribute");
        }
        const element = node.ownerElement;
        const caretManager = editor.caretManager;
        const guiOwnerLoc = caretManager.mustFromDataLocation(element, 0);
        // If the node we start with is an Element, then the node in guiOwnerLoc
        // is necessarily an Element too.
        const guiOwner = guiOwnerLoc.node;
        if (!guiOwner.classList.contains("_readonly")) {
            const encoded = node.name;
            const startLabel = childByClass(guiOwner, "__start_label");
            // An earlier version of this code relied on the order of attributes in the
            // data tree. However, this order is not consistent from platform to
            // platform. Using the order of attributes in the GUI is
            // consistent. Therefore we go to the GUI to find the next attribute.
            const values = startLabel.getElementsByClassName("_attribute_value");
            // We have to get the parent node because fromDataLocation brings us to the
            // text node that contains the value.
            const guiNode = caretManager.mustFromDataLocation(node, 0).node.parentNode;
            const index = indexOf(values, guiNode);
            const nextGUIValue = values[index + 1];
            const nextAttr = nextGUIValue != null ?
                editor.toDataNode(nextGUIValue) : null;
            editor.dataUpdater.setAttribute(element, encoded, null);
            // We set the caret inside the next attribute, or if it does not exist,
            // inside the label.
            if (nextAttr !== null) {
                editor.caretManager.setCaret(nextAttr, 0);
            }
            else {
                editor.caretManager.setCaret(guiOwner.getElementsByClassName("_element_name")[0], 0);
            }
        }
    }
    /**
     * @param forEditorAPI The editor for which to create transformations.
     */
    function makeTagTr(forEditor) {
        const ret = Object.create(null);
        ret.insert = new Transformation(forEditor, "insert", "Create new <name>", executeInsert, { abbreviatedDesc: "" });
        ret.unwrap =
            new Transformation(forEditor, "unwrap", "Unwrap the content of this element", executeUnwrap);
        ret.wrap = new Transformation(forEditor, "wrap", "Wrap in <name>", executeWrap);
        ret["wrap-content"] = new Transformation(forEditor, "wrap-content", "Wrap content in <name>", executeWrapContent);
        ret["delete-element"] =
            new Transformation(forEditor, "delete-element", "Delete this element", executeDeleteElement);
        ret["delete-parent"] =
            new Transformation(forEditor, "delete-parent", "Delete <name>", executeDeleteParent);
        ret["add-attribute"] = new Transformation(forEditor, "add-attribute", "Add @<name>", executeAddAttribute);
        ret["delete-attribute"] =
            new Transformation(forEditor, "delete-attribute", "Delete this attribute", executeDeleteAttribute);
        ret["insert-text"] = new Transformation(forEditor, "insert-text", "Insert \"<name>\"", (editor, data) => {
            editor.insertText(data.name);
        });
        ret.split = forEditor.splitNodeTr;
        return ret;
    }
    exports.makeTagTr = makeTagTr;
});
//  LocalWords:  TransformationRegistry Mangalam MPL Dubeau autoinsertion ie el
//  LocalWords:  autoinsert enterStartTag moveCaretTo xmlns guiLoc readonly
//  LocalWords:  guiOwnerLoc fromDataLocation
//# sourceMappingURL=generic-tr.js.map