// @design-system: primitives/StarRating
'use client';

import { useEffect, useState, useSyncExternalStore } from 'react';
import { Star } from 'lucide-react';
import { cn } from '@/lib/cn';
import { useT, useLocale } from '@/lib/i18n/react';
import { formatInteger } from '@/lib/format';

export interface StarRatingProps {
  /** Rating value between 0 and 5. Renders partial fill rounded to nearest half. */
  value: number;
  /** Number of reviews — rendered muted in parentheses if provided. Ignored in picker mode. */
  count?: number;
  /** Visual size of stars + text. Default: 'sm'. */
  size?: 'sm' | 'md';
  /**
   * When provided, renders an interactive star picker (radiogroup).
   * Calls onChange with the selected 1-5 integer value.
   * In picker mode the numeric readout and count are suppressed.
   */
  onChange?: (value: number) => void;
  className?: string;
}

const TOTAL = 5;

function subscribeReducedMotion(callback: () => void): () => void {
  if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return () => {};
  const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
  mediaQuery.addEventListener?.('change', callback);
  return () => mediaQuery.removeEventListener?.('change', callback);
}

function getReducedMotionSnapshot(): boolean {
  return typeof window !== 'undefined' && typeof window.matchMedia === 'function'
    ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
    : false;
}

// Internal sub-component that owns hover state — only mounted in picker mode
function StarRatingPicker({
  value,
  size = 'sm',
  onChange,
  className,
}: {
  value: number;
  size?: 'sm' | 'md';
  onChange: (value: number) => void;
  className?: string;
}) {
  const t = useT('star_rating');
  const [hovered, setHovered] = useState(0);

  return (
    <div
      role="radiogroup"
      aria-label={t('picker_group')}
      className={cn('flex items-center', size === 'md' ? 'gap-1' : 'gap-0.5', className)}
    >
      {Array.from({ length: TOTAL }, (_, i) => {
        const starN = i + 1;
        const active = starN <= (hovered || value);
        return (
          <button
            key={starN}
            type="button"
            role="radio"
            aria-checked={starN <= value}
            aria-label={t('picker_star').replace('{{n}}', String(starN))}
            className={cn(
              'focus-visible:ring-brand-primary-500 transition-colors focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none',
              active ? 'text-warning-400' : 'text-neutral-300',
            )}
            onMouseEnter={() => setHovered(starN)}
            onMouseLeave={() => setHovered(0)}
            onClick={() => onChange(starN)}
          >
            <Star
              width={size === 'md' ? 24 : 20}
              height={size === 'md' ? 24 : 20}
              fill="currentColor"
            />
          </button>
        );
      })}
    </div>
  );
}

/** Inline star rating. Read-only by default; pass onChange for interactive picker mode. RTL-safe. */
export function StarRating({ value, count, size = 'sm', onChange, className }: StarRatingProps) {
  const t = useT('star_rating');
  const { locale } = useLocale();
  const reducedMotion = useSyncExternalStore(
    subscribeReducedMotion,
    getReducedMotionSnapshot,
    () => false,
  );
  const [frameReady, setFrameReady] = useState(false);
  const sweepReady = reducedMotion || frameReady;

  const clampedValue = Math.min(TOTAL, Math.max(0, value));
  const rounded = Math.round(clampedValue * 2) / 2;

  useEffect(() => {
    if (typeof window === 'undefined') return;
    if (reducedMotion) return;
    const id = window.requestAnimationFrame(() => setFrameReady(true));
    return () => window.cancelAnimationFrame(id);
  }, [reducedMotion]);

  // ── Picker mode ───────────────────────────────────────────────────────────
  if (onChange) {
    return <StarRatingPicker value={value} size={size} onChange={onChange} className={className} />;
  }

  // ── Display mode (original) ───────────────────────────────────────────────
  const starSizePx = size === 'md' ? 16 : 12;
  const ariaBase = count != null ? t('aria_with_count') : t('aria_no_count');
  const ariaLabel = ariaBase
    .replace('{{value}}', value.toFixed(1))
    .replace('{{count}}', count != null ? formatInteger(count, locale) : '');

  return (
    <span
      className={cn('inline-flex items-center', size === 'md' ? 'gap-1' : 'gap-0.5', className)}
      aria-label={ariaLabel}
      role="img"
    >
      <span className="inline-flex items-center gap-px" aria-hidden>
        {Array.from({ length: TOTAL }, (_, i) => {
          const fill = i + 1 <= rounded ? 'full' : i + 0.5 === rounded ? 'half' : 'empty';
          return (
            <span key={i} className="relative inline-flex shrink-0">
              <Star
                width={starSizePx}
                height={starSizePx}
                className="text-text-muted opacity-30"
                fill="currentColor"
              />
              {fill !== 'empty' && (
                <span
                  className="absolute inset-0 overflow-hidden"
                  data-fx="star-sweep"
                  style={{
                    width: sweepReady ? (fill === 'half' ? '50%' : '100%') : '0%',
                    transitionProperty: 'width',
                    transitionDuration: 'var(--duration-slow)',
                    transitionTimingFunction: 'cubic-bezier(0.22, 1, 0.36, 1)',
                    transitionDelay: `${i * 40}ms`,
                  }}
                >
                  <Star
                    width={starSizePx}
                    height={starSizePx}
                    className="text-warning-500 absolute inset-0"
                    fill="currentColor"
                  />
                </span>
              )}
            </span>
          );
        })}
      </span>
      <span
        className={cn(
          'font-semibold tabular-nums',
          size === 'md' ? 'text-sm' : 'text-xs',
          'text-text-default',
        )}
      >
        {value.toFixed(1)}
      </span>
      {count != null && (
        <span className={cn('text-text-muted', size === 'md' ? 'text-sm' : 'text-xs')}>
          ({formatInteger(count, locale)})
        </span>
      )}
    </span>
  );
}
