import React, {
  ComponentPropsWithoutRef,
  forwardRef,
  isValidElement,
} from "react"
import { tv } from "tailwind-variants"
import { type VariantProps } from "tailwind-variants"
import type { IconComponent, IconProps } from "../../icons/types"
import {
  backgroundVariants,
  BorderColorVariantProps,
  borderVariants,
  disabledVariants,
} from "../../styles"
import { buttonLayoutVariants } from "../../styles/utils/buttonLayout"
import { classNames } from "../../utils"
import { Flex } from "../Flex"
import { Link, LinkProps } from "../Link"
import { Slot } from "../Slot"
import { Spinner } from "../Spinner"

export const buttonVariants = tv({
  extend: buttonLayoutVariants,
  base: "justify-center font-medium focus-visible:outline-hidden",
  variants: {
    rounded: {
      true: "rounded-full",
    },
    variant: {
      primary: backgroundVariants({
        background: "blue-3",
        interactive: true,
        className: "text-white",
      }),
      secondary: classNames(
        backgroundVariants({
          background: "primary",
          interactive: true,
        }),
        borderVariants({
          border: "border-1",
          interactive: true,
        }),
      ),
      "secondary-transparent": classNames(
        backgroundVariants({
          background: "primary",
          interactive: true,
          transparent: true,
        }),
        borderVariants({
          border: "border-1",
          interactive: true,
          transparent: true,
        }),
      ),
      tertiary: backgroundVariants({
        background: "additional-1",
        interactive: true,
      }),
      destructive: backgroundVariants({
        background: "error",
        interactive: true,
        class: "text-error",
      }),
      ghost: backgroundVariants({
        background: "ghost",
        interactive: true,
      }),
      frosted: classNames(
        backgroundVariants({
          background: "frosted",
          interactive: true,
        }),
        borderVariants({
          border: "frosted",
        }),
      ),
    },
    onlyIcon: {
      true: "",
    },
    isLoading: {
      true: "",
    },
  },
  compoundVariants: [
    { size: "xs", onlyIcon: true, isLoading: false, className: "w-6 p-1" },
    { size: "sm", onlyIcon: true, isLoading: false, className: "w-8 p-1.5" },
    { size: "md", onlyIcon: true, isLoading: false, className: "w-10 p-2.5" },
    { size: "lg", onlyIcon: true, isLoading: false, className: "w-12 p-3" },
    { size: "xs", onlyIcon: true, isLoading: true, className: "size-6 px-0" },
    { size: "sm", onlyIcon: true, isLoading: true, className: "size-8 px-0" },
    { size: "md", onlyIcon: true, isLoading: true, className: "size-10 px-0" },
    { size: "lg", onlyIcon: true, isLoading: true, className: "size-12 px-0" },
  ],
})

type BaseButtonProps = ComponentPropsWithoutRef<"button"> &
  Omit<
    VariantProps<typeof buttonVariants>,
    "onlyIcon" | "leftIcon" | "rightIcon"
  > &
  VariantProps<typeof disabledVariants> & {
    /**
     * Used to show a loading indicator in the button for async actions.
     */
    isLoading?: boolean
    renderLink?: React.ComponentType<LinkProps>
    iconSide?: "left" | "right"
  } & Omit<LinkProps, "variant">

export type ButtonWithIconProps = BaseButtonProps & {
  /**
   * Icon can either be used as a material icon or as a custom icon.
   */
  icon: IconComponent
  overrides?: {
    Icon: {
      props: IconProps
    }
  }
}

export type ButtonWithCustomIconProps = BaseButtonProps & {
  icon: React.ReactNode
}

type ButtonWithAnyIconProps = ButtonWithIconProps | ButtonWithCustomIconProps

export type ButtonProps = BaseButtonProps | ButtonWithAnyIconProps

export type ButtonSize = ButtonProps["size"]

const hasCustomIcon = (
  props: ButtonProps,
): props is ButtonWithCustomIconProps => {
  return isValidElement((props as ButtonWithCustomIconProps).icon)
}

const hasAnyIcon = (props: ButtonProps): props is ButtonWithAnyIconProps => {
  return (props as ButtonWithAnyIconProps).icon !== undefined
}

const LinkButton = forwardRef<HTMLAnchorElement, ButtonProps>(
  function LinkButton({ renderLink, ...rest }, ref) {
    const Comp = renderLink ?? Link
    return <Comp {...rest} ref={ref} variant="unstyled" />
  },
)

/**
 * Button is our standard interactive element that triggers a response.
 * You can place text and icons inside of a button.
 * Our Buttons can also act as a link by passing `href`.
 */
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  function Button(
    {
      renderLink,
      children,
      className,
      disabled = false,
      href,
      variant = "primary",
      size = "md",
      isLoading,
      type = "button",
      iconSide = "left",
      rounded,
      ...propsWithIcon
    },
    ref,
  ) {
    const hasIcon = hasAnyIcon(propsWithIcon)
    const onlyIcon = hasIcon && !children

    const renderContent = () => {
      if (isLoading) {
        let color: BorderColorVariantProps["color"]

        switch (variant) {
          case "primary":
            color = "white"
            break
          case "destructive":
            color = "error"
            break
        }

        return <Spinner color={color} />
      }

      const renderIcon = (buttonProps: ButtonWithAnyIconProps) => {
        const iconSize = size === "lg" ? 24 : size === "xs" ? 16 : 20
        return (
          <Flex aria-hidden="true" className="pointer-events-none">
            {hasCustomIcon(buttonProps) ? (
              buttonProps.icon
            ) : (
              <buttonProps.icon
                size={iconSize}
                {...buttonProps.overrides?.Icon.props}
              />
            )}
          </Flex>
        )
      }

      return (
        <>
          {hasIcon && iconSide === "left" && renderIcon(propsWithIcon)}

          {children}

          {hasIcon && iconSide === "right" && renderIcon(propsWithIcon)}
        </>
      )
    }

    const { icon: _, ...props } = propsWithIcon as typeof propsWithIcon & {
      icon?: unknown
    }
    return (
      <Slot
        className={classNames(
          buttonVariants({
            variant,
            size,
            leftIcon: hasIcon && !onlyIcon && iconSide === "left",
            rightIcon: hasIcon && !onlyIcon && iconSide === "right",
            onlyIcon,
            isLoading,
            rounded,
          }),
          disabledVariants({ disabled }),
          className,
        )}
        ref={ref}
        {...props}
      >
        {href ? (
          <LinkButton disabled={disabled} href={href} renderLink={renderLink}>
            {renderContent()}
          </LinkButton>
        ) : (
          <button disabled={disabled} type={type}>
            {renderContent()}
          </button>
        )}
      </Slot>
    )
  },
)
