import React, {
  type ComponentProps,
  type ComponentPropsWithoutRef,
  type ElementType,
  forwardRef,
  type ReactElement,
  Ref,
} from 'react';
import { Chip as MuiChip, chipClasses } from '@mui/material';
import { alpha, useTheme } from '@mui/material/styles';
import type { SvgIconComponent } from '@mui/icons-material';
import { CancelRounded, EditRounded } from '@mui/icons-material';

import { xSmallSize } from '@/mui/components/constants';
import { SHAPE_ROUNDED, SHAPE_SQUARE, SIZE_MEDIUM } from '@/mui/constants';
import { ALPHA_300, ALPHA_400, ALPHA_800 } from '@/mui/constants/opacity';

import { ShapeProp } from '@/mui/types';

import { blendColor } from '@/mui/helpers/color';

import IconButton from '../IconButton/IconButton';
import Stack from '../Stack/Stack';

import styles from './Chip.module.scss';

type HTMLTagMap = {
  div: HTMLDivElement;
  span: HTMLSpanElement;
  a: HTMLAnchorElement;
  button: HTMLButtonElement;
};

type MuiProps = ComponentProps<typeof MuiChip>;

type PolymorphicRef<C extends ElementType> = C extends keyof HTMLTagMap ? Ref<HTMLTagMap[C]> : Ref<unknown>;

export type Props<C extends ElementType> = {
  component?: C;
  shape?: ShapeProp;
  fullWidth?: boolean;
  onApplyIcon?: SvgIconComponent;
  onApply?: () => void;
  onCancelIcon?: SvgIconComponent;
  onCancel?: () => void;
} & MuiProps &
  Omit<ComponentPropsWithoutRef<C>, keyof MuiProps | 'component'>;

function PolymorphicComponentImpl<C extends ElementType = 'a'>(props: Props<C>, ref: PolymorphicRef<C>): ReactElement {
  const theme = useTheme();

  const { shape, fullWidth, onCancel, onApply, onApplyIcon, onCancelIcon, sx, variant, color, size, ...rest } = props;

  const singleColor = color !== undefined && !Object.prototype.hasOwnProperty.call(theme.palette, color);

  const overlayEdit = Boolean(onApply || onCancel);

  let shapeRadius = theme.shape.borderRadiusCircular;

  if (shape === SHAPE_ROUNDED) {
    shapeRadius = theme.shape.borderRadius;
  } else if (shape === SHAPE_SQUARE) {
    shapeRadius = 0;
  }

  if (overlayEdit) {
    const ApplyIcon = onApplyIcon || EditRounded;
    const CancelIcon = onCancelIcon || CancelRounded;

    // @ts-expect-error works as expected
    rest.onDelete = () => undefined;
    // @ts-expect-error works as expected
    rest.deleteIcon = (
      <Stack direction="row" alignItems="center" spacing={0}>
        {onApply && (
          <IconButton className={styles.OverlapButton} size="small" color="inherit" onClick={onApply}>
            <ApplyIcon fontSize={size ?? SIZE_MEDIUM} />
          </IconButton>
        )}
        {onCancel && (
          <IconButton className={styles.OverlapButton} size="small" color="inherit" onClick={onCancel}>
            <CancelIcon fontSize={size ?? SIZE_MEDIUM} />
          </IconButton>
        )}
      </Stack>
    );
  }

  return (
    <MuiChip
      {...rest}
      // TypeScript can't infer polymorphic ref on MUI base, safe to cast
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      ref={ref as Ref<any>}
      size={size}
      variant={variant}
      color={singleColor ? 'primary' : color}
      sx={{
        '--internal-custom-color': color,
        ...(shape === SHAPE_SQUARE && {
          borderRadius: 0,
        }),
        ...(shape === SHAPE_ROUNDED && {
          borderRadius: theme.shape.borderRadius,
        }),
        ...(fullWidth && {
          width: '100%',
        }),
        ...(overlayEdit && {
          minWidth: xSmallSize * 2,
          [`&:hover .${chipClasses.deleteIcon}`]: {
            opacity: 1,
            visibility: 'visible',
          },
          [`.${chipClasses.deleteIcon}`]: {
            position: 'absolute',
            top: 0,
            left: 0,
            bottom: 0,
            right: 0,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'flex-end',
            color: theme.palette.text.primary,
            backgroundColor: alpha(theme.palette.background.paper, ALPHA_800),
            zIndex: 1,
            margin: 0,
            opacity: 0,
            visibility: 'hidden',
            transitionProperty: 'opacity, visibility',
            transitionDuration: '300ms',
            borderRadius: shapeRadius,
            '&:hover': {
              color: theme.palette.text.primary,
              backgroundColor: alpha(theme.palette.background.paper, ALPHA_800),
            },
          },
        }),
        ...(singleColor && {
          color: 'var(--internal-custom-color, var(--theme-shades-900))',
          borderColor: variant === 'outlined' ? 'currentColor' : 'transparent',
          backgroundColor: variant === 'filled' ? blendColor(ALPHA_300, theme.palette.background.paper) : 'transparent',
          [`&.${chipClasses.clickable}:hover`]: {
            backgroundColor: blendColor(variant === 'filled' ? ALPHA_400 : ALPHA_300, theme.palette.background.paper),
          },
          [` .${chipClasses.deleteIcon}`]: {
            color: 'currentColor',
            opacity: ALPHA_800,
            '&:hover': {
              color: 'currentColor',
              opacity: 1,
            },
          },
        }),
        ...sx,
      }}
    />
  );
}

const PolymorphicComponent = forwardRef(
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  PolymorphicComponentImpl as unknown as (props: Props<any>, ref: PolymorphicRef<any>) => ReactElement,
);

const Chip = PolymorphicComponent as <C extends ElementType = 'a'>(
  props: Props<C> & { ref?: PolymorphicRef<C> },
) => ReactElement | null;

// @ts-expect-error TS can't detect displayName from forwardRef
Chip.displayName = 'Chip';

export default Chip;
