import { isDeepStrictEqual } from "node:util";
import React, { type FC, useState, useEffect, useRef, useCallback } from "react";
import type { PaintKeyboardEvent } from "paintcannon";
import arrayToRotated from "to-rotated";
import Indicator, { type Props as IndicatorProps } from "./select-indicator.tsx";
import ItemComponent, { type Props as ItemProps } from "./select-item.tsx";
import { useKeyboard } from "../../hooks/use-keyboard.ts";
import { TerminalFlex } from "../terminal-flex.tsx";
type Props<V> = {
  /**
   * Items to display in a list. Each item must be an object and have `label` and `value` props, it may also optionally have a `key` prop.
   * If no `key` prop is provided, `value` will be used as the item key.
   */
  readonly items?: Array<Item<V>>;

  /**
   * Listen to user's input. Useful in case there are multiple input components at the same time and input must be "routed" to a specific component.
   *
   * @default true
   */
  readonly isFocused?: boolean;

  /**
   * Index of initially-selected item in `items` array.
   *
   * @default 0
   */
  readonly initialIndex?: number;

  /**
   * Number of items to display.
   */
  readonly limit?: number;

  /**
   * Custom component to override the default indicator component.
   */
  readonly indicatorComponent?: FC<IndicatorProps>;

  /**
   * Custom component to override the default item component.
   */
  readonly itemComponent?: FC<ItemProps>;

  /**
   * Function to call when user selects an item. Item object is passed to that function as an argument.
   */
  readonly onSelect?: (item: Item<V>) => void;

  /**
   * Function to call when user highlights an item. Item object is passed to that function as an argument.
   */
  readonly onHighlight?: (item: Item<V>) => void;

  readonly onKeyDown?: (event: PaintKeyboardEvent) => void;
};
export type Item<V> = {
  key?: string;
  label: string;
  value: V;
};
function SelectInput<V>({
  items = [],
  isFocused = true,
  initialIndex = 0,
  indicatorComponent = Indicator,
  itemComponent = ItemComponent,
  limit: customLimit,
  onSelect,
  onHighlight,
  onKeyDown,
}: Props<V>) {
  const hasLimit = typeof customLimit === "number" && items.length > customLimit;
  const limit = hasLimit ? Math.min(customLimit, items.length) : items.length;
  const lastIndex = limit - 1;
  const [rotateIndex, setRotateIndex] = useState(
    initialIndex > lastIndex ? lastIndex - initialIndex : 0,
  );
  const [selectedIndex, setSelectedIndex] = useState(
    initialIndex ? (initialIndex > lastIndex ? lastIndex : initialIndex) : 0,
  );
  const previousItems = useRef<Array<Item<V>>>(items);
  useEffect(() => {
    if (
      !isDeepStrictEqual(
        previousItems.current.map(item => item.value),
        items.map(item => item.value),
      )
    ) {
      setRotateIndex(0);
      setSelectedIndex(0);
    }
    previousItems.current = items;
  }, [items]);
  useKeyboard(
    useCallback(
      event => {
        onKeyDown?.(event);
        if (event.defaultPrevented) return;

        if (event.key === "k" || event.key === "ArrowUp") {
          event.preventDefault();
          const lastIndex = (hasLimit ? limit : items.length) - 1;
          const atFirstIndex = selectedIndex === 0;
          const nextIndex = hasLimit ? selectedIndex : lastIndex;
          const nextRotateIndex = atFirstIndex ? rotateIndex + 1 : rotateIndex;
          const nextSelectedIndex = atFirstIndex ? nextIndex : selectedIndex - 1;
          setRotateIndex(nextRotateIndex);
          setSelectedIndex(nextSelectedIndex);
          const slicedItems = hasLimit
            ? arrayToRotated(items, nextRotateIndex).slice(0, limit)
            : items;
          if (typeof onHighlight === "function") {
            onHighlight(slicedItems[nextSelectedIndex]!);
          }
        }
        if (event.key === "j" || event.key === "ArrowDown") {
          event.preventDefault();
          const atLastIndex = selectedIndex === (hasLimit ? limit : items.length) - 1;
          const nextIndex = hasLimit ? selectedIndex : 0;
          const nextRotateIndex = atLastIndex ? rotateIndex - 1 : rotateIndex;
          const nextSelectedIndex = atLastIndex ? nextIndex : selectedIndex + 1;
          setRotateIndex(nextRotateIndex);
          setSelectedIndex(nextSelectedIndex);
          const slicedItems = hasLimit
            ? arrayToRotated(items, nextRotateIndex).slice(0, limit)
            : items;
          if (typeof onHighlight === "function") {
            onHighlight(slicedItems[nextSelectedIndex]!);
          }
        }

        // Enable selection directly from number keys.
        if (/^[1-9]$/.test(event.key)) {
          const targetIndex = Number.parseInt(event.key, 10) - 1;
          const visibleItems = hasLimit
            ? arrayToRotated(items, rotateIndex).slice(0, limit)
            : items;
          if (targetIndex >= 0 && targetIndex < visibleItems.length) {
            event.preventDefault();
            const selectedItem = visibleItems[targetIndex];
            if (selectedItem) {
              onSelect?.(selectedItem);
            }
          }
        }
        if (event.key === "Enter") {
          event.preventDefault();
          const slicedItems = hasLimit ? arrayToRotated(items, rotateIndex).slice(0, limit) : items;
          if (typeof onSelect === "function") {
            onSelect(slicedItems[selectedIndex]!);
          }
        }
      },
      [hasLimit, limit, rotateIndex, selectedIndex, items, onSelect, onHighlight, onKeyDown],
    ),
    isFocused,
  );
  const slicedItems: Item<V>[] = hasLimit
    ? arrayToRotated(items, rotateIndex).slice(0, limit)
    : items;
  return (
    <TerminalFlex
      style={{
        flexDirection: "column",
      }}
    >
      {slicedItems.map((item, index) => {
        const isSelected = index === selectedIndex;
        return (
          <TerminalFlex key={item.key ?? String(item.value)}>
            {React.createElement(indicatorComponent, {
              isSelected,
            })}
            {React.createElement(itemComponent, {
              ...item,
              isSelected,
            })}
          </TerminalFlex>
        );
      })}
    </TerminalFlex>
  );
}
export default SelectInput;
