import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'

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

const ratingVariants = cva('inline-flex items-center gap-1', {
  variants: {
    size: {
      sm: '[--rating-size:1rem]',
      md: '[--rating-size:1.25rem]',
      lg: '[--rating-size:1.5rem]',
    },
  },
  defaultVariants: {
    size: 'md',
  },
})

type RatingPrecision = 1 | 0.5

export interface RatingProps
  extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>,
    VariantProps<typeof ratingVariants> {
  value?: number
  defaultValue?: number
  onValueChange?: (value: number) => void
  max?: number
  precision?: RatingPrecision
  readOnly?: boolean
  disabled?: boolean
  showValue?: boolean
  label?: string
}

function clampRating(value: number, max: number, precision: RatingPrecision) {
  const normalized = Math.max(0, Math.min(max, value))
  return Math.round(normalized / precision) * precision
}

function RatingStar({ className }: { className?: string }) {
  return (
    <svg
      aria-hidden="true"
      viewBox="0 0 20 20"
      className={cn('size-[var(--rating-size)]', className)}
    >
      <path d="M10 1.7l2.3 4.7 5.2.8-3.8 3.7.9 5.2-4.6-2.5-4.6 2.5.9-5.2-3.8-3.7 5.2-.8L10 1.7z" />
    </svg>
  )
}

const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
  (
    {
      className,
      size,
      value,
      defaultValue = 0,
      onValueChange,
      max = 5,
      precision = 1,
      readOnly = false,
      disabled = false,
      showValue = false,
      label = 'Rating',
      ...props
    },
    ref
  ) => {
    const [internalValue, setInternalValue] = React.useState(defaultValue)
    const currentValue = clampRating(value ?? internalValue, max, precision)
    const interactive = !readOnly && !disabled

    const commitValue = (nextValue: number) => {
      const next = clampRating(nextValue, max, precision)
      if (value == null) setInternalValue(next)
      onValueChange?.(next)
    }

    return (
      <div
        ref={ref}
        role={interactive ? 'radiogroup' : 'img'}
        aria-label={label}
        aria-disabled={disabled || undefined}
        aria-readonly={readOnly || undefined}
        aria-valuenow={!interactive ? currentValue : undefined}
        aria-valuemax={!interactive ? max : undefined}
        data-slot="rating"
        data-disabled={disabled || undefined}
        className={cn(
          ratingVariants({ size }),
          disabled && 'cursor-not-allowed opacity-50',
          className
        )}
        {...props}
      >
        {Array.from({ length: max }, (_, index) => {
          const starValue = index + 1
          const fillRatio = Math.max(0, Math.min(1, currentValue - index))
          const halfValue = starValue - 0.5
          return (
            <span
              key={starValue}
              className="relative inline-flex size-[var(--rating-size)] shrink-0 text-border-tertiary"
            >
              <RatingStar className="fill-current" />
              <span
                aria-hidden="true"
                className="absolute inset-0 overflow-hidden text-warning"
                style={{ width: `${fillRatio * 100}%` }}
              >
                <RatingStar className="fill-current" />
              </span>
              {interactive ? (
                precision === 0.5 ? (
                  <span className="absolute inset-0 grid grid-cols-2">
                    <button
                      type="button"
                      role="radio"
                      aria-label={`${halfValue} of ${max}`}
                      aria-checked={currentValue === halfValue}
                      className="cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-border"
                      onClick={() => commitValue(halfValue)}
                    />
                    <button
                      type="button"
                      role="radio"
                      aria-label={`${starValue} of ${max}`}
                      aria-checked={currentValue === starValue}
                      className="cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-border"
                      onClick={() => commitValue(starValue)}
                    />
                  </span>
                ) : (
                  <button
                    type="button"
                    role="radio"
                    aria-label={`${starValue} of ${max}`}
                    aria-checked={currentValue === starValue}
                    className="absolute inset-0 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-border"
                    onClick={() => commitValue(starValue)}
                  />
                )
              ) : null}
            </span>
          )
        })}
        {showValue ? (
          <span className="ml-1 text-sm font-medium text-text-secondary">
            {currentValue.toFixed(precision === 0.5 && currentValue % 1 ? 1 : 0)}
          </span>
        ) : null}
      </div>
    )
  }
)
Rating.displayName = 'Rating'

export { Rating, ratingVariants }
export type { RatingPrecision }
