import React__default, { useState, useRef, useEffect, Children, Fragment, cloneElement } from 'react';
import _pt from 'prop-types';
import warning from '../../../warning/warning.esm.js';
import { useForkedRef, checkStyles, getOwnerDocument, makeId, wrapEvent, forwardRefWithAs, getDocumentDimensions } from '../../utils/dist/utils.esm.esm.js';
import { useId } from '../../auto-id/dist/auto-id.esm.esm.js';
import Portal from '../../portal/dist/portal.esm.esm.js';
import { useRect } from '../../rect/dist/rect.esm.esm.js';
import VisuallyHidden from '../../visually-hidden/dist/visually-hidden.esm.esm.js';

function _extends() {
  _extends = Object.assign || function (target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];

      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }

    return target;
  };

  return _extends.apply(this, arguments);
}

function _objectWithoutPropertiesLoose(source, excluded) {
  if (source == null) return {};
  var target = {};
  var sourceKeys = Object.keys(source);
  var key, i;

  for (i = 0; i < sourceKeys.length; i++) {
    key = sourceKeys[i];
    if (excluded.indexOf(key) >= 0) continue;
    target[key] = source[key];
  }

  return target;
}

var _on, _on2, _on3, _on4, _on5, _states;
var MOUSE_REST_TIMEOUT = 100;
var LEAVE_TIMEOUT = 500; ////////////////////////////////////////////////////////////////////////////////
// States
// Nothing goin' on

var IDLE = "IDLE"; // We're considering showing the tooltip, but we're gonna wait a sec

var FOCUSED = "FOCUSED"; // It's on!

var VISIBLE = "VISIBLE"; // Focus has left, but we want to keep it visible for a sec

var LEAVING_VISIBLE = "LEAVING_VISIBLE"; // The user clicked the tool, so we want to hide the thing, we can't just use
// IDLE because we need to ignore mousemove, etc.

var DISMISSED = "DISMISSED"; ////////////////////////////////////////////////////////////////////////////////
// Events

var BLUR = "BLUR";
var FOCUS = "FOCUS";
var GLOBAL_MOUSE_MOVE = "GLOBAL_MOUSE_MOVE";
var MOUSE_DOWN = "MOUSE_DOWN";
var MOUSE_ENTER = "MOUSE_ENTER";
var MOUSE_LEAVE = "MOUSE_LEAVE";
var MOUSE_MOVE = "MOUSE_MOVE";
var REST = "REST";
var SELECT_WITH_KEYBOARD = "SELECT_WITH_KEYBOARD";
var TIME_COMPLETE = "TIME_COMPLETE";
var chart = {
  initial: IDLE,
  states: (_states = {}, _states[IDLE] = {
    enter: clearContextId,
    on: (_on = {}, _on[MOUSE_ENTER] = FOCUSED, _on[FOCUS] = VISIBLE, _on)
  }, _states[FOCUSED] = {
    enter: startRestTimer,
    leave: clearRestTimer,
    on: (_on2 = {}, _on2[MOUSE_MOVE] = FOCUSED, _on2[MOUSE_LEAVE] = IDLE, _on2[MOUSE_DOWN] = DISMISSED, _on2[BLUR] = IDLE, _on2[REST] = VISIBLE, _on2)
  }, _states[VISIBLE] = {
    on: (_on3 = {}, _on3[FOCUS] = FOCUSED, _on3[MOUSE_ENTER] = FOCUSED, _on3[MOUSE_LEAVE] = LEAVING_VISIBLE, _on3[BLUR] = LEAVING_VISIBLE, _on3[MOUSE_DOWN] = DISMISSED, _on3[SELECT_WITH_KEYBOARD] = DISMISSED, _on3[GLOBAL_MOUSE_MOVE] = LEAVING_VISIBLE, _on3)
  }, _states[LEAVING_VISIBLE] = {
    enter: startLeavingVisibleTimer,
    leave: function leave() {
      clearLeavingVisibleTimer();
      clearContextId();
    },
    on: (_on4 = {}, _on4[MOUSE_ENTER] = VISIBLE, _on4[FOCUS] = VISIBLE, _on4[TIME_COMPLETE] = IDLE, _on4)
  }, _states[DISMISSED] = {
    leave: function leave() {
      // allows us to come on back later w/o entering something else first
      context.id = null;
    },
    on: (_on5 = {}, _on5[MOUSE_LEAVE] = IDLE, _on5[BLUR] = IDLE, _on5)
  }, _states)
};
/*
 * Chart context allows us to persist some data around, in Tooltip all we use
 * is the id of the current tooltip being interacted with.
 */

var context = {
  id: null
};
var state = chart.initial; ////////////////////////////////////////////////////////////////////////////////
// Subscriptions:
//
// We could require apps to render a <TooltipProvider> around the app and use
// React context to notify Tooltips of changes to our state machine, instead
// we manage subscriptions ourselves and simplify the Tooltip API.
//
// Maybe if default context could take a hook (instead of just a static value)
// that was rendered at the root for us, that'd be cool! But it doesn't.

var subscriptions = [];

function subscribe(fn) {
  subscriptions.push(fn);
  return function () {
    subscriptions.splice(subscriptions.indexOf(fn), 1);
  };
}

function notify() {
  subscriptions.forEach(function (fn) {
    return fn(state, context);
  });
} ////////////////////////////////////////////////////////////////////////////////
// Timeouts:
// Manages when the user "rests" on an element. Keeps the interface from being
// flashing tooltips all the time as the user moves the mouse around the screen.


var restTimeout;

function startRestTimer() {
  window.clearTimeout(restTimeout);
  restTimeout = window.setTimeout(function () {
    return transition(REST);
  }, MOUSE_REST_TIMEOUT);
}

function clearRestTimer() {
  window.clearTimeout(restTimeout);
} // Manages the delay to hide the tooltip after rest leaves.


var leavingVisibleTimer;

function startLeavingVisibleTimer() {
  window.clearTimeout(leavingVisibleTimer);
  leavingVisibleTimer = window.setTimeout(function () {
    return transition(TIME_COMPLETE);
  }, LEAVE_TIMEOUT);
}

function clearLeavingVisibleTimer() {
  window.clearTimeout(leavingVisibleTimer);
} // allows us to come on back later w/o entering something else first after the
// user leaves or dismisses


function clearContextId() {
  context.id = null;
} ////////////////////////////////////////////////////////////////////////////////

/**
 * useTooltip
 *
 * @param params
 */


function useTooltip(_temp) {
  var _ref = _temp === void 0 ? {} : _temp,
      idProp = _ref.id,
      onMouseEnter = _ref.onMouseEnter,
      onMouseMove = _ref.onMouseMove,
      onMouseLeave = _ref.onMouseLeave,
      onFocus = _ref.onFocus,
      onBlur = _ref.onBlur,
      onKeyDown = _ref.onKeyDown,
      onMouseDown = _ref.onMouseDown,
      forwardedRef = _ref.ref,
      DEBUG_STYLE = _ref.DEBUG_STYLE;

  var id = String(useId(idProp));

  var _useState = useState(DEBUG_STYLE ? true : id === null ? false : context.id === id && state === VISIBLE),
      isVisible = _useState[0],
      setIsVisible = _useState[1]; // hopefully they always pass a ref if they ever pass one


  var ownRef = useRef(null);
  var ref = useForkedRef(forwardedRef, ownRef); // TODO: Fix in utils

  var triggerRect = useRect(ownRef, isVisible);
  useEffect(function () {
    return subscribe(function () {
      if (context.id === id && (state === VISIBLE || state === LEAVING_VISIBLE)) {
        setIsVisible(true);
      } else {
        setIsVisible(false);
      }
    });
  }, [id]);
  useEffect(function () {
    return checkStyles("tooltip");
  }, []);
  useEffect(function () {
    var ownerDocument = getOwnerDocument(ownRef.current) || document;

    function listener(event) {
      if ((event.key === "Escape" || event.key === "Esc") && state === VISIBLE) {
        transition(SELECT_WITH_KEYBOARD);
      }
    }

    ownerDocument.addEventListener("keydown", listener);
    return function () {
      return ownerDocument.removeEventListener("keydown", listener);
    };
  }, []);

  function handleMouseEnter() {
    transition(MOUSE_ENTER, {
      id: id
    });
  }

  function handleMouseMove() {
    transition(MOUSE_MOVE, {
      id: id
    });
  }

  function handleFocus() {
    // @ts-ignore
    if (window.__REACH_DISABLE_TOOLTIPS) {
      return;
    }

    transition(FOCUS, {
      id: id
    });
  }

  function handleMouseLeave() {
    transition(MOUSE_LEAVE);
  }

  function handleBlur() {
    // Allow quick click from one tool to another
    if (context.id !== id) return;
    transition(BLUR);
  }

  function handleMouseDown() {
    // Allow quick click from one tool to another
    if (context.id !== id) return;
    transition(MOUSE_DOWN);
  }

  function handleKeyDown(event) {
    if (event.key === "Enter" || event.key === " ") {
      transition(SELECT_WITH_KEYBOARD);
    }
  }

  var trigger = {
    // The element that triggers the tooltip references the tooltip element with
    // `aria-describedby`.
    // https://www.w3.org/TR/wai-aria-practices-1.2/#tooltip
    "aria-describedby": isVisible ? makeId("tooltip", id) : undefined,
    "data-reach-tooltip-trigger": "",
    ref: ref,
    onMouseEnter: wrapEvent(onMouseEnter, handleMouseEnter),
    onMouseMove: wrapEvent(onMouseMove, handleMouseMove),
    onFocus: wrapEvent(onFocus, handleFocus),
    onBlur: wrapEvent(onBlur, handleBlur),
    onMouseLeave: wrapEvent(onMouseLeave, handleMouseLeave),
    onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
    onMouseDown: wrapEvent(onMouseDown, handleMouseDown)
  };
  var tooltip = {
    id: id,
    triggerRect: triggerRect,
    isVisible: isVisible
  };
  return [trigger, tooltip, isVisible];
} ////////////////////////////////////////////////////////////////////////////////

/**
 * Tooltip
 *
 * @see Docs https://reacttraining.com/reach-ui/tooltip#tooltip
 */

var Tooltip = /*#__PURE__*/forwardRefWithAs(function (_ref2, forwardedRef) {
  var children = _ref2.children,
      label = _ref2.label,
      DEPRECATED_ariaLabel = _ref2.ariaLabel,
      id = _ref2.id,
      DEBUG_STYLE = _ref2.DEBUG_STYLE,
      props = _objectWithoutPropertiesLoose(_ref2, ["children", "label", "ariaLabel", "id", "DEBUG_STYLE"]);

  var child = Children.only(children);
  process.env.NODE_ENV !== "production" ? warning(!DEPRECATED_ariaLabel, "The `ariaLabel prop is deprecated and will be removed from @reach/tooltip in a future version. Please use `aria-label` instead.") : void 0; // We need to pass some properties from the child into useTooltip
  // to make sure users can maintain control over the trigger's ref and events

  var _useTooltip = useTooltip({
    id: id,
    onMouseEnter: child.props.onMouseEnter,
    onMouseMove: child.props.onMouseMove,
    onMouseLeave: child.props.onMouseLeave,
    onFocus: child.props.onFocus,
    onBlur: child.props.onBlur,
    onKeyDown: child.props.onKeyDown,
    onMouseDown: child.props.onMouseDown,
    ref: child.ref,
    DEBUG_STYLE: DEBUG_STYLE
  }),
      trigger = _useTooltip[0],
      tooltip = _useTooltip[1];

  return React__default.createElement(Fragment, null, cloneElement(child, trigger), React__default.createElement(TooltipPopup, Object.assign({
    ref: forwardedRef,
    label: label,
    "aria-label": DEPRECATED_ariaLabel
  }, tooltip, props)));
});

if (process.env.NODE_ENV !== "production") {
  Tooltip.displayName = "Tooltip";
  Tooltip.propTypes = {
    children: _pt.node.isRequired,
    label: _pt.node.isRequired,
    ariaLabel: _pt.string
  };
}

/**
 * TooltipPopup
 *
 * @see Docs https://reacttraining.com/reach-ui/tooltip#tooltippopup
 */

var TooltipPopup = /*#__PURE__*/forwardRefWithAs(function TooltipPopup(_ref3, forwardRef) {
  var label = _ref3.label,
      DEPRECATED_ariaLabel = _ref3.ariaLabel,
      isVisible = _ref3.isVisible,
      id = _ref3.id,
      props = _objectWithoutPropertiesLoose(_ref3, ["label", "ariaLabel", "isVisible", "id"]);

  return isVisible ? React__default.createElement(Portal, null, React__default.createElement(TooltipContent, Object.assign({
    ref: forwardRef,
    label: label,
    "aria-label": DEPRECATED_ariaLabel,
    isVisible: isVisible
  }, props, {
    id: makeId("tooltip", String(id))
  }))) : null;
});

if (process.env.NODE_ENV !== "production") {
  TooltipPopup.displayName = "TooltipPopup";
  TooltipPopup.propTypes = {
    label: _pt.node.isRequired,
    ariaLabel: _pt.string,
    position: _pt.func
  };
}
/**
 * TooltipContent
 *
 * We need a separate component so that useRect works inside the portal.
 *
 * @see Docs https://reacttraining.com/reach-ui/tooltip#tooltipcontent
 */


var TooltipContent = /*#__PURE__*/forwardRefWithAs(function TooltipContent(_ref4, forwardedRef) {
  var ariaLabel = _ref4.ariaLabel,
      realAriaLabel = _ref4["aria-label"],
      _ref4$as = _ref4.as,
      Comp = _ref4$as === void 0 ? "div" : _ref4$as,
      id = _ref4.id,
      isVisible = _ref4.isVisible,
      label = _ref4.label,
      _ref4$position = _ref4.position,
      position = _ref4$position === void 0 ? positionDefault : _ref4$position,
      style = _ref4.style,
      triggerRect = _ref4.triggerRect,
      props = _objectWithoutPropertiesLoose(_ref4, ["ariaLabel", "aria-label", "as", "id", "isVisible", "label", "position", "style", "triggerRect"]);

  // The element that serves as the tooltip container has role tooltip.
  // https://www.w3.org/TR/wai-aria-practices-1.2/#tooltip
  // When an app passes an `aria-label`, we actually want to implement
  // `role="tooltip"` on a visually hidden element inside of the trigger.
  // In these cases we want the screen reader user to know both the content in
  // the tooltip, but also the content in the badge. For screen reader users,
  // the only content announced to them is whatever is in the tooltip.
  var hasAriaLabel = (realAriaLabel || ariaLabel) != null;
  var ownRef = useRef(null);
  var ref = useForkedRef(forwardedRef, ownRef);
  var tooltipRect = useRect(ownRef, isVisible);
  return React__default.createElement(Fragment, null, React__default.createElement(Comp, Object.assign({
    role: hasAriaLabel ? undefined : "tooltip"
  }, props, {
    ref: ref,
    "data-reach-tooltip": "",
    id: hasAriaLabel ? undefined : id,
    style: _extends(_extends({}, style), getStyles(position, triggerRect, tooltipRect))
  }), label), hasAriaLabel && React__default.createElement(VisuallyHidden, {
    role: "tooltip",
    id: id
  }, realAriaLabel || ariaLabel));
});

if (process.env.NODE_ENV !== "production") {
  TooltipContent.displayName = "TooltipContent";
  TooltipContent.propTypes = {};
} ////////////////////////////////////////////////////////////////////////////////
// feels awkward when it's perfectly aligned w/ the trigger


var OFFSET = 8;

function getStyles(position, triggerRect, tooltipRect) {
  var haventMeasuredTooltipYet = !tooltipRect;

  if (haventMeasuredTooltipYet) {
    return {
      visibility: "hidden"
    };
  }

  return position(triggerRect, tooltipRect);
}

var positionDefault = function positionDefault(triggerRect, tooltipRect) {
  var _getDocumentDimension = getDocumentDimensions(),
      windowWidth = _getDocumentDimension.width,
      windowHeight = _getDocumentDimension.height;

  if (!triggerRect || !tooltipRect) {
    return {};
  }

  var collisions = {
    top: triggerRect.top - tooltipRect.height < 0,
    right: windowWidth < triggerRect.left + tooltipRect.width,
    bottom: windowHeight < triggerRect.bottom + tooltipRect.height + OFFSET,
    left: triggerRect.left - tooltipRect.width < 0
  };
  var directionRight = collisions.right && !collisions.left;
  var directionUp = collisions.bottom && !collisions.top;
  return {
    left: directionRight ? triggerRect.right - tooltipRect.width + window.pageXOffset + "px" : triggerRect.left + window.pageXOffset + "px",
    top: directionUp ? triggerRect.top - OFFSET - tooltipRect.height + window.pageYOffset + "px" : triggerRect.top + OFFSET + triggerRect.height + window.pageYOffset + "px"
  };
}; ////////////////////////////////////////////////////////////////////////////////

/**
 * Finds the next state from the current state + action. If the chart doesn't
 * describe that transition, it will throw.
 *
 * It also manages lifecycles of the machine, (enter/leave hooks on the state
 * chart)
 *
 * @param event
 * @param payload
 */


var transition = function transition(event, payload) {
  var stateDef = chart.states[state];
  var nextState = stateDef && stateDef.on && stateDef.on[event]; // Really useful for debugging
  // console.log({ event, state, nextState, contextId: context.id });
  // !nextState && console.log('no transition taken')

  if (!nextState) {
    return;
  }

  if (stateDef && stateDef.leave) {
    stateDef.leave();
  }

  if (payload) {
    context = payload;
  }

  var nextDef = chart.states[nextState];

  if (nextDef && nextDef.enter) {
    nextDef.enter();
  }

  state = nextState;
  notify();
};

export { LEAVE_TIMEOUT, MOUSE_REST_TIMEOUT, Tooltip, TooltipPopup, useTooltip };
//# sourceMappingURL=tooltip.esm.esm.js.map
