import { defineComponent, h, PropType } from 'vue'

import { CElementCover } from '../element-cover'
import { CVirtualScroller } from '../virtual-scroller'

import { getNextSibling, getPreviousSibling } from '../../utils'
import type { Option, OptionsGroup } from './types'

const CMultiSelectOptions = defineComponent({
  name: 'CMultiSelectOptions',
  props: {
    loading: Boolean,
    options: {
      type: Array as PropType<(Option | OptionsGroup)[]>,
      default: () => [],
    },
    optionsGroupsSelectable: Boolean,
    optionsGroupsStyle: {
      type: String,
      default: 'checkbox',
      validator: (value: string) => {
        return ['checkbox', 'text'].includes(value)
      },
    },
    optionsMaxHeight: {
      type: [Number, String],
      default: 'auto',
    },
    optionsStyle: {
      type: String,
      default: 'checkbox',
      validator: (value: string) => {
        return ['checkbox', 'text'].includes(value)
      },
    },
    scopedSlots: Object,
    searchNoResultsLabel: {
      type: String,
      default: 'no items',
    },
    selected: {
      type: Array as PropType<Option[]>,
      default: () => [],
    },
    virtualScroller: Boolean,
    visibleItems: {
      type: Number,
      default: 10,
    },
  },
  emits: ['groupClick', 'optionClick'],
  setup(props, { emit }) {
    const handleHomeEndKeyDown = (event: KeyboardEvent) => {
      if (event.key !== 'Home' && event.key !== 'End') {
        return
      }

      event.preventDefault()
      const target = event.target as HTMLElement
      const container = target.closest('.form-multi-select-options') as HTMLElement | null

      if (!container) {
        return
      }

      const first = event.key === 'Home'
      const focusEdgeOption = () => {
        const options = container.querySelectorAll<HTMLElement>(
          '.form-multi-select-option:not(.disabled)'
        )

        if (options.length > 0) {
          options[first ? 0 : options.length - 1].focus()
        }
      }

      if (props.virtualScroller) {
        // The virtualized viewport only mounts a window of options — scroll it to
        // the edge so the true first/last options render, then focus after the
        // window has re-rendered (scroll event → state update → commit).
        container.scrollTop = first ? 0 : container.scrollHeight
        requestAnimationFrame(() => requestAnimationFrame(focusEdgeOption))
        return
      }

      focusEdgeOption()
    }

    const handleKeyDown = (event: KeyboardEvent, option: Option) => {
      if (event.code === 'Space' || event.key === 'Enter') {
        event.preventDefault()
        handleOptionClick && handleOptionClick(option)
        return
      }

      if (event.key === 'Down' || event.key === 'ArrowDown') {
        event.preventDefault()
        const target = event.target as HTMLElement
        const next = getNextSibling(target, '.form-multi-select-option')

        next && (next as HTMLElement).focus()
      }

      if (event.key === 'Up' || event.key === 'ArrowUp') {
        event.preventDefault()
        const target = event.target as HTMLElement
        const prev = getPreviousSibling(target, '.form-multi-select-option')

        prev && (prev as HTMLElement).focus()
      }

      handleHomeEndKeyDown(event)
    }

    const handleGroupKeyDown = (event: KeyboardEvent, group: OptionsGroup) => {
      if (event.code === 'Space' || event.key === 'Enter') {
        event.preventDefault()
        emit('groupClick', group)
        return
      }

      if (event.key === 'Down' || event.key === 'ArrowDown') {
        event.preventDefault()
        const next = getNextSibling(event.target as HTMLElement, '.form-multi-select-option')

        next && (next as HTMLElement).focus()
      }

      if (event.key === 'Up' || event.key === 'ArrowUp') {
        event.preventDefault()
        const prev = getPreviousSibling(event.target as HTMLElement, '.form-multi-select-option')

        prev && (prev as HTMLElement).focus()
      }

      handleHomeEndKeyDown(event)
    }

    const getGroupCheckboxState = (group: OptionsGroup) => {
      const groupOptions = (group.options ?? []).filter((option) => !option.disabled)
      const selectedCount = groupOptions.filter((option) =>
        props.selected.some((_option) => _option.value === option.value)
      ).length

      if (groupOptions.length > 0 && selectedCount >= groupOptions.length) {
        return 'all'
      }

      return selectedCount === 0 ? 'none' : 'indeterminate'
    }

    const handleOptionClick = (option: Option) => {
      emit('optionClick', option as Option)
    }

    const handleGroupClick = (group: OptionsGroup) => {
      emit('groupClick', group as OptionsGroup)
    }

    // TODO: find solution how to remove any
    const createOptions = (options: (Option | OptionsGroup)[]) =>
      options.length > 0
        ? options.map((option: Option | OptionsGroup) =>
            'value' in option
              ? h(
                  'div',
                  {
                    class: [
                      'form-multi-select-option',
                      {
                        'form-multi-select-option-with-checkbox': props.optionsStyle === 'checkbox',
                        'form-multi-selected': props.selected.some(
                          (_option) => _option.value === option.value
                        ),
                        disabled: option.disabled,
                      },
                    ],
                    onClick: () => handleOptionClick(option as Option),
                    onKeydown: (event: KeyboardEvent) => handleKeyDown(event, option as Option),
                    tabindex: 0,
                    role: 'option',
                    'aria-selected': props.selected.some(
                      (_option) => _option.value === option.value
                    ),
                  },
                  props.scopedSlots && props.scopedSlots['options']
                    ? h(props.scopedSlots['options'], { option: option })
                    : option.label
                )
              : [
                  h(
                    'div',
                    {
                      class: [
                        'form-multi-select-optgroup-label',
                        {
                          'form-multi-select-optgroup-label-with-checkbox':
                            props.optionsGroupsSelectable &&
                            props.optionsGroupsStyle === 'checkbox',
                          'form-multi-selected':
                            props.optionsGroupsSelectable &&
                            props.optionsGroupsStyle === 'checkbox' &&
                            getGroupCheckboxState(option) === 'all',
                          'form-multi-select-indeterminate':
                            props.optionsGroupsSelectable &&
                            props.optionsGroupsStyle === 'checkbox' &&
                            getGroupCheckboxState(option) === 'indeterminate',
                        },
                      ],
                      ...(props.optionsGroupsSelectable &&
                        props.optionsGroupsStyle === 'checkbox' && {
                          onClick: () => handleGroupClick(option as OptionsGroup),
                          onKeydown: (event: KeyboardEvent) =>
                            handleGroupKeyDown(event, option as OptionsGroup),
                          tabindex: 0,
                          role: 'button',
                        }),
                    },
                    props.scopedSlots && props.scopedSlots['options-groups']
                      ? h(props.scopedSlots['options-groups'], { option: option })
                      : option.label
                  ),
                ]
          )
        : h(
            'div',
            { class: 'form-multi-select-options-empty', role: 'status' },
            props.searchNoResultsLabel
          )

    return () => [
      props.virtualScroller
        ? h(
            CVirtualScroller,
            {
              class: 'form-multi-select-options',
              visibleItems: props.visibleItems,
            },
            {
              default: () => createOptions(props.options),
            }
          )
        : h(
            'div',
            {
              class: 'form-multi-select-options',
              ...(props.optionsMaxHeight !== 'auto' && {
                style: { maxHeight: props.optionsMaxHeight, overflow: 'scroll' },
              }),
            },
            createOptions(props.options)
          ),
      props.loading && h(CElementCover),
    ]
  },
})

export { CMultiSelectOptions }
