/**
 * External dependencies
 */
import { AccessibilityInfo } from 'react-native';
import { useSafeAreaInsets, useSafeAreaFrame } from 'react-native-safe-area-context';
import Animated, { runOnJS, runOnUI, useAnimatedStyle, useSharedValue, withDelay, withTiming, ZoomInEasyDown } from 'react-native-reanimated';

/**
 * WordPress dependencies
 */
import { Draggable, DraggableTrigger } from '@wordpress/components';
import { select, useSelect, useDispatch } from '@wordpress/data';
import { useCallback, useEffect, useRef, useState, Platform } from '@wordpress/element';
import { getBlockType } from '@wordpress/blocks';
import { generateHapticFeedback } from '@wordpress/react-native-bridge';
import RCTAztecView from '@wordpress/react-native-aztec';

/**
 * Internal dependencies
 */
import useScrollWhenDragging from './use-scroll-when-dragging';
import DraggableChip from './draggable-chip';
import { store as blockEditorStore } from '../../store';
import DroppingInsertionPoint from './dropping-insertion-point';
import useBlockDropZone from '../use-block-drop-zone';
import styles from './style.scss';
import { jsx as _jsx } from "react/jsx-runtime";
import { Fragment as _Fragment } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
const CHIP_OFFSET_TO_TOUCH_POSITION = 32;
const BLOCK_OPACITY_ANIMATION_CONFIG = {
  duration: 350
};
const BLOCK_OPACITY_ANIMATION_DELAY = 250;
const DEFAULT_LONG_PRESS_MIN_DURATION = 500;
const DEFAULT_IOS_LONG_PRESS_MIN_DURATION = DEFAULT_LONG_PRESS_MIN_DURATION - 50;

/**
 * Block draggable wrapper component
 *
 * This component handles all the interactions for dragging blocks.
 * It relies on the block list and its context for dragging, hence it
 * should be rendered between the `BlockListProvider` component and the
 * block list rendering. It also requires listening to scroll events,
 * therefore for this purpose, it returns the `onScroll` event handler
 * that should be attached to the list that renders the blocks.
 *
 *
 * @param {Object}      props          Component props.
 * @param {JSX.Element} props.children Children to be rendered.
 * @param {boolean}     props.isRTL    Check if current locale is RTL.
 *
 * @return {Function} Render function that passes `onScroll` event handler.
 */
const BlockDraggableWrapper = ({
  children,
  isRTL
}) => {
  const [draggedBlockIcon, setDraggedBlockIcon] = useState();
  const {
    selectBlock,
    startDraggingBlocks,
    stopDraggingBlocks
  } = useDispatch(blockEditorStore);
  const {
    left,
    right
  } = useSafeAreaInsets();
  const {
    width
  } = useSafeAreaFrame();
  const safeAreaOffset = left + right;
  const contentWidth = width - safeAreaOffset;
  const scroll = {
    offsetY: useSharedValue(0)
  };
  const chip = {
    x: useSharedValue(0),
    y: useSharedValue(0),
    width: useSharedValue(0),
    height: useSharedValue(0)
  };
  const currentYPosition = useSharedValue(0);
  const isDragging = useSharedValue(false);
  const [startScrolling, scrollOnDragOver, stopScrolling, draggingScrollHandler] = useScrollWhenDragging();
  const scrollHandler = event => {
    'worklet';

    const {
      contentOffset
    } = event;
    scroll.offsetY.value = contentOffset.y;
    draggingScrollHandler(event);
  };
  const {
    onBlockDragOverWorklet,
    onBlockDragEnd,
    onBlockDrop,
    targetBlockIndex
  } = useBlockDropZone();

  // Stop dragging blocks if the block draggable is unmounted.
  useEffect(() => {
    return () => {
      if (isDragging.value) {
        stopDraggingBlocks();
      }
    };
  }, []);
  const setDraggedBlockIconByClientId = clientId => {
    const blockName = select(blockEditorStore).getBlockName(clientId);
    const blockIcon = getBlockType(blockName)?.icon;
    if (blockIcon) {
      setDraggedBlockIcon(blockIcon);
    }
  };
  const onStartDragging = ({
    clientId,
    position
  }) => {
    if (clientId) {
      startDraggingBlocks([clientId]);
      setDraggedBlockIconByClientId(clientId);
      runOnUI(startScrolling)(position.y);
      generateHapticFeedback();
    } else {
      // We stop dragging if no block is found.
      runOnUI(stopDragging)();
    }
  };
  const onStopDragging = ({
    clientId
  }) => {
    if (clientId) {
      onBlockDrop({
        // Dropping is only allowed at root level
        srcRootClientId: '',
        srcClientIds: [clientId],
        type: 'block'
      });
      selectBlock(clientId);
      setDraggedBlockIcon(undefined);
    }
    onBlockDragEnd();
    stopDraggingBlocks();
  };
  const onChipLayout = ({
    nativeEvent: {
      layout
    }
  }) => {
    if (layout.width > 0) {
      chip.width.value = layout.width;
    }
    if (layout.height > 0) {
      chip.height.value = layout.height;
    }
  };
  const startDragging = ({
    x,
    y,
    id
  }) => {
    'worklet';

    const dragPosition = {
      x,
      y
    };
    chip.x.value = dragPosition.x;
    chip.y.value = dragPosition.y;
    currentYPosition.value = dragPosition.y;
    isDragging.value = true;
    runOnJS(onStartDragging)({
      clientId: id,
      position: dragPosition
    });
  };
  const updateDragging = ({
    x,
    y
  }) => {
    'worklet';

    const dragPosition = {
      x,
      y
    };
    chip.x.value = dragPosition.x;
    chip.y.value = dragPosition.y;
    currentYPosition.value = dragPosition.y;
    onBlockDragOverWorklet({
      x,
      y: y + scroll.offsetY.value
    });

    // Update scrolling velocity
    scrollOnDragOver(dragPosition.y);
  };
  const stopDragging = ({
    id
  }) => {
    'worklet';

    isDragging.value = false;
    stopScrolling();
    runOnJS(onStopDragging)({
      clientId: id
    });
  };
  const chipDynamicStyles = useAnimatedStyle(() => {
    const chipOffset = chip.width.value / 2;
    const translateX = !isRTL ? chip.x.value - chipOffset : -(contentWidth - (chip.x.value + chipOffset));
    return {
      transform: [{
        translateX
      }, {
        translateY: chip.y.value - chip.height.value - CHIP_OFFSET_TO_TOUCH_POSITION
      }]
    };
  });
  const chipStyles = [chipDynamicStyles, styles['draggable-chip__wrapper']];
  const exitingAnimation = ({
    currentHeight,
    currentWidth
  }) => {
    'worklet';

    const translateX = !isRTL ? 0 : currentWidth * -1;
    const duration = 150;
    const animations = {
      transform: [{
        translateY: withTiming(currentHeight, {
          duration
        })
      }, {
        translateX: withTiming(translateX, {
          duration
        })
      }, {
        scale: withTiming(0, {
          duration
        })
      }]
    };
    const initialValues = {
      transform: [{
        translateY: 0
      }, {
        translateX
      }, {
        scale: 1
      }]
    };
    return {
      initialValues,
      animations
    };
  };
  return /*#__PURE__*/_jsxs(_Fragment, {
    children: [/*#__PURE__*/_jsx(DroppingInsertionPoint, {
      scroll: scroll,
      currentYPosition: currentYPosition,
      isDragging: isDragging,
      targetBlockIndex: targetBlockIndex
    }), /*#__PURE__*/_jsx(Draggable, {
      onDragStart: startDragging,
      onDragOver: updateDragging,
      onDragEnd: stopDragging,
      testID: "block-draggable-wrapper",
      children: children({
        onScroll: scrollHandler
      })
    }), /*#__PURE__*/_jsx(Animated.View, {
      onLayout: onChipLayout,
      style: chipStyles,
      pointerEvents: "none",
      children: draggedBlockIcon && /*#__PURE__*/_jsx(Animated.View, {
        entering: ZoomInEasyDown.duration(200),
        exiting: exitingAnimation,
        children: /*#__PURE__*/_jsx(DraggableChip, {
          icon: draggedBlockIcon
        })
      })
    })]
  });
};
function useIsScreenReaderEnabled() {
  const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false);
  useEffect(() => {
    let mounted = true;
    const changeListener = AccessibilityInfo.addEventListener('screenReaderChanged', enabled => setIsScreenReaderEnabled(enabled));
    AccessibilityInfo.isScreenReaderEnabled().then(screenReaderEnabled => {
      if (mounted && screenReaderEnabled) {
        setIsScreenReaderEnabled(screenReaderEnabled);
      }
    });
    return () => {
      mounted = false;
      changeListener.remove();
    };
  }, []);
  return isScreenReaderEnabled;
}
function useIsEditingText() {
  const [isEditingText, setIsEditingText] = useState(() => RCTAztecView.InputState.isFocused());
  useEffect(() => {
    const onFocusChangeAztec = ({
      isFocused
    }) => {
      setIsEditingText(isFocused);
    };
    RCTAztecView.InputState.addFocusChangeListener(onFocusChangeAztec);
    return () => {
      RCTAztecView.InputState.removeFocusChangeListener(onFocusChangeAztec);
    };
  }, []);
  return isEditingText;
}

/**
 * Block draggable component
 *
 * This component serves for animating the block when it is being dragged.
 * Hence, it should be wrapped around the rendering of a block.
 *
 * @param {Object}      props                    Component props.
 * @param {JSX.Element} props.children           Children to be rendered.
 * @param {string}      props.clientId           Client id of the block.
 * @param {string}      [props.draggingClientId] Client id to use for dragging. If not defined, the value from `clientId` will be used.
 * @param {boolean}     [props.enabled]          Enables the draggable trigger.
 * @param {string}      [props.testID]           Id used for querying the long-press gesture handler in tests.
 *
 * @return {Function} Render function which includes the parameter `isDraggable` to determine if the block can be dragged.
 */
const BlockDraggable = ({
  clientId,
  children,
  draggingClientId,
  enabled = true,
  testID
}) => {
  const wasBeingDragged = useRef(false);
  const isEditingText = useIsEditingText();
  const isScreenReaderEnabled = useIsScreenReaderEnabled();
  const draggingAnimation = {
    opacity: useSharedValue(1)
  };
  const startDraggingBlock = () => {
    draggingAnimation.opacity.value = withTiming(0.4, BLOCK_OPACITY_ANIMATION_CONFIG);
  };
  const stopDraggingBlock = () => {
    draggingAnimation.opacity.value = withDelay(BLOCK_OPACITY_ANIMATION_DELAY, withTiming(1, BLOCK_OPACITY_ANIMATION_CONFIG));
  };
  const {
    isDraggable,
    isBeingDragged,
    isBlockSelected
  } = useSelect(_select => {
    const {
      getBlockRootClientId,
      getTemplateLock,
      isBlockBeingDragged,
      getSelectedBlockClientId
    } = _select(blockEditorStore);
    const rootClientId = getBlockRootClientId(clientId);
    const templateLock = rootClientId ? getTemplateLock(rootClientId) : null;
    const selectedBlockClientId = getSelectedBlockClientId();
    return {
      isBeingDragged: isBlockBeingDragged(clientId),
      isDraggable: 'all' !== templateLock,
      isBlockSelected: selectedBlockClientId && selectedBlockClientId === clientId
    };
  }, [clientId]);
  useEffect(() => {
    if (isBeingDragged !== wasBeingDragged.current) {
      if (isBeingDragged) {
        startDraggingBlock();
      } else {
        stopDraggingBlock();
      }
    }
    wasBeingDragged.current = isBeingDragged;
  }, [isBeingDragged]);
  const onLongPressDraggable = useCallback(() => {
    // Ensure that no text input is focused when starting the dragging gesture in order to prevent conflicts with text editing.
    RCTAztecView.InputState.blurCurrentFocusedElement();
  }, []);
  const animatedWrapperStyles = useAnimatedStyle(() => {
    return {
      opacity: draggingAnimation.opacity.value
    };
  });
  const wrapperStyles = [animatedWrapperStyles, styles['draggable-wrapper__container']];
  const canDragBlock = enabled && !isScreenReaderEnabled && (!isBlockSelected || !isEditingText);
  if (!isDraggable) {
    return children({
      isDraggable: false
    });
  }
  return /*#__PURE__*/_jsx(DraggableTrigger, {
    id: draggingClientId || clientId,
    enabled: enabled && canDragBlock,
    minDuration: Platform.select({
      // On iOS, using a lower min duration than the default
      // value prevents the long-press gesture from being
      // triggered in underneath elements. This is required to
      // prevent enabling text editing when dragging is available.
      ios: canDragBlock ? DEFAULT_IOS_LONG_PRESS_MIN_DURATION : DEFAULT_LONG_PRESS_MIN_DURATION,
      android: DEFAULT_LONG_PRESS_MIN_DURATION
    }),
    onLongPress: onLongPressDraggable,
    testID: testID,
    children: /*#__PURE__*/_jsx(Animated.View, {
      style: wrapperStyles,
      children: children({
        isDraggable: true
      })
    })
  });
};
export { BlockDraggableWrapper };
export default BlockDraggable;
//# sourceMappingURL=index.native.js.map