/**
 * Listens to changes on a tree and updates the GUI tree in response to changes.
 * @author Louis-Dominique Dubeau
 * @license MPL 2.0
 * @copyright Mangalam Research Center for Buddhist Languages
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
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", "jquery", "./convert", "./dloc", "./domtypeguards", "./domutil", "./tree-updater", "./util"], function (require, exports, jquery_1, convert, dloc_1, domtypeguards_1, domutil_1, tree_updater_1, util) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    jquery_1 = __importDefault(jquery_1);
    convert = __importStar(convert);
    util = __importStar(util);
    /**
     * Updates a GUI tree so that its data nodes (those nodes that are not
     * decorations) mirror a data tree.
     */
    class GUIUpdater extends tree_updater_1.TreeUpdater {
        /**
         * @param guiTree The DOM tree to update.
         *
         * @param treeUpdater A tree updater that updates the data tree. It serves as
         * a source of modification events which the object being created will listen
         * on.
         */
        constructor(guiTree, treeUpdater) {
            super(guiTree);
            this.treeUpdater = treeUpdater;
            this.haveTooltips =
                guiTree.ownerDocument.getElementsByClassName("wed-has-tooltip");
            this.treeUpdater.events.subscribe((ev) => {
                switch (ev.name) {
                    case "InsertNodeAt":
                        this._insertNodeAtHandler(ev);
                        break;
                    case "SetTextNodeValue":
                        this._setTextNodeValueHandler(ev);
                        break;
                    case "BeforeDeleteNode":
                        this._beforeDeleteNodeHandler(ev);
                        break;
                    case "SetAttributeNS":
                        this._setAttributeNSHandler(ev);
                        break;
                    default:
                    // Do nothing...
                }
            });
        }
        /**
         * Handles "InsertNodeAt" events.
         *
         * @param ev The event.
         */
        _insertNodeAtHandler(ev) {
            const guiCaret = this.fromDataLocation(ev.parent, ev.index);
            if (guiCaret === null) {
                throw new Error("cannot find gui tree position");
            }
            const clone = convert.toHTMLTree(this.tree.ownerDocument, ev.node);
            if (domtypeguards_1.isElement(ev.node)) {
                // If ev.node is an element, then the clone is an element too.
                domutil_1.linkTrees(ev.node, clone);
            }
            this.insertNodeAt(guiCaret, clone);
        }
        /**
         * Handles "SetTextNodeValue" events.
         *
         * @param ev The event.
         */
        _setTextNodeValueHandler(ev) {
            const guiCaret = this.fromDataLocation(ev.node, 0);
            if (guiCaret === null) {
                throw new Error("cannot find gui tree position");
            }
            this.setTextNodeValue(guiCaret.node, ev.value);
        }
        /**
         * Handles "BeforeDeleteNode" events.
         *
         * @param ev The event.
         */
        _beforeDeleteNodeHandler(ev) {
            const dataNode = ev.node;
            let toRemove;
            let element = false;
            switch (dataNode.nodeType) {
                case Node.TEXT_NODE:
                    const guiCaret = this.fromDataLocation(dataNode, 0);
                    if (guiCaret === null) {
                        throw new Error("cannot find gui tree position");
                    }
                    toRemove = guiCaret.node;
                    break;
                case Node.ELEMENT_NODE:
                    toRemove = jquery_1.default.data(dataNode, "wed_mirror_node");
                    element = true;
                    break;
                default:
            }
            this.deleteNode(toRemove);
            // We have to do this **after** we delete the node.
            if (element) {
                domutil_1.unlinkTree(dataNode);
                domutil_1.unlinkTree(toRemove);
            }
        }
        /**
         * Handles "SetAttributeNS" events.
         *
         * @param ev The event.
         */
        _setAttributeNSHandler(ev) {
            const guiCaret = this.fromDataLocation(ev.node, 0);
            if (guiCaret === null) {
                throw new Error("cannot find gui tree position");
            }
            this.setAttributeNS(guiCaret.node, "", util.encodeAttrName(ev.attribute), ev.newValue);
        }
        fromDataLocation(loc, offset) {
            let node;
            if (loc instanceof dloc_1.DLoc) {
                node = loc.node;
                offset = loc.offset;
            }
            else {
                node = loc;
                if (offset === undefined) {
                    throw new Error("must specify an offset");
                }
            }
            let guiNode = this.pathToNode(this.treeUpdater.nodeToPath(node));
            if (guiNode === null) {
                return null;
            }
            if (domtypeguards_1.isText(node)) {
                return dloc_1.DLoc.mustMakeDLoc(this.tree, guiNode, offset);
            }
            if (domutil_1.isAttr(node)) {
                // The check for the node type is to avoid getting a location inside a
                // placeholder.
                if (domtypeguards_1.isText(guiNode.firstChild)) {
                    guiNode = guiNode.firstChild;
                }
                return dloc_1.DLoc.mustMakeDLoc(this.tree, guiNode, offset);
            }
            if (offset === 0) {
                return dloc_1.DLoc.mustMakeDLoc(this.tree, guiNode, 0);
            }
            if (offset >= node.childNodes.length) {
                return dloc_1.DLoc.mustMakeDLoc(this.tree, guiNode, guiNode.childNodes.length);
            }
            const guiChild = this.pathToNode(this.treeUpdater.nodeToPath(node.childNodes[offset]));
            if (guiChild === null) {
                // This happens if for instance node has X children but the
                // corresponding node in tree has X-1 children.
                return dloc_1.DLoc.mustMakeDLoc(this.tree, guiNode, guiNode.childNodes.length);
            }
            return dloc_1.DLoc.mustMakeDLoc(this.tree, guiChild);
        }
        /**
         * Check whether a tooltip should be destroyed when the element is removed
         * from the tree. This function checks whether the element or any descendant
         * has a tooltip.
         *
         * @param el An element to check.
         *
         */
        removeTooltips(el) {
            for (const hasTooltip of Array.from(this.haveTooltips)) {
                if (!el.contains(hasTooltip)) {
                    continue;
                }
                const tt = jquery_1.default.data(hasTooltip, "bs.tooltip");
                if (tt != null) {
                    tt.destroy();
                }
                // We don't remove the wed-has-tooltip class. Generally, the elements
                // that have tooltips and are removed from the GUI tree won't be added
                // to the tree again. If they are added again, they'll most likely get
                // a new tooltip so removing the class does not gain us much because
                // it will be added again.
                //
                // If we *were* to remove the class, then the collection would change
                // as we go through it.
            }
        }
    }
    exports.GUIUpdater = GUIUpdater;
});
//  LocalWords:  domutil jquery pathToNode nodeToPath jQuery deleteNode Dubeau
//  LocalWords:  insertNodeAt MPL Mangalam gui setTextNodeValue TreeUpdater ev
//  LocalWords:  BeforeDeleteNode SetAttributeNS
//# sourceMappingURL=gui-updater.js.map