/**
 * Svelte action for arrow animations. Two strategies depending on mode:
 *
 *  • 'draw' / 'undraw' / 'draw-undraw' — reveals the stroke ALONG THE PATH using
 *    stroke-dasharray/offset keyed to the path's total length. This follows the
 *    real geometry, so curved, looping and spiral arrows draw the way they're
 *    shaped (a linear clip-path wipe could only sweep left↔right / top↔bottom).
 *    The arrowhead (a separate sub-path) is revealed in step. Any dashed/dotted
 *    pattern is overridden during the reveal and restored when fully drawn.
 *
 *  • 'flow' — marching ants. Continuously shifts stroke-dashoffset on the
 *    inner .arrow-path, so the dash pattern appears to flow along the path
 *    like a current. Forward/reverse controls flow direction. Inherently
 *    looping; the loop flag is ignored.
 */
export function arrowClipDraw(node, params) {
    let raf = 0;
    let pathEl = null;
    let headEl = null;
    let baseDash = '';
    let baseOffset = '';
    function restorePath() {
        // Back to the element's natural (fully-drawn) appearance.
        if (pathEl) {
            pathEl.style.strokeDasharray = baseDash;
            pathEl.style.strokeDashoffset = baseOffset;
        }
        if (headEl)
            headEl.style.opacity = '';
    }
    function reset() {
        if (raf)
            cancelAnimationFrame(raf);
        raf = 0;
        node.style.clipPath = '';
        restorePath();
    }
    function run() {
        reset();
        if (!params.enabled || params.mode === 'none')
            return;
        pathEl = node.querySelector('.arrow-path');
        headEl = node.querySelector('.arrow-head');
        if (!pathEl)
            return;
        baseDash = pathEl.style.strokeDasharray || '';
        baseOffset = pathEl.style.strokeDashoffset || '';
        // Round duration so an integer number of cycles fits in slide_duration.
        // Without this, a GIF loop boundary can show the arrow mid-draw → snap.
        const requested = Math.max(50, params.duration);
        const dur = params.slideDuration && params.slideDuration > 0 && (params.loop || params.mode === 'flow')
            ? params.slideDuration / Math.max(1, Math.round(params.slideDuration / requested))
            : requested;
        const start = performance.now();
        const m = params.mode;
        // FLOW: marching ants — animate dashoffset continuously, keep base pattern.
        if (m === 'flow') {
            const dashAttr = pathEl.getAttribute('stroke-dasharray');
            if (!dashAttr || dashAttr === 'none') {
                pathEl.style.strokeDasharray = '8 5';
            }
            const cycle = 24;
            const dir = params.reverse ? 1 : -1;
            function flowStep(now) {
                const elapsed = now - start;
                const offset = (dir * (elapsed / dur) * cycle) % (cycle * 1000);
                if (pathEl)
                    pathEl.style.strokeDashoffset = String(offset);
                raf = requestAnimationFrame(flowStep);
            }
            raf = requestAnimationFrame(flowStep);
            return;
        }
        // PATH-LENGTH REVEAL for draw / undraw / draw-undraw.
        let len = 0;
        try {
            len = pathEl.getTotalLength();
        }
        catch {
            len = 0;
        }
        if (!len) {
            // Degenerate path — nothing sensible to animate.
            restorePath();
            return;
        }
        // dashoffset reveals from the path start (positive) or the path end
        // (negative) toward the other end, so `reverse` flips the draw direction.
        const hideOffset = params.reverse ? -len : len;
        // p: 0 = nothing drawn, 1 = fully drawn (along the path).
        function setProgress(p) {
            if (!pathEl)
                return;
            pathEl.style.strokeDasharray = `${len} ${len}`;
            pathEl.style.strokeDashoffset = String(hideOffset * (1 - p));
        }
        // Head only shows once the end of the line is reached.
        function setHead(p) {
            if (headEl)
                headEl.style.opacity = p >= 0.999 ? '1' : '0';
        }
        const cubicOut = (t) => 1 - Math.pow(1 - t, 3);
        // Initial frame.
        if (m === 'undraw') {
            setProgress(1);
            setHead(1);
        }
        else {
            setProgress(0);
            setHead(0);
        }
        function step(now) {
            const elapsed = now - start;
            if (m === 'draw') {
                const t = Math.min(elapsed / dur, 1);
                const p = cubicOut(t);
                setProgress(p);
                setHead(p);
                if (t < 1)
                    raf = requestAnimationFrame(step);
                else if (params.loop)
                    run();
                else {
                    restorePath();
                    raf = 0;
                } // settle fully drawn (restores dashes)
            }
            else if (m === 'undraw') {
                const t = Math.min(elapsed / dur, 1);
                const p = 1 - cubicOut(t);
                setProgress(p);
                setHead(p);
                if (t < 1)
                    raf = requestAnimationFrame(step);
                else if (params.loop)
                    run();
                else {
                    setProgress(0);
                    setHead(0);
                    raf = 0;
                } // settle hidden
            }
            else if (m === 'draw-undraw') {
                const half = dur / 2;
                if (elapsed < half) {
                    const p = cubicOut(Math.min(elapsed / half, 1));
                    setProgress(p);
                    setHead(p);
                    raf = requestAnimationFrame(step);
                }
                else {
                    const t = Math.min((elapsed - half) / half, 1);
                    const p = 1 - cubicOut(t);
                    setProgress(p);
                    setHead(p);
                    if (t < 1)
                        raf = requestAnimationFrame(step);
                    else if (params.loop)
                        run();
                    else {
                        setProgress(0);
                        setHead(0);
                        raf = 0;
                    }
                }
            }
        }
        raf = requestAnimationFrame(step);
    }
    // Defer to next tick so the inner <path class="arrow-path"> is already
    // rendered when we query it.
    queueMicrotask(run);
    // Register so the video-export pipeline can restart this animation after
    // activating the virtual clock (see initFirstSlideAnimations).
    if (typeof window !== 'undefined') {
        const reg = (window.__svgAnimRestart ||= []);
        reg.push(run);
        node.__svgAnimRestart = run;
    }
    let lastKey = params.key;
    return {
        update(p) {
            // Only restart when the explicit key actually changed (avoids constant
            // resets from Svelte's per-render fresh params object).
            const keyChanged = p.key !== lastKey;
            params = p;
            if (keyChanged) {
                lastKey = p.key;
                queueMicrotask(run);
            }
        },
        destroy() {
            reset();
            if (typeof window !== 'undefined') {
                const reg = window.__svgAnimRestart;
                const fn = node.__svgAnimRestart;
                if (reg && fn) {
                    const idx = reg.indexOf(fn);
                    if (idx >= 0)
                        reg.splice(idx, 1);
                }
            }
        }
    };
}
