import clsx from 'clsx';
import React, { forwardRef } from 'react';
import { twMerge } from 'tailwind-merge';

import Link from '@/components/link/Link';

export enum ButtonVariant {
  /**
   * Mode-aware button color with solid background. This is our most commonly used type of button
   *
   * Foreground/background of button follows the overall app theme
   */
  Standard = 'standard',
  /**
   * Mode-aware primary button color with solid background
   *
   * Foreground/background of button is inverted compared to the overall app theme
   */
  Primary = 'primary',
  /**
   * Mode-aware secondary button color with transparent background and a border
   *
   * Foreground/background of button follows the overall app theme
   */
  Secondary = 'Secondary',
  /**
   * Mode-aware tertiary button color with transparent background
   *
   * Typically reserved for icon buttons. Foreground/background of button follows the overall app theme
   */
  Tertiary = 'Tertiary',
  /**
   * Light text on image background with grain, animates on hover
   */
  Aura = 'aura',
  /**
   * Dark text on light background
   */
  LightPrimary = 'light-primary',
  /**
   * Light text on dark background, blurs content underneath
   */
  DarkPrimary = 'dark-primary',
  /**
   * Light text on dark background
   */
  LightSecondary = 'light-secondary',
  /**
   * Dark text on light background
   */
  DarkSecondary = 'dark-secondary',
  /**
   * Medium gray glassy overlay that should work on light or dark image content, blurs content underneath
   */
  ImageGlass = 'image-glass',
  /**
   * Light text on dark background, blurs content underneath
   */
  Glass = 'glass-standard',
  /**
   * Light translucent, blurs content underneath
   */
  LightGlass = 'light-glass',
  /**
   * Light translucent, blurs content underneath
   */
  Fog = 'fog',
  /**
   * Dark translucent, blurs content underneath
   */
  Smoke = 'smoke',
  /**
   * Similar to the Tertiary button, but inherits the text color from outside
   */
  Inherit = 'inherit',
}

export enum ButtonSize {
  Micro = 'micro',
  Mini = 'mini',
  Small = 'small',
  Medium = 'medium',
  Large = 'large',
}

export enum ButtonShape {
  Rectangle = 'rectangle',
  Rounded = 'rounded',
  Pill = 'pill',
}

export const DEFAULT_BUTTON_AURA_IMAGE =
  'https://cdn-o.suno.com/aura_horizontal.jpg';

export const AURA_V2_STYLE = {
  backgroundImage:
    'linear-gradient(#0000001a, #0000001a), var(--button-background-image)',
  backgroundSize: 'cover',
  backgroundPosition: 'center',
} as React.CSSProperties;

// type OptionalKeys<T, P extends keyof T> = Omit<T, P> & Partial<Pick<T, P>>;
type RequireKeys<T, P extends keyof T> = Omit<T, P> & Required<Pick<T, P>>;

export type BaseButtonProps = {
  iconClassName?: string;
  contentClassName?: string;
  active?: boolean | null;
  disabled?: boolean;
  iconOnly?: boolean;
  aspectSquare?: boolean;
  enableHoverState?: boolean;
  variant?: ButtonVariant;
  size?: ButtonSize;
  shape?: ButtonShape;
  backgroundImage?: string;
  backgroundHoverAnimationSpeed?: number;
  iconStart?: React.ReactNode | React.ComponentType<{ className?: string }>;
  icon?: React.ReactNode | React.ComponentType<{ className?: string }>;
  iconEnd?: React.ReactNode | React.ComponentType<{ className?: string }>;
};

export type LinkButtonProps = BaseButtonProps &
  React.ComponentProps<typeof Link>;
export type NativeButtonProps = BaseButtonProps &
  (RequireKeys<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> & {
    href?: never;
  });
export type DivButtonProps = BaseButtonProps &
  (React.HTMLAttributes<HTMLDivElement> & { href?: never; onClick?: never });

export type Props = LinkButtonProps | NativeButtonProps | DivButtonProps;

const Button = forwardRef<
  HTMLAnchorElement | HTMLButtonElement | HTMLDivElement,
  Props
>((props, ref) => {
  const {
    className: explicitClassName,
    iconClassName: explicitIconClassName,
    contentClassName: explicitContentClassName,
    children,
    icon,
    iconStart,
    iconEnd,
    active,
    disabled,
    size = ButtonSize.Small,
    shape = ButtonShape.Rounded,
    variant = ButtonVariant.Standard,
    backgroundImage,
    backgroundHoverAnimationSpeed = backgroundImage ? 160 : 3,
    iconOnly = !children && children !== 0,
    aspectSquare = iconOnly && !!icon,
    enableHoverState,
    ...restProps
  } = props;

  const respondsToMouseDown = !!(restProps.onClick || restProps.onMouseDown);

  const isButton = !restProps.href && respondsToMouseDown;

  // Seperate hover state flags for `<button>` and everything else
  const buttonHover = isButton && enableHoverState !== false;
  const nonButtonHover =
    !isButton &&
    !disabled &&
    (enableHoverState === true ||
      (!!restProps.href && enableHoverState !== false));

  const isTranslucentVariant =
    variant === ButtonVariant.ImageGlass ||
    variant === ButtonVariant.Fog ||
    variant === ButtonVariant.Glass ||
    variant === ButtonVariant.LightGlass ||
    variant === ButtonVariant.Smoke;

  const className = twMerge(
    clsx(
      'relative inline-block',
      'bg-transparent text-current',
      'font-sans font-medium text-base/5 leading-none text-center',
      // ::before pseudo-element for border and background fill
      'before:absolute before:inset-0 before:pointer-events-none before:rounded-[inherit]',
      'before:border before:border-transparent before:bg-transparent',
      // ::after pseudo-element for overlay
      'after:absolute after:inset-0 after:pointer-events-none after:rounded-[inherit]',
      'after:bg-transparent after:opacity-0 enabled:hover:after:opacity-100',
      // Transitions
      'transition duration-75',
      'before:transition before:duration-75',
      'after:transition after:duration-75',
      // user selection behavior
      'select-none',
      {
        // Conditional styling
        'cursor-pointer': props.href || respondsToMouseDown,
        'disabled:cursor-not-allowed': isButton && disabled,
        'cursor-not-allowed': !isButton && disabled,
        // Size
        'px-1 py-0.5 text-[12px] leading-[14px]':
          ButtonSize.Micro === size && ButtonShape.Pill !== shape,
        'px-1.5 py-0.5 text-[12px] leading-[14px]':
          ButtonSize.Micro === size && ButtonShape.Pill === shape,
        'px-2 py-0.5 text-sm leading-[24px]':
          ButtonSize.Mini === size && ButtonShape.Pill !== shape,
        'px-3 py-0.5 text-sm leading-[24px]':
          ButtonSize.Mini === size && ButtonShape.Pill === shape,
        'px-4 py-2 text-[15px] leading-[24px]': ButtonSize.Small === size,
        'px-8 py-2.5 text-[17px] leading-[24px]': ButtonSize.Medium === size,
        'px-8 py-4 text-[17px] leading-[24px]': ButtonSize.Large === size,
        // Shape
        'rounded-0': ButtonShape.Rectangle === shape,
        'rounded-sm':
          ButtonShape.Rounded === shape && ButtonSize.Micro === size,
        'rounded-md':
          ButtonShape.Rounded === shape && ButtonSize.Micro !== size,
        'rounded-full': ButtonShape.Pill === shape,
      },
      aspectSquare && {
        'aspect-square': true,
        'p-0.5': ButtonSize.Mini === size || ButtonSize.Micro === size,
        'p-2': ButtonSize.Small === size,
        'p-2.5': ButtonSize.Medium === size,
        'p-4': ButtonSize.Large === size,
      },
      // Variant (color scheme)
      ButtonVariant.Standard === variant && {
        'text-foreground-primary bg-background-tertiary': true,
        'enabled:hover:before:bg-overlay-on-primary': buttonHover,
        'hover:before:bg-overlay-on-primary': nonButtonHover,
        'text-foreground-inactive': active === false,
      },
      ButtonVariant.Primary === variant &&
        active !== false && {
          'text-background-primary bg-foreground-primary': true,
          'enabled:hover:before:bg-overlay-on-light': buttonHover,
          'hover:before:bg-overlay-on-light': nonButtonHover,
        },
      ButtonVariant.Primary === variant &&
        active === false && {
          'text-foreground-primary bg-background-tertiary': true,
          'enabled:hover:before:bg-overlay-on-primary': buttonHover,
          'hover:before:bg-overlay-on-primary': nonButtonHover,
        },
      ButtonVariant.Secondary === variant &&
        active !== true && {
          'text-foreground-primary bg-transparent before:border-border-primary': true,
          'enabled:hover:before:bg-overlay-on-primary': buttonHover,
          'hover:before:bg-overlay-on-primary': nonButtonHover,
        },
      ButtonVariant.Secondary === variant &&
        active === true && {
          'text-background-primary bg-foreground-primary': true,
          'enabled:hover:before:bg-overlay-on-light': buttonHover,
          'hover:before:bg-overlay-on-light': nonButtonHover,
        },
      ButtonVariant.Tertiary === variant && {
        'text-foreground-primary bg-transparent': true,
        'enabled:hover:before:bg-overlay-on-primary': buttonHover,
        'hover:before:bg-overlay-on-primary': nonButtonHover,
        'text-foreground-inactive': active === false,
      },
      ButtonVariant.Inherit === variant && {
        'text-current bg-transparent before:bg-current before:opacity-0': true,
        'enabled:hover:before:opacity-10': buttonHover,
        'hover:before:opacity-10': nonButtonHover,
        'opacity-40': active === false,
        'enabled:hover:before:opacity-25': active === false && buttonHover,
        'hover:before:opacity-25': active === false && nonButtonHover,
      },
      ButtonVariant.Aura === variant && {
        'text-white bg-black': true,
        'bg-cover bg-center': true,
        'bg-(image:--button-background-image)': true,
        'before:bg-cover before:bg-center before:opacity-50': true,
        'before:bg-(image:--button-background-image-overlay)': true,
        'enabled:hover:before:bg-black/25 enabled:hover:before:opacity-45':
          isButton,
        'hover:before:bg-black/25 hover:before:opacity-45': nonButtonHover,
        'text-black/60': active === false,
      },
      ButtonVariant.Aura === variant &&
        backgroundHoverAnimationSpeed && [
          'animate-aura-background animate-stop hover:animate-play',
          'animate-duration-(--button-background-hover-speed)',
        ],
      ButtonVariant.LightPrimary === variant && {
        'text-gray-50 bg-gray-950': true,
        'enabled:hover:before:bg-overlay-on-dark': buttonHover,
        'hover:before:bg-overlay-on-dark': nonButtonHover,
        'text-gray-50/60': active === false,
      },
      ButtonVariant.DarkPrimary === variant && {
        'text-gray-950 bg-gray-50': true,
        'enabled:hover:before:bg-overlay-on-light': buttonHover,
        'hover:before:bg-overlay-on-light': nonButtonHover,
        'text-gray-950/60': active === false,
      },
      ButtonVariant.LightSecondary === variant &&
        active !== true && {
          'text-foreground-primary-on-dark bg-transparent before:border-border-primary-on-dark': true,
          'enabled:hover:before:bg-overlay-on-dark': buttonHover,
          'hover:before:bg-overlay-on-dark': nonButtonHover,
        },
      ButtonVariant.LightSecondary === variant &&
        active === true && {
          'text-foreground-primary-on-light bg-background-primary-light before:border-border-primary-on-dark': true,
          'enabled:hover:before:bg-overlay-on-light': buttonHover,
          'hover:before:bg-overlay-on-light': nonButtonHover,
        },
      ButtonVariant.DarkSecondary === variant &&
        active !== true && {
          'text-foreground-primary-on-light bg-transparent before:border-border-primary-on-light': true,
          'enabled:hover:before:bg-overlay-on-light': buttonHover,
          'hover:before:bg-overlay-on-light': nonButtonHover,
        },
      ButtonVariant.DarkSecondary === variant &&
        active === true && {
          'text-foreground-primary-on-dark bg-background-primary-dark before:border-border-primary-on-light': true,
          'enabled:hover:before:bg-overlay-on-dark': buttonHover,
          'hover:before:bg-overlay-on-dark': nonButtonHover,
        },
      ButtonVariant.ImageGlass === variant &&
        active !== true && {
          'text-foreground-primary-on-dark bg-dumbo-150/50': true,
          'before:border-border-primary-on-dark backdrop-blur-lg': true,
          'enabled:hover:bg-dumbo-150/75': buttonHover,
          'hover:bg-dumbo-150/75': nonButtonHover,
        },
      ButtonVariant.ImageGlass === variant &&
        active === true && {
          'text-foreground-primary-on-light bg-white/70 backdrop-blur-lg': true,
          'enabled:hover:bg-white/90': buttonHover,
          'hover:bg-white/90': nonButtonHover,
        },
      ButtonVariant.Glass === variant &&
        active !== true && {
          'text-gray-950 bg-gray-50/30 backdrop-blur-lg': true,
          'text-gray-950/80': active === false,
          'enabled:hover:bg-gray-50/60': buttonHover,
          'hover:bg-gray-50/60': nonButtonHover,
        },
      ButtonVariant.Glass === variant &&
        active === true && {
          'text-gray-50 bg-white/70 backdrop-blur-lg': true,
          'enabled:hover:bg-white/90': buttonHover,
          'hover:bg-white/90': nonButtonHover,
        },
      ButtonVariant.LightGlass === variant &&
        active !== true && {
          'text-foreground-primary-on-dark bg-white/10 backdrop-blur-lg': true,
          'enabled:hover:bg-white/20': buttonHover,
          'hover:bg-white/20': nonButtonHover,
        },
      ButtonVariant.LightGlass === variant &&
        active === true && {
          'text-foreground-primary-on-light bg-white/70 backdrop-blur-lg': true,
          'enabled:hover:bg-white/90': buttonHover,
          'hover:bg-white/90': nonButtonHover,
        },
      ButtonVariant.Fog === variant &&
        active !== false && {
          'text-foreground-primary-on-dark bg-background-fog-thin': true,
          'before:border-border-primary-on-dark backdrop-blur-md': true,
          'enabled:hover:bg-background-fog-dense': buttonHover,
          'hover:bg-background-fog-dense': nonButtonHover,
        },
      ButtonVariant.Fog === variant &&
        active === false && {
          'text-foreground-primary-on-dark/40 bg-background-fog-thin': true,
          'before:border-border-primary-on-dark backdrop-blur-md': true,
          'enabled:hover:bg-background-fog-dense': buttonHover,
          'hover:bg-background-fog-dense': nonButtonHover,
        },
      ButtonVariant.Smoke === variant &&
        active !== false && {
          'text-foreground-primary-on-light bg-background-smoke-thin': true,
          'before:border-border-primary-on-light backdrop-blur-md': true,
          'enabled:hover:bg-background-smoke-dense': buttonHover,
          'hover:bg-background-smoke-dense': nonButtonHover,
        },
      ButtonVariant.Smoke === variant &&
        active === false && {
          'text-foreground-primary-on-light/40 bg-background-smoke-thin': true,
          'before:border-border-primary-on-light backdrop-blur-md': true,
          'enabled:hover:bg-background-smoke-dense': buttonHover,
          'hover:bg-background-smoke-dense': nonButtonHover,
        },
      // Disabled states
      isButton && {
        'disabled:after:bg-background-primary disabled:after:opacity-50':
          !isTranslucentVariant,
        'disabled:opacity-30': isTranslucentVariant,
      },
      !isButton && {
        'disabled:after:bg-background-primary disabled:after:opacity-50':
          disabled && !isTranslucentVariant,
        'opacity-30': disabled && isTranslucentVariant,
      }
    ),
    explicitClassName
  );

  const iconClassName = twMerge(
    clsx(
      'text-current shrink-0',
      ButtonSize.Micro === size && {
        'w-3 h-3': true,
        'm-0 -ml-0.5': !iconOnly,
        'm-1': iconOnly,
      },
      ButtonSize.Micro !== size && {
        'w-4 h-4': true,
        'm-0 my-1': !iconOnly,
        'm-1': iconOnly,
      }
    ),
    explicitIconClassName
  );

  // The "content" inside the button: text, icons, etc.
  const contentClassName = twMerge(
    clsx('relative flex flex-row items-center justify-center gap-2', {
      'gap-0.5': ButtonSize.Micro === size,
      'gap-1': ButtonSize.Mini === size,
    }),
    explicitContentClassName
  );

  const style = backgroundImage
    ? ({
        '--button-background-image': `url('${backgroundImage}')`,
        '--button-background-hover-speed': `${backgroundHoverAnimationSpeed}s`,
        ...props.style,
      } as React.CSSProperties)
    : props.style;

  const content = (
    <span className={contentClassName}>
      {typeof iconStart === 'function'
        ? React.createElement(iconStart, { className: iconClassName })
        : iconStart}
      {typeof icon === 'function'
        ? React.createElement(icon, { className: iconClassName })
        : icon}
      {children}
      {typeof iconEnd === 'function'
        ? React.createElement(iconEnd, { className: iconClassName })
        : iconEnd}
    </span>
  );

  return restProps.href ? (
    <Link
      {...(restProps as React.ComponentProps<typeof Link>)}
      onClick={disabled ? undefined : restProps.onClick}
      className={className}
      style={style}
      ref={ref as React.Ref<HTMLAnchorElement>}
    >
      {content}
    </Link>
  ) : respondsToMouseDown ? (
    <button
      disabled={disabled}
      type='button'
      {...(restProps as React.ButtonHTMLAttributes<HTMLButtonElement>)}
      className={className}
      style={style}
      ref={ref as React.Ref<HTMLButtonElement>}
    >
      {content}
    </button>
  ) : (
    <div
      {...(restProps as React.HTMLAttributes<HTMLDivElement>)}
      className={className}
      style={style}
      ref={ref as React.Ref<HTMLDivElement>}
    >
      {content}
    </div>
  );
});
Button.displayName = 'Button';

export default Button;
