import { __assign } from "tslib";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useRef, useImperativeHandle, useMemo, forwardRef, useEffect, } from 'react';
import noop from '../utils/noop';
import MountElement from './MountElement';
import PurePortal from './PurePortal';
import { getNodeFromSelector, hasScrollbarY } from './util';
import memorize from '../utils/memorize-one';
import createElement from '../utils/dom/createElement';
import { SCROLLBAR_WIDTH } from '../utils/getScrollbarWidth';
import { setValueForStyles } from '../utils/style/CSSPropertyOperations';
import { addEventListener } from '../utils/component/event-handler';
import isBrowser from '../utils/isBrowser';
import { useIsomorphicLayoutEffect } from '../utils/hooks/useIsomorphicLayoutEffect';
function diffStyle(prev, next) {
    var result = {};
    var prevKeys = Object.keys(prev);
    for (var i = 0; i < prevKeys.length; i += 1) {
        var key = prevKeys[i];
        if (!next[key]) {
            result[key] = '';
        }
    }
    var nextKeys = Object.keys(next);
    for (var i = 0; i < prevKeys.length; i += 1) {
        var key = nextKeys[i];
        result[key] = next[key];
    }
    return result;
}
var patched = new Map();
function patchElement(parent) {
    var meta = patched.get(parent);
    if (meta) {
        meta.count += 1;
    }
    else {
        var _a = parent.style, overflowY = _a.overflowY, paddingRight = _a.paddingRight;
        var originalPadding = getComputedStyle(parent).paddingRight;
        var newPadding = parseFloat(originalPadding || '0') + SCROLLBAR_WIDTH;
        parent.style.overflowY = 'hidden';
        parent.style.paddingRight = newPadding + "px";
        var newMeta = {
            count: 1,
            overflowY: overflowY,
            paddingRight: paddingRight,
        };
        patched.set(parent, newMeta);
    }
}
function restoreElement(parent) {
    var meta = patched.get(parent);
    if (!meta) {
        throw new Error('This looks like a bug of zent, please file an issue');
    }
    if (meta.count === 1) {
        patched.delete(parent);
        parent.style.overflowY = meta.overflowY;
        parent.style.paddingRight = meta.paddingRight;
    }
    else {
        meta.count -= 1;
    }
}
export var Portal = forwardRef(function (props, ref) {
    var _a = props.visible, visible = _a === void 0 ? true : _a, _b = props.layer, layer = _b === void 0 ? 'div' : _b, _c = props.selector, selector = _c === void 0 ? 'body' : _c, _d = props.useLayerForClickAway, useLayerForClickAway = _d === void 0 ? false : _d, className = props.className, style = props.style, _e = props.blockPageScroll, blockPageScroll = _e === void 0 ? false : _e, _f = props.closeOnESC, closeOnESC = _f === void 0 ? false : _f, _g = props.closeOnClickOutside, closeOnClickOutside = _g === void 0 ? false : _g, children = props.children, append = props.append;
    var node = useMemo(function () { return (isBrowser ? createElement(layer) : null); }, [layer]);
    var getParent = useMemo(function () { return memorize(getNodeFromSelector); }, []);
    var propsRef = useRef(props);
    propsRef.current = props;
    var prevStyleRef = useRef(style);
    var purePortalRef = useRef(null);
    useImperativeHandle(ref, function () { return ({
        contains: function (node) {
            var purePortal = purePortalRef.current;
            if (!purePortal) {
                return false;
            }
            return purePortal.contains(node);
        },
        purePortalRef: purePortalRef,
        container: node,
    }); }, [node]);
    useIsomorphicLayoutEffect(function () {
        className && (node.className = className);
    }, [node, className]);
    useIsomorphicLayoutEffect(function () {
        var result = diffStyle(prevStyleRef.current || {}, style || {});
        setValueForStyles(node, result);
        prevStyleRef.current = style;
    }, [node, style]);
    useIsomorphicLayoutEffect(function () {
        if (!visible || !useLayerForClickAway) {
            return noop;
        }
        var _a = node.style, position = _a.position, top = _a.top, bottom = _a.bottom, left = _a.left, right = _a.right;
        var parent = getParent(selector);
        node.style.position = parent === document.body ? 'fixed' : 'absolute';
        node.style.top = '0';
        node.style.bottom = '0';
        node.style.left = '0';
        node.style.right = '0';
        return function () {
            node.style.position = position;
            node.style.top = top;
            node.style.bottom = bottom;
            node.style.left = left;
            node.style.right = right;
        };
    }, [node, useLayerForClickAway, visible, selector, getParent]);
    useIsomorphicLayoutEffect(function () {
        var parent = getParent(selector);
        if (!visible ||
            !blockPageScroll ||
            !(parent instanceof HTMLElement) ||
            !hasScrollbarY(parent)) {
            return noop;
        }
        patchElement(parent);
        return function () { return restoreElement(parent); };
    }, [selector, visible, blockPageScroll, getParent]);
    useIsomorphicLayoutEffect(function () {
        function handler(event) {
            var _a = propsRef.current, closeOnClickOutside = _a.closeOnClickOutside, onClose = _a.onClose, visible = _a.visible;
            var purePortal = purePortalRef.current;
            if (event.defaultPrevented ||
                !closeOnClickOutside ||
                !visible ||
                !purePortal) {
                return;
            }
            var target = event.target;
            if (!(target instanceof Node) ||
                target === node ||
                !purePortal.contains(target)) {
                onClose && onClose(event);
            }
        }
        var dispose = noop;
        if (closeOnClickOutside) {
            var cancelTouchStart_1;
            var cancelClick_1;
            if (useLayerForClickAway) {
                cancelTouchStart_1 = addEventListener(node, 'touchstart', handler);
                cancelClick_1 = addEventListener(node, 'click', handler);
            }
            else {
                cancelTouchStart_1 = addEventListener(window, 'touchstart', handler);
                cancelClick_1 = addEventListener(window, 'click', handler);
            }
            dispose = function () {
                cancelClick_1();
                cancelTouchStart_1();
            };
        }
        var onLayerReady = propsRef.current.onLayerReady;
        onLayerReady && onLayerReady(node);
        return dispose;
    }, [useLayerForClickAway, closeOnClickOutside, node]);
    useEffect(function () {
        if (!visible || !closeOnESC) {
            return noop;
        }
        function onKeyUp(e) {
            var onClose = propsRef.current.onClose;
            if (!onClose) {
                return;
            }
            if (e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) {
                onClose(e);
            }
        }
        return addEventListener(document.body, 'keyup', onKeyUp);
    }, [closeOnESC, visible]);
    return visible && node ? (_jsxs(PurePortal, __assign({ ref: purePortalRef, append: append, selector: node }, { children: [_jsx(MountElement, { node: node, getParent: getParent, selector: selector }, void 0), children] }), void 0)) : null;
});
Portal.displayName = 'ZentPortal';
export default Portal;
