import * as React from 'react'
import { Loader2 } from 'lucide-react'
import { cn } from '../lib/cn'
import { useReducedMotion } from '../hooks/use-reduced-motion'

export interface SpinnerProps {
  size?: 'sm' | 'md' | 'lg'
  /**
   * Optional progress percentage (0-100). When prefers-reduced-motion is
   * active, shown as static "{percent}%" text instead of a rotating glyph.
   */
  percent?: number
  /** Accessible label. Shown as static text under reduced motion. Default: "Loading…" */
  label?: string
  className?: string
}

const sizeMap: Record<NonNullable<SpinnerProps['size']>, string> = {
  sm: 'h-4 w-4',
  md: 'h-6 w-6',
  lg: 'h-8 w-8',
}

const textSizeMap: Record<NonNullable<SpinnerProps['size']>, string> = {
  sm: 'text-xs',
  md: 'text-sm',
  lg: 'text-base',
}

/**
 * Rotating spinner with WCAG 2.1 AA reduced-motion support (spec 115).
 *
 * Under prefers-reduced-motion: reduce, a static text fallback is rendered
 * (percentage if `percent` is provided, otherwise the `label`). CSS
 * motion-reduce:animate-none alone is insufficient because the icon continues
 * to exist in the DOM — a static replacement is required.
 *
 * Loading containers must expose aria-busy="true" on the containing element.
 */
export function Spinner({ size = 'md', percent, label = 'Loading…', className }: SpinnerProps) {
  const reducedMotion = useReducedMotion()

  if (reducedMotion) {
    // Static text fallback — no rotating element, only readable text.
    const displayText = percent !== undefined ? `${percent}%` : label
    return (
      <span
        role="status"
        aria-label={displayText}
        className={cn('inline-flex items-center justify-center text-current font-medium', textSizeMap[size], className)}
      >
        <span aria-hidden="true">{displayText}</span>
      </span>
    )
  }

  return (
    <Loader2
      role="status"
      aria-label={label}
      className={cn('animate-spin text-current', sizeMap[size], className)}
    />
  )
}

Spinner.displayName = 'Spinner'
