"use client"

/**
 * Barre d'onglets « rail », primitive partagée du portail SAR.
 *
 * Les sections se choisissent par de vrais BOUTONS, pas par des liens : la
 * navbar juste au-dessus est déjà une rangée de liens, et du texte souligné y
 * serait redondant autant qu'ambigu. Le contour, la surface cliquable et le
 * remplissage de l'actif disent « ceci se clique » dès l'arrivée sur la page.
 *
 * Aucune icône : l'état est porté par le contour, le remplissage rouge SAR et
 * le mouvement du bloc actif, jamais par un pictogramme ou un chevron.
 *
 * Le bloc rouge de l'onglet actif est MESURÉ (offsetLeft/Top/Width/Height) puis
 * animé en x, y, largeur et hauteur. Mesurer plutôt que projeter par `layoutId`
 * permet au bloc de rejoindre correctement un bouton passé à la ligne suivante
 * quand la rangée se replie sur les petits écrans.
 */

import { useCallback, useEffect, useRef, useState } from "react"
import { motion, useReducedMotion } from "framer-motion"

export type OngletRail = { id: string; label: string }

type Props = {
  onglets: readonly OngletRail[]
  actif: number
  onChange: (index: number) => void
  ariaLabel: string
  /** Espace de nommage ARIA : onglet-${railId}-${id} / panneau-${railId}-${id} */
  railId: string
  taille?: "lg" | "sm"
  className?: string
}

export default function OngletsRail({
  onglets,
  actif,
  onChange,
  ariaLabel,
  railId,
  taille = "lg",
  className = "",
}: Props) {
  const reduire = !!useReducedMotion()
  const listeRef = useRef<HTMLDivElement | null>(null)
  const boutonsRef = useRef<(HTMLButtonElement | null)[]>([])
  const [bloc, setBloc] = useState<{ x: number; y: number; w: number; h: number } | null>(null)

  // ── Mesure du bloc actif ────────────────────────────────────────────────────
  // offsetLeft/offsetTop sont relatifs au conteneur (qui porte `relative`), donc
  // justes quelle que soit la rangée sur laquelle le bouton a été replié.
  const mesurer = useCallback(() => {
    const el = boutonsRef.current[actif]
    if (!el) return
    setBloc({ x: el.offsetLeft, y: el.offsetTop, w: el.offsetWidth, h: el.offsetHeight })
  }, [actif])

  useEffect(() => {
    mesurer()
  }, [mesurer, onglets])

  useEffect(() => {
    const liste = listeRef.current
    if (!liste) return
    // La largeur disponible change (redimensionnement, repli de rangée) et les
    // polices web décalent la largeur des libellés après le premier rendu.
    const ro = new ResizeObserver(() => mesurer())
    ro.observe(liste)
    boutonsRef.current.forEach((b) => b && ro.observe(b))
    document.fonts?.ready.then(() => mesurer()).catch(() => {})
    return () => ro.disconnect()
  }, [mesurer])

  // ── Clavier : flèches (avec bouclage), Home / End ───────────────────────────
  const surTouche = (e: React.KeyboardEvent<HTMLDivElement>) => {
    const dernier = onglets.length - 1
    let suivant = -1
    if (e.key === "ArrowRight") suivant = actif === dernier ? 0 : actif + 1
    else if (e.key === "ArrowLeft") suivant = actif === 0 ? dernier : actif - 1
    else if (e.key === "Home") suivant = 0
    else if (e.key === "End") suivant = dernier
    if (suivant < 0) return
    e.preventDefault()
    // Certains composants enfants écoutent encore les flèches au niveau window.
    e.stopPropagation()
    onChange(suivant)
    boutonsRef.current[suivant]?.focus()
  }

  const styles =
    taille === "lg"
      // Le rembourrage horizontal est reduit sur les plus petits ecrans : a
      // trois colonnes, il mangeait la largeur utile et poussait les libelles
      // hors de leur bouton.
      ? { espacement: "gap-2 sm:gap-2.5", bouton: "px-2.5 py-2.5 sm:px-6 sm:py-3", texte: "text-[13px] sm:text-base" }
      : { espacement: "gap-1.5 sm:gap-2", bouton: "px-2 py-2 sm:px-4", texte: "text-[11px] sm:text-sm" }

  return (
    <div className={className}>
      {/* Sous 1024 px, les six boutons ne tiennent pas sur une ligne : plutôt
          qu'un repli irrégulier (2 puis 3 puis 1), une grille les répartit en
          rangées nettes. Deux colonnes sur téléphone, où trois ne laissaient
          pas la place à des libellés comme « Projets stratégiques », trois à
          partir de 640 px, rangée unique centrée au-delà de 1024.
          Rien n'est jamais masqué, aucun geste à deviner. */}
      <div
        ref={listeRef}
        role="tablist"
        aria-label={ariaLabel}
        aria-orientation="horizontal"
        onKeyDown={surTouche}
        className={`relative grid grid-cols-2 sm:grid-cols-3 lg:flex lg:flex-wrap lg:items-center lg:justify-center ${styles.espacement}`}
      >
        {/* Bloc rouge de l'onglet actif, mesuré, donc juste même après repli */}
        {bloc && (
          <motion.span
            aria-hidden
            className="pointer-events-none absolute left-0 top-0 bg-primary"
            initial={false}
            animate={{ x: bloc.x, y: bloc.y, width: bloc.w, height: bloc.h }}
            transition={reduire ? { duration: 0 } : { type: "spring", stiffness: 420, damping: 38 }}
          />
        )}

        {onglets.map((o, i) => {
          const on = i === actif
          return (
            <button
              key={o.id}
              // React 19 : un callback ref ne doit RIEN retourner, accolades obligatoires.
              ref={(el) => {
                boutonsRef.current[i] = el
              }}
              type="button"
              role="tab"
              id={`onglet-${railId}-${o.id}`}
              aria-controls={`panneau-${railId}-${o.id}`}
              aria-selected={on}
              // Tabulation glissante : un seul arrêt de tabulation pour tout le rail.
              tabIndex={on ? 0 : -1}
              onClick={() => onChange(i)}
              // Fond transparent : c'est le bloc rouge qui passe DERRIÈRE le
              // bouton actif, d'où le z-10 sur le libellé.
              // `min-w-0` autorise la cellule de grille a se retrecir sous la
              // largeur de son contenu, et `break-words` coupe le mot trop long
              // plutot que de le laisser deborder du cadre.
              //
              // `touch-manipulation` : sans lui, le navigateur mobile attend
              // environ 300 ms apres l'appui pour voir si un second suit, qui
              // declencherait le zoom. D'ou des boutons qui paraissent lents.
              //
              // `min-h-[44px]` : en dessous de cette hauteur, l'appui manque
              // sa cible une fois sur plusieurs, ce qui donne l'impression que
              // le bouton refuse. C'est la taille minimale recommandee pour une
              // commande tactile.
              //
              // `select-none` : un appui maintenu selectionnait le libelle au
              // lieu d'activer l'onglet.
              className={`relative z-10 inline-flex min-h-[44px] min-w-0 select-none items-center justify-center break-words border text-center font-bold leading-tight outline-none transition-colors duration-200 touch-manipulation lg:whitespace-nowrap focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-900 ${styles.bouton} ${styles.texte} ${
                on
                  ? "border-primary text-white"
                  : "border-neutral-300 text-gray-700 hover:border-gray-900 hover:text-gray-900"
              }`}
            >
              {o.label}
            </button>
          )
        })}
      </div>
    </div>
  )
}
