import { computed, Ref, ref, watch, defineComponent } from 'vue'
import styles from './typeahead.module.css'
import { isEventWithTarget, useState } from '@/utils/vue-tsx-utils'
import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, isNullOrUndefined } from 'camino-common/src/typescript-tools'
import type { JSX } from 'vue/jsx-runtime'
type TypeAheadRecord = Record<string | symbol | number, any>

type Props<T extends TypeAheadRecord, K extends keyof T> = {
  overrideItem: (Pick<T, K> & Partial<Omit<T, K>>) | null
  disabled?: boolean
  props: {
    id: string
    itemKey: NoInfer<K>
    placeholder: string
    items: T[]
    minInputLength: number
    alwaysOpen?: boolean
    itemChipLabel: (key: NoInfer<T>) => string
    displayItemInList?: (item: NoInfer<T>) => JSX.Element
    onSelectItem: (item: NoInfer<T> | undefined) => void
    onInput?: (item: string) => void
    clearAfterSelect?: boolean
  }
}

export const TypeAheadSingle = defineComponent(<T extends TypeAheadRecord, K extends keyof T>(props: Props<T, K>) => {
  const wrapperId = computed(() => `${props.props.id}_wrapper`)
  const getItem = (item: (Pick<T, K> & Partial<Omit<T, K>>) | null): T | null =>
    props.props.items.find(i => {
      return i[props.props.itemKey] === item?.[props.props.itemKey]
    }) ?? null
  const selectedItem = ref<T | null>(getItem(props.overrideItem)) as Ref<T | null>

  const initItem = getItem(props.overrideItem)
  const input = ref<string>(initItem !== null ? props.props.itemChipLabel(initItem) : '')

  watch(
    () => props.overrideItem,
    newItem => {
      selectedItem.value = getItem(newItem)
      const newItemTranslate = getItem(props.overrideItem)

      input.value = isNotNullNorUndefined(newItemTranslate) ? props.props.itemChipLabel(newItemTranslate) : ''
    },
    { deep: true }
  )
  const [isInputFocused, setInputFocused] = useState<boolean>(false)
  const currentSelectionIndex = ref<number>(0)

  const isListVisible = computed<boolean>(() => {
    return props.props.alwaysOpen === true ? true : isInputFocused.value && input.value.length >= props.props.minInputLength && isNotNullNorUndefinedNorEmpty(props.props.items)
  })
  const onInput = (payload: Event) => {
    if (isListVisible.value && currentSelectionIndex.value >= props.props.items.length) {
      currentSelectionIndex.value = (props.props.items.length || 1) - 1
    }
    if (isEventWithTarget(payload)) {
      props.props.onInput?.(payload.target.value)
      input.value = payload.target.value
    }
  }
  const onFocus = () => {
    setInputFocused(true)
  }
  const onBlur = () => {
    setInputFocused(false)

    setTimeout(() => {
      if (isNullOrUndefined(selectedItem.value) || props.props.itemChipLabel(selectedItem.value) !== input.value) {
        deleteSelection(true)
      }
    }, 100)
  }
  const scrollSelectionIntoView = () => {
    setTimeout(() => {
      const listNode = document.querySelector<HTMLElement>(`#${wrapperId.value} .${styles['typeahead-list']}`)
      const activeNode = document.querySelector<HTMLElement>(`#${wrapperId.value} .${styles['typeahead-list-item']}.${styles['typeahead-list-item-active']}`)

      if (listNode && activeNode) {
        if (!(activeNode.offsetTop >= listNode.scrollTop && activeNode.offsetTop + activeNode.offsetHeight < listNode.scrollTop + listNode.offsetHeight)) {
          let scrollTo = 0
          if (activeNode.offsetTop > listNode.scrollTop) {
            scrollTo = activeNode.offsetTop + activeNode.offsetHeight - listNode.offsetHeight
          } else if (activeNode.offsetTop < listNode.scrollTop) {
            scrollTo = activeNode.offsetTop
          }

          listNode.scrollTo(0, scrollTo)
        }
      }
    })
  }

  const onArrowDown = () => {
    if (isListVisible.value && currentSelectionIndex.value < props.props.items.length - 1 - (selectedItem.value !== null ? 1 : 0)) {
      currentSelectionIndex.value++
    }
    scrollSelectionIntoView()
  }
  const onArrowUp = () => {
    if (isListVisible.value && currentSelectionIndex.value > 0) {
      currentSelectionIndex.value--
    }
    scrollSelectionIntoView()
  }
  const deleteSelection = (emptyInput: boolean) => {
    selectedItem.value = null
    props.props.onSelectItem(undefined)

    if (emptyInput) {
      input.value = ''
    }
  }
  const notSelectedItems = computed(() => {
    const selectItemKey = selectedItem.value?.[props.props.itemKey]

    return props.props.items.filter(item => selectItemKey !== item[props.props.itemKey])
  })
  const currentSelection = computed(() => {
    return isListVisible.value && currentSelectionIndex.value < notSelectedItems.value.length ? notSelectedItems.value[currentSelectionIndex.value] : undefined
  })
  const selectItem = (item: T) => {
    input.value = props.props.itemChipLabel(item)

    currentSelectionIndex.value = 0
    document.getElementById(props.props.id)?.focus()

    selectedItem.value = item

    props.props.onSelectItem(item)
    if (props.props.clearAfterSelect === true) {
      deleteSelection(true)
    }

    setInputFocused(false)
  }
  const myTypeaheadInput = ref<HTMLOrSVGElement | null>(null)

  const selectCurrentSelection = (event: KeyboardEvent) => {
    if (currentSelection.value) {
      selectItem(currentSelection.value)
      event.stopPropagation()
    }
  }

  return () => (
    <div id={wrapperId.value} class={[styles.typeahead]}>
      <div class={['flex']}>
        <input
          id={props.props.id}
          ref={myTypeaheadInput}
          value={input.value}
          type="text"
          title={props.props.placeholder}
          name={props.props.id}
          disabled={props.disabled}
          class={'fr-input'}
          placeholder={props.props.placeholder}
          autocomplete="off"
          onInput={onInput}
          onFocus={onFocus}
          onBlur={onBlur}
          role="combobox"
          aria-controls={`${props.props.id}-control`}
          aria-activedescendant={isListVisible.value ? `${props.props.id}-control-${currentSelectionIndex.value}` : `${props.props.id}-control`}
          aria-expanded={isListVisible.value}
          aria-autocomplete="list"
          onKeydown={payload => {
            setInputFocused(true)

            // TODO 2023-06-19 il doit bien y avoir une enum quelque part dans la lib du dom avec la liste des keys non ?
            // Oui --> https://github.com/Moh-Snoussi/keyboard-event-key-type
            // Underlying issue: https://github.com/microsoft/TypeScript/issues/38886
            if (payload.key === 'Backspace') {
              deleteSelection(false)
            }
            if (payload.key === 'ArrowDown') {
              onArrowDown()
              payload.preventDefault()
            }
            if (payload.key === 'ArrowUp') {
              onArrowUp()
              payload.preventDefault()
            }
            if (payload.key === 'Enter') {
              payload.preventDefault()
              payload.stopPropagation()
            }
          }}
          onKeyup={payload => {
            if (payload.key === 'Enter') {
              selectCurrentSelection(payload)
              payload.preventDefault()
              payload.stopPropagation()
            }
          }}
        />
      </div>
      <ul class={`${styles['typeahead-list']} ${isListVisible.value ? styles['typeahead-list--visible'] : ''}`} tabindex="-1" id={`${props.props.id}-control`} role="listbox">
        {notSelectedItems.value.map((item, index) => {
          return (
            <li
              key={index}
              class={`${styles['typeahead-list-item']} ${currentSelectionIndex.value === index ? styles['typeahead-list-item-active'] : ''}`}
              onMousedown={payload => payload.preventDefault()}
              onClick={() => selectItem(item)}
              onMouseenter={() => (currentSelectionIndex.value = index)}
              aria-selected={isListVisible.value && currentSelectionIndex.value === index}
              id={`${props.props.id}-control-${index}`}
            >
              {props.props.displayItemInList ? props.props.displayItemInList(item) : <span>{props.props.itemChipLabel(item)}</span>}
            </li>
          )
        })}
      </ul>
    </div>
  )
})

// @ts-ignore waiting for https://github.com/vuejs/core/issues/7833
TypeAheadSingle.props = ['overrideItem', 'props', 'disabled']
