"use client"

import { useEffect, useState } from "react"
import { SCROLL_MODE_EVENT, SCROLL_MODE_STORAGE_KEY, isScrollMode, type ScrollMode } from "@/lib/scroll-mode"

function readInitialMode(): ScrollMode {
  if (typeof window === "undefined") return "native"
  const stored = localStorage.getItem(SCROLL_MODE_STORAGE_KEY)
  return isScrollMode(stored) ? stored : "native"
}

// Ne pas intercepter le scroll à l'intérieur d'une zone qui gère déjà le
// sien (popups/lightbox en overflow-auto, carrousels horizontaux, champs
// de saisie) — sinon on casse ces interactions.
function shouldIgnore(target: EventTarget | null): boolean {
  if (!(target instanceof Element)) return false
  if (target.closest('[role="dialog"], [data-scroll-ignore]')) return true
  const tag = target.tagName
  if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true
  return (target as HTMLElement).isContentEditable
}

// `html { scroll-behavior: smooth }` (globals.css) s'applique à TOUT scroll
// programmatique, y compris la forme scrollTo(x, y) — sur trackpad, qui
// déclenche des dizaines d'événements wheel par seconde, chaque appel
// relançait une animation smooth par-dessus la précédente et les faisait
// s'empiler/se battre entre elles (le scroll "ne fonctionnait pas bien").
// On pilote nous-mêmes l'easing (mode glitch) ou on veut du 1:1 immédiat
// (mode inversé) : il faut donc explicitement forcer un positionnement
// instantané à chaque appel, sans quoi le CSS re-anime par-dessus.
function scrollToInstant(y: number) {
  window.scrollTo({ top: y, left: 0, behavior: "instant" })
}

function easeOutCubic(t: number): number {
  return 1 - Math.pow(1 - t, 3)
}

function animateScrollTo(from: number, to: number, duration: number, onDone?: () => void) {
  const startTime = performance.now()
  function frame(now: number) {
    const t = Math.min(1, (now - startTime) / duration)
    scrollToInstant(from + (to - from) * easeOutCubic(t))
    if (t < 1) {
      requestAnimationFrame(frame)
    } else {
      onDone?.()
    }
  }
  requestAnimationFrame(frame)
}

// Chaque déclenchement du mode "glitch" tire un raté différent au hasard —
// pas toujours le même aller-retour, pour que ce soit vraiment imprévisible
// à chaque fois plutôt qu'un seul effet reconnaissable qui se répète.
type GlitchEffect = (baseY: number, clampY: (y: number) => number, onDone: () => void) => void

const glitchEffects: GlitchEffect[] = [
  // Retour en arrière puis rattrapage un peu plus loin.
  (baseY, clampY, onDone) => {
    const kick = (Math.random() < 0.5 ? -1 : 1) * (40 + Math.random() * 80)
    animateScrollTo(baseY, clampY(baseY - kick), 90, () => {
      animateScrollTo(clampY(baseY - kick), clampY(baseY + kick * 0.35), 170, onDone)
    })
  },
  // Double saccade rapide, deux petits à-coups avant de se stabiliser.
  (baseY, clampY, onDone) => {
    const k1 = (Math.random() < 0.5 ? -1 : 1) * (20 + Math.random() * 30)
    const k2 = -k1 * (0.6 + Math.random() * 0.4)
    animateScrollTo(baseY, clampY(baseY + k1), 70, () => {
      animateScrollTo(clampY(baseY + k1), clampY(baseY + k2), 70, () => {
        animateScrollTo(clampY(baseY + k2), baseY, 120, onDone)
      })
    })
  },
  // Gel bref (comme un lag), puis reprise normale.
  (baseY, _clampY, onDone) => {
    window.setTimeout(onDone, 140 + Math.random() * 120)
  },
  // Sur-saut vers l'avant qui dépasse puis revient (rebond élastique).
  (baseY, clampY, onDone) => {
    const overshoot = (Math.random() < 0.5 ? -1 : 1) * (60 + Math.random() * 100)
    animateScrollTo(baseY, clampY(baseY + overshoot), 110, () => {
      animateScrollTo(clampY(baseY + overshoot), baseY, 160, onDone)
    })
  },
]

// Monté une seule fois dans le layout racine : actif sur toutes les pages,
// même si le sélecteur (ScrollModeToggle) ne vit que sur la page d'accueil.
export function ScrollModeController() {
  const [mode, setMode] = useState<ScrollMode>(readInitialMode)
  const [glitching, setGlitching] = useState(false)

  useEffect(() => {
    function onChange(e: Event) {
      const detail = (e as CustomEvent<ScrollMode>).detail
      if (isScrollMode(detail)) setMode(detail)
    }
    window.addEventListener(SCROLL_MODE_EVENT, onChange)
    return () => window.removeEventListener(SCROLL_MODE_EVENT, onChange)
  }, [])

  useEffect(() => {
    if (mode === "native") return

    function clampY(y: number) {
      const max = document.documentElement.scrollHeight - window.innerHeight
      return Math.max(0, Math.min(y, max))
    }

    // La molette scrolle à l'envers — pas de surprise programmée, juste le
    // réflexe qui se retourne contre soi à chaque geste.
    if (mode === "inverted") {
      function onWheel(e: WheelEvent) {
        if (shouldIgnore(e.target)) return
        e.preventDefault()
        scrollToInstant(clampY(window.scrollY - e.deltaY))
      }
      window.addEventListener("wheel", onWheel, { passive: false })
      return () => window.removeEventListener("wheel", onWheel)
    }

    // mode === "glitch" : le scroll suit normalement la molette, mais à
    // intervalles imprévisibles (probabiliste, pas une minuterie fixe) la
    // page a un raté — un effet différent tiré au hasard à chaque fois
    // (voir glitchEffects) — avant de reprendre son cours normal.
    let glitchAnimating = false
    let lastGlitchTime = 0

    function onWheel(e: WheelEvent) {
      if (shouldIgnore(e.target)) return
      e.preventDefault()
      if (glitchAnimating) return

      const next = clampY(window.scrollY + e.deltaY)
      scrollToInstant(next)

      const now = performance.now()
      if (now - lastGlitchTime > 1400 && Math.random() < 0.18) {
        lastGlitchTime = now
        glitchAnimating = true
        setGlitching(true)
        window.setTimeout(() => setGlitching(false), 220)

        const effect = glitchEffects[Math.floor(Math.random() * glitchEffects.length)]
        effect(next, clampY, () => {
          glitchAnimating = false
        })
      }
    }

    window.addEventListener("wheel", onWheel, { passive: false })
    return () => window.removeEventListener("wheel", onWheel)
  }, [mode])

  if (!glitching) return null
  return <div aria-hidden="true" className="scroll-glitch-flash" />
}
