import { clsx } from 'clsx';
import { useRef, useState, useContext } from 'react';
import Dimmer from '../../dimmer/Dimmer.mjs';
import Drawer from '../../drawer/Drawer.mjs';
import { OverlayIdContext } from '../../provider/overlay/OverlayIdProvider.mjs';
import SlidingPanel from '../../slidingPanel/SlidingPanel.mjs';
import { CloseButton } from '../closeButton/CloseButton.mjs';
import { isServerSide } from '../domHelpers/documentIosClick.mjs';
import { useMedia } from '../hooks/useMedia.mjs';
import { Breakpoint } from '../propsValues/breakpoint.mjs';
import { Position } from '../propsValues/position.mjs';
import { jsx, jsxs } from 'react/jsx-runtime';
import { useConditionalListener } from '../hooks/useConditionalListener/useConditionalListener.mjs';

const INITIAL_Y_POSITION = 0;
const CONTENT_SCROLL_THRESHOLD = 1;
const MOVE_OFFSET_THRESHOLD = 50;
/**
 * Neptune: https://transferwise.github.io/neptune/components/bottom-sheet/
 *
 * Neptune Web: https://transferwise.github.io/neptune-web/components/overlays/BottomSheet
 *
 */
const BottomSheet = ({
  role = 'dialog',
  ...props
}) => {
  const bottomSheetReference = useRef(null);
  const topBarReference = useRef(null);
  const contentReference = useRef(null);
  const [pressed, setPressed] = useState(false);
  /**
   * Used to track `requestAnimationFrame` requests
   *
   * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame#return_value
   */
  const animationId = useRef(0);
  /**
   * Difference between initial coordinate ({@link initialYCoordinate})
   * and new position (when user moves component), it's get calculated on `onTouchMove` and `onMouseMove` events
   *
   * @see {@link calculateOffsetAfterMove}
   */
  const moveOffset = useRef(0);
  const initialYCoordinate = useRef(0);
  // apply shadow to the bottom of top-bar when scroll over content
  useConditionalListener({
    attachListener: props.open && !isServerSide(),
    callback: () => {
      if (topBarReference.current !== null) {
        const {
          classList
        } = topBarReference.current;
        if (!isContentScrollPositionAtTop()) {
          classList.add('np-bottom-sheet--top-bar--shadow');
        } else {
          classList.remove('np-bottom-sheet--top-bar--shadow');
        }
      }
    },
    eventType: 'scroll',
    parent: isServerSide() ? undefined : document
  });
  function move(newHeight) {
    if (bottomSheetReference.current !== null) {
      bottomSheetReference.current.style.transform = `translateY(${newHeight}px)`;
    }
  }
  function close(event) {
    setPressed(false);
    moveOffset.current = INITIAL_Y_POSITION;
    if (bottomSheetReference.current !== null) {
      bottomSheetReference.current.style.removeProperty('transform');
    }
    if (props.onClose) {
      props.onClose(event);
    }
  }
  const onSwipeStart = event => {
    initialYCoordinate.current = ('touches' in event ? event.touches[0] : event).clientY;
    setPressed(true);
  };
  const onSwipeMove = event => {
    if (pressed) {
      const {
        clientY
      } = 'touches' in event ? event.touches[0] : event;
      const offset = calculateOffsetAfterMove(clientY);
      // check whether move is to the bottom only and content scroll position is at the top
      if (offset > INITIAL_Y_POSITION && isContentScrollPositionAtTop()) {
        moveOffset.current = offset;
        animationId.current = requestAnimationFrame(() => {
          if (animationId.current !== undefined && bottomSheetReference.current !== null) {
            move(offset);
          }
        });
      }
    }
  };
  function onSwipeEnd(event) {
    // stop moving component
    cancelAnimationFrame(animationId.current);
    setPressed(false);
    // check whether move down is strong enough
    // and content scroll position is at the top to close the component
    if (moveOffset.current > MOVE_OFFSET_THRESHOLD && isContentScrollPositionAtTop()) {
      close(event);
    }
    // otherwise move component back to default (initial) position
    else {
      move(INITIAL_Y_POSITION);
    }
    moveOffset.current = INITIAL_Y_POSITION;
  }
  function isContentScrollPositionAtTop() {
    return contentReference?.current?.scrollTop !== undefined && contentReference.current.scrollTop <= CONTENT_SCROLL_THRESHOLD;
  }
  /**
   * Calculates how hard user moves component,
   * result value used to determine whether to hide component or re-position to default state
   *
   * @param afterMoveYCoordinate
   */
  function calculateOffsetAfterMove(afterMoveYCoordinate) {
    return afterMoveYCoordinate - initialYCoordinate.current;
  }
  /**
   * Set `max-height` for content part (in order to keep it scrollable for content overflow cases) of the component
   * and ensures space for safe zone (32px) at the top.
   */
  function setContentMaxHeight() {
    const safeZoneHeight = '64px';
    const topbarHeight = '32px';
    const windowHight = isServerSide() ? 0 : window.innerHeight;
    /**
     * Calculate _real_ height of the screen (taking into account parts of browser interface).
     *
     * See https://css-tricks.com/the-trick-to-viewport-units-on-mobile for more details.
     */
    const screenHeight = `${windowHight * 0.01 * 100}px`;
    return {
      maxHeight: `calc(${screenHeight} - ${safeZoneHeight} - ${topbarHeight})`
    };
  }
  const is400Zoom = useMedia(`(max-width: ${Breakpoint.ZOOM_400}px)`);
  const overlayId = useContext(OverlayIdContext);
  return is400Zoom ? /*#__PURE__*/jsx(Drawer, {
    "aria-labelledby": props['aria-labelledby'],
    "aria-label": props['aria-label'],
    role: role,
    open: props.open,
    className: props.className,
    onClose: close,
    children: props.children
  }) : /*#__PURE__*/jsx(Dimmer, {
    open: props.open,
    fadeContentOnEnter: true,
    fadeContentOnExit: true,
    onClose: close,
    children: /*#__PURE__*/jsx(SlidingPanel, {
      ref: bottomSheetReference,
      open: props.open,
      position: Position.BOTTOM,
      className: clsx('np-bottom-sheet', props.className),
      children: /*#__PURE__*/jsxs("div", {
        id: overlayId,
        "aria-labelledby": props['aria-labelledby'] || undefined,
        "aria-label": props['aria-label'] || undefined,
        role: role,
        "aria-modal": true,
        onTouchStart: onSwipeStart,
        onTouchMove: onSwipeMove,
        onTouchEnd: onSwipeEnd,
        onMouseDown: onSwipeStart,
        onMouseMove: onSwipeMove,
        onMouseUp: onSwipeEnd,
        children: [/*#__PURE__*/jsxs("div", {
          ref: topBarReference,
          className: "np-bottom-sheet--top-bar",
          children: [/*#__PURE__*/jsx("div", {
            className: "np-bottom-sheet--handler"
          }), /*#__PURE__*/jsx(CloseButton, {
            size: "sm",
            className: "sr-only np-bottom-sheet--close-btn",
            onClick: close
          })]
        }), /*#__PURE__*/jsx("div", {
          ref: contentReference,
          style: setContentMaxHeight(),
          className: "np-bottom-sheet--content",
          children: props.children
        })]
      })
    })
  });
};

export { BottomSheet as default };
//# sourceMappingURL=BottomSheet.mjs.map
