import './styles/pages/_home.scss';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { SmoothScroll, type WindowWithLenis } from './animations/scrollEffects/SmoothScroll';
import { TrailEffect } from './animations/gsap/TrailEffect';
import { AltitudeSidebar } from './animations/gsap/AltitudeSidebar';
import { ParallaxEffect } from './animations/gsap/ParallaxEffect';
import { HorizontalPinScroll } from './animations/gsap/HorizontalPinScroll';
import { LottieDescent } from './animations/gsap/LottieDescent';
import { THEME_CHANGE_EVENT } from './core/components/ThemeToggle';

interface TrailImage {
    url: string;
    alt: string;
}

/**
 * Type guard to validate unknown data matches TrailImage[] interface
 */
function isTrailImageArray(data: unknown): data is TrailImage[] {
    return Array.isArray(data) && data.every(
        item => typeof item === 'object' &&
                item !== null &&
                'url' in item &&
                'alt' in item &&
                typeof item.url === 'string' &&
                typeof item.alt === 'string'
    );
}

document.addEventListener('DOMContentLoaded', () => {
    // Check for reduced motion preference
    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    // Initialize smooth scroll
    const smoothScroll = new SmoothScroll();
    smoothScroll.initialize();

    // All ScrollTriggers below get their start/end positions measured at
    // DOMContentLoaded — before the custom serif font swaps in and before
    // images (project cards, media) finish loading, both of which shift
    // element heights and stale every trigger's measurements (symptom: a
    // scroll range that's off on first visit but correct after a reload,
    // once everything is cache-warm). Re-measure once the page has actually
    // settled.
    const refreshScrollTrigger = () => ScrollTrigger.refresh();
    if (document.readyState === 'complete') {
        refreshScrollTrigger();
    } else {
        window.addEventListener('load', refreshScrollTrigger, { once: true });
    }
    void document.fonts?.ready?.then(refreshScrollTrigger);

    // Fixed mountain-mask backdrop behind the hero/Haute-altitude jointure —
    // same ambient scroll effect as assets/about.ts: settles into place and
    // warms up from beige to the dark "mountain" band color as the visitor
    // scrolls past the hero.
    const homeBg = document.querySelector<HTMLElement>('[data-home-bg="true"]');
    let homeBgTween: gsap.core.Tween | null = null;
    if (homeBg) {
        const createHomeBgTween = () => gsap.fromTo(homeBg,
            { y: 0 },
            {
                y: 200,
                backgroundColor: 'transparent',
                ease: 'none',
                scrollTrigger: {
                    trigger: document.body,
                    start: 'top top',
                    end: 'bottom bottom',
                    scrub: true,
                },
            }
        );
        homeBgTween = createHomeBgTween();

        // GSAP resolves the "from" backgroundColor once, from the element's
        // computed style at tween creation — a theme switch afterwards changes
        // the underlying --color-primary-lighter var, but the tween keeps
        // interpolating from its stale cached color until the next full page
        // load. Rebuild the tween so it re-reads the fresh computed color.
        document.addEventListener(THEME_CHANGE_EVENT, () => {
            homeBgTween?.scrollTrigger?.kill();
            homeBgTween?.kill();
            gsap.set(homeBg, { clearProps: 'backgroundColor' });
            homeBgTween = createHomeBgTween();
        });
    }

    // Préhome — full-viewport intro screen overlaying home-intro/header (both
    // `position: fixed`, see _home.scss) until it fades itself out. A plain
    // spacer sibling reserves the scroll room the draw/fade scrubs against —
    // no GSAP `pin`, which fought with the Lenis smooth-scroll instance and
    // broke page-wide scrolling.
    const prehomeSpacer = document.querySelector('[data-prehome-spacer]') as HTMLElement;
    const prehomeSection = document.querySelector('[data-prehome]') as HTMLElement;
    const prehomeTrailContainer = document.querySelector('[data-prehome-trail-container]') as HTMLElement;
    const prehomeTrailImagesData = prehomeTrailContainer?.querySelector('[data-trail-images]') as HTMLElement;
    const prehomeCharacterEl = document.querySelector('[data-prehome-character]') as HTMLElement;
    const prehomeContentEl = document.querySelector('[data-prehome-content]') as HTMLElement;
    const prehomeScrollBtn = document.querySelector('[data-prehome-scroll]') as HTMLAnchorElement;

    // Once the visitor has scrolled through the préhome once, don't show it
    // again for the rest of the browser session — reload/return to the
    // homepage lands straight on home-intro.
    const PREHOME_SEEN_KEY = 'galta:prehome-seen';

    // Back-office switch (dashboard card "Blocage de la préhome"). Off, the intro
    // replays on every load and leaves nothing in sessionStorage — the flag is
    // neither read nor written, and any flag left over from when the lock was still
    // on is cleared, so switching the card off takes effect on the next load rather
    // than at the end of the session.
    const prehomeSessionLockEnabled = prehomeSection?.getAttribute('data-prehome-session-lock') !== '0';

    if (!prehomeSessionLockEnabled) {
        try {
            sessionStorage.removeItem(PREHOME_SEEN_KEY);
        } catch {
            // sessionStorage unavailable — nothing to clear
        }
    }

    const prehomeAlreadySeen = prehomeSessionLockEnabled && (() => {
        try {
            return sessionStorage.getItem(PREHOME_SEEN_KEY) === '1';
        } catch {
            return false; // sessionStorage unavailable (e.g. private mode) — fail open
        }
    })();

    let prehomeTrailEffect: TrailEffect | null = null;
    let prehomeDescent: LottieDescent | null = null;
    let prehomeScrollTrigger: ScrollTrigger | null = null;
    let prehomeTimeline: gsap.core.Timeline | null = null;

    if (prehomeSpacer && prehomeSection) {
        if (prefersReducedMotion || prehomeAlreadySeen) {
            // Skip the animated overlay entirely — it would otherwise
            // permanently cover home-intro/header for visitors who never
            // trigger the scroll-driven fade-out.
            prehomeSpacer.style.display = 'none';
            prehomeSection.style.display = 'none';
        } else {
            document.body.classList.add('prehome-active');

            // The sequence is scrubbed against the spacer from the very top of the
            // document. A scroll position restored by the browser on reload would
            // consume it instantly — with the back-office lock off, where the intro
            // is meant to replay on every load, it would look like it never played.
            // `manual` is handed back to the browser once the préhome is done, so
            // ordinary reloads keep restoring their position.
            if ('scrollRestoration' in history) {
                history.scrollRestoration = 'manual';
            }
            window.scrollTo(0, 0);
            // Lenis keeps its own smoothed position and would glide back to it.
            (window as unknown as WindowWithLenis).lenis?.scrollTo(0, { immediate: true });

            if (prehomeScrollBtn) {
                prehomeScrollBtn.addEventListener('click', (e) => {
                    e.preventDefault();
                    document.querySelector('[data-home-intro]')?.scrollIntoView({ behavior: 'smooth' });
                });
            }

            // Préhome timings, as a fraction of the spacer's scroll range (2.5
            // viewports, see _home.scss). The character's descent owns nearly all of
            // it; the wordmark steps aside immediately so the descent reads on its own.
            //   0 ─────────────────────────────── .8 ── .9 ───── 1
            //   wordmark fade (0 → .15)          fade   reveal
            //      descent (.05 → .85)
            //
            // The descent starts just after 0 rather than at 0: the first pixels of
            // scroll then move the character instead of snapping it off its mark.
            //
            // The fade finishes at REVEAL_PROGRESS, well before the spacer's scroll
            // range is fully consumed — the menu comes back and the rest is classic
            // page scroll, not tied to the overlay anymore, right as the fade
            // completes (not when home-intro physically arrives).
            const REVEAL_PROGRESS = 0.9;
            const WORDMARK_FADE_START = 0;
            const WORDMARK_FADE_DURATION = 0.15;
            const DESCENT_START = 0.05;
            const DESCENT_DURATION = 0.8;
            const SECTION_FADE_START = 0.8;
            const SECTION_FADE_DURATION = 0.1;

            let locked = false;

            const buildPrehomeTimeline = (): void => {
                if (locked) {
                    return;
                }

                prehomeScrollTrigger?.kill();
                prehomeTimeline?.kill();

                // Scrub (no pin) against the spacer's scroll room.
                const tl = gsap.timeline({
                    scrollTrigger: {
                        trigger: prehomeSpacer,
                        start: 'top top',
                        end: 'bottom top',
                        scrub: 1,
                        onUpdate: (self) => {
                            if (locked) return;

                            if (self.progress >= REVEAL_PROGRESS) {
                                // One-way ratchet: once revealed, lock it —
                                // scrolling back up must not bring the
                                // overlay back for the rest of this visit,
                                // and future visits (this session) skip it
                                // entirely.
                                locked = true;

                                // Lock switched off in the back office: the préhome
                                // must replay on every load, so nothing is written
                                // to sessionStorage.
                                if (prehomeSessionLockEnabled) {
                                    try {
                                        sessionStorage.setItem(PREHOME_SEEN_KEY, '1');
                                    } catch {
                                        // sessionStorage unavailable — the lock still holds for this pageload
                                    }
                                }
                                document.body.classList.remove('prehome-active');
                                prehomeSection.style.display = 'none';
                                self.kill();

                                // Sequence over — the browser can restore scroll
                                // positions normally again.
                                if ('scrollRestoration' in history) {
                                    history.scrollRestoration = 'auto';
                                }
                            } else {
                                document.body.classList.toggle('prehome-active', self.progress < REVEAL_PROGRESS);
                            }
                        },
                    },
                });

                // With `scrub`, GSAP maps the *full* scroll range to the
                // timeline's *total* duration regardless of the numbers used
                // below, so an explicit no-op at t=1 pads it out to keep
                // REVEAL_PROGRESS meaningful — otherwise the fade tween would
                // silently stretch to fill 100% of the scroll again.
                if (prehomeContentEl) {
                    tl.to(prehomeContentEl, {
                        autoAlpha: 0,
                        ease: 'none',
                        duration: WORDMARK_FADE_DURATION,
                    }, WORDMARK_FADE_START);
                }

                // The descent is built standalone and normalised to a duration of 1 so
                // it can be rescaled and nested at any position here. LottieDescent
                // returns it paused, so it never advances on the root timeline before
                // being nested — unpause it or the parent cannot drive it.
                const descent = prehomeDescent?.buildTimeline();
                if (descent) {
                    descent.duration(DESCENT_DURATION);
                    descent.paused(false);
                    tl.add(descent, DESCENT_START);
                }

                tl.to(prehomeSection, {
                    autoAlpha: 0,
                    ease: 'none',
                    duration: SECTION_FADE_DURATION,
                }, SECTION_FADE_START);
                tl.set(prehomeSection, {}, 1);

                prehomeTimeline = tl;
                prehomeScrollTrigger = (tl as unknown as { scrollTrigger: ScrollTrigger }).scrollTrigger;
            };

            if (prehomeCharacterEl) {
                const lightSrc = prehomeCharacterEl.getAttribute('data-lottie-src-light');
                const darkSrc = prehomeCharacterEl.getAttribute('data-lottie-src-dark');
                if (lightSrc && darkSrc) {
                    prehomeDescent = new LottieDescent({
                        container: prehomeCharacterEl,
                        lightSrc,
                        darkSrc,
                    });
                }
            }

            // The descent needs its two scenes loaded before the timeline can be
            // built — but the timeline is built either way, so a failed scene fetch
            // degrades to the plain fade-out instead of leaving the overlay stuck
            // over the page.
            void (prehomeDescent?.init() ?? Promise.resolve(false)).then(() => {
                buildPrehomeTimeline();
                ScrollTrigger.refresh();
            });
        }

        if (!prefersReducedMotion && !prehomeAlreadySeen && prehomeTrailContainer && prehomeTrailImagesData) {
            try {
                const imagesJson = prehomeTrailImagesData.getAttribute('data-trail-images');
                if (imagesJson) {
                    const parsed: unknown = JSON.parse(imagesJson);
                    if (isTrailImageArray(parsed)) {
                        prehomeTrailEffect = new TrailEffect({
                            container: prehomeTrailContainer,
                            listenTarget: prehomeSection,
                            images: parsed,
                            maxImages: 8,
                            fadeInDuration: 0.4,
                            fadeOutDuration: 0.6,
                            fadeOutDelay: 1.0,
                            imageSize: 250,
                        });
                        prehomeTrailEffect.start();
                    }
                }
            } catch (e) {
                console.warn('Failed to parse prehome trail images data:', e);
            }
        }
    }

    // Horizontal scroll-pin on each mountain section's project cards —
    // independent of the intro section, so it runs even if the intro block
    // is absent.
    const horizontalPinEffects: HorizontalPinScroll[] = [];
    document.querySelectorAll<HTMLElement>('.home-mountain').forEach((section) => {
        const pinWrapper = section.querySelector<HTMLElement>('.home-mountain__pin');
        const viewport = section.querySelector<HTMLElement>('.home-mountain__cards-viewport');
        const track = section.querySelector<HTMLElement>('[data-horizontal-track]');
        const title = section.querySelector<HTMLElement>('.home-mountain__title');
        if (!pinWrapper || !viewport || !track) {
            return;
        }
        const pin = new HorizontalPinScroll({
            pinTarget: pinWrapper,
            trackTarget: track,
            triggerTarget: title ?? pinWrapper,
        });
        pin.play();
        horizontalPinEffects.push(pin);
    });

    // Home intro elements
    const introSection = document.querySelector('[data-home-intro]') as HTMLElement;
    const trailContainer = document.querySelector('[data-trail-container]') as HTMLElement;
    const trailImagesData = trailContainer?.querySelector('[data-trail-images]') as HTMLElement;
    const scrollButton = document.querySelector('[data-scroll-trigger]') as HTMLButtonElement;

    if (!introSection) {
        return; // Not on home page or intro not present
    }

    // Skip animations if user prefers reduced motion
    if (prefersReducedMotion) {
        console.log('[HomeIntro] Animations disabled - user prefers reduced motion');
        // Still wire up scroll button for basic scroll functionality
        if (scrollButton) {
            scrollButton.addEventListener('click', () => {
                const nextSection = introSection.nextElementSibling as HTMLElement;
                if (nextSection) {
                    nextSection.scrollIntoView({ behavior: 'smooth' });
                }
            });
        }
        return;
    }

    // Parse trail images data with runtime type validation
    let trailImages: TrailImage[] = [];
    if (trailImagesData) {
        try {
            const imagesJson = trailImagesData.getAttribute('data-trail-images');
            if (imagesJson) {
                const parsed: unknown = JSON.parse(imagesJson);
                if (isTrailImageArray(parsed)) {
                    trailImages = parsed;
                    // Preload images for instant trail effect
                    trailImages.forEach(img => {
                        const preloadImg = new Image();
                        preloadImg.src = img.url;
                    });
                } else {
                    console.error('Invalid trail images data structure:', parsed);
                }
            }
        } catch (e) {
            console.warn('Failed to parse trail images data:', e);
        }
    }

    // Initialize trail effect
    let trailEffect: TrailEffect | null = null;
    if (trailContainer && trailImages.length > 0) {
        trailEffect = new TrailEffect({
            container: trailContainer,
            listenTarget: introSection,
            images: trailImages,
            maxImages: 8,
            fadeInDuration: 0.4,
            fadeOutDuration: 0.6,
            fadeOutDelay: 1.0,
            imageSize: 250,
        });
        trailEffect.start();
    }

    // Wire scroll button click
    if (scrollButton) {
        scrollButton.addEventListener('click', () => {
            const nextSection = introSection.nextElementSibling as HTMLElement;
            if (nextSection) {
                nextSection.scrollIntoView({ behavior: 'smooth' });
            }
        });
    }

    // Initialize altitude sidebar (scroll-linked readout + track marker)
    let altitudeSidebar: AltitudeSidebar | null = null;
    const altitudeValueEl = document.querySelector('[data-altitude-value]') as HTMLElement;
    const altitudeBlobEl = document.querySelector('[data-altitude-blob]') as HTMLElement;
    const altitudeLevelEls = Array.from(document.querySelectorAll('[data-altitude-level]')) as HTMLElement[];
    const altitudeReadoutSections = Array.from(document.querySelectorAll('[data-section][data-altitude]')) as HTMLElement[];
    const altitudeLevelSections = Array.from(document.querySelectorAll('.home-mountain[data-altitude]')) as HTMLElement[];

    if (altitudeValueEl && altitudeBlobEl && altitudeLevelEls.length > 0 && altitudeReadoutSections.length > 0) {
        altitudeSidebar = new AltitudeSidebar({
            valueEl: altitudeValueEl,
            readoutSections: altitudeReadoutSections,
            blobEl: altitudeBlobEl,
            levelEls: altitudeLevelEls,
            levelSections: altitudeLevelSections,
        });
        altitudeSidebar.init();
    }

    // Parallax on the home-quote section's two images
    const quoteParallaxEffects: ParallaxEffect[] = [];
    document.querySelectorAll<HTMLElement>('.home-quote__image').forEach((wrapper, index) => {
        const img = wrapper.querySelector<HTMLElement>('.home-quote__img');
        if (!img) {
            return;
        }
        const parallax = new ParallaxEffect({
            trigger: wrapper,
            target: img,
            yPercent: index % 2 === 0 ? 15 : -15,
            start: 'top bottom',
            end: 'bottom top',
        });
        parallax.play();
        quoteParallaxEffects.push(parallax);

        const wrapperParallax = new ParallaxEffect({
            trigger: wrapper,
            target: wrapper,
            yPercent: index % 2 === 0 ? -50 : 50,
            start: 'top bottom',
            end: 'bottom top',
        });
        wrapperParallax.play();
        quoteParallaxEffects.push(wrapperParallax);
    });

    // Cleanup on page unload
    window.addEventListener('beforeunload', () => {
        homeBgTween?.scrollTrigger?.kill();
        homeBgTween?.kill();
        prehomeTrailEffect?.destroy();
        prehomeDescent?.destroy();
        prehomeTimeline?.kill();
        prehomeScrollTrigger?.kill();
        trailEffect?.destroy();
        altitudeSidebar?.destroy();
        quoteParallaxEffects.forEach(effect => effect.destroy());
        horizontalPinEffects.forEach(effect => effect.destroy());
    });
});
