import React, { useEffect, useMemo } from 'react';
import {
  autoUpdate,
  offset,
  shift,
  UseFloatingOptions,
  ShiftOptions as BaseShiftOptions,
  DetectOverflowOptions,
} from '@floating-ui/react-dom';

import { usePopover, UsePopoverReturn } from '../utils/hooks/use-popover';
import { createContextSelector } from '../utils/react';

/*-- Context --*/

export type DropdownMenuContextValues = Pick<
  UsePopoverReturn,
  'refs' | 'attributes' | 'styles'
> & {
  open: boolean;
  keepMounted: boolean;
  withArrow: boolean;
};

const [DropdownMenuContext, useDropdownMenuContext] =
  createContextSelector<DropdownMenuContextValues>(
    {
      open: false,
      keepMounted: false,
      withArrow: false,
      attributes: {
        floating: {},
      },
      styles: {
        arrow: {},
        floating: {},
      },
      refs: {
        arrow: { current: null },
        floating: { current: null },
        reference: { current: null },
        setArrow: () => {},
        setFloating: () => {},
        setReference: () => {},
      },
    },
    { name: 'DropdownMenuContext' },
  );

/*-- Provider --*/

type ShiftOptions = Partial<BaseShiftOptions & DetectOverflowOptions>;

export interface DropdownMenuProviderProps {
  /** A distance between reference (target) and floating (popper) elements. */
  offset?: number;
  /** A shift of floating element from a reference center. */
  skidding?: number;
  placement?: UseFloatingOptions['placement'];
  /**
   * Specifies whether to use [shift()](https://floating-ui.com/docs/shift) middleware
   * or not. `shift()` moves the floating element along the specified axes in order to
   * keep it in view.
   */
  shift?: boolean | ShiftOptions;
  withArrow?: boolean;
  open?: boolean;
  /** Specifies whether to unmount floating (popper) element or not when closed. */
  keepMounted?: boolean;
  children: React.ReactNode;
}

function DropdownMenuProvider({
  placement: initialPlacement,
  withArrow = false,
  offset: mainAxis = 8,
  skidding: alignmentAxis = withArrow ? -12 : 0,
  shift: userShift = true,
  children,
  open = false,
  keepMounted = false,
}: DropdownMenuProviderProps) {
  const shiftOptions = {
    padding: 8,
    ...(userShift as ShiftOptions),
  };

  const {
    update: updateFloating,
    styles,
    attributes,
    refs,
  } = usePopover({
    placement: initialPlacement,
    arrow: withArrow ? { padding: 12 } : false,
    // Order of middlewares matters
    middleware: [
      offset({ mainAxis, alignmentAxis }),
      userShift && shift(shiftOptions),
    ],
  });

  useEffect(() => {
    if (!open || !refs.reference.current || !refs.floating.current) {
      return;
    }

    return autoUpdate(
      refs.reference.current,
      refs.floating.current,
      updateFloating,
      { ancestorScroll: false, layoutShift: false },
    );
  }, [open, refs.reference, refs.floating, updateFloating]);

  const contextValue = useMemo(
    () => ({
      open,
      keepMounted,
      withArrow,
      styles,
      attributes,
      refs,
    }),
    [open, keepMounted, withArrow, styles, attributes, refs],
  );

  return (
    <DropdownMenuContext.Provider value={contextValue}>
      {children}
    </DropdownMenuContext.Provider>
  );
}

export { DropdownMenuProvider, useDropdownMenuContext };
