import type { gsap as Gsap } from 'gsap'
import type { ScrollTrigger as ScrollTriggerType } from 'gsap/ScrollTrigger'
import type { MotionConditions } from '~/utils/motion'

export interface MotionContext<Extra extends string = never> {
  gsap: typeof Gsap
  ScrollTrigger: typeof ScrollTriggerType
  /** Résultat des requêtes MEDIA (et des requêtes propres au composant). */
  conditions: MotionConditions & Record<Extra, boolean>
  root: HTMLElement
  /** querySelectorAll limité au composant. */
  select: <T extends HTMLElement = HTMLElement>(selector: string) => T[]
}

/**
 * Point d'entrée unique des animations d'un composant.
 *
 * `setup` est exécuté dans un gsap.matchMedia() : il est rejoué dès qu'une
 * condition change (passage mobile ↔ desktop, mouvement réduit…) et tout ce
 * qu'il a créé — tweens, timelines, ScrollTriggers — est annulé avant chaque
 * relance ainsi qu'au démontage. La fonction renvoyée par `setup`, s'il y en a
 * une, sert de nettoyage complémentaire.
 */
export function useMotion<Extra extends string = never>(
  root: Ref<HTMLElement | null>,
  setup: (context: MotionContext<Extra>) => unknown,
  extraQueries?: Record<Extra, string>,
) {
  let media: gsap.MatchMedia | undefined

  onMounted(() => {
    const element = root.value
    if (!element) return

    const { $gsap: gsap, $ScrollTrigger: ScrollTrigger } = useNuxtApp()

    media = gsap.matchMedia(element)
    media.add({ ...MEDIA, ...extraQueries }, (context) => {
      const cleanup = setup({
        gsap,
        ScrollTrigger,
        conditions: context.conditions as MotionConditions & Record<Extra, boolean>,
        root: element,
        select: <T extends HTMLElement>(selector: string) =>
          Array.from(element.querySelectorAll<T>(selector)),
      })
      return typeof cleanup === 'function' ? (cleanup as () => void) : undefined
    })
  })

  onBeforeUnmount(() => media?.revert())
}
