<template>
  <div
    ref="rootRef"
    v-bind="$attrs"
  >
    <!-- Ligne de mesure cachée : largeur réelle de chaque onglet (mêmes classes de
         layout, placeholder à la place de l'icône pour ne pas fausser les tests),
         afin de calculer combien d'onglets tiennent. Invisible et hors flux. -->
    <div
      ref="measureRef"
      aria-hidden="true"
      class="pointer-events-none invisible absolute left-0 top-0 flex"
    >
      <span
        v-for="tab in tabs"
        :key="tab.key"
        class="flex items-center gap-[18px] text-[24px] font-[600]"
      >
        <span
          v-if="tab.icon"
          class="inline-block shrink-0"
          :style="{ width: `${tab.iconSize ?? 24}px`, height: `${tab.iconSize ?? 24}px` }"
        />
        {{ tab.label }}
      </span>
    </div>

    <div v-if="isWindowed" class="flex items-center justify-center gap-[36px] border-b border-m-primary">
      <button
        type="button"
        aria-label="Onglets précédents"
        data-test="tab-prev"
        :disabled="!canPrev"
        :class="[
          'transition-colors',
          canPrev
            ? 'cursor-pointer text-m-btn-primary hover:text-m-btn-primary-hover active:text-m-btn-primary-active'
            : 'cursor-not-allowed text-m-disabled',
        ]"
        @click="prev"
      >
        <IconifyIcon icon="mdi:chevron-left" :width="28" />
      </button>

      <div
        role="tablist"
        class="flex flex-1 justify-center gap-[60px]"
      >
        <button
          v-for="tab in visibleTabs"
          :id="`${componentId}-tab-${tab.key}`"
          :key="tab.key"
          role="tab"
          type="button"
          :aria-selected="activeTab === tab.key"
          :aria-controls="`${componentId}-panel-${tab.key}`"
          :aria-disabled="!!tab.disabled"
          :tabindex="focusedKey === tab.key ? 0 : -1"
          :disabled="tab.disabled"
          :class="[
            'relative flex items-center gap-[18px] text-[24px] font-[600] transition-colors',
            activeTab === tab.key
              ? 'cursor-pointer text-m-primary after:content-[\'\'] after:absolute after:-bottom-[3px] after:left-0 after:right-0 after:h-[3px] after:bg-m-primary'
              : tab.disabled
                ? 'cursor-not-allowed text-m-primary/50'
                : 'cursor-pointer text-m-primary/50 hover:text-m-primary hover:after:content-[\'\'] hover:after:absolute hover:after:-bottom-[3px] hover:after:left-0 hover:after:right-0 hover:after:h-[3px] hover:after:bg-m-primary',
          ]"
          @click="selectTab(tab.key)"
        >
          <IconifyIcon
            v-if="tab.icon"
            :icon="tab.icon"
            :width="tab.iconSize ?? 24"
          />
          {{ tab.label }}
        </button>
      </div>

      <button
        type="button"
        aria-label="Onglets suivants"
        data-test="tab-next"
        :disabled="!canNext"
        :class="[
          'transition-colors',
          canNext
            ? 'cursor-pointer text-m-btn-primary hover:text-m-btn-primary-hover active:text-m-btn-primary-active'
            : 'cursor-not-allowed text-m-disabled',
        ]"
        @click="next"
      >
        <IconifyIcon icon="mdi:chevron-right" :width="28" />
      </button>
    </div>

    <div
      v-else
      role="tablist"
      class="flex justify-center gap-[60px] border-b border-m-primary"
    >
      <button
        v-for="tab in visibleTabs"
        :id="`${componentId}-tab-${tab.key}`"
        :key="tab.key"
        role="tab"
        type="button"
        :aria-selected="activeTab === tab.key"
        :aria-controls="`${componentId}-panel-${tab.key}`"
        :aria-disabled="!!tab.disabled"
        :tabindex="focusedKey === tab.key ? 0 : -1"
        :disabled="tab.disabled"
        :class="[
          'relative flex items-center gap-[18px] text-[24px] font-[600] transition-colors',
          activeTab === tab.key
            ? 'cursor-pointer text-m-primary after:content-[\'\'] after:absolute after:-bottom-[3px] after:left-0 after:right-0 after:h-[3px] after:bg-m-primary'
            : tab.disabled
              ? 'cursor-not-allowed text-m-primary/50'
              : 'cursor-pointer text-m-primary/50 hover:text-m-primary hover:after:content-[\'\'] hover:after:absolute hover:after:-bottom-[3px] hover:after:left-0 hover:after:right-0 hover:after:h-[3px] hover:after:bg-m-primary',
        ]"
        @click="selectTab(tab.key)"
      >
        <IconifyIcon
          v-if="tab.icon"
          :icon="tab.icon"
          :width="tab.iconSize ?? 24"
        />
        {{ tab.label }}
      </button>
    </div>

    <div
      v-for="tab in tabs"
      v-show="activeTab === tab.key"
      :id="`${componentId}-panel-${tab.key}`"
      :key="tab.key"
      role="tabpanel"
      :aria-labelledby="isTabRendered(tab.key) ? `${componentId}-tab-${tab.key}` : undefined"
      :aria-label="isTabRendered(tab.key) ? undefined : tab.label"
    >
      <slot :name="tab.key" />
    </div>
  </div>
</template>

<script setup lang="ts">
import {computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch} from 'vue'
import {Icon as IconifyIcon} from '@iconify/vue'
import {computeVisibleCount} from './tabFit'

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

type Tab = {
  key: string
  label: string
  icon?: string
  iconSize?: string
  disabled?: boolean
}

const props = withDefaults(defineProps<{
  tabs: Tab[]
  modelValue?: string
  id?: string
  maxVisibleTabs?: number
}>(), {
  modelValue: undefined,
  id: '',
  maxVisibleTabs: undefined,
})

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

const generatedId = useId()
const componentId = computed(() => props.id || `malio-tab-list-${generatedId}`)

const isControlled = computed(() => props.modelValue !== undefined)
const localValue = ref(props.tabs.length > 0 ? props.tabs[0].key : '')

const activeTab = computed(() =>
  isControlled.value ? props.modelValue! : localValue.value,
)

const TAB_GAP = 60
const CHEVRON_RESERVE = 110

const rootRef = ref<HTMLElement | null>(null)
const measureRef = ref<HTMLElement | null>(null)
const containerWidth = ref(0)
const tabWidths = ref<number[]>([])

// Nombre d'onglets affichés, calculé pour qu'ils tiennent dans la largeur réelle
// (cf. tabFit.ts) — la structure « flèches fixes » est conservée, et comme le
// nombre est choisi pour tenir, pas de débordement sur les flèches ni de rognage.
// `maxVisibleTabs` reste un plafond optionnel. Sans mesure (SSR/jsdom), repli sur
// ce plafond / tous les onglets.
const visibleCount = computed(() => computeVisibleCount({
  count: props.tabs.length,
  containerWidth: containerWidth.value,
  tabWidths: tabWidths.value,
  gap: TAB_GAP,
  chevronReserve: CHEVRON_RESERVE,
  maxVisibleTabs: props.maxVisibleTabs,
}))

const isWindowed = computed(() => props.tabs.length > visibleCount.value)

const maxStartIndex = computed(() => Math.max(0, props.tabs.length - visibleCount.value))

const startIndex = ref(0)

const visibleTabs = computed(() =>
  isWindowed.value
    ? props.tabs.slice(startIndex.value, startIndex.value + visibleCount.value)
    : props.tabs,
)

function measureTabWidths() {
  const el = measureRef.value
  if (!el) return
  tabWidths.value = Array.from(el.children).map(c => (c as HTMLElement).offsetWidth)
}

function measureContainer() {
  if (rootRef.value) containerWidth.value = rootRef.value.clientWidth
}

let resizeObserver: ResizeObserver | null = null

onMounted(() => {
  measureTabWidths()
  measureContainer()
  if (typeof ResizeObserver !== 'undefined' && rootRef.value) {
    resizeObserver = new ResizeObserver(() => measureContainer())
    resizeObserver.observe(rootRef.value)
  }
})

onBeforeUnmount(() => resizeObserver?.disconnect())

// Re-mesure quand la liste change (labels/icônes → largeurs différentes).
watch(() => props.tabs, () => nextTick(() => {
  measureTabWidths()
  measureContainer()
}), {deep: true})

const focusedKey = computed(() => {
  if (!isWindowed.value) return activeTab.value
  const inView = visibleTabs.value.some(t => t.key === activeTab.value)
  return inView ? activeTab.value : (visibleTabs.value[0]?.key ?? '')
})

const isTabRendered = (key: string) => !isWindowed.value || visibleTabs.value.some(t => t.key === key)

const canPrev = computed(() => isWindowed.value && startIndex.value > 0)
const canNext = computed(() => isWindowed.value && startIndex.value < maxStartIndex.value)

function prev() {
  if (!canPrev.value) return
  startIndex.value -= 1
}

function next() {
  if (!canNext.value) return
  startIndex.value += 1
}

// Clamp startIndex back in range if tabs or maxVisibleTabs change.
watch(maxStartIndex, (max) => {
  if (startIndex.value > max) startIndex.value = max
})

// Reset the window to the start when the tab list is replaced.
watch(() => props.tabs, () => {
  startIndex.value = 0
})

function selectTab(key: string) {
  const tab = props.tabs.find(t => t.key === key)
  if (tab?.disabled) return
  if (!isControlled.value) {
    localValue.value = key
  }
  emit('update:modelValue', key)
}
</script>
