<script lang="ts">
    import { navigating } from '$app/stores';

    /**
     * Thin top-of-viewport progress bar shown during client-side navigation.
     * Most data routes run their `load` with `ssr = false`, so SvelteKit holds
     * the previous page on screen while the next one's data resolves — with no
     * feedback. This bar is that feedback: it trickles toward the right edge
     * while a navigation is pending, snaps to full and fades out once it
     * settles.
     *
     * Decorative (`aria-hidden`). Animates `transform`/`opacity` only (both
     * compositor-friendly), and drops `will-change` when idle. Under
     * prefers-reduced-motion the trickle keyframe is neutralized by the global
     * override, leaving the `.loading` base transform as a static visible bar.
     */

    // idle: hidden. loading: trickling. done: snap to full, then fade to idle.
    let phase = $state<'idle' | 'loading' | 'done'>('idle');
    // Bumped whenever a *new* navigation begins. Keying the element on it
    // remounts the bar so the trickle animation restarts even when one
    // navigation supersedes another (the store stays truthy the whole time,
    // just swapping Navigation objects).
    let run = $state(0);
    // Plain locals (not $state) so reading them in the effect doesn't make the
    // effect depend on its own writes. `runCount` mirrors `run` so we can bump
    // it without reactively reading the `run` state.
    let runCount = 0;
    let wasNavigating = false;
    let doneTimer: ReturnType<typeof setTimeout> | undefined;

    $effect(() => {
        const isNavigating = !!$navigating;
        if (isNavigating) {
            clearTimeout(doneTimer);
            run = ++runCount;
            phase = 'loading';
        } else if (wasNavigating) {
            phase = 'done';
            doneTimer = setTimeout(() => {
                phase = 'idle';
            }, 260);
        }
        wasNavigating = isNavigating;
        return () => clearTimeout(doneTimer);
    });
</script>

{#key run}
    <div
        class="nav-progress"
        class:loading={phase === 'loading'}
        class:done={phase === 'done'}
        data-testid="nav-progress"
        data-phase={phase}
        aria-hidden="true"
    ></div>
{/key}

<style>
    .nav-progress {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 3px;
        background: var(--primary);
        z-index: var(--z-nav-progress);
        transform: scaleX(0);
        transform-origin: left;
        opacity: 0;
        pointer-events: none;
    }

    .nav-progress.loading {
        opacity: 1;
        /* Base (non-animated) target — this is what reduced-motion users see,
           and the keyframe's end state, so disabling the animation still
           leaves a clearly visible bar. */
        transform: scaleX(0.9);
        animation: nav-progress-trickle 8s cubic-bezier(0.1, 0.7, 0.3, 1) forwards;
        will-change: transform;
    }

    .nav-progress.done {
        opacity: 0;
        transform: scaleX(1);
        /* Complete to full quickly, then fade out. */
        transition:
            transform 120ms ease-out,
            opacity 200ms ease-out 120ms;
    }

    /* The reader hides all its own chrome in fullscreen; the progress bar
       would be the one visible element left, so hide it too. */
    :global(html[data-reader-fullscreen='true']) .nav-progress {
        display: none;
    }

    @keyframes nav-progress-trickle {
        0% {
            transform: scaleX(0);
        }
        50% {
            transform: scaleX(0.65);
        }
        100% {
            transform: scaleX(0.9);
        }
    }
</style>
