<template>
  <ul class="options-list" data-testid="select-options" ref="listElement">
    <li
      v-for="(option, index) in displayedOptions"
      :key="index"
      :data-testid="option.value"
      :class="{
        'datalist-option': true,
        selected: index === currentSelectedItemIndex,
        small,
      }"
      @mousedown.prevent
      @click.stop="onClick(option)"
    >
      <slot name="option" :option="option">
        {{ option.label === null ? 'N/A' : option.label }}
      </slot>
    </li>
    <li
      v-if="
        $slots['search-hint'] &&
        displayedOptions.length === 0 &&
        !displayNewOption
      "
      :class="['datalist-option', { small }]"
    >
      <slot name="search-hint" />
    </li>
    <li
      v-if="displayNewOption"
      data-testid="add-new-option"
      @click.stop="$emits('addNewOption', newOption)"
      :class="{
        'datalist-option': true,
        selected: currentSelectedItemIndex === options.length,
        small,
      }"
    >
      <slot name="new-option" :newOption="newOption">
        {{ newOption }} (nouvelle option)
      </slot>
    </li>
  </ul>
</template>

<script setup lang="ts">
import { InputOption } from '@/models';
import {
  computed,
  nextTick,
  onMounted,
  onUnmounted,
  ref,
  useTemplateRef,
  watch,
} from 'vue';

export interface Props {
  options: InputOption[];
  isInputFocused: boolean;
  canAddNewOption?: boolean;
  newOption?: string;
  small?: boolean;
  size?: number;
  selectedValue?: any;
}

const props = withDefaults(defineProps<Props>(), {
  canAddNewOption: false,
  newOption: '',
  small: false,
  size: 20,
});

const currentSelectedItemIndex = ref<number>(-1);
const listElement = useTemplateRef('listElement');

const $emits = defineEmits<{
  select: [option: any];
  addNewOption: [option: string];
  blur: [];
}>();

const keyDownEventListener = (ev: KeyboardEvent) => {
  if (props.isInputFocused) {
    switch (ev.key) {
      case 'ArrowDown':
        ev.preventDefault();
        return keyDown();
      case 'ArrowUp':
        ev.preventDefault();
        return keyUp();
      case 'Enter':
        ev.preventDefault();
        return keyEnter();
      case 'Escape':
        ev.preventDefault();
        return keyEscape();
      default:
        return;
    }
  }
};

onMounted(() => {
  document.addEventListener('keydown', keyDownEventListener);
  listElement.value?.parentElement?.addEventListener('scroll', onScroll);
});

onUnmounted(() => {
  document.removeEventListener('keydown', keyDownEventListener);
});

const currentSize = ref(props.size);
const displayedOptions = computed(() =>
  props.options.slice(0, currentSize.value),
);

const optionsLength = computed(() => {
  if (props.canAddNewOption) return props.options.length;
  return props.options.length - 1;
});

const keyDown = () => {
  if (currentSelectedItemIndex.value < optionsLength.value) {
    currentSelectedItemIndex.value++;
  } else {
    currentSelectedItemIndex.value = 0;
  }
};

const keyUp = () => {
  if (currentSelectedItemIndex.value > 0) {
    currentSelectedItemIndex.value--;
  } else {
    currentSelectedItemIndex.value = props.options.length - 1;
  }
};

const keyEnter = () => {
  if (currentSelectedItemIndex.value >= 0) {
    if (currentSelectedItemIndex.value === props.options.length) {
      $emits('addNewOption', props.newOption);
    } else {
      $emits('select', props.options[currentSelectedItemIndex.value]);
    }
    currentSelectedItemIndex.value = -1;
  }
};

const keyEscape = () => {
  $emits('blur');
};

const onClick = (option: { label: string; value: any }) => {
  $emits('select', option);
};

const displayNewOption = computed(
  () =>
    !!props.canAddNewOption &&
    !!props.newOption &&
    !props.options.find((o) => o.value === props.newOption),
);

const onScroll = () => {
  const scrollTop = listElement.value?.parentElement?.scrollTop;
  const listHeight = listElement.value?.clientHeight;
  const parentHeight = listElement.value?.parentElement?.clientHeight;
  if (
    scrollTop &&
    listHeight &&
    parentHeight &&
    scrollTop > listHeight - (parentHeight + 50)
  ) {
    currentSize.value += props.size;
  }
};

watch(currentSelectedItemIndex, () => {
  listElement.value
    ?.querySelector(
      `[data-testid="${props.options[currentSelectedItemIndex.value]?.value}"]`,
    )
    ?.scrollIntoView?.({ block: 'nearest' });

  if (currentSelectedItemIndex.value > currentSize.value * 0.8) {
    currentSize.value += props.size;
  }
});

watch(
  () => props.isInputFocused,
  async (focused) => {
    if (
      focused &&
      props.selectedValue !== undefined &&
      props.selectedValue !== null &&
      props.selectedValue !== ''
    ) {
      const targetValue = Array.isArray(props.selectedValue)
        ? props.selectedValue[0]
        : props.selectedValue;
      const idx = props.options.findIndex((o) => o.value === targetValue);
      if (idx >= 0) {
        await nextTick();
        currentSelectedItemIndex.value = idx;
      }
    } else if (!focused) {
      currentSelectedItemIndex.value = -1;
    }
  },
);
</script>

<style lang="scss" scoped>
ul {
  border-style: none;
  border-radius: 0 0 4px 4px;
  text-decoration: none;
  list-style-type: none;
  padding: 0;
  margin: 0;
  --field-padding: 0.4em;
  --field-height: 4rem;

  li:first-of-type {
    border-top: 1px solid var(--bms-border-color);
  }

  li:first-child {
    border-top-left-radius: var(--bms-border-radius);
    border-top-right-radius: var(--bms-border-radius);
  }
  li:last-child {
    border-bottom-left-radius: var(--bms-border-radius);
    border-bottom-right-radius: var(--bms-border-radius);
  }

  li {
    display: flex;
    align-items: center;
    cursor: pointer;
    padding: var(--field-padding);
    border: 1px solid var(--bms-border-color);
    border-top: none;
    background-color: var(--bms-white);
    min-height: var(--field-height);
    word-break: break-word;
    line-break: anywhere;

    &.small {
      --field-padding: 0.2em 0.4em;
      --field-height: 2rem;
    }

    &:hover,
    &.selected {
      background-color: light-dark(var(--bms-grey-10), var(--bms-grey-100));
    }
  }
}
</style>
