/**
 * Svelte action: animates SVG paths inside the host node using a JS RAF loop.
 *
 * Why JS instead of CSS animations: CSS animations had timing issues — by the
 * time the action finished computing each path's actual length via
 * getTotalLength(), the CSS animation had already started using a fallback
 * value (1000) which doesn't match the path's real length, causing the path
 * to either flicker or stay fully visible. Doing everything in JS is reliable.
 *
 * Modes:
 *   • draw         — strokes appear from 0 → length (handle reverse to flip)
 *   • undraw       — strokes disappear from length → 0
 *   • draw-undraw  — appear then disappear, repeats if loop=true
 *   • flow         — marching ants. Keeps base dasharray, shifts offset.
 *   • none         — no animation; restore original attributes.
 */
/** Returns true if the element has a visible stroke (so dashoffset animation
 * will actually look like something). Fill-only paths can't be path-traced. */
function hasStroke(el) {
    const stroke = el.getAttribute('stroke') ?? getComputedStyle(el).stroke;
    if (!stroke || stroke === 'none' || stroke === 'transparent')
        return false;
    // rgba(0,0,0,0) == transparent
    if (/^rgba?\([^)]*,\s*0(\.0+)?\s*\)$/i.test(stroke))
        return false;
    return true;
}
export function traceSvgPaths(node, params) {
    let raf = 0;
    let states = [];
    /** When true, the SVG is fill-based and we use clip-path on the wrapper
     * instead of per-path dashoffset (which does nothing on filled shapes). */
    let useClipPath = false;
    function gather() {
        const els = node.querySelectorAll('path, circle, rect, line, polyline, polygon, ellipse');
        states = [];
        let strokeCount = 0;
        let totalCount = 0;
        for (const el of els) {
            let len = 0;
            try {
                if (typeof el.getTotalLength === 'function')
                    len = el.getTotalLength();
            }
            catch {
                len = 0;
            }
            if (len > 0) {
                totalCount++;
                if (hasStroke(el)) {
                    strokeCount++;
                    states.push({
                        el,
                        length: len,
                        baseDash: el.getAttribute('stroke-dasharray'),
                        baseOffset: el.getAttribute('stroke-dashoffset')
                    });
                }
            }
        }
        // If most/all paths are fill-based (no stroke), fall back to wrapper clip-path.
        useClipPath = totalCount > 0 && strokeCount < totalCount * 0.5;
    }
    function restore() {
        for (const s of states) {
            if (s.baseDash)
                s.el.style.strokeDasharray = s.baseDash;
            else
                s.el.style.removeProperty('stroke-dasharray');
            if (s.baseOffset)
                s.el.style.strokeDashoffset = s.baseOffset;
            else
                s.el.style.removeProperty('stroke-dashoffset');
        }
        // Clear any clip-path we may have set on the wrapper.
        node.style.clipPath = '';
    }
    function reset() {
        if (raf)
            cancelAnimationFrame(raf);
        raf = 0;
        restore();
    }
    function run() {
        reset();
        if (!params.enabled || params.mode === 'none')
            return;
        gather();
        const dur = Math.max(50, params.duration);
        let start = null;
        const m = params.mode;
        // Fill-based SVGs (e.g. complex logos/glyphs with fill="#fff") can't be
        // meaningfully animated via stroke-dashoffset. Fall back to a clip-path
        // reveal on the wrapper element. The flow/marching-ants mode also can't
        // work without a stroke, so for fill-only SVGs we treat all draw modes
        // as a wipe reveal.
        if (useClipPath) {
            const reveal = m === 'flow' ? 'draw' : m; // flow → wipe-in for fill icons
            const dir = params.reverse ? 'right' : 'left';
            function clip(insetPct) {
                switch (dir) {
                    case 'left': return `inset(0 ${insetPct}% 0 0)`;
                    case 'right': return `inset(0 0 0 ${insetPct}%)`;
                    case 'top': return `inset(0 0 ${insetPct}% 0)`;
                    case 'bottom': return `inset(${insetPct}% 0 0 0)`;
                }
            }
            node.style.clipPath = reveal === 'draw' || reveal === 'draw-undraw' ? clip(100) : clip(0);
            function clipStep(t) {
                if (start === null)
                    start = t;
                const elapsed = t - start;
                const progress = Math.min(elapsed / dur, 1);
                const eased = 1 - Math.pow(1 - progress, 3);
                let inset = 0;
                if (reveal === 'draw')
                    inset = 100 * (1 - eased);
                else if (reveal === 'undraw')
                    inset = 100 * eased;
                else if (reveal === 'draw-undraw') {
                    if (progress < 0.5) {
                        const lt = progress / 0.5;
                        const le = 1 - Math.pow(1 - lt, 3);
                        inset = 100 * (1 - le);
                    }
                    else {
                        const lt = (progress - 0.5) / 0.5;
                        const le = 1 - Math.pow(1 - lt, 3);
                        inset = 100 * le;
                    }
                }
                node.style.clipPath = clip(inset);
                if (progress < 1)
                    raf = requestAnimationFrame(clipStep);
                else if (params.loop) {
                    start = null;
                    raf = requestAnimationFrame(clipStep);
                }
                else {
                    raf = 0;
                }
            }
            raf = requestAnimationFrame(clipStep);
            return;
        }
        if (states.length === 0)
            return;
        if (m === 'flow') {
            // Marching ants — keep each path's existing dash pattern. If a path
            // has none, give it one so the flow is visible.
            for (const s of states) {
                if (!s.baseDash || s.baseDash === 'none')
                    s.el.style.strokeDasharray = '8 5';
            }
            const cycle = 24;
            const dir = params.reverse ? 1 : -1;
            function flowStep(t) {
                if (start === null)
                    start = t;
                const elapsed = t - start;
                const offset = (dir * (elapsed / dur) * cycle) % (cycle * 1000);
                for (const s of states)
                    s.el.style.strokeDashoffset = String(offset);
                raf = requestAnimationFrame(flowStep);
            }
            raf = requestAnimationFrame(flowStep);
            return;
        }
        // Draw / undraw / draw-undraw: dasharray = each path's actual length so
        // the animation spans the full configured duration.
        for (const s of states)
            s.el.style.strokeDasharray = String(s.length);
        function drawStep(t) {
            if (start === null)
                start = t;
            const elapsed = t - start;
            const progress = Math.min(elapsed / dur, 1);
            let offsetFrac; // 0 = fully drawn, 1 = fully invisible
            if (m === 'draw') {
                const eased = 1 - Math.pow(1 - progress, 3);
                offsetFrac = 1 - eased;
            }
            else if (m === 'undraw') {
                const eased = 1 - Math.pow(1 - progress, 3);
                offsetFrac = eased;
            }
            else {
                // draw-undraw
                if (progress < 0.5) {
                    const lt = progress / 0.5;
                    const le = 1 - Math.pow(1 - lt, 3);
                    offsetFrac = 1 - le;
                }
                else {
                    const lt = (progress - 0.5) / 0.5;
                    const le = 1 - Math.pow(1 - lt, 3);
                    offsetFrac = le;
                }
            }
            if (params.reverse)
                offsetFrac = 1 - offsetFrac;
            for (const s of states)
                s.el.style.strokeDashoffset = String(s.length * offsetFrac);
            if (progress < 1) {
                raf = requestAnimationFrame(drawStep);
            }
            else if (params.loop) {
                start = null;
                raf = requestAnimationFrame(drawStep);
            }
            else {
                raf = 0;
            }
        }
        raf = requestAnimationFrame(drawStep);
    }
    // Defer to a microtask so injected (@html) SVG content is in the DOM.
    queueMicrotask(run);
    // Register this action so the video-export pipeline can restart it after
    // activating the virtual clock. Without this hook, RAFs scheduled before
    // __startVirtualClock() never fire under the virtual system and the
    // animation appears frozen or drifts.
    if (typeof window !== 'undefined') {
        const reg = (window.__svgAnimRestart ||= []);
        reg.push(run);
        // Stash the restart fn on the destroy path.
        node.__svgAnimRestart = run;
    }
    return {
        update(p) {
            params = p;
            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);
                }
            }
        }
    };
}
