import { computed } from 'vue';
import type { ListboxOption } from './MOptionListbox.types';

type SelectionValue = string | number | null | (string | number)[];

type Props = {
  modelValue: SelectionValue;
  options: ListboxOption[];
  multiple?: boolean;
  readonly?: boolean;
  checkableSections?: boolean;
};

export function useListboxSelection(
  props: Props,
  emit: (on: 'update:modelValue', value: SelectionValue) => void,
) {
  const selection = computed<SelectionValue>({
    get() {
      return props.modelValue;
    },
    set(value) {
      emit('update:modelValue', value);
    },
  });

  const sectionMap = computed(() => {
    const map = new Map<string, ListboxOption[]>();
    let currentSection: ListboxOption | null = null;

    props.options.forEach((option) => {
      if (option.type === 'section') {
        currentSection = option;
        map.set(currentSection?.value?.toString() || currentSection.label, []);
      } else if (currentSection) {
        map
          .get(currentSection?.value?.toString() || currentSection.label)!
          .push(option);
      }
    });

    return map;
  });

  function isSelectable(item: ListboxOption): boolean {
    return !!(
      (item.type !== 'section' && !item.disabled) ||
      (item.type === 'section' && props.checkableSections && props.multiple)
    );
  }

  function isOptionSelected(item: ListboxOption) {
    if (item.value === undefined || item.value === null) return false;

    if (Array.isArray(selection.value)) {
      return (selection.value as (string | number)[]).includes(item.value);
    }

    return item.value === selection.value;
  }

  function isSectionSelected(item: ListboxOption) {
    if (!props.checkableSections || !props.multiple) return false;

    const sectionItems =
      sectionMap.value.get(item.value?.toString() || item.label) || [];
    const selectedItems = Array.isArray(selection.value)
      ? (selection.value as (string | number)[])
      : [];

    return sectionItems.every((opt) => selectedItems.includes(opt.value!));
  }

  function isIndeterminate(item: ListboxOption) {
    const section = sectionMap.value.get(item.value?.toString() || item.label);
    return (
      section?.some((option) => isOptionSelected(option)) &&
      !section?.every((option) => isOptionSelected(option))
    );
  }

  function toggleValue(item?: ListboxOption) {
    if (!item || !isSelectable(item)) return;

    if (Array.isArray(selection.value)) {
      if (isOptionSelected(item)) {
        selection.value = (selection.value as (string | number)[]).filter(
          (el) => el !== item.value,
        );
      } else {
        selection.value = [...selection.value, item.value!];
      }
    } else {
      selection.value = isOptionSelected(item) ? null : item.value!;
    }
  }

  function toggleSection(item: ListboxOption) {
    if (!props.checkableSections || !props.multiple) return;

    const sectionItems =
      sectionMap.value.get(item.value?.toString() || item.label) || [];
    const selectedItems = Array.isArray(selection.value)
      ? (selection.value as (string | number)[])
      : [];

    if (isSectionSelected(item)) {
      selection.value = selectedItems.filter(
        (opt) => !sectionItems.find((i) => i.value === opt),
      );
    } else {
      selection.value = [
        ...selectedItems,
        ...sectionItems
          .filter((opt) => !selectedItems.includes(opt.value!))
          .map((i) => i.value!),
      ];
    }
  }

  function selectAll() {
    selection.value = props.options
      .filter(
        (option) =>
          option.value !== undefined &&
          option.value !== null &&
          !option.disabled &&
          option.type !== 'section',
      )
      .map((item) => item.value!);
  }

  function clearSelection() {
    selection.value = Array.isArray(selection.value) ? [] : null;
  }

  return {
    selection,
    sectionMap,
    isSelectable,
    isOptionSelected,
    isSectionSelected,
    isIndeterminate,
    toggleValue,
    toggleSection,
    selectAll,
    clearSelection,
  };
}
