import React, { useRef, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import { useResizeObserver } from 'usehooks-ts';

import {
  PolymorphicComponent,
  PolymorphicComponentProps,
} from '@/utils/polymorphic';

export enum AspectRatioFillType {
  Width = 'width',
  Height = 'height',
}

export type UseAspectRatioFillDirectionOptions = {
  targetWidth?: number;
  targetHeight?: number;
  targetAspectRatio?: number;
};

export type UseAspectRatioFillDirectionReturn<E extends HTMLElement> = [
  ref: React.RefObject<E | null>,
  containerFillType: AspectRatioFillType | null,
];

/**
 * Listens for resize events on the container and compares the aspect ratio to
 * the supplied target aspect ratio.
 *
 * This information can be used to stretch a child element horizontally or
 * vertically to fill the container.
 */
export function useAspectRatioFillDirection<E extends HTMLElement>(
  options?: UseAspectRatioFillDirectionOptions
) {
  const {
    targetWidth,
    targetHeight,
    targetAspectRatio = targetWidth &&
      targetHeight &&
      targetWidth / targetHeight,
  } = options || {};

  const ref = useRef<E>(null);
  const [containerFillType, setContainerFillType] =
    useState<AspectRatioFillType | null>(null);

  useResizeObserver({
    ref: ref as React.RefObject<HTMLElement>, // https://github.com/juliencrn/usehooks-ts/pull/675
    onResize: ({ width: containerWidth, height: containerHeight }) => {
      const containerAspectRatio =
        containerWidth && containerHeight && containerWidth / containerHeight;
      setContainerFillType(
        containerAspectRatio != null && targetAspectRatio != null
          ? containerAspectRatio > targetAspectRatio
            ? AspectRatioFillType.Height
            : AspectRatioFillType.Width
          : null
      );
    },
  });

  return [
    ref,
    containerFillType,
  ] satisfies UseAspectRatioFillDirectionReturn<E>;
}

export type AspectRatioContainerProps = {
  width?: number;
  height?: number;
};

/**
 * Container that facilitates responsively scaling another element by comparing
 * the container size to a target aspect ratio.
 *
 * For example, you might use this to size an element to be as large as
 * possible within a container while maintaining a specific aspect ratio
 */
const AspectRatioContainer: PolymorphicComponent<AspectRatioContainerProps> = <
  C extends React.ElementType<any, keyof React.JSX.IntrinsicElements> = 'div',
  // C extends React.ElementType = 'div',
>(
  props: PolymorphicComponentProps<C, AspectRatioContainerProps>
) => {
  const {
    as: Component = 'div',
    className,
    style,
    width,
    height,
    aspectRatio,
    ...restProps
  } = props;

  const [ref, containerFillType] = useAspectRatioFillDirection({
    targetWidth: width,
    targetHeight: height,
    targetAspectRatio: aspectRatio,
  });

  return (
    <Component
      ref={ref as React.RefObject<any>}
      className={twMerge('absolute inset-0 size-full', className)}
      data-container-fill-type={containerFillType}
      style={
        {
          '--aspect-ratio':
            width && height
              ? `${width}/${height}`
              : aspectRatio
                ? aspectRatio
                : 'auto',
          '--aspect-ratio-container-fill-type': containerFillType,
          '--aspect-ratio-child-width':
            containerFillType === AspectRatioFillType.Width ? '100%' : 'auto',
          '--aspect-ratio-child-height':
            containerFillType === AspectRatioFillType.Height ? '100%' : 'auto',
          ...style,
        } as React.CSSProperties
      }
      {...restProps}
    />
  );
};

export default AspectRatioContainer;
