import gsap from 'gsap';
import lottie, { type AnimationItem } from 'lottie-web/build/player/lottie_light';

/** Background rectangle baked into both Lottie files (2086x768, cream). Dropped on load:
 *  the préhome already owns its themed background, and the rectangle would show its
 *  edges as soon as the viewport ratio differs from the artwork's. */
const BACKGROUND_LAYER = 'Fond';

export interface LottieDescentOptions {
    /** Host element the two animations are appended to. */
    container: HTMLElement;
    /** Lottie JSON of the "Été" scene (cycliste). */
    lightSrc: string;
    /** Lottie JSON of the "Hiver" scene (skieur). */
    darkSrc: string;
}

interface LottieSlot {
    element: HTMLElement;
    animation: AnimationItem;
}

interface LottieData {
    layers: { nm?: string }[];
}

/**
 * LottieDescent — the préhome scene: a character (cycliste = Été, skieur = Hiver)
 * riding down a mountain ridge, authored as two Lottie files and scrubbed by scroll.
 *
 * Like MountainDraw, this class only prepares the targets and returns a *paused*
 * timeline of duration 1 — the scroll scrub stays in assets/home.ts, which owns the
 * whole préhome sequence.
 *
 * Both scenes are mounted side by side and switched in CSS on `[data-theme='dark']`,
 * so a theme change mid-descent costs no re-fetch and loses no scroll progress. Both
 * are driven on every tick (shortcut: two SVG renders per frame instead of one —
 * drive only the visible one if profiling says so).
 */
export class LottieDescent {
    private readonly container: HTMLElement;
    private readonly lightSrc: string;
    private readonly darkSrc: string;

    private slots: LottieSlot[] = [];
    private timeline: gsap.core.Timeline | null = null;

    constructor(options: LottieDescentOptions) {
        this.container = options.container;
        this.lightSrc = options.lightSrc;
        this.darkSrc = options.darkSrc;
    }

    /**
     * Fetches both scenes and mounts them on frame 0. Resolves to `false` when
     * anything is missing — the préhome keeps working without the descent.
     */
    public async init(): Promise<boolean> {
        const [light, dark] = await Promise.all([
            this.load(this.lightSrc, 'light'),
            this.load(this.darkSrc, 'dark'),
        ]);
        if (!light || !dark) {
            light?.animation.destroy();
            dark?.animation.destroy();
            light?.element.remove();
            dark?.element.remove();
            return false;
        }

        this.slots = [light, dark];

        return true;
    }

    /** Builds the (paused) timeline: its progress 0 → 1 plays the scene frame 0 → last. */
    public buildTimeline(): gsap.core.Timeline | null {
        if (this.slots.length === 0) {
            return null;
        }

        const lastFrame = Math.max(...this.slots.map(({ animation }) => animation.totalFrames - 1));
        const playhead = { frame: 0 };

        const tl = gsap.timeline({ paused: true });
        tl.to(playhead, {
            frame: lastFrame,
            duration: 1,
            ease: 'none',
            onUpdate: () => {
                this.slots.forEach(({ animation }) => animation.goToAndStop(playhead.frame, true));
            },
        }, 0);

        this.timeline = tl;

        return tl;
    }

    public destroy(): void {
        this.timeline?.kill();
        this.timeline = null;
        this.slots.forEach(({ animation, element }) => {
            animation.destroy();
            element.remove();
        });
        this.slots = [];
    }

    private async load(src: string, theme: 'light' | 'dark'): Promise<LottieSlot | null> {
        let data: LottieData;
        try {
            const response = await fetch(src);
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }
            data = await response.json() as LottieData;
        } catch (error) {
            console.warn('[LottieDescent] Failed to load animation:', src, error);
            return null;
        }

        data.layers = data.layers.filter((layer) => layer.nm !== BACKGROUND_LAYER);

        const element = document.createElement('div');
        element.setAttribute('data-lottie-slot', theme);
        this.container.appendChild(element);

        const animation = lottie.loadAnimation({
            container: element,
            renderer: 'svg',
            loop: false,
            autoplay: false,
            animationData: data,
            // `meet`: the whole ridge always stays in frame, whatever the viewport.
            rendererSettings: { preserveAspectRatio: 'xMidYMid meet' },
        });
        animation.goToAndStop(0, true);

        return { element, animation };
    }
}
