import makeBlockie from "ethereum-blockies-base64"
import React, {
  forwardRef,
  ComponentPropsWithoutRef,
  ForwardedRef,
  useMemo,
} from "react"
import { Image } from "../../icons"
import type { IconComponent, IconProps } from "../../icons/types"
import {
  ObjectFitVariantProps,
  objectFitVariants,
} from "../../styles/utils/objectFit"
import { classNames } from "../../utils"
import { CenterAligned, CenterAlignedProps } from "../CenterAligned"
import { generateAvatarBadgeClipPath } from "./avatar-badge"
import { AvatarBadge } from "./AvatarBadge"

export type AvatarBaseProps = CenterAlignedProps & {
  /**
   * Whether the avatar should be a circle.
   */
  rounded?: boolean
  /**
   * Add an inset border
   */
  border?: boolean
  borderClassName?: string
  /**
   * Width and height of the avatar.
   */
  size?: number
  /**
   * This is useful for usage in NextJS projects that want to use NextImage.
   **/
  renderImage?: React.ComponentType<Omit<AvatarImageProps, "renderImage">>
  /**
   * Renders a badge in the bottom right corner of the avatar.
   */
  badge?: React.JSX.Element
}

export type AvatarImageProps = AvatarBaseProps & {
  /**
   * Optional url src for the image.
   */
  src: string | null | undefined
  icon?: undefined
  seed?: string
} & Omit<ComponentPropsWithoutRef<"img">, "width" | "height"> &
  ObjectFitVariantProps

export type AvatarVideoProps = AvatarBaseProps & {
  src: string
  icon?: undefined
} & ComponentPropsWithoutRef<"video">

export type AvatarIconProps = AvatarBaseProps & {
  /**
   * Optional svg icon component.
   */
  icon: IconComponent
  overrides?: {
    Icon?: IconProps
  }
  src?: undefined
}

export type AvatarProps = AvatarImageProps | AvatarIconProps | AvatarBaseProps

const isIcon = (props: AvatarProps): props is AvatarIconProps => {
  return (props as Partial<AvatarIconProps>).icon !== undefined
}

// Filters a className string based on prefix
// E.g. filterClassName("mx-3 rounded-xl", "rounded") -> "rounded-xl"
const filterClassName = (className: string, prefix: string) => {
  return classNames(className.split(" ").filter(c => c.startsWith(prefix)))
}

export const isVideo = (props: AvatarProps): props is AvatarVideoProps => {
  return [".webm", ".mp4", ".m4v", ".ogg", ".ogv", ".mov"].some(ext =>
    (props as Partial<AvatarVideoProps>).src?.toLowerCase().endsWith(ext),
  )
}

const ImgAvatar = forwardRef<HTMLImageElement, AvatarImageProps>(
  function ImgAvatar({ renderImage, seed, src, ...rest }, ref) {
    const Comp = renderImage ?? "img"

    const dataUrl = useMemo(() => {
      if (seed) {
        return makeBlockie(seed)
      }
    }, [seed])

    return <Comp {...rest} ref={ref} src={src || dataUrl} />
  },
)

const VideoAvatar = forwardRef<HTMLVideoElement, AvatarVideoProps>(
  function VideoAvatar(props, ref) {
    return <video {...props} ref={ref} />
  },
)

const DEFAULT_ICON_SIZE = 24

export type AvatarElement = HTMLDivElement | HTMLImageElement | HTMLVideoElement

const AvatarBase = forwardRef<AvatarElement, AvatarProps>(function Avatar(
  {
    className: propsClassName,
    badge,
    renderImage,
    rounded,
    size = DEFAULT_ICON_SIZE,
    border,
    borderClassName,
    style,
    ...props
  },
  ref,
) {
  const setDefaultSize =
    size === DEFAULT_ICON_SIZE &&
    (isIcon(props) ? !props.overrides?.Icon?.size : true)

  const className = classNames(
    "aspect-square overflow-hidden",
    rounded && "rounded-full",
    setDefaultSize && "size-6",
    propsClassName,
  )

  const renderAvatar = (assetStyles: React.CSSProperties = {}) => {
    if (isVideo(props) && !("frameTime" in props)) {
      return (
        <VideoAvatar
          className={className}
          ref={ref as ForwardedRef<HTMLVideoElement>}
          size={size}
          style={assetStyles}
          {...props}
        />
      )
    } else if (isIcon(props)) {
      const { icon: IconComponent } = props
      return (
        <IconComponent
          aria-label={props["aria-label"]}
          size={size}
          style={assetStyles}
          title={props.title}
          {...props.overrides?.Icon}
          className={classNames(props.overrides?.Icon?.className, className)}
        />
      )
    } else if (props.children) {
      // when children, we render a div so we can nest skeletons underneath Avatars
      // and they'll automatically be same size/interchangeable with images.
      return (
        <CenterAligned
          className={className}
          ref={ref as ForwardedRef<HTMLDivElement>}
          style={assetStyles}
          {...props}
        />
      )
    } else {
      // The default case is an image since <Image> has a placeholder component
      const {
        objectFit = "cover",
        src,
        seed,
        ...rest
      } = props as AvatarImageProps

      if (!renderImage && !src && !seed) {
        return (
          <CenterAligned
            className={classNames("bg-bg-additional-2", className)}
            ref={ref as ForwardedRef<HTMLDivElement>}
            style={assetStyles}
            {...props}
          >
            <Image className="size-1/2" />
          </CenterAligned>
        )
      }

      return (
        <ImgAvatar
          className={classNames(objectFitVariants({ objectFit }), className)}
          ref={ref as ForwardedRef<HTMLImageElement>}
          renderImage={renderImage}
          seed={seed}
          size={size}
          src={src}
          style={assetStyles}
          {...rest}
        />
      )
    }
  }

  const styles: React.CSSProperties = {
    ...(size !== DEFAULT_ICON_SIZE && {
      width: `${size}px`,
      height: `${size}px`,
    }),
    ...style,
  }

  if (border || badge) {
    const badgeStyles: React.CSSProperties = badge
      ? {
          clipPath: `path("${generateAvatarBadgeClipPath(size, badge.props.size)}")`,
        }
      : {}

    return (
      <div className="relative inline-block shrink-0" style={styles}>
        <div style={badgeStyles}>{renderAvatar(styles)}</div>
        {border && (
          <div
            className={classNames(
              "absolute inset-0",
              borderClassName ?? "inset-shadow-border",
              // We want to apply the border-radius only from the props to the border for visual consistency
              filterClassName(className, "rounded"),
            )}
            style={badgeStyles}
          />
        )}
        {badge}
      </div>
    )
  }

  return renderAvatar(styles)
})

export const Avatar = Object.assign({}, AvatarBase, { Badge: AvatarBadge })
