/**
 * Audace — smooth snap scrolling via the `scroll-snap` library.
 *
 * Uses the esm.sh CDN to load it as a native ES module (no bundler).
 * Replaces the former CSS `scroll-snap-type` rules; the library takes
 * over and programmatically animates scrollTop after the user stops.
 *
 * Stride = one viewport minus the admin bar + site header, so each
 * snap point lands with the slide's top at the sticky line.
 */
import createScrollSnap from '../vendor/scroll-snap.js?v=5';

const MIN_WIDTH = 782;

// Duration (ms) of the menu → slide scroll animation on desktop.
// Increase for a slower, more visible scroll; decrease for a snappier jump.
const MENU_SCROLL_MS = 2000;

function topOffset() {
    const adminBar = document.getElementById('wpadminbar');
    const header   = document.querySelector(
        '.audace-site-header, .wp-block-template-part.site-header, header.wp-block-template-part, #masthead'
    );
    const adminH  = adminBar ? adminBar.getBoundingClientRect().height : 0;
    const headerH = header   ? header.getBoundingClientRect().height   : 0;
    return adminH + headerH;
}

function slideHeight() {
    return Math.max(1, window.innerHeight - topOffset());
}

let controller = null;
let isSnapEngaged = false;

/* Scroll position past which snap is disengaged. Computed from the number of
 * sticky slides on the page: the last snap point sits at (slides-1) * stride,
 * and beyond it we want the footer to be freely reachable. */
function lastSnapTop() {
    const slides = document.querySelectorAll('.is-style-slide');
    if (!slides.length) return 0;
    return (slides.length - 1) * slideHeight();
}

function engageSnap() {
    if (controller && !isSnapEngaged) {
        try { controller.bind(); isSnapEngaged = true; } catch (e) {}
    }
}

function disengageSnap() {
    if (controller && isSnapEngaged) {
        try { controller.unbind(); isSnapEngaged = false; } catch (e) {}
    }
}

/* Keep snap engaged while scrolling within the slides area; turn it off
 * entirely once past the last slide, or whenever the page is scrolled to the
 * bottom, so the footer is reachable. The 2px hysteresis avoids thrashing
 * right at the boundary. */
function evaluateSnapState() {
    if (!controller) return;
    const scroller = document.scrollingElement || document.documentElement;
    const threshold = lastSnapTop();
    const atBottom  = scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 2;
    if (atBottom || scroller.scrollTop > threshold + 2) disengageSnap();
    else engageSnap();
}

/* Animate scrollTop to `to` over MENU_SCROLL_MS with easeOutCubic.
 * Continuous writes keep the snap library from settling mid-way, and
 * the final value is a stride multiple so there's nothing to snap to. */
function animateScrollTo(to) {
    const scroller = document.scrollingElement || document.documentElement;
    const from     = scroller.scrollTop;
    if (Math.abs(to - from) < 1) return;
    const t0   = performance.now();
    const ease = (t) => 1 - Math.pow(1 - t, 3);
    function step(now) {
        const k = Math.min(1, (now - t0) / MENU_SCROLL_MS);
        scroller.scrollTop = from + (to - from) * ease(k);
        if (k < 1) requestAnimationFrame(step);
        else scroller.scrollTop = to;
    }
    requestAnimationFrame(step);
}

function activate() {
    if (controller) {
        try { controller.unbind(); } catch (e) {}
        controller = null;
        isSnapEngaged = false;
    }
    if (window.innerWidth < MIN_WIDTH) return;

    const scroller = document.scrollingElement || document.documentElement;
    if (!scroller) return;

    const api = createScrollSnap(
        scroller,
        {
            snapDestinationX: '0px',
            snapDestinationY: slideHeight() + 'px',
            timeout:  100,
            duration: 400,
            threshold: 0.2,
            snapStop: false,
            enableKeyboard: true,
        }
    );
    api.bind();
    controller = api; // { bind, unbind, snap }
    isSnapEngaged = true;
    evaluateSnapState();
}

window.addEventListener('scroll', evaluateSnapState, { passive: true });

if (document.readyState === 'complete') {
    activate();
} else {
    window.addEventListener('load', activate, { once: true });
}

/* Public API.
 *
 * snapTo(yTarget): step the library's internal `snap(direction)` one
 * stride at a time until the current stride matches the stride that
 * contains `yTarget`. Works WITH the library instead of against it, so
 * the subsequent user scroll isn't yanked back to the previous snap
 * point. Only usable on desktop (library isn't bound on mobile).
 */
window.audaceScrollSnap = {
    isActive() { return !!controller; },
    /* Jump to the slide that contains `targetEl`. With sticky slides, scroll
     * position inside a stride tells you nothing about which slide is
     * visually on top, so `scrollY/stride` is unreliable for going "up".
     * Use the slide's actual DOM index among all .is-style-slide elements
     * and set scrollTop = index * stride directly. Instant, no animation. */
    snapToElement(targetEl) {
        if (!controller) return false;
        const slides = Array.prototype.slice.call(
            document.querySelectorAll('.is-style-slide')
        );
        let idx = -1;
        for (let i = 0; i < slides.length; i++) {
            if (slides[i] === targetEl || slides[i].contains(targetEl)) {
                idx = i;
                break;
            }
        }
        if (idx < 0) return false;
        animateScrollTo(idx * slideHeight());
        return true;
    },
};

/* Rebind on:
 *   - desktop/mobile threshold crossing (width-based), OR
 *   - a meaningful VERTICAL resize on desktop — because
 *     `snapDestinationY` is captured at bind time, so a taller/shorter
 *     viewport would leave the library snapping at stale offsets.
 * Height-only rebinds are debounced so every intermediate size during a
 * drag doesn't trigger a rebind storm. */
let lastIsDesktop = window.innerWidth >= MIN_WIDTH;
let lastHeight    = window.innerHeight;
let heightTimer   = 0;
window.addEventListener('resize', () => {
    const isDesktop = window.innerWidth >= MIN_WIDTH;
    if (isDesktop !== lastIsDesktop) {
        lastIsDesktop = isDesktop;
        setTimeout(() => {
            try { activate(); }
            catch (e) { console.warn('[audace] scroll-snap activate failed:', e); }
        }, 50);
        return;
    }
    if (isDesktop && Math.abs(window.innerHeight - lastHeight) > 2) {
        clearTimeout(heightTimer);
        heightTimer = setTimeout(() => {
            lastHeight = window.innerHeight;
            try { activate(); }
            catch (e) { console.warn('[audace] scroll-snap activate failed:', e); }
            // Realign to the nearest stride so the library and visible
            // scroll position match after the viewport changed.
            const stride = slideHeight();
            const scroller = document.scrollingElement || document.documentElement;
            const nearest  = Math.round(scroller.scrollTop / stride) * stride;
            animateScrollTo(nearest);
        }, 200);
    }
});
