/**
 * @license
 * Copyright Tyler Technologies, Inc. 
 * License: Apache-2.0
 */
import { computePosition, flip as flipMiddleware, hide as hideMiddleware, shift as shiftMiddleware, offset as offsetMiddleware, arrow as arrowMiddleware } from '@floating-ui/dom';
import { roundByDPR } from './utils';
export const DEFAULT_FALLBACK_PLACEMENTS = ['top-start', 'top', 'top-end', 'left-start', 'left', 'left-end', 'right-start', 'right', 'right-end'];
export class VirtualElement {
    constructor(x, y, height = 0, width = 0) {
        this.x = x;
        this.y = y;
        this.height = height;
        this.width = width;
    }
    getBoundingClientRect() {
        return {
            x: this.x,
            y: this.y,
            top: this.y,
            left: this.x,
            bottom: this.y,
            right: this.x,
            width: this.height,
            height: this.width,
            toJSON() {
                return;
            }
        };
    }
    static fromElement(element) {
        const rect = element.getBoundingClientRect();
        return new VirtualElement(rect.left, rect.top, rect.height, rect.width);
    }
    static fromEvent(event) {
        if (event instanceof MouseEvent) {
            return new VirtualElement(event.clientX, event.clientY);
        }
        if (event instanceof TouchEvent) {
            return new VirtualElement(event.touches[0].clientX, event.touches[0].clientY);
        }
        throw new Error('Unsupported event type');
    }
}
/**
 * Calculates an elements position relative to another element.
 * @param {IPositionElementConfig} config Configuration to provide when positioning the element.
 * @returns {IPositionElementResult} The result of the positioning logic.
 */
export async function positionElementAsync({ element, anchorElement, placement = 'bottom', offset = false, offsetOptions, strategy = 'fixed', apply = true, flip = true, flipOptions = {
    fallbackPlacements: DEFAULT_FALLBACK_PLACEMENTS,
    fallbackStrategy: 'initialPlacement'
}, shift = true, shiftOptions, hide = false, hideOptions, arrowElement, arrowOptions = {} }) {
    var _a, _b, _c;
    const middleware = [];
    //
    // Order of the following middleware is **important**
    //
    if (offset) {
        middleware.push(offsetMiddleware(offsetOptions));
    }
    if (shift) {
        middleware.push(shiftMiddleware(shiftOptions));
    }
    if (flip) {
        middleware.push(flipMiddleware(flipOptions));
    }
    if (hide) {
        middleware.push(hideMiddleware(hideOptions));
    }
    if (arrowElement) { // Must come before the topLayer middleware
        middleware.push(arrowMiddleware(Object.assign(Object.assign({}, arrowOptions), { element: arrowElement })));
    }
    const { x, y, placement: finalPlacement, middlewareData } = await computePosition(anchorElement, element, { strategy, placement, middleware });
    // Should we apply the position information to the element?
    if (apply) {
        const styles = {
            left: '0',
            top: '0',
            translate: `${roundByDPR(x)}px ${roundByDPR(y)}px`
        };
        Object.assign(element.style, styles);
        // We use `display` here to ensure that any child overlays are also hidden
        if ((_a = middlewareData.hide) === null || _a === void 0 ? void 0 : _a.referenceHidden) {
            element.style.display = 'none';
        }
        else {
            element.style.removeProperty('display');
        }
    }
    return {
        x,
        y,
        hidden: (_c = (_b = middlewareData.hide) === null || _b === void 0 ? void 0 : _b.referenceHidden) !== null && _c !== void 0 ? _c : false,
        placement: finalPlacement,
        arrow: middlewareData.arrow
    };
}
