import { Loader2 } from 'lucide-react'
import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'

import { cn } from '@/lib/cn'

export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger' | 'link'
export type ButtonSize = 'sm' | 'md' | 'lg'

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: ButtonVariant
  size?: ButtonSize
  shape?: ButtonShape
  isLoading?: boolean
  leadingIcon?: ReactNode
  trailingIcon?: ReactNode
  fullWidth?: boolean
}

/*
 * Every variant is a flat, solid fill. There is no gradient anywhere by design:
 * the product brief rules them out, and a solid accent on a white surface is
 * what makes a primary action read as deliberate rather than decorative.
 */
const VARIANTS: Record<ButtonVariant, string> = {
  primary:
    'bg-accent text-white hover:bg-accent-hover active:bg-accent-active shadow-xs disabled:bg-accent/50',
  secondary:
    'bg-surface text-text-primary border border-border-subtle hover:bg-surface-hover active:bg-surface-active shadow-xs',
  ghost: 'bg-transparent text-text-secondary hover:bg-surface-hover hover:text-text-primary',
  danger: 'bg-critical text-white hover:brightness-95 active:brightness-90 shadow-xs',
  link: 'bg-transparent text-text-accent underline decoration-border-strong underline-offset-4 hover:decoration-current',
}

/*
 * Height, padding and radius per size. Radius lives here rather than in the
 * base classes so `shape` can replace it outright -- `cn` joins classes without
 * resolving Tailwind conflicts, so two competing `rounded-*` utilities would
 * leave the winner up to stylesheet order.
 */
const SIZES: Record<ButtonSize, string> = {
  // Touch targets stay at least 36-44px tall; the marketplace is browsed on phones.
  sm: 'h-9 px-3 text-sm gap-1.5',
  md: 'h-10 px-4 text-sm gap-2',
  lg: 'h-12 px-6 text-base gap-2',
}

const RADII: Record<ButtonSize, string> = {
  sm: 'rounded-md',
  md: 'rounded-md',
  lg: 'rounded-lg',
}

/**
 * `pill` is for buttons sitting inside a fully rounded container, where the
 * standard radius reads as a mismatch rather than as a choice. It is a shape,
 * not a variant: the fill, states and sizing are unchanged.
 */
export type ButtonShape = 'default' | 'pill'

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
  {
    variant = 'primary',
    size = 'md',
    shape = 'default',
    isLoading = false,
    leadingIcon,
    trailingIcon,
    fullWidth = false,
    className,
    children,
    disabled,
    type = 'button',
    ...props
  },
  ref,
) {
  return (
    <button
      ref={ref}
      type={type}
      // A loading button must not be pressable twice: a double-submitted offer
      // is a real problem, not a cosmetic one.
      disabled={disabled === true || isLoading}
      aria-busy={isLoading || undefined}
      className={cn(
        'inline-flex items-center justify-center font-medium whitespace-nowrap',
        'transition-colors duration-150',
        'disabled:cursor-not-allowed disabled:opacity-60',
        VARIANTS[variant],
        SIZES[size],
        shape === 'pill' ? 'rounded-full' : RADII[size],
        fullWidth && 'w-full',
        className,
      )}
      {...props}
    >
      {isLoading ? (
        <Loader2 aria-hidden className="size-4 animate-spin" />
      ) : (
        leadingIcon
      )}
      {children}
      {!isLoading && trailingIcon}
    </button>
  )
})
