import * as React from "react";
import { UIManager, findNodeHandle } from "react-native";

import { useViewportEventsContext } from "../ViewportEvents";
import * as Utils from "../utils";

type Props = {
  innerRef?: (ref: React.Ref<any>) => void;
  preTriggerRatio?: number;
  onViewportEnter?: () => void;
  onViewportLeave?: () => void;
  children: React.ReactElement<any>;
  name?: string;
  testDimensions?: {
    componentOffsetX: number;
    componentOffsetY: number;
    componentWidth: number;
    componentHeight: number;
    inViewport: boolean;
  };
};

const initialDimensions = {
  componentOffsetX: null,
  componentOffsetY: null,
  componentWidth: null,
  componentHeight: null,
  inViewport: false,
};

function getTestDimensions(testDimensions) {
  return (process.env.NODE_ENV === "test" && testDimensions) || null;
}

function ViewportAwareComponent(props: Props, ref) {
  const viewportEvents = useViewportEventsContext();

  const {
    preTriggerRatio,
    onViewportEnter,
    onViewportLeave,
    children,
    testDimensions,
  } = props;

  const state = React.useRef(
    getTestDimensions(testDimensions) || initialDimensions
  );

  const [viewportChangeEvent, setViewportChangeEvent] = React.useState(null);

  function assignRef(_ref) {
    if (_ref && !ref) {
      ref = _ref;
    }
  }

  const checkInViewport = (viewportChangeEvent, layoutEvent) => {
    const inVerticalViewport = Utils.isInViewport(
      viewportChangeEvent.offsetY,
      viewportChangeEvent.height,
      layoutEvent.offsetY,
      layoutEvent.height,
      preTriggerRatio
    );

    const inHorizontalViewport = Utils.isInViewport(
      viewportChangeEvent.offsetX,
      viewportChangeEvent.width,
      layoutEvent.offsetX,
      layoutEvent.width,
      preTriggerRatio
    );

    return inVerticalViewport && inHorizontalViewport;
  };

  const checkViewportEnterOrLeave = (newInViewportState) => {
    const { inViewport } = state.current;

    if (!inViewport && newInViewportState) {
      onViewportEnter && onViewportEnter();

      return true;
    } else if (inViewport && !newInViewportState) {
      onViewportLeave && onViewportLeave();

      return true;
    }

    return false;
  };

  const onViewportChange = (viewportChangeEvent) => {
    setViewportChangeEvent(viewportChangeEvent);

    const nodeHandle = findNodeHandle(ref);

    if (!nodeHandle) {
      return;
    }

    const {
      componentOffsetX,
      componentOffsetY,
      componentWidth,
      componentHeight,
    } = state.current;

    if (
      viewportChangeEvent.shouldMeasureLayout ||
      componentOffsetX == null ||
      componentOffsetY == null ||
      componentWidth == null ||
      componentHeight == null
    ) {
      UIManager.measureLayout(
        nodeHandle,
        viewportChangeEvent.parentHandle,
        () => {},
        (offsetX, offsetY, width, height) => {
          const inViewport = checkInViewport(viewportChangeEvent, {
            offsetX,
            offsetY,
            width,
            height,
          });

          checkViewportEnterOrLeave(inViewport);

          state.current = {
            componentOffsetX: offsetX,
            componentOffsetY: offsetY,
            componentWidth: width,
            componentHeight: height,
            inViewport,
          };
        }
      );
    } else {
      const inViewport = checkInViewport(viewportChangeEvent, {
        offsetX: componentOffsetX,
        offsetY: componentOffsetY,
        width: componentWidth,
        height: componentHeight,
      });

      checkViewportEnterOrLeave(inViewport);

      state.current = {
        ...state.current,
        inViewport,
      };
    }
  };

  React.useEffect(() => {
    if (viewportEvents) {
      viewportEvents?.addListener?.(onViewportChange);

      viewportChangeEvent &&
        setTimeout(() => onViewportChange(viewportChangeEvent), 50);

      return () => {
        viewportEvents?.removeListener?.(onViewportChange);
      };
    }
  }, [viewportEvents]);

  return React.cloneElement(React.Children.only(children), {
    ref: assignRef,
  });
}

export const ViewportAware = React.forwardRef(ViewportAwareComponent);
