import React, { Component, cloneElement, Fragment, } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { SoftPortal } from '../Portal';
import { SoftTooltipPopover } from './SoftTooltipPopover';
import { findPopoverPosition } from '../../services';
import makeId from '../Form/form_row/make_id';
import { SoftResizeObserver } from '../Observer/resize_observer';
const positionsToClassNameMap = {
    top: 'softToolTip--top',
    right: 'softToolTip--right',
    bottom: 'softToolTip--bottom',
    left: 'softToolTip--left',
};
export const POSITIONS = Object.keys(positionsToClassNameMap);
const delayToClassNameMap = {
    regular: null,
    long: 'SoftToolTip--delayLong',
};
export const DELAY = Object.keys(delayToClassNameMap);
const DEFAULT_TOOLTIP_STYLES = {
    // position the tooltip content near the top-left
    // corner of the window so it can't create scrollbars
    // 50,50 because who knows what negative margins, padding, etc
    top: 50,
    left: 50,
    // just in case, avoid any potential flicker by hiding
    // the tooltip before it is positioned
    opacity: 0
};
export class SoftToolTip extends Component {
    constructor(props) {
        super(props);
        this.state = {
            visible: false,
            hasFocus: false,
            calculatedPosition: this.props.position,
            toolTipStyles: DEFAULT_TOOLTIP_STYLES,
            arrowStyles: {},
            id: this.props.id || makeId(),
        };
        this.testAnchor = () => {
            // when the tooltip is visible, this checks if the anchor is still part of document
            // this fixes when the react root is removed from the dom without unmounting
            // https://github.com/elastic/Soft/issues/1105
            if (document.body.contains(this.anchor) === false) {
                // the anchor is no longer part of `document`
                this.hideToolTip();
            }
            else {
                if (this.state.visible) {
                    // if still visible, keep checking
                    requestAnimationFrame(this.testAnchor);
                }
            }
        };
        this.setPopoverRef = ref => {
            this.popover = ref;
            // if the popover has been unmounted, clear
            // any previous knowledge about its size
            if (ref == null) {
                this.setState({
                    toolTipStyles: DEFAULT_TOOLTIP_STYLES,
                    arrowStyles: {}
                });
            }
        };
        this.showToolTip = () => {
            this.setState({ visible: true });
        };
        this.positionToolTip = () => {
            const requestedPosition = this.props.position;
            const { position, left, top, arrow } = findPopoverPosition({
                anchor: this.anchor,
                popover: this.popover,
                position: requestedPosition,
                offset: 16,
                arrowConfig: {
                    arrowWidth: 12,
                    arrowBuffer: 4
                }
            });
            // If encroaching the right edge of the window:
            // When `props.content` changes and is longer than `prevProps.content`, the tooltip width remains and
            // the resizeObserver callback will fire twice (once for vertical resize caused by text line wrapping,
            // once for a subsequent position correction) and cause a flash rerender and reposition.
            // To prevent this, we can orient from the right so that text line wrapping does not occur, negating
            // the second resizeObserver callback call.
            const windowWidth = document.documentElement.clientWidth || window.innerWidth;
            const useRightValue = (windowWidth / 2) < left;
            const toolTipStyles = {
                top,
                left: useRightValue ? 'auto' : left,
                right: useRightValue ? (windowWidth - left - this.popover.offsetWidth) : 'auto',
            };
            this.setState({
                visible: true,
                calculatedPosition: position,
                toolTipStyles,
                arrowStyles: arrow,
            });
        };
        this.hideToolTip = () => {
            if (this._isMounted) {
                this.setState({ visible: false });
            }
        };
        this.onFocus = () => {
            this.setState({
                hasFocus: true,
            });
            this.showToolTip();
        };
        this.onBlur = () => {
            this.setState({
                hasFocus: false,
            });
            this.hideToolTip();
        };
        this.onMouseOut = (e) => {
            // Prevent mousing over children from hiding the tooltip by testing for whether the mouse has
            // left the anchor for a non-child.
            if (this.anchor === e.relatedTarget || !this.anchor.contains(e.relatedTarget)) {
                if (!this.state.hasFocus) {
                    this.hideToolTip();
                }
            }
            if (this.props.onMouseOut) {
                this.props.onMouseOut();
            }
        };
    }
    componentDidMount() {
        this._isMounted = true;
    }
    componentWillUnmount() {
        this._isMounted = false;
    }
    componentDidUpdate(prevProps, prevState) {
        if (prevState.visible === false && this.state.visible === true) {
            requestAnimationFrame(this.testAnchor);
        }
    }
    render() {
        const { children, className, anchorClassName, content, title, delay, ...rest } = this.props;
        const { arrowStyles, id, toolTipStyles, visible } = this.state;
        const classes = classNames('softToolTip', positionsToClassNameMap[this.state.calculatedPosition], delayToClassNameMap[delay], className);
        const anchorClasses = classNames('softToolTipAnchor', anchorClassName);
        let tooltip;
        if (visible && (content || title)) {
            tooltip = (React.createElement(SoftPortal, null,
                React.createElement(SoftTooltipPopover, Object.assign({ className: classes, style: toolTipStyles, positionToolTip: this.positionToolTip, popoverRef: this.setPopoverRef, title: title, id: id, role: "tooltip" }, rest),
                    React.createElement("div", { style: arrowStyles, className: "softToolTip__arrow" }),
                    React.createElement(SoftResizeObserver, { onResize: this.positionToolTip }, resizeRef => React.createElement("div", { ref: resizeRef }, content)))));
        }
        const anchor = (React.createElement("span", { ref: anchor => this.anchor = anchor, className: anchorClasses, onMouseOver: this.showToolTip, onMouseOut: this.onMouseOut }, cloneElement(children, {
            onFocus: this.showToolTip,
            onBlur: this.hideToolTip,
            'aria-describedby': this.state.id,
        })));
        return (React.createElement(Fragment, null,
            anchor,
            tooltip));
    }
}
SoftToolTip.propTypes = {
    /**
     * The in-view trigger for your tooltip.
     */
    children: PropTypes.element.isRequired,
    /**
     * The main content of your tooltip.
     */
    content: PropTypes.node,
    /**
     * An optional title for your tooltip.
     */
    title: PropTypes.node,
    /**
     * Suggested position. If there is not enough room for it this will be changed.
     */
    position: PropTypes.oneOf(POSITIONS),
    /**
     * Delay before showing tooltip. Good for repeatable items.
     */
    delay: PropTypes.oneOf(DELAY),
    /**
     * Passes onto the tooltip itself, not the trigger.
     */
    className: PropTypes.string,
    /**
     * Passes onto the the trigger.
     */
    anchorClassName: PropTypes.string,
    /**
     * Unless you provide one, this will be randomly generated.
     */
    id: PropTypes.string,
};
SoftToolTip.defaultProps = {
    position: 'top',
    delay: 'regular',
};
//# sourceMappingURL=SoftTooltip.js.map