import { computed, nextTick, ref, watch, type Ref, type ShallowRef } from 'vue';
import type { ListboxOption } from './MOptionListbox.types';

type Props = {
  open?: boolean;
  id: string;
};

type Deps = {
  filteredResults: Ref<ListboxOption[]>;
  optionsList: Readonly<ShallowRef<HTMLElement | null>>;
  isSelectable: (item: ListboxOption) => boolean;
  isOptionSelected: (item: ListboxOption) => boolean;
  toggleValue: (item: ListboxOption) => void;
  toggleSection: (item: ListboxOption) => void;
};

export function useListboxNavigation(
  props: Props,
  emit: (on: 'open' | 'close') => void,
  deps: Deps,
) {
  const activeIndex = ref<number>(-1);

  const activeDescendantId = computed(() =>
    activeIndex.value >= 0
      ? `option-${props.id}-${activeIndex.value}`
      : undefined,
  );

  function moveActive(delta: number) {
    if (!props.open || deps.filteredResults.value.length === 0) return;

    const total = deps.filteredResults.value.length;
    let nextIndex = activeIndex.value;
    for (let i = 0; i < total; i++) {
      nextIndex += delta;
      if (nextIndex < 0) nextIndex = total - 1;
      if (nextIndex >= total) nextIndex = 0;
      if (deps.isSelectable(deps.filteredResults.value[nextIndex])) {
        activeIndex.value = nextIndex;
        return;
      }
    }
  }

  function getFirstSelectableIndex() {
    return deps.filteredResults.value.findIndex((item) =>
      deps.isSelectable(item),
    );
  }

  function getLastSelectableIndex() {
    for (let i = deps.filteredResults.value.length - 1; i >= 0; i--) {
      if (deps.isSelectable(deps.filteredResults.value[i])) return i;
    }
    return -1;
  }

  function getSelectedOptionIndex() {
    return deps.filteredResults.value.findIndex(
      (item) =>
        item.type !== 'section' &&
        deps.isSelectable(item) &&
        deps.isOptionSelected(item),
    );
  }

  function getInitialActiveIndex(direction: 'forward' | 'backward') {
    const selectedIndex = getSelectedOptionIndex();

    if (selectedIndex >= 0) return selectedIndex;

    return direction === 'backward'
      ? getLastSelectableIndex()
      : getFirstSelectableIndex();
  }

  function selectActive() {
    const item = deps.filteredResults.value[activeIndex.value];
    if (!item || !deps.isSelectable(item)) return;

    if (item.type === 'section') {
      deps.toggleSection(item);
    } else {
      deps.toggleValue(item);
    }
  }

  function scrollActiveOptionIntoView() {
    if (!props.open || activeIndex.value < 0) return;

    const activeOption = deps.optionsList.value?.querySelector<HTMLElement>(
      `#option-${props.id}-${activeIndex.value}`,
    );

    activeOption?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
  }

  function handleKeydown(event: KeyboardEvent) {
    switch (event.key) {
      case 'ArrowDown':
        event.preventDefault();
        if (!props.open) {
          emit('open');
          activeIndex.value = getInitialActiveIndex('forward');
        } else {
          moveActive(1);
        }
        break;
      case 'ArrowUp':
        event.preventDefault();
        if (!props.open) {
          emit('open');
          activeIndex.value = getInitialActiveIndex('backward');
        } else {
          moveActive(-1);
        }
        break;
      case 'Enter':
        event.preventDefault();
        if (!props.open) {
          emit('open');
          activeIndex.value = getInitialActiveIndex('forward');
        } else {
          selectActive();
        }
        break;
      case 'Escape':
        event.preventDefault();
        emit('close');
        break;
    }
  }

  watch([activeIndex, () => props.open], async ([index, isOpen]) => {
    if (!isOpen || index < 0) return;
    await nextTick();
    scrollActiveOptionIntoView();
  });

  return {
    activeIndex,
    activeDescendantId,
    handleKeydown,
  };
}
