import { computed, nextTick, provide, ref, type ComputedRef, type InjectionKey } from 'vue'

import { SELECTOR_CHIP_FOCUSABLE } from '../chip/const'
import { isRTL } from '../../utils'

export interface ChipSetConfig {
  disabled?: boolean
  filter?: boolean
  removable?: boolean
  removeIcon?: string | object
  selectable?: boolean
  selectedIcon?: string | object
}

export interface ChipSetContext {
  /** Config forwarded to every chip in the set; the chip's own props win. */
  config: ComputedRef<ChipSetConfig>
  isSelected: (value: string) => boolean
  toggleSelected: (value: string, next: boolean, event: MouseEvent | KeyboardEvent) => void
  removeChip: (value: string, event: MouseEvent | KeyboardEvent) => void
}

export const chipSetContextKey: InjectionKey<ChipSetContext> = Symbol('CChipSet')

export interface UseChipSetOptions {
  config: ComputedRef<ChipSetConfig>
  selectionMode?: () => 'single' | 'multiple'
  /** Controlled selected values (e.g. v-model:selected). Undefined keeps it uncontrolled. */
  selected?: () => string[] | undefined
  defaultSelected?: string[]
  /** Move focus to a neighbor after removal. Containers with their own target (CChipInput → input) pass false. */
  restoreFocusOnRemove?: boolean
  onSelectionChange?: (selected: string[]) => void
  onRemove?: (value: string) => void
}

/**
 * Shared chip-set engine for CChipSet and CChipInput — the Vue equivalent of the
 * vanilla `ChipInput extends ChipSet`. It owns selection coordination
 * (single/multiple, controlled-or-uncontrolled), roving focus, and the
 * focus-a-neighbor-after-removal behavior, and `provide`s a context that CChip
 * injects. Existence of the chips stays with the caller.
 */
export const useChipSet = (options: UseChipSetOptions) => {
  const {
    config,
    selectionMode = () => 'multiple',
    selected = () => undefined,
    defaultSelected = [],
    restoreFocusOnRemove = true,
    onSelectionChange,
    onRemove,
  } = options

  const rootRef = ref<HTMLElement>()
  const internalSelected = ref<string[]>(defaultSelected)

  const selectedValues = computed(() => {
    const controlled = selected()
    return controlled === undefined ? internalSelected.value : controlled
  })

  const getFocusableChips = (): HTMLElement[] => [
    ...(rootRef.value?.querySelectorAll<HTMLElement>(SELECTOR_CHIP_FOCUSABLE) ?? []),
  ]

  const emitSelection = (next: string[]): void => {
    if (selected() === undefined) {
      internalSelected.value = next
    }
    onSelectionChange?.(next)
  }

  const toggleSelected = (value: string, next: boolean): void => {
    const isSelected = selectedValues.value.includes(value)
    if (next === isSelected) {
      return
    }

    if (next) {
      // Single selection mode keeps only the freshly selected chip.
      emitSelection(selectionMode() === 'single' ? [value] : [...selectedValues.value, value])
      return
    }

    emitSelection(selectedValues.value.filter((item) => item !== value))
  }

  const clearSelection = (): void => {
    if (selectedValues.value.length > 0) {
      emitSelection([])
    }
  }

  const removeChip = (value: string): void => {
    if (restoreFocusOnRemove) {
      const chips = getFocusableChips()
      const index = chips.findIndex((chip) => chip.dataset.coreuiChipValue === value)
      // Prefer the next chip, fall back to the previous one at the end.
      const neighbor = chips[index + 1] ?? chips[index - 1] ?? null
      const neighborValue = neighbor?.dataset.coreuiChipValue ?? null

      if (neighborValue !== null) {
        nextTick(() => {
          rootRef.value
            ?.querySelector<HTMLElement>(`[data-coreui-chip-value="${neighborValue}"]`)
            ?.focus()
        })
      }
    }

    emitSelection(selectedValues.value.filter((item) => item !== value))
    onRemove?.(value)
  }

  // Roving focus: arrow keys move between chips and Home/End jump to the edges,
  // with no cycling. Returns true when the key was handled.
  const handleKeydown = (event: KeyboardEvent): boolean => {
    const chip = (event.target as HTMLElement).closest<HTMLElement>(SELECTOR_CHIP_FOCUSABLE)
    if (!chip) {
      return false
    }

    const chips = getFocusableChips()
    const index = chips.indexOf(chip)
    // In RTL the visual direction is mirrored, so left/right swap.
    const rtl = isRTL(rootRef.value)

    switch (event.key) {
      case 'ArrowLeft': {
        event.preventDefault()
        chips[rtl ? index + 1 : index - 1]?.focus()
        return true
      }

      case 'ArrowRight': {
        event.preventDefault()
        chips[rtl ? index - 1 : index + 1]?.focus()
        return true
      }

      case 'Home': {
        event.preventDefault()
        chips[0]?.focus()
        return true
      }

      case 'End': {
        event.preventDefault()
        chips[chips.length - 1]?.focus()
        return true
      }

      default: {
        return false
      }
    }
  }

  provide(chipSetContextKey, {
    config,
    isSelected: (value: string) => selectedValues.value.includes(value),
    toggleSelected: (value, next) => toggleSelected(value, next),
    removeChip: (value) => removeChip(value),
  })

  return { rootRef, selectedValues, clearSelection, getFocusableChips, handleKeydown }
}
