"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = require("../../utils/common");
const NAMES = {
    size: ['height', 'width'],
    clientSize: ['clientHeight', 'clientWidth'],
    offsetSize: ['offsetHeight', 'offsetWidth'],
    maxSize: ['maxHeight', 'maxWidth'],
    before: ['top', 'left'],
    marginBefore: ['marginTop', 'marginLeft'],
    after: ['bottom', 'right'],
    marginAfter: ['marginBottom', 'marginRight'],
    scrollOffset: ['pageYOffset', 'pageXOffset'],
    offset: ['offsetY', 'offsetY'],
    offsetOpt: ['yOffset', 'xOffset'],
    bodyScroll: ['scrollHeight', 'scrollWidth'],
    inner: ['innerHeight', 'innerWidth'],
};
function normalizeRect(rect) {
    return {
        top: rect.top,
        bottom: rect.bottom,
        left: rect.left,
        right: rect.right,
    };
}
// eslint-disable-next-line complexity
function default_1(elem, initialAnchor, side = 'bottom', options = {}) {
    let anchor;
    if (initialAnchor instanceof Element || initialAnchor instanceof Range) {
        anchor = normalizeRect(elem.getBoundingClientRect());
    }
    else {
        anchor = initialAnchor;
    }
    const anchorRect = Object.assign({
        top: anchor.bottom || 0,
        bottom: anchor.top || 0,
        left: anchor.right || 0,
        right: anchor.left || 0,
    }, anchor);
    const boundRect = {
        top: 0,
        left: 0,
        bottom: window.innerHeight,
        right: window.innerWidth,
    };
    if (options.bound) {
        if (options.bound instanceof Element || options.bound instanceof Range) {
            options.bound = normalizeRect(options.bound.getBoundingClientRect());
        }
        Object.assign(boundRect, options.bound);
    }
    const elemStyle = getComputedStyle(elem);
    const { primary, secondary } = Object.entries(NAMES).reduce((acc, [key, value]) => ({
        primary: { ...acc.primary, [key]: value[side === 'top' || side === 'bottom' ? 0 : 1] },
        secondary: {
            ...acc.secondary,
            [key]: value[side === 'top' || side === 'bottom' ? 1 : 0],
        },
    }), { primary: {}, secondary: {} });
    elem.style.position = 'absolute';
    elem.style.maxWidth = '';
    elem.style.maxHeight = '';
    const offset = options[primary.offsetOpt] || 0;
    // Constrain the maximum size of the popup along the secondary axis.
    const secondaryMarginBefore = parseInt(elemStyle[secondary.marginBefore], 10);
    const secondaryMarginAfter = parseInt(elemStyle[secondary.marginAfter], 10);
    const secondaryMargin = secondaryMarginBefore + secondaryMarginAfter;
    const secondaryMaxSize = boundRect[secondary.after] - boundRect[secondary.before] - secondaryMargin;
    const styledSecondaryMaxSize = parseInt(elemStyle[secondary.maxSize], 10);
    if (!styledSecondaryMaxSize || secondaryMaxSize < styledSecondaryMaxSize) {
        elem.style[secondary.maxSize] = (0, common_1.px)(secondaryMaxSize);
    }
    // Calculate the available room on either side of the anchor element. If
    // the size of the popup is more than is available on the given side, then we
    // will switch to the side with more room.
    const margin = parseInt(elemStyle[primary.marginBefore], 10) +
        parseInt(elemStyle[primary.marginAfter], 10);
    const roomBefore = anchorRect[primary.before] - boundRect[primary.before] - margin;
    const roomAfter = boundRect[primary.after] - anchorRect[primary.after] - margin - offset;
    if ((side === primary.before && elem[primary.offsetSize] > roomBefore) ||
        (side === primary.after && elem[primary.offsetSize] > roomAfter)) {
        side = roomBefore > roomAfter ? primary.before : primary.after;
    }
    // If the size of the popup exceeds the room available on this side, then
    // we will give the popup an explicit size so that it doesn't go off-screen.
    const primaryMaxSize = side === primary.before ? roomBefore : roomAfter;
    const styledPrimaryMaxSize = parseInt(elemStyle[primary.maxSize], 10);
    if (!styledPrimaryMaxSize || primaryMaxSize < styledPrimaryMaxSize) {
        elem.style[primary.maxSize] = (0, common_1.px)(primaryMaxSize);
    }
    // Set the position of the popup element along the primary axis using the
    // anchor's bounding rect. If we are working in the context of position:
    // absolute, then we will need to add the window's scroll position as well.
    const maxOffset = document.body[primary.bodyScroll] - window[primary.inner];
    const scrollOffset = Math.max(Math.min(window[primary.scrollOffset], maxOffset), 0);
    const boundPrimaryPos = (pos) => {
        return Math.max(boundRect[primary.before], Math.min(pos, boundRect[primary.after] - elem[primary.offsetSize] - margin));
    };
    const boundSecondaryPos = (pos) => {
        return Math.max(boundRect[secondary.before], Math.min(pos, boundRect[secondary.after] - elem[secondary.offsetSize] - secondaryMargin));
    };
    if (side === primary.before) {
        // top or left
        elem.style[primary.before] =
            scrollOffset +
                boundPrimaryPos(anchorRect[primary.before] - elem[primary.offsetSize] - margin) -
                offset +
                'px';
        elem.style[primary.after] = 'auto';
    }
    else {
        // bottom or right
        elem.style[primary.before] = (0, common_1.px)(scrollOffset + boundPrimaryPos(anchorRect[primary.after]) + offset);
        elem.style[primary.after] = 'auto';
    }
    // Set the position of the popup element along the secondary axis.
    const maxSecondaryOffset = document.body[secondary.bodyScroll] - window[secondary.inner];
    const secondaryScrollOffset = Math.max(Math.min(window[secondary.scrollOffset], maxSecondaryOffset), 0);
    elem.style[secondary.before] = (0, common_1.px)(secondaryScrollOffset +
        boundSecondaryPos(anchorRect[secondary.before] - secondaryMarginBefore));
    elem.style[secondary.after] = 'auto';
    elem.dataset.side = side;
    return {
        side,
        anchorRect,
        boundRect,
    };
}
exports.default = default_1;
