/**
 * Audace — Pure-JS sticky-slide effect for `.is-style-slides-container`.
 *
 * Each `.is-style-slide` becomes `position: fixed` at the header offset.
 * We synthesize one "scroll budget" per slide: an entry phase (slide
 * rises from below), a pin phase (slide stays fixed while its tallest
 * overflowing column translates up), and an exit phase (only for the
 * last slide so the user can scroll past into normal content).
 *
 * The slides-container gets a computed height equal to the total scroll
 * budget so the page has real scroll distance to drive the effect.
 */
(function () {
    'use strict';

    var MIN_WIDTH = 782;
    var CONTAINER_SELECTOR = '.is-style-slides-container';
    var SLIDE_SELECTOR     = ':scope > .is-style-slide';

    var containers = []; // [{ el, slides: [{ el, tallCol, entry, pin, exit, start }], total }]

    function topOffset() {
        const adminBar = document.getElementById('wpadminbar');
        const header = document.querySelector('.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 ensureSlideInner(slide) {
        // Wrap the slide's direct children in a single translatable element
        // so we can pan it upward during the pin phase. `display: flow-root`
        // establishes a block formatting context so children's vertical
        // margins stay inside and don't collapse out of the wrapper — that
        // keeps offsetHeight accurate. Idempotent.
        const first = slide.firstElementChild;
        if (slide.children.length === 1 && first && first.classList.contains('audace-slide-inner')) {
            first.style.display = 'flow-root';
            return first;
        }
        const inner = slide.ownerDocument.createElement('div');
        inner.className = 'audace-slide-inner';
        inner.style.display = 'flow-root';
        while (slide.firstChild) {
            inner.appendChild(slide.firstChild);
        }
        slide.appendChild(inner);
        return inner;
    }

    function tallestOverflowingCol(slide, maxH) {
        // Columns-style slides: pick the tallest overflowing direct column.
        const cols = slide.querySelectorAll(':scope > .wp-block-column');
        if (cols.length) {
            let pick = null;
            let picked = 0;
            for (let i = 0; i < cols.length; i++) {
                const over = cols[i].scrollHeight - maxH;
                if (over > picked) { picked = over; pick = cols[i]; }
            }
            return pick;
        }
        // No columns (e.g. a group slide): treat the slide's whole content as
        // a single translatable block, but only if it actually overflows.
        if (slide.scrollHeight > maxH + 1) {
            return ensureSlideInner(slide);
        }
        return null;
    }

    function clearSlide(slide) {
        slide.style.position = '';
        slide.style.top = '';
        slide.style.left = '';
        slide.style.right = '';
        slide.style.width = '';
        slide.style.height = '';
        slide.style.zIndex = '';
        slide.style.transform = '';
        slide.style.visibility = '';
        slide.style.justifyContent = '';
        slide.style.alignItems = '';
        const tr = slide.querySelectorAll(':scope > .wp-block-column, :scope > .audace-slide-inner');
        tr.forEach(c => { c.style.transform = ''; c.style.translate = ''; });
    }

    function teardown() {
        containers.forEach(c => {
            c.el.style.height = '';
            c.el.style.position = '';
            c.slides.forEach(s => clearSlide(s.el));
        });
        containers = [];
    }

    function build() {
        teardown();

        const topH = topOffset();
        const slideH = window.innerHeight - topH;
        const mobile = window.innerWidth < MIN_WIDTH;

        document.querySelectorAll(CONTAINER_SELECTOR).forEach(container => {
            const slideEls = container.querySelectorAll(SLIDE_SELECTOR);
            if (!slideEls.length) return;

            // Measure container's layout rect BEFORE we pin anything.
            const cRect = container.getBoundingClientRect();
            const cLeft = cRect.left + window.scrollX;
            const cWidth = cRect.width;

            const slides = [];
            let cursor = 0; // cumulative scroll budget (px)
            slideEls.forEach((el, idx) => {
                // On mobile, a `is-style-bottom-sticky` cover in the 2nd column
                // stays fixed at the bottom 40vh — reduce the text column's
                // available height accordingly.
                const hasBottomSticky = mobile && !!el.querySelector(':scope > .wp-block-column:nth-child(2) .wp-block-cover.is-style-bottom-sticky');
                const colMaxH = hasBottomSticky ? slideH - window.innerHeight * 0.4 : slideH;
                const tall = tallestOverflowingCol(el, colMaxH);
                // For a group-slide wrapper, the slide's own padding eats into
                // the area available to show content, so subtract it when
                // computing how far the wrapper must pan.
                let availH = colMaxH;
                if (tall && tall.classList.contains('audace-slide-inner')) {
                    const cs = getComputedStyle(el);
                    availH = colMaxH - (parseFloat(cs.paddingTop) || 0) - (parseFloat(cs.paddingBottom) || 0);
                    // Flex-centered group slides clip both the top and the
                    // bottom when their content overflows — translating the
                    // inner up only reveals the bottom, so force the slide to
                    // align its content from the top while it's pinned. The
                    // original alignment is restored on teardown.
                    el.style.justifyContent = 'flex-start';
                    el.style.alignItems = 'stretch';
                }
                const tallH = tall ? Math.max(tall.offsetHeight || 0, tall.scrollHeight || 0) : 0;
                const pin = tall ? Math.max(0, tallH - availH) : 0;
                const entry = idx === 0 ? 0 : slideH;                 // first slide already on-screen
                const exit  = idx === slideEls.length - 1 ? slideH : 0; // last slide scrolls off so page can continue
                slides.push({ el, tallCol: tall, entry, pin, exit, start: cursor });
                cursor += entry + pin + exit;
            });

            // Reserve scroll distance: container height = total budget so the
            // natural content below starts exactly when the last slide's exit
            // phase finishes.
            container.style.position = 'relative';
            container.style.height = cursor + 'px';

            // Pin each slide at the header offset, aligned with the container's layout box.
            slideEls.forEach((el, idx) => {
                el.style.position = 'fixed';
                el.style.top = topH + 'px';
                el.style.left = cLeft + 'px';
                el.style.width = cWidth + 'px';
                el.style.height = slideH + 'px';
                el.style.zIndex = String(idx + 1);
                el.style.willChange = 'transform';
                // Clip to the pinned box so the inner wrapper (or a tall
                // column) can translate up during the pin phase without
                // its off-box content bleeding onto the page.
                el.style.overflow = 'hidden';
            });

            containers.push({ el: container, slides: slides, total: cursor, cLeft, cWidth, topH, maxH: slideH });
        });

        update();
    }

    function update() {
        const scrollY = window.scrollY;

        containers.forEach(c => {
            const cTop = c.el.getBoundingClientRect().top + window.scrollY;
            // Where inside this container are we? 0 when container top hits sticky offset.
            const p = scrollY - (cTop - c.topH);

            // Outside the container's scroll window → hide every slide so
            // they don't cover content above or below the slides-container.
            const insideContainer = p >= 0 && p <= c.total;

            // During the last slide's exit phase, lift the entire stack up
            // by the same amount so earlier slides (which would otherwise
            // stay pinned behind it) scroll off together.
            const last = c.slides[c.slides.length - 1];
            const exitStart = last.start + last.entry + last.pin;
            const containerShift = p > exitStart ? -Math.min(p - exitStart, last.exit) : 0;

            c.slides.forEach((s) => {
                const local = p - s.start;
                const entry = s.entry;
                const pin   = s.pin;

                let translateY = 0, colOffset = 0, visible = insideContainer;

                if (!insideContainer) {
                    // hidden
                } else if (local < 0) {
                    translateY = c.maxH;
                    visible = false;
                } else if (local < entry) {
                    translateY = c.maxH - local;
                } else if (local < entry + pin) {
                    colOffset = local - entry;
                } else {
                    // Past its own pin; wait here until the container-wide
                    // exit shift carries it off.
                    colOffset = pin;
                }

                translateY += containerShift;

                s.el.style.transform = 'translate3d(0,' + translateY + 'px,0)';
                s.el.style.visibility = visible ? '' : 'hidden';
                if (s.tallCol) {
                    const isInner = s.tallCol.classList.contains('audace-slide-inner');
                    if (isInner) {
                        // Group slide: use the CSS `translate` property so it
                        // composes cleanly with any transforms the slide (or
                        // descendants) may carry.
                        s.tallCol.style.translate = colOffset ? '0 ' + (-colOffset) + 'px' : '';
                    } else {
                        s.tallCol.style.transform = colOffset
                            ? 'translate3d(0,' + (-colOffset) + 'px,0)'
                            : '';
                    }
                }
            });
        });
    }

    // Track viewport width at last build. On mobile (iOS Safari, Chrome
    // Android) the address bar show/hide only changes innerHeight — firing a
    // `resize` event with the same width. Rebuilding on that would shift every
    // slide's cached position mid-scroll and produce visible jumps. Only
    // rebuild when the width actually changes (true orientation / window
    // resize / DPR change).
    var lastBuildW = window.innerWidth;
    function onResize() {
        if (window.innerWidth === lastBuildW) return;
        lastBuildW = window.innerWidth;
        build();
    }
    function onScroll() { if (containers.length) update(); }

    // Public helper: scrollY required to bring `el` to the top of the
    // viewport, accounting for the slides-container pinning. Falls back
    // to the normal getBoundingClientRect calc for elements outside any
    // slides-container.
    window.audaceScrollYFor = function (el) {
        console.log('[audace] audaceScrollYFor called with el:', el);
        if (!el) { console.log('[audace] no el → null'); return null; }
        var topH = topOffset();
        console.log('[audace] topOffset =', topH, 'containers.length =', containers.length);
        for (var i = 0; i < containers.length; i++) {
            var c = containers[i];
            console.log('[audace] checking container', i, 'with', c.slides.length, 'slides');
            for (var j = 0; j < c.slides.length; j++) {
                var slide = c.slides[j].el;
                if (slide === el || slide.contains(el)) {
                    var cRect = c.el.getBoundingClientRect();
                    var cTop  = cRect.top + window.scrollY;
                    // slide.start puts the slide at the START of its entry
                    // phase (still off-screen below). Add entry so the slide
                    // has finished rising and its top is aligned with topH.
                    var y = (cTop - topH) + c.slides[j].start + c.slides[j].entry;
                    console.log('[audace] match in container', i, 'slide', j,
                        '→ cTop =', cTop, ', start =', c.slides[j].start,
                        ', entry =', c.slides[j].entry, ', y =', y);
                    return y;
                }
            }
        }
        var r = el.getBoundingClientRect();
        var y2 = window.scrollY + r.top - topH;
        console.log('[audace] el not inside any container → natural y =', y2, 'rect:', r);
        return y2;
    };

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

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