import { useRef } from 'react';
import type { KeyboardEvent, ReactNode } from 'react';
import { cn } from '@/lib/utils';

export interface SegmentedOption<T extends string> {
  value: T;
  label: ReactNode;
  title?: string;
}

export interface SegmentedControlProps<T extends string> {
  /** Names the group for assistive tech. Required — a bare radiogroup is noise. */
  label: string;
  options: SegmentedOption<T>[];
  value: T;
  onChange: (value: T) => void;
  className?: string;
  optionClassName?: string;
}

/**
 * The one-of-N control the shell uses for speed and for the run switcher.
 *
 * It exists because a row of buttons with `aria-checked` is not a radiogroup:
 * the pattern also requires a ROVING tabindex, so Tab moves past the whole
 * control rather than through every option, and the arrow keys move within it.
 * Getting that wrong is the most common a11y bug in a segmented control, which
 * is why there is one implementation here instead of three inline copies.
 *
 * Shape (§9): a 6px rail on surface-2 with a 1px border; options 6px, 44px on
 * touch and 36px from lg, the active one surface-3 + fg. No shadow — the
 * ground step is the state.
 */
export function SegmentedControl<T extends string>({
  label,
  options,
  value,
  onChange,
  className,
  optionClassName,
}: SegmentedControlProps<T>) {
  const refs = useRef<(HTMLButtonElement | null)[]>([]);
  const index = Math.max(
    options.findIndex((option) => option.value === value),
    0,
  );

  const move = (to: number, event: KeyboardEvent) => {
    if (options.length === 0) return;
    event.preventDefault();
    // Wraps: a radiogroup's arrows cycle rather than dead-ending.
    const next = (to + options.length) % options.length;
    const option = options[next];
    if (!option) return;
    onChange(option.value);
    refs.current[next]?.focus();
  };

  const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
    switch (event.key) {
      case 'ArrowRight':
      case 'ArrowDown':
        move(index + 1, event);
        break;
      case 'ArrowLeft':
      case 'ArrowUp':
        move(index - 1, event);
        break;
      case 'Home':
        move(0, event);
        break;
      case 'End':
        move(options.length - 1, event);
        break;
      default:
        break;
    }
  };

  return (
    <div
      role="radiogroup"
      aria-label={label}
      onKeyDown={onKeyDown}
      className={cn(
        'inline-flex flex-wrap items-center gap-0.5 rounded-md border border-border bg-surface-2 p-0.5',
        className,
      )}
    >
      {options.map((option, position) => {
        const selected = option.value === value;
        return (
          <button
            key={option.value}
            type="button"
            role="radio"
            aria-checked={selected}
            tabIndex={position === index ? 0 : -1}
            {...(option.title ? { title: option.title } : {})}
            ref={(node) => {
              refs.current[position] = node;
            }}
            onClick={() => onChange(option.value)}
            className={cn(
              // `min-w-11` as well as `min-h-11`: a two-glyph option ("#1",
              // "1×") is 34px wide on padding alone, which is a thumb-sized
              // row with a fingertip-sized hit area. Measured at 390px.
              'min-h-11 min-w-11 rounded-md px-3 text-ui font-medium transition-colors duration-1 ease-enter lg:min-h-9 lg:min-w-9',
              selected ? 'bg-surface-3 text-fg' : 'text-muted hover:text-fg',
              optionClassName,
            )}
          >
            {option.label}
          </button>
        );
      })}
    </div>
  );
}
