import * as React from 'react';
import { Star } from 'lucide-react';
import { cn } from '../../shared/utils';

interface RatingProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
  value?: number;
  onChange?: (value: number) => void;
  max?: number;
  /** Prevents interaction but keeps visible. Does not grey-out (use `disabled` for that). */
  readonly?: boolean;
  /** Disables interaction and applies opacity-50 styling. */
  disabled?: boolean;
  size?: 'sm' | 'md' | 'lg';
  showValue?: boolean;
  /** Enables half-star precision (0.5 increments). */
  allowHalf?: boolean;
  /** Custom aria-label generator. Receives the star value and max. */
  getAriaLabel?: (value: number, max: number) => string;
}

/**
 * Star-based rating input or display.
 *
 * @description
 * Allows users to rate items by clicking stars, or displays existing ratings
 * in read-only mode. Supports custom max stars, sizes (sm/md/lg), half-star
 * precision, and value display.
 *
 * @ai-rules
 * 1. Use `readonly={true}` to display an existing rating without allowing interaction.
 * 2. Use `disabled={true}` to show a greyed-out, non-interactive rating.
 * 3. Use `allowHalf={true}` for 0.5-step precision ratings.
 * 4. Use `onChange` to capture the selected rating value in form state.
 */
const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
  (
    {
      className,
      value = 0,
      onChange,
      max = 5,
      readonly = false,
      disabled = false,
      size = 'md',
      showValue = false,
      allowHalf = false,
      getAriaLabel,
      ...props
    },
    ref
  ) => {
    const [hoverValue, setHoverValue] = React.useState<number | null>(null);
    const isInteractive = !readonly && !disabled;

    const sizeStyles = {
      sm: 'h-4 w-4',
      md: 'h-5 w-5',
      lg: 'h-6 w-6',
    };

    const handleClick = (rating: number) => {
      if (isInteractive) onChange?.(rating);
    };

    const handleMouseMove = (e: React.MouseEvent<HTMLButtonElement>, rating: number) => {
      if (!isInteractive) return;
      if (allowHalf) {
        const rect = e.currentTarget.getBoundingClientRect();
        setHoverValue(e.clientX - rect.left < rect.width / 2 ? rating - 0.5 : rating);
      } else {
        setHoverValue(rating);
      }
    };

    const handleMouseLeave = () => {
      if (isInteractive) setHoverValue(null);
    };

    const handleClickWithHalf = (e: React.MouseEvent<HTMLButtonElement>, rating: number) => {
      if (!isInteractive) return;
      if (allowHalf) {
        const rect = e.currentTarget.getBoundingClientRect();
        const half = e.clientX - rect.left < rect.width / 2 ? rating - 0.5 : rating;
        onChange?.(half);
      } else {
        handleClick(rating);
      }
    };

    const displayValue = hoverValue ?? value;

    return (
      <div
        ref={ref}
        className={cn('flex items-center gap-1', disabled && 'opacity-50', className)}
        {...props}
      >
        <div className="flex items-center gap-0.5" role="group" aria-label="Rating">
          {Array.from({ length: max }, (_, index) => {
            const rating = index + 1;
            const isFull = rating <= displayValue;
            const isHalf = allowHalf && !isFull && rating - 0.5 <= displayValue;

            return (
              <button
                key={index}
                type="button"
                onClick={e => handleClickWithHalf(e, rating)}
                onMouseMove={e => handleMouseMove(e, rating)}
                onMouseLeave={handleMouseLeave}
                disabled={readonly || disabled}
                aria-label={getAriaLabel ? getAriaLabel(rating, max) : `${rating} / ${max}`}
                className={cn(
                  'relative transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
                  isInteractive && 'cursor-pointer hover:scale-110',
                  !isInteractive && 'cursor-default'
                )}
              >
                {/* Background (empty) star */}
                <Star className={cn(sizeStyles[size], 'fill-none text-muted-foreground')} />
                {/* Filled overlay — full or half */}
                {(isFull || isHalf) && (
                  <span
                    className={cn('absolute inset-0 overflow-hidden', isHalf ? 'w-1/2' : 'w-full')}
                  >
                    <Star className={cn(sizeStyles[size], 'fill-warning text-warning')} />
                  </span>
                )}
              </button>
            );
          })}
        </div>
        {showValue && (
          <span className="ml-2 text-sm text-muted-foreground">
            {value % 1 === 0 ? value.toFixed(0) : value.toFixed(1)}
          </span>
        )}
      </div>
    );
  }
);
Rating.displayName = 'Rating';

export { Rating };
export type { RatingProps };
