"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensurePortalElement = exports.getConstrainRect = exports.getConstrainElement = void 0;
const tslib_1 = require("tslib");
const React = tslib_1.__importStar(require("react"));
const react_1 = require("react");
const debounce_1 = tslib_1.__importDefault(require("lodash/debounce"));
const batchUpdate_1 = tslib_1.__importDefault(require("../utils/batchUpdate"));
const selectParent_1 = tslib_1.__importDefault(require("../utils/selectParent"));
const useProperty_1 = tslib_1.__importDefault(require("../utils/useProperty"));
const Overlay_1 = tslib_1.__importDefault(require("./Overlay"));
const join_1 = tslib_1.__importDefault(require("../utils/join"));
const usePrevious_1 = tslib_1.__importDefault(require("../utils/usePrevious"));
const utils_1 = require("./utils");
const useAgGridClassName_1 = tslib_1.__importDefault(require("./useAgGridClassName"));
const contains_1 = tslib_1.__importDefault(require("../utils/contains"));
const UIHelper_1 = require("../../View/UIHelper");
const InfiniteTable_1 = require("../InfiniteTable");
const AdaptableContext_1 = require("../../View/AdaptableContext");
const overlayBaseZIndex_1 = require("../overlayBaseZIndex");
const getConstrainElement = (target, constrainTo) => {
    let el = null;
    if (typeof constrainTo === 'string') {
        el = (0, selectParent_1.default)(constrainTo, target);
    }
    if (typeof constrainTo === 'function') {
        el = constrainTo(target);
    }
    return el;
};
exports.getConstrainElement = getConstrainElement;
const getConstrainRect = (target, constrainTo) => {
    let el = (0, exports.getConstrainElement)(target, constrainTo);
    if (el && el.tagName) {
        return (0, utils_1.getRect)(el);
    }
    return (0, utils_1.getDocRect)();
};
exports.getConstrainRect = getConstrainRect;
let portalElement;
const OVERLAY_TRIGGER_Z_INDEX = overlayBaseZIndex_1.OVERLAY_BASE_Z_INDEX;
const ensurePortalElement = () => {
    if (!(0, UIHelper_1.isBrowserDocumentAvailable)()) {
        return;
    }
    if (portalElement) {
        return portalElement;
    }
    portalElement = document.createElement('div');
    portalElement.style.position = 'absolute';
    portalElement.style.zIndex = OVERLAY_TRIGGER_Z_INDEX.toString();
    portalElement.style.top = '0px';
    portalElement.style.left = '0px';
    portalElement.dataset.name = 'OverlayTriggerPortal';
    document.body.appendChild(portalElement);
    return portalElement;
};
exports.ensurePortalElement = ensurePortalElement;
const defaultProps = {
    showEvent: 'mouseenter',
    hideEvent: 'mouseleave',
    anchor: 'vertical',
    targetOffset: 10,
    opacityTransitionDuration: '250ms',
};
const OverlayTrigger = React.forwardRef((givenProps, ref) => {
    const props = { ...defaultProps, ...givenProps };
    const adaptable = (0, AdaptableContext_1.useAdaptable)();
    let { visible: _, showTriangle, showEvent, hideEvent, render, targetOffset, preventPortalEventPropagation = false, anchor, hideDelay = 0, opacityTransitionDuration, onVisibleChange, alignPosition = [
        // overlay - target
        ['TopLeft', 'BottomLeft'],
        ['TopRight', 'BottomRight'],
        ['TopCenter', 'BottomCenter'],
        ['BottomCenter', 'TopCenter'],
        ['TopRight', 'BottomCenter'],
        ['TopRight', 'BottomLeft'],
        ['TopRight', 'BottomLeft'],
        ['BottomLeft', 'TopLeft'],
        ['BottomRight', 'TopRight'],
        ['BottomRight', 'TopCenter'],
        ['BottomRight', 'TopLeft'],
        ['TopRight', 'CenterLeft'],
        ['TopRight', 'TopLeft'],
        ['TopLeft', 'TopRight'],
        ['CenterRight', 'CenterLeft'],
        ['CenterLeft', 'CenterRight'],
    ], constrainTo, target: targetProp, ...domProps } = props;
    const { showOverlay, clearAll: clearAllOverlays, hideOverlay, portal, } = (0, InfiniteTable_1.useOverlay)({
        portalContainer: portalElement,
    });
    const domRef = (0, react_1.useRef)(null);
    const targetRef = (0, react_1.useRef)(null);
    const overlayRef = (0, react_1.useRef)(null);
    const [visible, doSetVisible] = (0, useProperty_1.default)(props, 'visible', false);
    const hideTimeoutRef = (0, react_1.useRef)(null);
    const setVisible = React.useCallback(
    // visible state may quickly change from true -> false -> true
    // when moving the mouse cursor from the trigger to the overlay
    // for this case we debounce the visible change for a very small amount of time
    (0, debounce_1.default)((visible) => {
        onVisibleChange?.(visible);
        if (!visible) {
            hideTimeoutRef.current = setTimeout(() => {
                hideTimeoutRef.current = null;
                doSetVisible(false);
            }, hideDelay);
            return;
        }
        if (hideTimeoutRef.current) {
            clearTimeout(hideTimeoutRef.current);
            hideTimeoutRef.current = null;
        }
        doSetVisible(true);
    }, 50), []);
    const prevVisible = (0, usePrevious_1.default)(visible, false);
    (0, exports.ensurePortalElement)();
    const onShow = React.useCallback((event) => {
        const target = targetRef.current;
        if (event && preventPortalEventPropagation && !(0, contains_1.default)(target, event.target)) {
            // because of how React portals behave - see https://github.com/facebook/react/issues/11387
            // after the portal is rendered, even though an item is in a separate dom parent, if it's in the portal,
            // events from it are propagated to the components in the portal and thus break some stuff
            return;
        }
        (0, batchUpdate_1.default)(() => {
            setVisible(true);
        }).commit();
    }, [constrainTo, preventPortalEventPropagation]);
    const onHide = React.useCallback((_event) => {
        const shouldHide = props.shouldHide ? props.shouldHide(_event) : true;
        if (shouldHide) {
            setVisible(false);
        }
    }, [setVisible]);
    (0, react_1.useEffect)(() => {
        if (ref) {
            const api = {
                show: onShow,
                hide: onHide,
            };
            if (typeof ref === 'function') {
                ref(api);
            }
            else {
                ref.current = api;
            }
        }
    }, [ref]);
    (0, react_1.useEffect)(() => {
        let target = domRef.current.previousSibling;
        if (targetProp) {
            target = targetProp(target);
        }
        if (!target) {
            adaptable.logger.warn('No OverlayTrigger target - make sure you render a child inside the OverlayTrigger, which will be the overlay target');
            return;
        }
        targetRef.current = target;
        let attached = false;
        let onShowFn = onShow;
        let onHideFn = onHide;
        if (props.visible === undefined) {
            attached = true;
            target.addEventListener(showEvent, onShowFn);
            target.addEventListener(hideEvent, onHideFn);
        }
        if (props.visible && !prevVisible) {
            onShowFn();
        }
        return () => {
            if (attached) {
                target.removeEventListener(showEvent, onShowFn);
                target.removeEventListener(hideEvent, onHideFn);
            }
        };
    }, [props.visible, showEvent, hideEvent, onShow, onHide]);
    React.useEffect(() => {
        const target = targetRef.current;
        if (!target) {
            return;
        }
        const targetWidth = target.getBoundingClientRect().width;
        if ((prevVisible && !visible) || visible) {
            const overlayContent = (React.createElement(Overlay_1.default, { ...domProps, ref: (node) => {
                    if (overlayRef.current && overlayRef.current != node) {
                        overlayRef.current.removeEventListener(showEvent, onShow);
                        overlayRef.current.removeEventListener(hideEvent, onHide);
                    }
                    overlayRef.current = node;
                    if (node) {
                        node.addEventListener(showEvent, onShow);
                        node.addEventListener(hideEvent, onHide);
                    }
                }, className: (0, join_1.default)('ab-Overlay', showTriangle ? 'ab-Overlay--show-triangle' : '', agGridClassName, domProps.className), style: { transition: `opacity ${opacityTransitionDuration}` }, visible: visible, onTransitionEnd: () => {
                    if (!visible) {
                        clearAllOverlays();
                        hideOverlay('overlay-trigger');
                    }
                } }, props.render({ targetWidth: targetWidth })));
            let preparedConstrainTo;
            if (constrainTo) {
                preparedConstrainTo = (0, exports.getConstrainElement)(targetRef.current, constrainTo);
            }
            // show only if visible or if it was visible and now it is invisible
            const alignToRect = (0, utils_1.getRect)(target, targetOffset ?? 0);
            const showOverlayOptions = {
                id: 'overlay-trigger', // add id on props so component is not unmounted
                alignPosition,
                constrainTo: preparedConstrainTo?.getBoundingClientRect?.() ?? true, // at least constrain to document
                alignTo: alignToRect,
            };
            showOverlay(() => overlayContent, showOverlayOptions);
        }
        else {
            clearAllOverlays();
        }
    }, [visible, props.render]);
    const agGridClassName = (0, useAgGridClassName_1.default)([visible]);
    return (React.createElement(React.Fragment, null,
        React.Children.only(props.children),
        React.createElement("div", { "data-name": "OverlayTrigger", "data-visible": visible, ref: domRef, style: {
                visibility: 'hidden',
                flex: 'none',
                width: 0,
                height: 0,
                pointerEvents: 'none',
                display: 'inline-flex',
            } }),
        portal));
});
exports.default = OverlayTrigger;
