import { type Accessor, createComputed, createSignal, createUniqueId, on } from "solid-js";

/** Options for createListNavigation. */
export interface CreateListNavigationOptions {
  /** Ordered list of valid item values. Reactive -- can change (e.g., filtering). */
  items: Accessor<string[]>;
  /** "selection" for Listbox/Select/Combobox. "activation" for Menu. */
  mode: "selection" | "activation";
  /** @default "vertical" */
  orientation?: "vertical" | "horizontal" | undefined;
  /** Wrap at list boundaries. @default true */
  loop?: boolean | undefined;
  /** Controlled selected value (single selection mode). */
  value?: Accessor<string | undefined> | undefined;
  /** Initial uncontrolled value. */
  defaultValue?: string | undefined;
  /** Called when selection changes (single). */
  onValueChange?: ((value: string) => void) | undefined;
  /** Allow multiple selection. @default false */
  multiple?: boolean | undefined;
  /** For multiple mode: controlled values. */
  values?: Accessor<string[]> | undefined;
  /** For multiple mode: initial uncontrolled values. */
  defaultValues?: string[] | undefined;
  /** For multiple mode: called when selection changes. */
  onValuesChange?: ((values: string[]) => void) | undefined;
  /** Called when an item is activated (activation mode). */
  onActivate?: ((value: string) => void) | undefined;
  /** Enable typeahead. @default true */
  typeahead?: boolean | undefined;
  /** Return the display label for a value. Used for typeahead matching. */
  getLabel?: ((value: string) => string) | undefined;
  /** Base ID for generating item IDs. Uses createUniqueId() if not provided. */
  baseId?: string | undefined;
}

/** State returned by createListNavigation. */
export interface ListNavigationState {
  /** Currently highlighted item value (virtual focus). */
  highlightedValue: Accessor<string | undefined>;
  /** Currently selected value (selection mode, single). */
  selectedValue: Accessor<string | undefined>;
  /** Currently selected values (selection mode, multiple). */
  selectedValues: Accessor<string[]>;
  /** Props to spread on the list container. */
  containerProps: {
    role: "listbox" | "menu";
    "aria-orientation": "horizontal" | "vertical";
    "aria-activedescendant": string | undefined;
    onKeyDown: (e: KeyboardEvent) => void;
    onPointerLeave: () => void;
  };
  /** Get props for a specific item by value. */
  getItemProps: (value: string) => {
    id: string;
    role: "option" | "menuitem";
    "aria-selected": "true" | "false" | undefined;
    "data-highlighted": "" | undefined;
    "data-value": string;
    onPointerEnter: () => void;
    onPointerMove: () => void;
    onClick: () => void;
  };
  /** Imperatively set highlighted value. */
  highlight: (value: string | undefined) => void;
  /** Highlight the first item. */
  highlightFirst: () => void;
  /** Highlight the last item. */
  highlightLast: () => void;
  /** Clear highlight. */
  clearHighlight: () => void;
}

/** Default label resolver that returns the value as-is. */
function defaultGetLabel(v: string): string {
  return v;
}

/**
 * Value-based keyboard navigation, selection/activation, and typeahead for list-like components.
 * Uses aria-activedescendant (virtual focus) so the container/input retains DOM focus.
 */
export function createListNavigation(options: CreateListNavigationOptions): ListNavigationState {
  const baseId = options.baseId ?? createUniqueId();
  const orientation: "horizontal" | "vertical" = options.orientation ?? "vertical";
  const loop = options.loop ?? true;
  const isTypeaheadEnabled = options.typeahead ?? true;
  const getLabel = options.getLabel ?? defaultGetLabel;

  const [highlighted, setHighlighted] = createSignal<string | undefined>(undefined);

  const [internalValue, setInternalValue] = createSignal<string | undefined>(options.defaultValue);
  const resolveValue = (): string | undefined => {
    if (options.value) return options.value();
    return internalValue();
  };

  const [internalValues, setInternalValues] = createSignal<string[]>(options.defaultValues ?? []);
  const resolveValues = (): string[] => {
    if (options.values) return options.values();
    return internalValues();
  };

  createComputed(
    on(
      () => options.items(),
      (items) => {
        const h = highlighted();
        if (h !== undefined && !items.includes(h)) {
          setHighlighted(undefined);
        }
      },
    ),
  );

  let typeaheadBuffer = "";
  let typeaheadTimer: ReturnType<typeof setTimeout> | undefined;

  function handleTypeahead(key: string): void {
    if (!isTypeaheadEnabled || key.length !== 1) return;
    clearTimeout(typeaheadTimer);
    typeaheadBuffer += key;
    typeaheadTimer = setTimeout(() => {
      typeaheadBuffer = "";
    }, 500);

    const items = options.items();
    const search = typeaheadBuffer.toLowerCase();
    const match = items.find((v) => getLabel(v).toLowerCase().startsWith(search));
    if (match) setHighlighted(match);
  }

  function getNextValue(current: string | undefined, direction: 1 | -1): string | undefined {
    const items = options.items();
    if (items.length === 0) return undefined;
    if (current === undefined) {
      return direction === 1 ? items[0] : items[items.length - 1];
    }

    const index = items.indexOf(current);
    if (index === -1) return items[0];

    const nextIndex = index + direction;
    if (loop) {
      return items[(nextIndex + items.length) % items.length];
    }
    if (nextIndex < 0 || nextIndex >= items.length) return current;
    return items[nextIndex];
  }

  function selectItem(value: string): void {
    if (options.mode === "activation") {
      options.onActivate?.(value);
      return;
    }
    if (options.multiple) {
      const current = resolveValues();
      const next = current.includes(value)
        ? current.filter((v) => v !== value)
        : [...current, value];
      setInternalValues(next);
      options.onValuesChange?.(next);
    } else {
      setInternalValue(value);
      options.onValueChange?.(value);
    }
  }

  const nextKey = orientation === "horizontal" ? "ArrowRight" : "ArrowDown";
  const prevKey = orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";

  function handleKeyDown(e: KeyboardEvent): void {
    switch (e.key) {
      case nextKey: {
        e.preventDefault();
        setHighlighted(getNextValue(highlighted(), 1));
        break;
      }
      case prevKey: {
        e.preventDefault();
        setHighlighted(getNextValue(highlighted(), -1));
        break;
      }
      case "Home": {
        e.preventDefault();
        const items = options.items();
        if (items.length > 0) setHighlighted(items[0]);
        break;
      }
      case "End": {
        e.preventDefault();
        const items = options.items();
        if (items.length > 0) setHighlighted(items[items.length - 1]);
        break;
      }
      case "Enter":
      case " ": {
        e.preventDefault();
        const h = highlighted();
        if (h !== undefined) selectItem(h);
        break;
      }
      case "Escape": {
        setHighlighted(undefined);
        break;
      }
      default: {
        if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
          handleTypeahead(e.key);
        }
      }
    }
  }

  const itemRole: "option" | "menuitem" = options.mode === "selection" ? "option" : "menuitem";

  const containerProps = {
    get role(): "listbox" | "menu" {
      return options.mode === "selection" ? "listbox" : "menu";
    },
    get "aria-orientation"(): "horizontal" | "vertical" {
      return orientation;
    },
    get "aria-activedescendant"() {
      const h = highlighted();
      return h !== undefined ? `${baseId}-${itemRole}-${h}` : undefined;
    },
    onKeyDown: handleKeyDown,
    onPointerLeave: () => setHighlighted(undefined),
  };

  /** Get props for a specific item by value. */
  function getItemProps(value: string) {
    return {
      get id() {
        return `${baseId}-${itemRole}-${value}`;
      },
      role: itemRole,
      get "aria-selected"(): "true" | "false" | undefined {
        if (options.mode !== "selection") return undefined;
        if (options.multiple) {
          return resolveValues().includes(value) ? "true" : "false";
        }
        return resolveValue() === value ? "true" : "false";
      },
      get "data-highlighted"() {
        return highlighted() === value ? ("" as const) : undefined;
      },
      get "data-value"() {
        return value;
      },
      onPointerEnter: () => { setHighlighted(value); },
      onPointerMove: () => {
        if (highlighted() !== value) setHighlighted(value);
      },
      onClick: () => {
        setHighlighted(value);
        selectItem(value);
      },
    };
  }

  return {
    highlightedValue: highlighted,
    selectedValue: () => (options.mode === "selection" ? resolveValue() : undefined),
    selectedValues: () => (options.mode === "selection" ? resolveValues() : []),
    containerProps,
    getItemProps,
    highlight: setHighlighted,
    highlightFirst: () => {
      const items = options.items();
      if (items.length > 0) setHighlighted(items[0]);
    },
    highlightLast: () => {
      const items = options.items();
      if (items.length > 0) setHighlighted(items[items.length - 1]);
    },
    clearHighlight: () => setHighlighted(undefined),
  };
}
