/**
 * Svelte action for arrow animations. Two strategies depending on mode:
 *
 *  • 'draw' / 'undraw' / 'draw-undraw' — clip-path inset on the host <svg>.
 *    Doesn't touch stroke-dasharray, so dashed/dotted patterns are preserved
 *    while the path is progressively revealed/hidden from one side.
 *
 *  • '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 baseDash = '';
    let baseOffset = '';
    function clearArrowPath() {
        if (!pathEl)
            return;
        pathEl.style.strokeDasharray = baseDash;
        pathEl.style.strokeDashoffset = baseOffset;
    }
    function reset() {
        if (raf)
            cancelAnimationFrame(raf);
        raf = 0;
        node.style.clipPath = '';
        clearArrowPath();
    }
    function run() {
        reset();
        if (!params.enabled || params.mode === 'none')
            return;
        // Capture the inner arrow path (used by 'flow' mode).
        pathEl = node.querySelector('.arrow-path');
        if (pathEl) {
            baseDash = pathEl.style.strokeDasharray || '';
            baseOffset = pathEl.style.strokeDashoffset || '';
        }
        // Pick the dominant axis so vertical arrows reveal top↔bottom and horizontal
        // arrows reveal left↔right. clip-path inset(top right bottom left).
        const dx = params.endX - params.startX;
        const dy = params.endY - params.startY;
        const horizontal = Math.abs(dx) >= Math.abs(dy);
        const positive = horizontal ? dx >= 0 : dy >= 0;
        const goesPositive = params.reverse ? !positive : positive;
        // Build the clip-path string for a given progress (0 = fully hidden,
        // 100 = fully visible) on the dominant axis.
        function clip(insetPct) {
            if (horizontal) {
                return goesPositive
                    ? `inset(0 ${insetPct}% 0 0)` // hide from right edge
                    : `inset(0 0 0 ${insetPct}%)`; // hide from left edge
            }
            return goesPositive
                ? `inset(0 0 ${insetPct}% 0)` // hide from bottom
                : `inset(${insetPct}% 0 0 0)`; // hide from top
        }
        const clipFull = clip(100); // fully hidden (100% inset on hide side)
        const clipNone = clip(0);
        // Round duration so an integer number of cycles fits in slide_duration.
        // Without this, GIF loop boundary shows the arrow mid-draw → snap to
        // invisible → re-draw, which the user perceives as a reset.
        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') {
            if (!pathEl)
                return;
            // If the arrow has no inline dasharray (i.e. it's solid), give it one
            // so dashes are visible to flow. Otherwise keep the user's pattern.
            const dashAttr = pathEl.getAttribute('stroke-dasharray');
            if (!dashAttr || dashAttr === 'none') {
                pathEl.style.strokeDasharray = '8 5';
            }
            // One pixel of dashoffset shift per ms feels like a steady current; use
            // `duration` as the ms per cycle of 24px (one base dash repeat-ish).
            const cycle = 24;
            const dir = params.reverse ? 1 : -1;
            function flowStep(now) {
                const elapsed = now - start;
                const offset = (dir * (elapsed / dur) * cycle) % (cycle * 1000); // huge mod just to keep it bounded
                if (pathEl)
                    pathEl.style.strokeDashoffset = String(offset);
                raf = requestAnimationFrame(flowStep);
            }
            raf = requestAnimationFrame(flowStep);
            return;
        }
        // Initial clip-path state for draw modes.
        if (m === 'draw' || m === 'draw-undraw') {
            node.style.clipPath = clipFull;
        }
        else if (m === 'undraw') {
            node.style.clipPath = clipNone;
        }
        function step(now) {
            const elapsed = now - start;
            if (m === 'draw') {
                const t = Math.min(elapsed / dur, 1);
                const eased = 1 - Math.pow(1 - t, 3);
                node.style.clipPath = clip(100 * (1 - eased));
                if (t < 1)
                    raf = requestAnimationFrame(step);
                else if (params.loop)
                    run();
                else {
                    node.style.clipPath = clipNone;
                    raf = 0;
                }
            }
            else if (m === 'undraw') {
                const t = Math.min(elapsed / dur, 1);
                const eased = 1 - Math.pow(1 - t, 3);
                node.style.clipPath = clip(100 * eased);
                if (t < 1)
                    raf = requestAnimationFrame(step);
                else if (params.loop)
                    run();
                else {
                    node.style.clipPath = clipFull;
                    raf = 0;
                }
            }
            else if (m === 'draw-undraw') {
                const half = dur / 2;
                if (elapsed < half) {
                    const t = Math.min(elapsed / half, 1);
                    const eased = 1 - Math.pow(1 - t, 3);
                    node.style.clipPath = clip(100 * (1 - eased));
                    raf = requestAnimationFrame(step);
                }
                else {
                    const t = Math.min((elapsed - half) / half, 1);
                    const eased = 1 - Math.pow(1 - t, 3);
                    node.style.clipPath = clip(100 * eased);
                    if (t < 1)
                        raf = requestAnimationFrame(step);
                    else if (params.loop)
                        run();
                    else {
                        node.style.clipPath = clipFull;
                        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);
                }
            }
        }
    };
}
