import React, { useState } from 'react'
import {
  autoPlacement,
  autoUpdate,
  flip,
  FloatingPortal,
  type FloatingPortalProps,
  offset,
  type Placement,
  shift,
  useClick,
  useDismiss,
  useFloating,
  useInteractions,
  useRole,
  useTransitionStyles,
} from '@floating-ui/react'

import styles from './_popover.module.scss'
import { type PopoverHeaderProps } from '../ComponentTypes.models'
import isClient from '../../services/isClient'
import PopoverHeader from '../PopoverHeader/PopoverHeader'
import { useMediaQuery } from '../../hooks/responsiveHooks'

type ChildrenProp = ({
  setVisible,
  visible,
  floatingRef,
}: {
  setVisible: React.Dispatch<React.SetStateAction<boolean>>
  visible: boolean
  floatingRef: React.MutableRefObject<HTMLElement | null>
}) => React.JSX.Element

export type PopoverProps = {
  /** The element that needs to be clicked to open the popover */
  children: ChildrenProp
  /** The content inside the popover */
  popoverContent: ChildrenProp | React.JSX.Element
  /** Optional className added to the popover */
  className?: string
  /** Function that is called on blur of the popover */
  onClickOutside?: () => void
  /** Optional PopoverHeader props available */
  header?: PopoverHeaderProps
  /** Optional `noPadding` prop is used to remove the padding of the content. */
  noPadding?: boolean
  /** Optional placement prop for the popover */
  position?: Placement | 'auto' | 'auto-start' | 'auto-end'
  /** Optional prop to restrict the max width of the popover */
  maxWidth?: string
  /** Optional element to append the popover to */
  appendTo?: FloatingPortalProps['root'] | 'parent'
  /** Optional prop to disable the popover */
  disabled?: boolean
  /** Optional prop to set the color of the arrow */
  /**
   * @deprecated This prop is no longer supported.
   **/
  arrowColor?:
    | 'medium-purple'
    | 'dark-red'
    | 'dark-green'
    | 'dark-blue'
    | 'lighter-gray'
    | 'white'
  /** Optional prop to disable closing the Popover on scrolling */
  disableCloseOnScroll?: boolean
  /** Optional prop to add a test id to the popover for QA testing */
  qaTestId?: string
}

const Popover = ({
  children,
  popoverContent,
  position = 'right',
  className = '',
  onClickOutside,
  header,
  noPadding,
  maxWidth,
  appendTo,
  disabled = false,
  disableCloseOnScroll = false,
  qaTestId = 'popover',
}: PopoverProps): React.JSX.Element => {
  const [visible, setVisible] = useState(false)
  const isAutoPosition =
    position === 'auto' || position === 'auto-start' || position === 'auto-end'

  const { refs, floatingStyles, context } = useFloating({
    ...(!isAutoPosition ? { placement: position } : {}),
    open: visible && !disabled,
    onOpenChange(_, event, reason) {
      if (reason === 'outside-press' && !disabled) {
        onClickOutside?.()
        setVisible(false)
      }
      if (event?.type === 'scroll' && !disableCloseOnScroll) {
        setVisible(false)
      }
    },
    middleware: [
      offset(8),
      ...(isAutoPosition
        ? [
            autoPlacement({
              ...(position.includes('start')
                ? { alignment: 'start' }
                : position.includes('end')
                  ? { alignment: 'end' }
                  : {}),
            }),
          ]
        : [flip()]),
      shift({ padding: 4 }),
    ],
    whileElementsMounted: autoUpdate,
  })

  const click = useClick(context, {
    keyboardHandlers: false,
  })
  const dismiss = useDismiss(context, {
    ancestorScroll: !disableCloseOnScroll,
    outsidePressEvent: 'click',
  })
  const role = useRole(context, { role: 'dialog' })
  const { getReferenceProps, getFloatingProps } = useInteractions([
    click,
    dismiss,
    role,
  ])

  const { isMounted, styles: transitionStyles } = useTransitionStyles(context, {
    initial: {
      opacity: 0,
      transform: 'scale(0.8)',
    },
    duration: 200, // transition duration when opening and closing the Popover
  })

  const screenIsMdMin = useMediaQuery({ type: 'min', breakpoint: 'md' })
  const popoverWidth = screenIsMdMin
    ? maxWidth
      ? maxWidth
      : '400px'
    : 'calc(100vw - 60px)' // 10px padding in the popover + 20px space on each side of the popover

  const renderContent = () => (
    <>
      {header ? <PopoverHeader {...header} /> : null}
      <div
        className={
          header ? (noPadding ? styles.noPadding : styles.popoverContent) : ''
        }
      >
        {typeof popoverContent === 'function'
          ? popoverContent({ setVisible, visible, floatingRef: refs.floating })
          : popoverContent}
      </div>
    </>
  )

  const resolveAppendTo = (): FloatingPortalProps['root'] | null => {
    if (!isClient) return null

    // Reason: `document.body` can be null transiently during navigation/teardown.
    // Fall back to `document.documentElement` to ensure the portal always has a stable root.
    const defaultRoot = document.body ?? document.documentElement

    if (!appendTo) return defaultRoot

    if (appendTo === 'parent') {
      const referenceElement = refs.reference?.current
      const parentElement =
        referenceElement instanceof HTMLElement
          ? referenceElement.parentElement
          : null

      return parentElement ?? defaultRoot
    }

    return appendTo ?? defaultRoot
  }

  const portalRoot = resolveAppendTo()
  return (
    <>
      <span
        ref={refs.setReference}
        {...getReferenceProps()}
        className={`${header || noPadding ? styles.noPadding : ''}`}
        data-testid='popover-trigger'
        qa-test-id={`${qaTestId}-trigger`}
      >
        {children({ setVisible, visible, floatingRef: refs.floating })}
      </span>
      {isMounted && popoverContent && portalRoot ? (
        <FloatingPortal root={portalRoot}>
          <div
            ref={refs.setFloating}
            style={{
              ...floatingStyles,
              maxWidth: popoverWidth,
              zIndex: 2 ** 31 - 1, // Max z-index (2 ** 31 - 1) to ensure the tooltip appears above all elements and stacking contexts
            }}
            {...getFloatingProps()}
            qa-test-id={`${qaTestId}-floating`} // test id for QA testing
            data-testid='popover-floating' // test id for unit testing
            data-placement={context.placement}
          >
            <div
              className={`${styles.popoverContainer} ${className ? className : ''} ${noPadding ? styles.noPadding : header ? '' : styles.popoverPadding}`}
              style={{ ...transitionStyles }}
              data-testid='popover-container'
            >
              {renderContent()}
            </div>
          </div>
        </FloatingPortal>
      ) : null}
    </>
  )
}

export default Popover
