import React, { useCallback, useEffect, useRef } from "react";

export interface SelectableListProps<T> {
  items: T[];
  selectedValue?: T | null;
  renderLabel: (item: T) => React.ReactNode;
  onSelect: (item: T) => void;
  getChildren?: (item: T) => T[] | undefined;
}

export const SelectableList = <T,>(
  props: SelectableListProps<T>,
): JSX.Element => {
  const { items, selectedValue, renderLabel, onSelect, getChildren } = props;

  const flattenedItems = React.useMemo(() => {
    const flattened: { item: T; depth: number }[] = [];

    const walk = (source: T[], depth: number) => {
      source.forEach((item) => {
        flattened.push({ item, depth });

        const children = getChildren?.(item) ?? [];
        if (children.length > 0) {
          walk(children, depth + 1);
        }
      });
    };

    walk(items, 0);

    return flattened;
  }, [items, getChildren]);

  const flatItems = flattenedItems.map((entry) => entry.item);

  const listRef = useRef<HTMLUListElement | null>(null);
  const isFocusedRef = useRef(false);
  const prevSelectedValueRef = useRef<T | null | undefined>(selectedValue);

  const itemRefs = useRef<(HTMLLIElement | null)[]>([]);

  const handleKeyDown = useCallback(
    (e: React.KeyboardEvent<HTMLUListElement>) => {
      if (!isFocusedRef.current) return;
      if (!flatItems.length) return;

      const currentIndex = selectedValue
        ? flatItems.indexOf(selectedValue)
        : -1;

      if (e.key === "ArrowDown") {
        e.preventDefault();
        const nextIndex =
          currentIndex < flatItems.length - 1 ? currentIndex + 1 : 0;
        onSelect(flatItems[nextIndex]);
      }

      if (e.key === "ArrowUp") {
        e.preventDefault();
        const prevIndex =
          currentIndex > 0 ? currentIndex - 1 : flatItems.length - 1;
        onSelect(flatItems[prevIndex]);
      }
    },
    [flatItems, selectedValue, onSelect],
  );

  useEffect(() => {
    if (!isFocusedRef.current) return;
    if (!selectedValue) return;

    const index = flatItems.indexOf(selectedValue);
    if (index < 0) return;

    const el = itemRefs.current[index];
    if (el) {
      el.focus({ preventScroll: false });
    }
  }, [flatItems, selectedValue]);

  // Separate effect for scrolling - only when selection changes
  useEffect(() => {
    // Only scroll if the selected value actually changed
    if (prevSelectedValueRef.current === selectedValue) return;

    prevSelectedValueRef.current = selectedValue;

    if (!selectedValue) return;

    const index = flatItems.indexOf(selectedValue);
    if (index < 0) return;

    const el = itemRefs.current[index];
    if (el) {
      el.scrollIntoView({ block: "nearest", behavior: "smooth" });
    }
  }, [selectedValue, flatItems]);

  return (
    <ul
      ref={listRef}
      className="selectable-list"
      tabIndex={0}
      role="listbox"
      aria-activedescendant={
        selectedValue ? `option-${flatItems.indexOf(selectedValue)}` : undefined
      }
      onFocus={() => (isFocusedRef.current = true)}
      onBlur={() => (isFocusedRef.current = false)}
      onKeyDown={handleKeyDown}
    >
      {flattenedItems.map(({ item, depth }, index) => {
        const isSelected = selectedValue === item;
        const className = isSelected ? "selected" : "";

        return (
          <li
            key={index}
            id={`option-${index}`}
            ref={(el) => (itemRefs.current[index] = el)}
            tabIndex={isSelected ? 0 : -1}
            role="option"
            aria-selected={isSelected}
            onClick={() => onSelect(item)}
            className={className}
            style={{ paddingLeft: `${depth * 16}px` }}
          >
            {renderLabel(item)}
          </li>
        );
      })}
    </ul>
  );
};
