function flipPos(position) {
    switch (position) {
        case "top": return "bottom";
        case "bottom": return "top";
        case "left": return "right";
        case "right": return "left";
        case "ne": return "sw";
        case "nw": return "se";
        case "se": return "nw";
        case "sw": return "ne";
        default: return position;
    }
}
function flipAlign(align) {
    switch (align) {
        case "start": return "end";
        case "end": return "start";
        default: return align;
    }
}
class PositionResolver {
    /**
     * The computed left coordinate (i.e. x)
     */
    left;
    /**
     * The computed top coordinate (i.e. y)
     */
    top;
    /**
     * The computed width constraint. If undefined it means that the width is not constrained.
     */
    width;
    /**
     * The computed height constraint. If undefined it means that the height is not constrained.
     */
    height;
    /**
     * Alignment axis. The axis is computed from the position constraints.
     * If no axis is found - no alignment will be done
     */
    alignAxis;
    /**
     * A gap size between the anchor element and the target element.
     */
    gap;
    constructor(gap = 0) {
        this.gap = gap;
    }
    position(pos, anchorRect, elemRect) {
        switch (pos) {
            case "top": {
                this.top = anchorRect.top - elemRect.height - this.gap;
                this.alignAxis = 'x';
                break;
            }
            case "bottom": {
                this.top = anchorRect.bottom + this.gap;
                this.alignAxis = 'x';
                break;
            }
            case "left": {
                this.left = anchorRect.left - elemRect.width - this.gap;
                this.alignAxis = 'y';
                break;
            }
            case "right": {
                this.left = anchorRect.right + this.gap;
                this.alignAxis = 'y';
                break;
            }
            case "ne": {
                this.top = anchorRect.top - elemRect.height - this.gap;
                this.left = anchorRect.right + this.gap;
                break;
            }
            case "nw": {
                this.top = anchorRect.top - elemRect.height - this.gap;
                this.left = anchorRect.left - elemRect.width - this.gap;
                break;
            }
            case "se": {
                this.top = anchorRect.bottom + this.gap;
                this.left = anchorRect.right + this.gap;
                break;
            }
            case "sw": {
                this.top = anchorRect.bottom + this.gap;
                this.left = anchorRect.left - elemRect.width - this.gap;
                break;
            }
        }
    }
    align(align, anchorRect, elemRect) {
        if (!this.alignAxis)
            return; // no alignment axis
        switch (align) {
            case 'start': {
                if (this.alignAxis === 'x') {
                    this.left = anchorRect.left;
                }
                else {
                    this.top = anchorRect.top;
                }
                break;
            }
            case 'end': {
                if (this.alignAxis === 'x') {
                    this.left = anchorRect.right - elemRect.width;
                }
                else {
                    this.top = anchorRect.bottom - elemRect.height;
                }
                break;
            }
            case 'center': {
                // centering depend on the final value of the width / height
                if (this.alignAxis === 'x') {
                    this.left = anchorRect.left + anchorRect.width / 2 - elemRect.width / 2;
                }
                else {
                    this.top = anchorRect.top + anchorRect.height / 2 - elemRect.height / 2;
                }
                break;
            }
            case 'fill': {
                if (this.alignAxis === 'x') {
                    this.left = anchorRect.left;
                    this.width = anchorRect.right - anchorRect.left;
                }
                else {
                    this.top = anchorRect.top;
                    this.height = anchorRect.bottom - anchorRect.top;
                }
                break;
            }
        }
    }
    computePosition(constraints, elemRect, anchorRect) {
        this.position(constraints.position, anchorRect, elemRect);
        constraints.align && this.align(constraints.align, anchorRect, elemRect);
        if (!this.left && !this.top) {
            throw new Error("Invalid position. Cannot compute x,y coordinates");
        }
        const constrainWidth = this.width != null;
        const constrainHeight = this.height != null;
        const width = constrainWidth ? this.width : elemRect.width;
        const height = constrainHeight ? this.height : elemRect.height;
        return {
            rect: new DOMRect(this.left, this.top, width, height),
            constrainWidth,
            constrainHeight,
            position: constraints.position,
            align: constraints.align
        };
    }
    flipAxis(constraints, axis) {
        if (this.alignAxis === axis) { // flip alignment
            if (constraints.align) {
                const newAlign = flipAlign(constraints.align);
                if (newAlign !== constraints.align) {
                    return { ...constraints, align: newAlign };
                }
            }
        }
        else { // flip positioning
            const newPos = flipPos(constraints.position);
            if (newPos !== constraints.position) {
                return { ...constraints, position: newPos };
            }
        }
        return null; // nothing to do
    }
}
function isElementVisible(elemRect, clientRect) {
    return elemRect.left >= clientRect.left && elemRect.right <= clientRect.right
        &&
            elemRect.top >= clientRect.top && elemRect.bottom <= clientRect.bottom;
}
function isElementVisibleOnAxis(elemRect, clientRect, axis) {
    if (axis === 'x') {
        return elemRect.left >= clientRect.left && elemRect.right <= clientRect.right;
    }
    else {
        return elemRect.top >= clientRect.top && elemRect.bottom <= clientRect.bottom;
    }
}
/**
 * Compute the position by trying to adjust the constraints until the computed position fits into the client area.
 * Returns the best position that fits the constraints.
 * @param constraints
 * @param elemRect
 * @param anchorRect
 * @param clientRect
 * @returns null if the element cannot be positioned otherwise returns a position object
 */
export function computePosition(constraints, elemRect, anchorRect, clientRect) {
    const resolver = new PositionResolver(constraints.gap);
    let computedPos = resolver.computePosition(constraints, elemRect, anchorRect);
    const isVisibleOnXAxis = isElementVisibleOnAxis(computedPos.rect, clientRect, 'x');
    const isVisibleOnYAxis = isElementVisibleOnAxis(computedPos.rect, clientRect, 'y');
    if (isVisibleOnXAxis && isVisibleOnYAxis) {
        return computedPos; // the element is visible on both axis
    }
    let newConstraints = null;
    if (!isVisibleOnXAxis) {
        newConstraints = resolver.flipAxis(constraints, 'x');
    }
    if (!isVisibleOnYAxis) {
        newConstraints = resolver.flipAxis(newConstraints || constraints, 'y');
    }
    if (!newConstraints) {
        return null; // cannot find a better position
    }
    computedPos = new PositionResolver(resolver.gap).computePosition(newConstraints, elemRect, anchorRect);
    if (isElementVisible(computedPos.rect, clientRect)) {
        return computedPos;
    }
    return null; // cannot find a better position
}
//# sourceMappingURL=position.js.map