/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import React, { useEffect, useState, useRef } from 'react';
import { Text, Box } from 'ink';
import { theme } from '../../semantic-colors.js';
import { useKeypress } from '../../hooks/useKeypress.js';

/**
 * Represents a single option for the RadioButtonSelect.
 * Requires a label for display and a value to be returned on selection.
 */
export interface RadioSelectItem<T> {
  label: string;
  value: T;
  disabled?: boolean;
  themeNameDisplay?: string;
  themeTypeDisplay?: string;
}

/**
 * Props for the RadioButtonSelect component.
 * @template T The type of the value associated with each radio item.
 */
export interface RadioButtonSelectProps<T> {
  /** An array of items to display as radio options. */
  items: Array<RadioSelectItem<T>>;
  /** The initial index selected */
  initialIndex?: number;
  /** Function called when an item is selected. Receives the `value` of the selected item. */
  onSelect: (value: T) => void;
  /** Function called when an item is highlighted. Receives the `value` of the selected item. */
  onHighlight?: (value: T) => void;
  /** Whether this select input is currently focused and should respond to input. */
  isFocused?: boolean;
  /** Whether to show the scroll arrows. */
  showScrollArrows?: boolean;
  /** The maximum number of items to show at once. */
  maxItemsToShow?: number;
  /** Whether to show numbers next to items. */
  showNumbers?: boolean;
}

/**
 * A custom component that displays a list of items with radio buttons,
 * supporting scrolling and keyboard navigation.
 *
 * @template T The type of the value associated with each radio item.
 */
export function RadioButtonSelect<T>({
  items,
  initialIndex = 0,
  onSelect,
  onHighlight,
  isFocused = true,
  showScrollArrows = false,
  maxItemsToShow = 10,
  showNumbers = true,
}: RadioButtonSelectProps<T>): React.JSX.Element {
  const [activeIndex, setActiveIndex] = useState(initialIndex);
  const [scrollOffset, setScrollOffset] = useState(0);
  const [numberInput, setNumberInput] = useState('');
  const numberInputTimer = useRef<NodeJS.Timeout | null>(null);

  useEffect(() => {
    const newScrollOffset = Math.max(
      0,
      Math.min(activeIndex - maxItemsToShow + 1, items.length - maxItemsToShow),
    );
    if (activeIndex < scrollOffset) {
      setScrollOffset(activeIndex);
    } else if (activeIndex >= scrollOffset + maxItemsToShow) {
      setScrollOffset(newScrollOffset);
    }
  }, [activeIndex, items.length, scrollOffset, maxItemsToShow]);

  useEffect(
    () => () => {
      if (numberInputTimer.current) {
        clearTimeout(numberInputTimer.current);
      }
    },
    [],
  );

  useKeypress(
    (key) => {
      const { sequence, name } = key;
      const isNumeric = showNumbers && /^[0-9]$/.test(sequence);

      // Any key press that is not a digit should clear the number input buffer.
      if (!isNumeric && numberInputTimer.current) {
        clearTimeout(numberInputTimer.current);
        setNumberInput('');
      }

      if (name === 'k' || name === 'up') {
        const newIndex = activeIndex > 0 ? activeIndex - 1 : items.length - 1;
        setActiveIndex(newIndex);
        onHighlight?.(items[newIndex]!.value);
        return;
      }

      if (name === 'j' || name === 'down') {
        const newIndex = activeIndex < items.length - 1 ? activeIndex + 1 : 0;
        setActiveIndex(newIndex);
        onHighlight?.(items[newIndex]!.value);
        return;
      }

      if (name === 'return') {
        onSelect(items[activeIndex]!.value);
        return;
      }

      // Handle numeric input for selection.
      if (isNumeric) {
        if (numberInputTimer.current) {
          clearTimeout(numberInputTimer.current);
        }

        const newNumberInput = numberInput + sequence;
        setNumberInput(newNumberInput);

        const targetIndex = Number.parseInt(newNumberInput, 10) - 1;

        // A single '0' is not a valid selection since items are 1-indexed.
        if (newNumberInput === '0') {
          numberInputTimer.current = setTimeout(() => setNumberInput(''), 350);
          return;
        }

        if (targetIndex >= 0 && targetIndex < items.length) {
          const targetItem = items[targetIndex]!;
          setActiveIndex(targetIndex);
          onHighlight?.(targetItem.value);

          // If the typed number can't be a prefix for another valid number,
          // select it immediately. Otherwise, wait for more input.
          const potentialNextNumber = Number.parseInt(newNumberInput + '0', 10);
          if (potentialNextNumber > items.length) {
            onSelect(targetItem.value);
            setNumberInput('');
          } else {
            numberInputTimer.current = setTimeout(() => {
              onSelect(targetItem.value);
              setNumberInput('');
            }, 350); // Debounce time for multi-digit input.
          }
        } else {
          // The typed number is out of bounds, clear the buffer
          setNumberInput('');
        }
      }
    },
    { isActive: !!(isFocused && items.length > 0) },
  );

  const visibleItems = items.slice(scrollOffset, scrollOffset + maxItemsToShow);

  return (
    <Box flexDirection="column">
      {showScrollArrows && (
        <Text
          color={scrollOffset > 0 ? theme.text.primary : theme.text.secondary}
        >
          ▲
        </Text>
      )}
      {visibleItems.map((item, index) => {
        const itemIndex = scrollOffset + index;
        const isSelected = activeIndex === itemIndex;

        let textColor = theme.text.primary;
        let numberColor = theme.text.primary;
        if (isSelected) {
          textColor = theme.status.success;
          numberColor = theme.status.success;
        } else if (item.disabled) {
          textColor = theme.text.secondary;
          numberColor = theme.text.secondary;
        }

        if (!showNumbers) {
          numberColor = theme.text.secondary;
        }

        const numberColumnWidth = String(items.length).length;
        const itemNumberText = `${String(itemIndex + 1).padStart(
          numberColumnWidth,
        )}.`;

        return (
          <Box key={item.label} alignItems="center">
            <Box minWidth={2} flexShrink={0}>
              <Text
                color={isSelected ? theme.status.success : theme.text.primary}
              >
                {isSelected ? '●' : ' '}
              </Text>
            </Box>
            <Box
              marginRight={1}
              flexShrink={0}
              minWidth={itemNumberText.length}
            >
              <Text color={numberColor}>{itemNumberText}</Text>
            </Box>
            {item.themeNameDisplay && item.themeTypeDisplay ? (
              <Text color={textColor} wrap="truncate">
                {item.themeNameDisplay}{' '}
                <Text color={theme.text.secondary}>
                  {item.themeTypeDisplay}
                </Text>
              </Text>
            ) : (
              <Text color={textColor} wrap="truncate">
                {item.label}
              </Text>
            )}
          </Box>
        );
      })}
      {showScrollArrows && (
        <Text
          color={
            scrollOffset + maxItemsToShow < items.length
              ? theme.text.primary
              : theme.text.secondary
          }
        >
          ▼
        </Text>
      )}
    </Box>
  );
}
