import React, { useEffect, useRef } from 'react'
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { useSceneStore } from '../store/useSceneStore'
import { animateDashboard } from '../animations/dashboardAnimation'

gsap.registerPlugin(ScrollTrigger)

// Scroll length per device tier. The animation is driven by NORMALISED progress
// (0→1), so compressing the spacer height only shortens how far the user has to
// scroll — every camera/truck keyframe still lands at the same progress value,
// preserving the visuals while cutting the 900vh marathon down to a tighter,
// less laggy travel (and less time touch-scrolling a heavy WebGL page on mobile).
const SCROLL_HEIGHT_VH = { desktop: 600, tablet: 550, mobile: 500 }

/**
 * ScrollRig
 * ---------------------------------------------------------------------------
 * Owns the single ScrollTrigger that drives the whole experience (progress, pin
 * state, active section).
 *
 * FIRST-LOAD CORRECTNESS (the bug this fixes):
 * The stage is pinned by a manual `position: fixed` toggle driven by this
 * trigger. On a client-side route navigation (Home → /how-it-works) the trigger
 * could be created against a not-yet-settled layout (the heavy Elementor hero +
 * web fonts above it are still loading), so its start/end were wrong and it never
 * fired — the section didn't pin and `scrollProgress` never advanced, so cards
 * and section activation were dead until a manual refresh (which loads against a
 * settled, cached layout).
 *
 * The fix is lifecycle-driven, NOT timeout-based:
 *  - `onRefresh` re-syncs the store from the trigger's CURRENT progress every
 *    time the layout settles, so state is correct without needing a scroll event.
 *    ScrollTrigger fires onRefresh on creation too, so First Mile is correct on
 *    mount.
 *  - A debounced ResizeObserver on the document refreshes whenever the page
 *    height changes (hero/fonts/images settling, scene mounting, orientation).
 *  - `document.fonts.ready` triggers a refresh once web fonts are applied.
 *  - The `ready` prop (scene loaded) triggers a final refresh.
 */
export default function ScrollRig({ dashboardRefs, onPinState, tier = 'desktop', ready = false }) {
  const setScrollProgress = useSceneStore((state) => state.setScrollProgress)
  const setActiveSection = useSceneStore((state) => state.setActiveSection)
  const containerRef = useRef(null)
  const activeSectionRef = useRef(0)
  const pinStateRef = useRef('before')
  const triggerRef = useRef(null)

  useEffect(() => {
    const element = containerRef.current
    if (!element) return

    // Single place that maps a progress value → all derived state. Called from
    // BOTH onUpdate (scroll) and onRefresh (layout settle) so the store is always
    // correct for the current scroll position, even with no scroll interaction.
    const syncProgress = (progress) => {
      setScrollProgress(progress)

      const ns = progress <= 0.0002 ? 'before' : progress >= 0.9998 ? 'after' : 'pinned'
      if (ns !== pinStateRef.current) {
        pinStateRef.current = ns
        onPinState?.(ns)
      }

      let section = 0
      if (progress >= 0.92) section = 3
      else if (progress >= 0.5) section = 2
      else if (progress >= 0.12) section = 1
      if (section !== activeSectionRef.current) {
        activeSectionRef.current = section
        setActiveSection(section)
      }

      if (dashboardRefs) {
        const dp = progress >= 0.92 ? (progress - 0.92) / 0.08 : 0
        animateDashboard(dashboardRefs.bars || [], dashboardRefs.pieQuarters || [], dp)
      }
    }

    const trigger = ScrollTrigger.create({
      trigger: element,
      start: 'top top',
      end: 'bottom bottom',
      scrub: 2.5,
      invalidateOnRefresh: true,
      onUpdate: (self) => syncProgress(self.progress),
      // Re-sync after every refresh (incl. the initial one) so state reflects the
      // settled layout immediately — this is what makes first-load work without
      // a manual page refresh.
      onRefresh: (self) => syncProgress(self.progress),
    })
    triggerRef.current = trigger

    // Refresh on the next frame (after the browser has laid the new DOM out) —
    // a paint-synced wait, not an arbitrary millisecond delay.
    const raf = requestAnimationFrame(() => ScrollTrigger.refresh())

    // Catch late layout shifts: the Elementor hero + web fonts + images above
    // settle asynchronously on a fresh navigation, changing the document height
    // and therefore this trigger's start/end. Refresh (debounced to one per
    // frame) whenever that happens.
    let roRaf = 0
    const ro = new ResizeObserver(() => {
      cancelAnimationFrame(roRaf)
      roRaf = requestAnimationFrame(() => ScrollTrigger.refresh())
    })
    ro.observe(document.documentElement)

    // Web fonts change text metrics → the hero's height → our start position.
    let fontsCancelled = false
    if (document.fonts?.ready) {
      document.fonts.ready.then(() => {
        if (!fontsCancelled) ScrollTrigger.refresh()
      })
    }

    return () => {
      cancelAnimationFrame(raf)
      cancelAnimationFrame(roRaf)
      ro.disconnect()
      fontsCancelled = true
      trigger.kill()
      triggerRef.current = null
    }
  }, [setScrollProgress, setActiveSection, dashboardRefs, onPinState])

  // When the 3D scene finishes loading it can shift layout / it confirms the
  // experience is fully mounted — do a final authoritative refresh.
  useEffect(() => {
    if (ready) ScrollTrigger.refresh()
  }, [ready])

  return (
    <div
      ref={containerRef}
      id="scroll-trigger-trigger"
      style={{
        position: 'relative',
        width: '100%',
        // Tier-driven scroll length (was a fixed 900vh). Shorter travel = less
        // scrub lag, especially while touch-scrolling a heavy WebGL page.
        height: `${SCROLL_HEIGHT_VH[tier] ?? 600}vh`,
        pointerEvents: 'none', // Allow interacting with the R3F Canvas underneath
        zIndex: 0,
      }}
    />
  )
}
