import * as tslib_1 from "tslib";
import * as React from 'react';
import { getClassNames } from './PositioningContainer.styles';
import { Layer } from '../../Layer';
import { BaseComponent, assign, css, elementContains, focusFirstChild, getWindow, getDocument, createRef } from '../../../Utilities';
import { getMaxHeight, positionElement, RectangleEdge } from '../../../utilities/positioning';
import { AnimationClassNames, mergeStyles } from '../../../Styling';
var OFF_SCREEN_STYLE = { opacity: 0 };
// In order for some of the max height logic to work
// properly we need to set the border.
// The value is abitrary.
var BORDER_WIDTH = 1;
var SLIDE_ANIMATIONS = (_a = {},
    _a[RectangleEdge.top] = 'slideUpIn20',
    _a[RectangleEdge.bottom] = 'slideDownIn20',
    _a[RectangleEdge.left] = 'slideLeftIn20',
    _a[RectangleEdge.right] = 'slideRightIn20',
    _a);
var PositioningContainer = /** @class */ (function (_super) {
    tslib_1.__extends(PositioningContainer, _super);
    function PositioningContainer(props) {
        var _this = _super.call(this, props) || this;
        /**
         * The primary positioned div.
         */
        _this._positionedHost = createRef();
        // @TODO rename to reflect the name of this class
        _this._contentHost = createRef();
        /**
         * Deprecated. Use onResize instead.
         * @deprecated
         */
        _this.dismiss = function (ev) {
            _this.onResize(ev);
        };
        _this.onResize = function (ev) {
            var onDismiss = _this.props.onDismiss;
            if (onDismiss) {
                onDismiss(ev);
            }
            else {
                _this._updateAsyncPosition();
            }
        };
        _this._setInitialFocus = function () {
            if (_this._contentHost.current && _this.props.setInitialFocus && !_this._didSetInitialFocus && _this.state.positions) {
                _this._didSetInitialFocus = true;
                focusFirstChild(_this._contentHost.current);
            }
        };
        _this._onComponentDidMount = function () {
            // This is added so the positioningContainer will dismiss when the window is scrolled
            // but not when something inside the positioningContainer is scrolled. The delay seems
            // to be required to avoid React firing an async focus event in IE from
            // the target changing focus quickly prior to rendering the positioningContainer.
            _this._async.setTimeout(function () {
                _this._events.on(_this._targetWindow, 'scroll', _this._async.throttle(_this._dismissOnScroll, 10), true);
                _this._events.on(_this._targetWindow, 'resize', _this._async.throttle(_this.onResize, 10), true);
                _this._events.on(_this._targetWindow.document.body, 'focus', _this._dismissOnLostFocus, true);
                _this._events.on(_this._targetWindow.document.body, 'click', _this._dismissOnLostFocus, true);
            }, 0);
            if (_this.props.onLayerMounted) {
                _this.props.onLayerMounted();
            }
            _this._updateAsyncPosition();
            _this._setHeightOffsetEveryFrame();
        };
        _this._didSetInitialFocus = false;
        _this.state = {
            positions: undefined,
            heightOffset: 0
        };
        _this._positionAttempts = 0;
        return _this;
    }
    PositioningContainer.prototype.componentWillMount = function () {
        this._setTargetWindowAndElement(this._getTarget());
    };
    PositioningContainer.prototype.componentDidMount = function () {
        this._onComponentDidMount();
    };
    PositioningContainer.prototype.componentDidUpdate = function () {
        this._setInitialFocus();
        this._updateAsyncPosition();
    };
    PositioningContainer.prototype.componentWillUpdate = function (newProps) {
        // If the target element changed, find the new one. If we are tracking
        // target with class name, always find element because we do not know if
        // fabric has rendered a new element and disposed the old element.
        var newTarget = this._getTarget(newProps);
        var oldTarget = this._getTarget();
        if (newTarget !== oldTarget || typeof newTarget === 'string' || newTarget instanceof String) {
            this._maxHeight = undefined;
            this._setTargetWindowAndElement(newTarget);
        }
        if (newProps.offsetFromTarget !== this.props.offsetFromTarget) {
            this._maxHeight = undefined;
        }
        if (newProps.finalHeight !== this.props.finalHeight) {
            this._setHeightOffsetEveryFrame();
        }
    };
    PositioningContainer.prototype.render = function () {
        // If there is no target window then we are likely in server side rendering and we should not render anything.
        if (!this._targetWindow) {
            return null;
        }
        var _a = this.props, className = _a.className, positioningContainerWidth = _a.positioningContainerWidth, positioningContainerMaxHeight = _a.positioningContainerMaxHeight, children = _a.children;
        var positions = this.state.positions;
        var styles = getClassNames();
        var directionalClassName = positions && positions.targetEdge ? AnimationClassNames[SLIDE_ANIMATIONS[positions.targetEdge]] : '';
        var getContentMaxHeight = this._getMaxHeight() + this.state.heightOffset;
        var contentMaxHeight = positioningContainerMaxHeight && positioningContainerMaxHeight > getContentMaxHeight
            ? getContentMaxHeight
            : positioningContainerMaxHeight;
        var content = (React.createElement("div", { ref: this._positionedHost, className: css('ms-PositioningContainer', styles.container) },
            React.createElement("div", { className: mergeStyles('ms-PositioningContainer-layerHost', styles.root, className, directionalClassName, !!positioningContainerWidth && { width: positioningContainerWidth }), 
                // tslint:disable-next-line:jsx-ban-props
                style: positions ? positions.elementPosition : OFF_SCREEN_STYLE, tabIndex: -1, 
                // See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
                ref: this._contentHost },
                children,
                // @TODO apply to the content container
                contentMaxHeight)));
        return this.props.doNotLayer ? content : React.createElement(Layer, null, content);
    };
    PositioningContainer.prototype._dismissOnScroll = function (ev) {
        var preventDismissOnScroll = this.props.preventDismissOnScroll;
        if (this.state.positions && !preventDismissOnScroll) {
            this._dismissOnLostFocus(ev);
        }
    };
    PositioningContainer.prototype._dismissOnLostFocus = function (ev) {
        var target = ev.target;
        var clickedOutsideCallout = this._positionedHost.current && !elementContains(this._positionedHost.current, target);
        if ((!this._target && clickedOutsideCallout) ||
            (ev.target !== this._targetWindow &&
                clickedOutsideCallout &&
                (this._target.stopPropagation ||
                    (!this._target || (target !== this._target && !elementContains(this._target, target)))))) {
            this.onResize(ev);
        }
    };
    PositioningContainer.prototype._updateAsyncPosition = function () {
        var _this = this;
        this._async.requestAnimationFrame(function () { return _this._updatePosition(); });
    };
    PositioningContainer.prototype._updatePosition = function () {
        var positions = this.state.positions;
        var _a = this.props, offsetFromTarget = _a.offsetFromTarget, onPositioned = _a.onPositioned;
        var hostElement = this._positionedHost.current;
        var positioningContainerElement = this._contentHost.current;
        if (hostElement && positioningContainerElement) {
            var currentProps = void 0;
            currentProps = assign(currentProps, this.props);
            currentProps.bounds = this._getBounds();
            currentProps.target = this._target;
            if (document.body.contains(currentProps.target)) {
                currentProps.gapSpace = offsetFromTarget;
                var newPositions_1 = positionElement(currentProps, hostElement, positioningContainerElement);
                // Set the new position only when the positions are not exists or one of the new positioningContainer positions are different.
                // The position should not change if the position is within 2 decimal places.
                if ((!positions && newPositions_1) ||
                    (positions && newPositions_1 && !this._arePositionsEqual(positions, newPositions_1) && this._positionAttempts < 5)) {
                    // We should not reposition the positioningContainer more than a few times, if it is then the content is likely resizing
                    // and we should stop trying to reposition to prevent a stack overflow.
                    this._positionAttempts++;
                    this.setState({
                        positions: newPositions_1
                    }, function () {
                        if (onPositioned) {
                            onPositioned(newPositions_1);
                        }
                    });
                }
                else {
                    this._positionAttempts = 0;
                    if (onPositioned) {
                        onPositioned(newPositions_1);
                    }
                }
            }
            else if (positions !== undefined) {
                this.setState({
                    positions: undefined
                });
            }
        }
    };
    PositioningContainer.prototype._getBounds = function () {
        if (!this._positioningBounds) {
            var currentBounds = this.props.bounds;
            if (!currentBounds) {
                currentBounds = {
                    top: 0 + this.props.minPagePadding,
                    left: 0 + this.props.minPagePadding,
                    right: this._targetWindow.innerWidth - this.props.minPagePadding,
                    bottom: this._targetWindow.innerHeight - this.props.minPagePadding,
                    width: this._targetWindow.innerWidth - this.props.minPagePadding * 2,
                    height: this._targetWindow.innerHeight - this.props.minPagePadding * 2
                };
            }
            this._positioningBounds = currentBounds;
        }
        return this._positioningBounds;
    };
    /**
     * Return the maximum height the container can grow to
     * without going out of the specified bounds
     */
    PositioningContainer.prototype._getMaxHeight = function () {
        var _a = this.props, directionalHintFixed = _a.directionalHintFixed, offsetFromTarget = _a.offsetFromTarget, directionalHint = _a.directionalHint;
        if (!this._maxHeight) {
            if (directionalHintFixed && this._target) {
                var gapSpace = offsetFromTarget ? offsetFromTarget : 0;
                this._maxHeight = getMaxHeight(this._target, directionalHint, gapSpace, this._getBounds());
            }
            else {
                this._maxHeight = this._getBounds().height - BORDER_WIDTH * 2;
            }
        }
        return this._maxHeight;
    };
    PositioningContainer.prototype._arePositionsEqual = function (positions, newPosition) {
        return this._comparePositions(positions.elementPosition, newPosition.elementPosition);
    };
    PositioningContainer.prototype._comparePositions = function (oldPositions, newPositions) {
        for (var key in newPositions) {
            // This needs to be checked here and below because there is a linting error if for in does not immediately have an if statement
            if (newPositions.hasOwnProperty(key)) {
                var oldPositionEdge = oldPositions[key];
                var newPositionEdge = newPositions[key];
                if (oldPositionEdge && newPositionEdge) {
                    if (oldPositionEdge.toFixed(2) !== newPositionEdge.toFixed(2)) {
                        return false;
                    }
                }
            }
        }
        return true;
    };
    PositioningContainer.prototype._setTargetWindowAndElement = function (target) {
        if (target) {
            if (typeof target === 'string') {
                var currentDoc = getDocument();
                this._target = currentDoc ? currentDoc.querySelector(target) : null;
                this._targetWindow = getWindow();
            }
            else if (target.stopPropagation) {
                this._targetWindow = getWindow(target.toElement);
                this._target = target;
            }
            else if (target.x !== undefined && target.y !== undefined) {
                this._targetWindow = getWindow();
                this._target = target;
            }
            else {
                var targetElement = target;
                this._targetWindow = getWindow(targetElement);
                this._target = target;
            }
        }
        else {
            this._targetWindow = getWindow();
        }
    };
    /**
     * Animates the height if finalHeight was given.
     */
    PositioningContainer.prototype._setHeightOffsetEveryFrame = function () {
        var _this = this;
        if (this._contentHost && this.props.finalHeight) {
            this._setHeightOffsetTimer = this._async.requestAnimationFrame(function () {
                if (!_this._contentHost.current) {
                    return;
                }
                var positioningContainerMainElem = _this._contentHost.current.lastChild;
                var cardScrollHeight = positioningContainerMainElem.scrollHeight;
                var cardCurrHeight = positioningContainerMainElem.offsetHeight;
                var scrollDiff = cardScrollHeight - cardCurrHeight;
                _this.setState({
                    heightOffset: _this.state.heightOffset + scrollDiff
                });
                if (positioningContainerMainElem.offsetHeight < _this.props.finalHeight) {
                    _this._setHeightOffsetEveryFrame();
                }
                else {
                    _this._async.cancelAnimationFrame(_this._setHeightOffsetTimer);
                }
            });
        }
    };
    PositioningContainer.prototype._getTarget = function (props) {
        if (props === void 0) { props = this.props; }
        var target = props.target;
        return target;
    };
    PositioningContainer.defaultProps = {
        preventDismissOnScroll: false,
        offsetFromTarget: 0,
        minPagePadding: 8,
        directionalHint: 7 /* bottomAutoEdge */
    };
    return PositioningContainer;
}(BaseComponent));
export { PositioningContainer };
var _a;
//# sourceMappingURL=PositioningContainer.js.map