<template>
  <div v-bind="$attrs" :class="rootClass">
    <slot />
  </div>
</template>

<script setup lang="ts">
import {computed, provide, ref, useId} from 'vue'
import {twMerge} from 'tailwind-merge'
import {accordionContextKey, type AccordionItemRegistration} from './context'

defineOptions({name: 'MalioAccordion', inheritAttrs: false})

const props = withDefaults(defineProps<{
  mode?: 'single' | 'multiple'
  modelValue?: string | string[]
  id?: string
  groupClass?: string
}>(), {
  mode: 'multiple',
  modelValue: undefined,
  id: '',
  groupClass: '',
})

const emit = defineEmits<{
  (e: 'update:modelValue', value: string | string[]): void
}>()

const generatedId = useId()
const baseId = computed(() => props.id || `malio-accordion-${generatedId}`)
const mode = computed(() => props.mode)

const isControlled = computed(() => props.modelValue !== undefined)
const localOpen = ref<string[]>([])

const items = ref<AccordionItemRegistration[]>([])

const openKeys = computed<string[]>(() => {
  if (isControlled.value) {
    const v = props.modelValue
    if (props.mode === 'single') return v ? [v as string] : []
    if (Array.isArray(v)) return v
    return v ? [v as string] : []
  }
  return localOpen.value
})

function isOpen(value: string) {
  return openKeys.value.includes(value)
}

function toggle(value: string) {
  const current = openKeys.value
  let next: string[]
  if (props.mode === 'single') {
    next = current.includes(value) ? [] : [value]
  } else {
    next = current.includes(value)
      ? current.filter(v => v !== value)
      : [...current, value]
  }
  if (!isControlled.value) {
    localOpen.value = next
  }
  emit('update:modelValue', props.mode === 'single' ? (next[0] ?? '') : next)
}

function register(item: AccordionItemRegistration, defaultOpen: boolean) {
  items.value.push(item)
  if (defaultOpen && !isControlled.value) {
    if (props.mode === 'single') {
      if (localOpen.value.length === 0) localOpen.value = [item.value]
    } else if (!localOpen.value.includes(item.value)) {
      localOpen.value.push(item.value)
    }
  }
}

function unregister(value: string) {
  items.value = items.value.filter(i => i.value !== value)
}

// `items` est ordonné par ordre de montage (= ordre du DOM pour des sections
// statiques/ajoutées en fin). Si un consommateur réordonne dynamiquement les
// items, cet ordre peut diverger de l'ordre visuel ; trier par position DOM
// serait alors nécessaire (hors périmètre v1).
function focusSibling(value: string, offset: 1 | -1) {
  const enabled = items.value.filter(i => !i.isDisabled())
  const idx = enabled.findIndex(i => i.value === value)
  if (idx === -1) return
  const next = enabled[(idx + offset + enabled.length) % enabled.length]
  next?.getHeaderEl()?.focus()
}

const rootClass = computed(() =>
  twMerge('divide-y divide-m-border border-y border-m-border', props.groupClass),
)

provide(accordionContextKey, {
  mode,
  baseId,
  isOpen,
  toggle,
  register,
  unregister,
  focusSibling,
})
</script>
