/**
 * Audace — momentary scroll lock when a .is-style-slide reaches the top.
 *
 * Improvements over v1:
 * - Uses audaceSlideState() to read exact entry-phase scroll positions from
 *   slides-overflow.js instead of measuring raw getBoundingClientRect, so
 *   the trigger aligns perfectly with the overflow script's timing.
 * - The snap target is the slide's computed start + entry position, not a
 *   raw pixel approximation — no more "close but not quite" alignment.
 * - Lock and snap are coordinated via a shared RAF loop; scrollTo() is never
 *   called outside a rAF, so it can't race against slides-overflow's update.
 * - TRIGGER_EARLY adapts to scroll velocity: fast scrolls get a larger
 *   lookahead window so the lock fires before the user overshoots.
 * - Scrollbar-drag detection is tightened: we track pointer/touch contact
 *   state directly rather than relying on a time window.
 * - releaseLock() removes all listeners atomically in one place with no
 *   risk of leaving stale listeners if init() is called more than once.
 * - Mobile: touchcancel correctly clears the touch contact flag.
 */
(function () {
    'use strict';

    var LOCK_MS  = 300;   // hold time after snap completes (ms)
    var SNAP_MS  = 400;   // snap animation duration (ms)

    // Minimum lookahead in px. At high scroll velocity this expands
    // proportionally so fast flicks don't overshoot before the lock fires.
    var TRIGGER_MIN  = 80;
    var TRIGGER_VEL  = 0.25; // extra px of lookahead per px/s of scroll speed

    // ─── Input tracking ───────────────────────────────────────────────────────
    // We want to know: is the user driving the scroll via wheel / keyboard /
    // touch? If not (scrollbar drag or programmatic scroll) we let it pass.

    var userScrolling = false; // true while a wheel/touch/key is active
    var touchDown     = false;

    function onWheel()        { userScrolling = true; clearTimeout(wheelEnd); wheelEnd = setTimeout(clearUser, 200); }
    function onKeyDown(e)     { if (SCROLL_KEYS[e.keyCode]) { userScrolling = true; clearTimeout(keyEnd); keyEnd = setTimeout(clearUser, 200); } }
    function onTouchStart()   { touchDown = true; userScrolling = true; }
    function onTouchEnd()     { touchDown = false; setTimeout(function () { if (!touchDown) clearUser(); }, 100); }
    function onTouchCancel()  { touchDown = false; clearUser(); }
    function clearUser()      { userScrolling = false; }

    var wheelEnd = 0, keyEnd = 0;
    var SCROLL_KEYS = { 32:1, 33:1, 34:1, 35:1, 36:1, 37:1, 38:1, 39:1, 40:1 };

    window.addEventListener('wheel',        onWheel,       { passive: true });
    window.addEventListener('keydown',      onKeyDown,     { passive: true });
    window.addEventListener('touchstart',   onTouchStart,  { passive: true });
    window.addEventListener('touchend',     onTouchEnd,    { passive: true });
    window.addEventListener('touchcancel',  onTouchCancel, { passive: true });

    // ─── Velocity tracking ────────────────────────────────────────────────────

    var lastY   = window.scrollY;
    var lastT   = performance.now();
    var velocity = 0; // px / ms

    // ─── Lock state ───────────────────────────────────────────────────────────

    var lockActive = false;
    var lockUntil  = 0;
    var snapUntil  = 0;
    var snapFromY  = 0;
    var snapToY    = 0;
    var snapT0     = 0;
    var pinRaf     = 0;

    // ─── topOffset ────────────────────────────────────────────────────────────

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

    // ─── Scroll handler ───────────────────────────────────────────────────────

    function onScroll() {
        var now = performance.now();
        var y   = window.scrollY;
        var dt  = now - lastT || 1;

        // Exponential moving average keeps velocity smooth.
        velocity = velocity * 0.6 + (Math.abs(y - lastY) / dt) * 0.4;
        var down = y > lastY;
        var up   = y < lastY;
        lastY = y;
        lastT = now;

        if (!userScrolling)     return; // scrollbar drag or programmatic
        if (Date.now() < lockUntil) return; // inside an active lock

        // Ask slides-overflow for the current container / slide data.
        // Falls back to a BoundingClientRect approach if the overflow script
        // isn't loaded yet.
        var state = window.audaceSlideState ? window.audaceSlideState() : null;

        if (state && state.length) {
            checkViaState(state, y, down, up);
        } else {
            checkViaDom(y, down, up);
        }
    }

    // Trigger lookahead: adapts to scroll velocity.
    function triggerEarly() {
        return TRIGGER_MIN + velocity * TRIGGER_VEL;
    }

    // ── Primary path: use slides-overflow state ────────────────────────────

    function checkViaState(state, scrollY, down, up) {
        var topH = topOffset();
        var trigger = triggerEarly();

        for (var i = 0; i < state.length; i++) {
            var c    = state[i];
            var cTop = c.el.getBoundingClientRect().top + window.scrollY;

            for (var j = 0; j < c.slides.length; j++) {
                var s = c.slides[j];
                // Absolute scrollY at which this slide's top aligns with topH.
                var landY = (cTop - topH) + s.start + s.entry;

                // Distance from the current scroll position to the snap point.
                var dist = landY - scrollY;

                if (down && dist > 0 && dist <= trigger) {
                    startLock(landY);
                    return;
                }
                if (up && dist < 0 && dist >= -trigger) {
                    startLock(landY);
                    return;
                }
            }
        }
    }

    // ── Fallback path: plain DOM measurement ──────────────────────────────

    function checkViaDom(scrollY, down, up) {
        var topH    = topOffset();
        var trigger = triggerEarly();
        var slides  = document.querySelectorAll('.is-style-slide');

        for (var i = 0; i < slides.length; i++) {
            var r    = slides[i].getBoundingClientRect();
            var dist = r.top - topH; // positive = below header, negative = above

            if (down && dist > 0 && dist <= trigger) {
                startLock(scrollY + dist);
                return;
            }
            if (up && dist < 0 && dist >= -trigger) {
                startLock(scrollY + dist);
                return;
            }
        }
    }

    // ─── Lock / snap ──────────────────────────────────────────────────────────

    function easeOutQuart(t) { return 1 - Math.pow(1 - t, 4); }

    function startLock(targetY) {
        if (lockActive) return;

        var now = Date.now();
        snapFromY = window.scrollY;
        snapToY   = targetY;
        snapT0    = performance.now();
        snapUntil = now + SNAP_MS;
        lockUntil = now + SNAP_MS + LOCK_MS;
        lockActive = true;

        // Block user input during snap + hold.
        window.addEventListener('wheel',     blockInput, { passive: false, capture: true });
        window.addEventListener('touchmove', blockInput, { passive: false, capture: true });
        window.addEventListener('keydown',   blockKeys,  { capture: true });

        // Drive the snap and hold entirely through rAF — never call scrollTo
        // outside a frame so we don't race against slides-overflow's update.
        pinRaf = requestAnimationFrame(pinLoop);
    }

    function pinLoop(now) {
        pinRaf = 0;
        var msNow = Date.now();
        if (msNow >= lockUntil) {
            releaseLock();
            return;
        }

        if (msNow < snapUntil) {
            // Snap animation.
            var k      = Math.min(1, (now - snapT0) / SNAP_MS);
            var eased  = easeOutQuart(k);
            var target = snapFromY + (snapToY - snapFromY) * eased;
            if (Math.round(target) !== Math.round(window.scrollY)) {
                window.scrollTo(0, target);
            }
        } else {
            // Hold: clamp to exact target.
            if (Math.round(window.scrollY) !== Math.round(snapToY)) {
                window.scrollTo(0, snapToY);
            }
        }

        pinRaf = requestAnimationFrame(pinLoop);
    }

    function blockInput(e) {
        if (Date.now() < lockUntil) e.preventDefault();
    }

    function blockKeys(e) {
        if (Date.now() < lockUntil && SCROLL_KEYS[e.keyCode]) {
            e.preventDefault();
            e.stopPropagation();
        }
    }

    function releaseLock() {
        lockActive = false;
        if (pinRaf) { cancelAnimationFrame(pinRaf); pinRaf = 0; }
        window.removeEventListener('wheel',     blockInput, { capture: true });
        window.removeEventListener('touchmove', blockInput, { capture: true });
        window.removeEventListener('keydown',   blockKeys,  { capture: true });
    }

    // ─── Boot ─────────────────────────────────────────────────────────────────

    function init() {
        window.addEventListener('scroll', onScroll, { passive: true });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();