/**
 * @author Louis-Dominique Dubeau
 * @license MPL 2.0
 * @copyright Mangalam Research Center for Buddhist Languages
 */
define(["require", "exports", "./dloc", "./domtypeguards", "./guiroot"], function (require, exports, dloc_1, domtypeguards_1, guiroot_1) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    // Utility function for boundaryXY.
    function parentBoundary(node, root) {
        const parent = node.parentNode;
        // Cannot find a sensible boundary
        if (!root.contains(parent)) {
            return { left: 0, top: 0, bottom: 0 };
        }
        return boundaryXY(dloc_1.DLoc.mustMakeDLoc(root, node));
    }
    // tslint:disable-next-line:max-func-body-length
    function boundaryXY(boundary) {
        const node = boundary.node;
        let offset = boundary.offset;
        const nodeIsElement = domtypeguards_1.isElement(node);
        const nodeIsText = domtypeguards_1.isText(node);
        let nodeLen;
        if (nodeIsElement) {
            nodeLen = node.childNodes.length;
        }
        else if (nodeIsText) {
            nodeLen = node.length;
        }
        else {
            throw new Error(`unexpected node type: ${node.nodeType}`);
        }
        // The node is empty ...
        if (nodeLen === 0) {
            return parentBoundary(node, boundary.root);
        }
        const range = node.ownerDocument.createRange();
        let rect;
        let child;
        while (offset < nodeLen) {
            // The array is empty if the node is a text node, and child will be
            // undefined.
            child = node.childNodes[offset];
            // We use getClientRects()[0] so that when we are working with an inline
            // node, we get only the first rect of the node. If the node is a block,
            // then there should be only one rect anyway.
            if (domtypeguards_1.isElement(child)) {
                rect = child.getClientRects()[0];
            }
            else {
                range.setStart(node, offset);
                range.setEnd(node, offset + 1);
                rect = range.getClientRects()[0];
            }
            // If the element that covers the range is invisible, then getClientRects
            // can return undefined. A 0, 0, 0, 0 rect is also theoretically possible.
            if (rect != null &&
                (rect.left !== 0 || rect.right !== 0 || rect.top !== 0 ||
                    rect.bottom !== 0)) {
                return { left: rect.left, top: rect.top, bottom: rect.bottom };
            }
            offset++;
        }
        // We failed to find something after our offset from which to get
        // coordinates. Try again.
        offset = boundary.offset;
        const win = node.ownerDocument.defaultView;
        while (offset !== 0) {
            offset--;
            child = undefined;
            // We check whether the thing we are going to cover with the range is
            // inline.
            let inline;
            if (nodeIsText) {
                inline = true;
            }
            else if (nodeIsElement) {
                child = node.childNodes[offset];
                if (domtypeguards_1.isText(child)) {
                    inline = true;
                }
                else if (domtypeguards_1.isElement(child)) {
                    const display = win.getComputedStyle(child).getPropertyValue("display");
                    inline = (display === "inline" || display === "inline-block");
                }
                else {
                    throw new Error(`unexpected node type: ${child.nodeType}`);
                }
            }
            else {
                throw new Error(`unexpected node type: ${node.nodeType}`);
            }
            // If it is not empty, and offset is at the end of the contents, then there
            // must be something *before* the point indicated by offset. Get a rectangle
            // around that and return the right side as the left value.
            let rects;
            if (domtypeguards_1.isElement(child)) {
                rects = child.getClientRects();
            }
            else {
                range.setStart(node, offset);
                range.setEnd(node, offset + 1);
                rects = range.getClientRects();
            }
            rect = rects[rects.length - 1];
            if (rect != null) {
                return (inline ?
                    // Yep, we use the right side when it is inline.
                    { left: rect.right, top: rect.top, bottom: rect.bottom } :
                    { left: rect.left, top: rect.top, bottom: rect.bottom });
            }
        }
        // We can get here with an offset of 0. In this case, we have to move to the
        // parent.
        return parentBoundary(node, boundary.root);
    }
    exports.boundaryXY = boundaryXY;
    function getAttrValueNode(attrVal) {
        if (!attrVal.classList.contains("_attribute_value")) {
            throw new Error("getAttrValueNode operates only on attribute values");
        }
        let ret = attrVal;
        let child = attrVal.firstChild;
        if (child !== null) {
            while (child !== null && !domtypeguards_1.isText(child)) {
                child = child.nextSibling;
            }
            if (child !== null) {
                ret = child;
            }
        }
        return ret;
    }
    exports.getAttrValueNode = getAttrValueNode;
    function getGUINodeIfExists(editor, node) {
        if (node == null) {
            return undefined;
        }
        try {
            const caret = editor.caretManager.fromDataLocation(node, 0);
            return caret != null ? caret.node : undefined;
        }
        catch (ex) {
            if (ex instanceof guiroot_1.AttributeNotFound) {
                return undefined;
            }
            throw ex;
        }
    }
    exports.getGUINodeIfExists = getGUINodeIfExists;
});
//  LocalWords:  MPL domutil util boundaryXY nodeType getClientRects rect
//  LocalWords:  getAttrValueNode
//# sourceMappingURL=wed-util.js.map