import * as Solid from 'solid-js';
import { getLocationChangeInfo, trimPathRight } from '@tanstack/router-core';
import { isServer } from '@tanstack/router-core/isServer';
import { useRouter } from './useRouter';
import { describeInitial, describeNavigation, takeRequest } from './observe';
/** The history change was the arrival's own canonicalization, not a request. */
const ARRIVAL = Symbol();
/**
 * `offerPending`'s publish: a match still `pending` at or above the
 * not-found boundary. The matches below a not-found never load, and stay
 * `pending` in the publish that lands; an offer whose pending match is
 * below one already shows the destination, the not-found.
 */
function isPendingOffer(matches) {
    for (const match of matches) {
        if (match.status === 'pending')
            return true;
        if (match._notFound)
            return false;
    }
    return false;
}
function getResolvedLocation(router) {
    const resolvedLocation = router.stores.resolvedLocation.get();
    if (resolvedLocation?.href === router.latestLocation.href &&
        resolvedLocation.state.__TSR_key === router.latestLocation.state.__TSR_key) {
        return resolvedLocation;
    }
    return;
}
export function Transitioner() {
    const router = useRouter();
    const acks = [];
    let committed;
    const isCommitted = (expected) => !!committed &&
        committed.length === expected.length &&
        expected.every((match, index) => committed[index] === match);
    // Solid's observe tier attributes what the user waited on to the
    // navigation that caused it: wrap the write whose landing is the
    // destination showing — here the match publish, after router-core awaited
    // the loaders — and describe the request, which predates it. The first
    // history change since the last publish is that request: when the user
    // asked, and the interaction they asked in, gone from the stack by the time
    // the publish runs. A push or replace while it is pending (a redirect, or
    // another navigation) sends it elsewhere, as `@solidjs/router` folds one:
    // a hop. `ARRIVAL` when the change was the arrival being canonicalized
    // (below): the initial declaration already covers it.
    let request;
    let canonicalizing = false;
    // The location the initial declaration names, canonical: what a navigation
    // is from until the first publish resolves one (a redirect while it loads).
    let arrival;
    // Ack when the commit's transition settles (the atomic swap), not when the
    // flush parks it; superseded or rolled-back commits resolve false.
    router.startTransition = (fn, expectedMatches) => {
        if (isServer ?? router.isServer) {
            fn();
            return Promise.resolve(true);
        }
        return new Promise((resolve) => {
            const ack = [expectedMatches, resolve];
            acks.push(ack);
            let publish = fn;
            // The pending offer (`offerPending`) is not the destination: published
            // undeclared, the request kept for the publish that lands. Every other
            // publish answers the request.
            if (Solid.OBSERVE !== undefined && !isPendingOffer(expectedMatches)) {
                const answered = request;
                request = undefined;
                const refs = answered === undefined || answered === ARRIVAL
                    ? undefined
                    : describeNavigation(router, answered, router.stores.resolvedLocation.get() ?? arrival, answered.hops);
                if (refs !== undefined) {
                    const observe = Solid.OBSERVE;
                    publish = refs.reduceRight((inner, ref) => () => observe.attribution.withOrigin(ref, inner), fn);
                }
            }
            Solid.runWithOwner(null, publish);
            try {
                Solid.flush();
            }
            catch {
                // Solid auto-flushes when this is called from a reactive context.
            }
            // A commit that changed nothing produces no settlement to observe.
            if (acks.includes(ack) && isCommitted(expectedMatches)) {
                acks.splice(acks.indexOf(ack), 1);
                resolve(true);
            }
        });
    };
    // No server early-return here or below: Solid 2 derives hydration keys
    // from the reactive owner tree, so the server must register the same slots
    // as the client. The callbacks themselves never run on the server.
    Solid.createEffect(() => router.stores.matches.get(), (current) => {
        committed = current;
        if (acks.length) {
            for (const [expected, resolve] of acks.splice(0)) {
                resolve(isCommitted(expected));
            }
        }
    });
    Solid.onSettled(() => {
        const unsub = router.history.subscribe(({ location, action }) => {
            if (Solid.OBSERVE !== undefined) {
                // A push or replace was requested through `commitLocation`, maybe
                // before an `await` on the blockers; anything else is the browser
                // moving, requested now.
                const write = action.type === 'PUSH' || action.type === 'REPLACE';
                const noted = write ? takeRequest(router) : undefined;
                if (canonicalizing)
                    request ?? (request = ARRIVAL);
                else if (write && request !== undefined && request !== ARRIVAL) {
                    request.hops.push({
                        location: router.parseLocation(request.location),
                        at: noted?.at ?? performance.now(),
                    });
                    request.location = location;
                }
                else {
                    // A new request: the browser moving (back, forward) supersedes a
                    // pending one rather than redirecting it, as in `@solidjs/router`.
                    request = {
                        ...(noted ?? {
                            at: performance.now(),
                            interaction: Solid.OBSERVE.attribution.currentOrigin(),
                        }),
                        location,
                        hops: [],
                    };
                }
            }
            queueMicrotask(() => router.load().catch(console.error));
        });
        // The route the document arrived on is declared around establishing it,
        // canonicalization included, as `@solidjs/router` does: the record
        // names the canonical location, and the commit is the arrival's.
        const establish = () => {
            // The URL may have changed synchronously between render and settlement.
            router.updateLatestLocation();
            const nextLocation = router.buildLocation({
                to: router.latestLocation.pathname,
                search: true,
                params: true,
                hash: true,
                state: true,
                _includeValidateSearch: true,
            });
            if (trimPathRight(router.latestLocation.publicHref) !==
                trimPathRight(nextLocation.publicHref)) {
                arrival = nextLocation;
                // The history notifies synchronously inside the commit (no blocker
                // to await), so the subscriber above sees the flag.
                canonicalizing = true;
                try {
                    router.commitLocation({
                        ...nextLocation,
                        replace: true,
                        ignoreBlocker: true,
                    });
                }
                finally {
                    canonicalizing = false;
                }
                return;
            }
            arrival = router.latestLocation;
            if (!getResolvedLocation(router) && !router._tx) {
                queueMicrotask(() => router.load().catch(console.error));
            }
        };
        const observe = Solid.OBSERVE;
        if (observe !== undefined) {
            observe.attribution.withOrigin(describeInitial(router, () => arrival ?? router.latestLocation), establish);
        }
        else
            establish();
        return unsub;
    });
    return null;
}
export function Rendered() {
    const router = useRouter();
    Solid.onSettled(() => {
        const resolvedLocation = getResolvedLocation(router);
        if (resolvedLocation) {
            router.emit({
                type: 'onRendered',
                ...getLocationChangeInfo(resolvedLocation, resolvedLocation),
            });
        }
    });
    return null;
}
//# sourceMappingURL=Transitioner.jsx.map