import * as Solid from 'solid-js';
import { createControlledPromise, getLocationChangeInfo, invariant, isNotFound, isRedirect, rootRouteId, } from '@tanstack/router-core';
import { isServer } from '@tanstack/router-core/isServer';
import { Dynamic } from '@solidjs/web';
import { CatchBoundary, ErrorComponent } from './CatchBoundary';
import { useRouter } from './useRouter';
import { CatchNotFound, getNotFound } from './not-found';
import { nearestMatchContext } from './matchContext';
import { SafeFragment } from './SafeFragment';
import { renderRouteNotFound } from './renderRouteNotFound';
import { ScrollRestoration } from './scroll-restoration';
const NearestMatchContext = nearestMatchContext;
export const Match = (props) => {
    const router = useRouter();
    const match = Solid.createMemo(() => {
        const id = props.matchId;
        if (!id)
            return undefined;
        return router.stores.activeMatchStoresById.get(id)?.state;
    });
    const rawMatchState = Solid.createMemo(() => {
        const currentMatch = match();
        if (!currentMatch) {
            return null;
        }
        const routeId = currentMatch.routeId;
        const parentRouteId = router.routesById[routeId]?.parentRoute
            ?.id;
        return {
            matchId: currentMatch.id,
            routeId,
            ssr: currentMatch.ssr,
            _displayPending: currentMatch._displayPending,
            parentRouteId: parentRouteId,
        };
    });
    const hasPendingMatch = Solid.createMemo(() => {
        const currentRouteId = rawMatchState()?.routeId;
        return currentRouteId
            ? Boolean(router.stores.pendingRouteIds.state[currentRouteId])
            : false;
    });
    const nearestMatch = {
        matchId: () => rawMatchState()?.matchId,
        routeId: () => rawMatchState()?.routeId,
        match,
        hasPending: hasPendingMatch,
    };
    return (<Solid.Show when={rawMatchState()}>
      {(currentMatchState) => {
            const route = Solid.createMemo(() => router.routesById[currentMatchState().routeId]);
            const resolvePendingComponent = Solid.createMemo(() => route().options.pendingComponent ??
                router.options.defaultPendingComponent);
            const routeErrorComponent = Solid.createMemo(() => route().options.errorComponent ??
                router.options.defaultErrorComponent);
            const routeOnCatch = Solid.createMemo(() => route().options.onCatch ?? router.options.defaultOnCatch);
            const routeNotFoundComponent = Solid.createMemo(() => route().isRoot
                ? // If it's the root route, use the globalNotFound option, with fallback to the notFoundRoute's component
                    (route().options.notFoundComponent ??
                        router.options.notFoundRoute?.options.component)
                : route().options.notFoundComponent);
            const resolvedNoSsr = Solid.createMemo(() => currentMatchState().ssr === false ||
                currentMatchState().ssr === 'data-only');
            const ResolvedSuspenseBoundary = Solid.createMemo(() => resolvedNoSsr() ? SafeFragment : Solid.Loading);
            const ResolvedCatchBoundary = Solid.createMemo(() => routeErrorComponent() ? CatchBoundary : SafeFragment);
            const ResolvedNotFoundBoundary = Solid.createMemo(() => routeNotFoundComponent() ? CatchNotFound : SafeFragment);
            const ShellComponent = Solid.createMemo(() => route().isRoot
                ? (route().options.shellComponent ??
                    SafeFragment)
                : SafeFragment);
            return (<Dynamic component={ShellComponent()}>
            <NearestMatchContext value={nearestMatch}>
              <Dynamic component={ResolvedSuspenseBoundary()} fallback={
                // Don't show fallback on server when using no-ssr mode to avoid hydration mismatch
                (isServer ?? router.isServer) &&
                    resolvedNoSsr() ? undefined : (<Dynamic component={resolvePendingComponent()}/>)}>
                <Dynamic component={ResolvedCatchBoundary()} getResetKey={() => router.stores.loadedAt.state} errorComponent={routeErrorComponent() || ErrorComponent} onCatch={(error) => {
                    // Forward not found errors (we don't want to show the error component for these)
                    const notFoundError = getNotFound(error);
                    if (notFoundError) {
                        notFoundError.routeId ?? (notFoundError.routeId = currentMatchState()
                            .routeId);
                        throw notFoundError;
                    }
                    if (process.env.NODE_ENV !== 'production') {
                        console.warn(`Warning: Error in route match: ${currentMatchState().routeId}`);
                    }
                    routeOnCatch()?.(error);
                }}>
                  <Dynamic component={ResolvedNotFoundBoundary()} fallback={(error) => {
                    const notFoundError = getNotFound(error) ?? error;
                    notFoundError.routeId ?? (notFoundError.routeId = currentMatchState()
                        .routeId);
                    // If the current not found handler doesn't exist or it has a
                    // route ID which doesn't match the current route, rethrow the error
                    if (!routeNotFoundComponent() ||
                        (notFoundError.routeId &&
                            notFoundError.routeId !==
                                currentMatchState().routeId) ||
                        (!notFoundError.routeId && !route().isRoot))
                        throw notFoundError;
                    return (<Dynamic component={routeNotFoundComponent()} {...notFoundError}/>);
                }}>
                    <Solid.Switch>
                      <Solid.Match when={resolvedNoSsr()}>
                        <Solid.Show when={!(isServer ?? router.isServer)} fallback={<Dynamic component={resolvePendingComponent()}/>}>
                          <MatchInner />
                        </Solid.Show>
                      </Solid.Match>
                      <Solid.Match when={!resolvedNoSsr()}>
                        <MatchInner />
                      </Solid.Match>
                    </Solid.Switch>
                  </Dynamic>
                </Dynamic>
              </Dynamic>
            </NearestMatchContext>

            {currentMatchState().parentRouteId === rootRouteId ? (<>
                <OnRendered />
                {router.options.scrollRestoration &&
                        (isServer ?? router.isServer) ? (<ScrollRestoration />) : null}
              </>) : null}
          </Dynamic>);
        }}
    </Solid.Show>);
};
// On Rendered can't happen above the root layout because it actually
// renders a dummy dom element to track the rendered state of the app.
// We render a script tag with a key that changes based on the current
// location state.__TSR_key. Also, because it's below the root layout, it
// allows us to fire onRendered events even after a hydration mismatch
// error that occurred above the root layout (like bad head/link tags,
// which is common).
//
// In Solid, createEffect(source, fn) fires on initial mount as well as on
// reactive changes. OnRendered can also remount when the first child route
// changes (e.g. navigating from / to /posts). We deduplicate by tracking
// the last emitted resolvedLocation key per router so each unique resolved
// location only triggers one onRendered event regardless of remounts.
const lastOnRenderedKey = new WeakMap();
function OnRendered() {
    const router = useRouter();
    const location = Solid.createMemo(() => router.stores.resolvedLocation.state?.state.__TSR_key);
    const locationState = Solid.createMemo(() => router.stores.location.state);
    const resolvedLocationState = Solid.createMemo(() => router.stores.resolvedLocation.state);
    Solid.createEffect(() => [location(), locationState(), resolvedLocationState()], ([location, currentLocationState, currentResolvedLocationState]) => {
        if (!location)
            return;
        if (lastOnRenderedKey.get(router) === location)
            return;
        lastOnRenderedKey.set(router, location);
        router.emit({
            type: 'onRendered',
            ...getLocationChangeInfo(currentLocationState, currentResolvedLocationState),
        });
    });
    return null;
}
export const MatchInner = () => {
    const router = useRouter();
    const match = Solid.useContext(nearestMatchContext).match;
    const rawMatchState = Solid.createMemo(() => {
        const currentMatch = match();
        if (!currentMatch) {
            return null;
        }
        const routeId = currentMatch.routeId;
        const remountFn = router.routesById[routeId].options.remountDeps ??
            router.options.defaultRemountDeps;
        const remountDeps = remountFn?.({
            routeId,
            loaderDeps: currentMatch.loaderDeps,
            params: currentMatch._strictParams,
            search: currentMatch._strictSearch,
        });
        const key = remountDeps ? JSON.stringify(remountDeps) : undefined;
        return {
            key,
            routeId,
            match: {
                id: currentMatch.id,
                status: currentMatch.status,
                error: currentMatch.error,
                _forcePending: currentMatch._forcePending ?? false,
                _displayPending: currentMatch._displayPending ?? false,
            },
        };
    });
    return (<Solid.Show when={rawMatchState()}>
      {(currentMatchState) => {
            const route = Solid.createMemo(() => router.routesById[currentMatchState().routeId]);
            const currentMatch = Solid.createMemo(() => currentMatchState().match);
            const componentKey = Solid.createMemo(() => currentMatchState().key ?? currentMatchState().match.id);
            const Comp = Solid.createMemo(() => route().options.component ?? router.options.defaultComponent);
            const OutComponent = Solid.createMemo(() => {
                const C = Comp();
                return C || Outlet;
            });
            const RenderOut = () => <Dynamic component={OutComponent()}/>;
            const keyedOut = () => (<Solid.Show when={componentKey()} keyed>
            {(_key) => <RenderOut />}
          </Solid.Show>);
            return (<Solid.Switch>
            <Solid.Match when={currentMatch()._displayPending}>
              {(_) => {
                    const displayPendingResult = Solid.createMemo(() => router.getMatch(currentMatch().id)?._nonReactive
                        .displayPendingPromise);
                    return <>{displayPendingResult()}</>;
                }}
            </Solid.Match>
            <Solid.Match when={currentMatch()._forcePending}>
              {(_) => {
                    const minPendingResult = Solid.createMemo(() => router.getMatch(currentMatch().id)?._nonReactive
                        .minPendingPromise);
                    return <>{minPendingResult()}</>;
                }}
            </Solid.Match>
            <Solid.Match when={currentMatch().status === 'pending'}>
              {(_) => {
                    const pendingMinMs = Solid.untrack(() => route().options.pendingMinMs ??
                        router.options.defaultPendingMinMs);
                    if (pendingMinMs) {
                        const routerMatch = Solid.untrack(() => router.getMatch(currentMatch().id));
                        if (routerMatch &&
                            !routerMatch._nonReactive.minPendingPromise) {
                            // Create a promise that will resolve after the minPendingMs
                            if (!(isServer ?? router.isServer)) {
                                const minPendingPromise = createControlledPromise();
                                routerMatch._nonReactive.minPendingPromise =
                                    minPendingPromise;
                                setTimeout(() => {
                                    minPendingPromise.resolve();
                                    // We've handled the minPendingPromise, so we can delete it
                                    routerMatch._nonReactive.minPendingPromise = undefined;
                                }, pendingMinMs);
                            }
                        }
                    }
                    const loaderResult = Solid.createMemo(async () => {
                        await new Promise((r) => setTimeout(r, 0));
                        return router.getMatch(currentMatch().id)?._nonReactive
                            .loadPromise;
                    });
                    const FallbackComponent = Solid.untrack(() => route().options.pendingComponent ??
                        router.options.defaultPendingComponent);
                    return (<>
                    {FallbackComponent && pendingMinMs > 0 ? (<Dynamic component={FallbackComponent}/>) : null}
                    {loaderResult()}
                  </>);
                }}
            </Solid.Match>
            <Solid.Match when={currentMatch().status === 'notFound'}>
              {(_) => {
                    const matchError = Solid.untrack(() => currentMatch().error);
                    if (!isNotFound(matchError)) {
                        if (process.env.NODE_ENV !== 'production') {
                            throw new Error('Invariant failed: Expected a notFound error');
                        }
                        invariant();
                    }
                    // Use Show with keyed to ensure re-render when routeId changes
                    return (<Solid.Show when={currentMatchState().routeId} keyed>
                    {(_routeId) => Solid.untrack(() => renderRouteNotFound(router, route(), matchError))}
                  </Solid.Show>);
                }}
            </Solid.Match>
            <Solid.Match when={currentMatch().status === 'redirected'}>
              {(_) => {
                    const matchError = Solid.untrack(() => currentMatch().error);
                    if (!isRedirect(matchError)) {
                        if (process.env.NODE_ENV !== 'production') {
                            throw new Error('Invariant failed: Expected a redirect error');
                        }
                        invariant();
                    }
                    return null;
                }}
            </Solid.Match>
            <Solid.Match when={currentMatch().status === 'error'}>
              {(_) => {
                    const matchError = Solid.untrack(() => currentMatch().error);
                    if (isServer ?? router.isServer) {
                        const RouteErrorComponent = (route().options.errorComponent ??
                            router.options.defaultErrorComponent) ||
                            ErrorComponent;
                        return (<RouteErrorComponent error={matchError} info={{
                                componentStack: '',
                            }}/>);
                    }
                    throw matchError;
                }}
            </Solid.Match>
            <Solid.Match when={currentMatch().status === 'success'}>
              {keyedOut()}
            </Solid.Match>
          </Solid.Switch>);
        }}
    </Solid.Show>);
};
export const Outlet = () => {
    const router = useRouter();
    const nearestParentMatch = Solid.useContext(nearestMatchContext);
    const parentMatch = nearestParentMatch.match;
    const routeId = nearestParentMatch.routeId;
    const route = Solid.createMemo(() => routeId() ? router.routesById[routeId()] : undefined);
    const parentGlobalNotFound = Solid.createMemo(() => parentMatch()?.globalNotFound ?? false);
    const childMatchId = Solid.createMemo(() => {
        const currentRouteId = routeId();
        return currentRouteId
            ? router.stores.childMatchIdByRouteId.state[currentRouteId]
            : undefined;
    });
    const childRouteId = Solid.createMemo(() => {
        const id = childMatchId();
        if (!id)
            return undefined;
        return router.stores.activeMatchStoresById.get(id)?.state.routeId;
    });
    const childRoute = Solid.createMemo(() => {
        const id = childRouteId();
        return id ? router.routesById[id] : undefined;
    });
    const childPendingComponent = Solid.createMemo(() => childRoute()?.options.pendingComponent ??
        router.options.defaultPendingComponent);
    const childMatchStatus = Solid.createMemo(() => {
        const id = childMatchId();
        if (!id)
            return undefined;
        return router.stores.activeMatchStoresById.get(id)?.state.status;
    });
    // Only show not-found if we're not in a redirected state
    const shouldShowNotFound = () => childMatchStatus() !== 'redirected' && parentGlobalNotFound();
    return (<Solid.Show when={!shouldShowNotFound() && childMatchId()} fallback={<Solid.Show when={shouldShowNotFound() && route()}>
          {(resolvedRoute) => Solid.untrack(() => renderRouteNotFound(router, resolvedRoute(), undefined))}
        </Solid.Show>}>
      {(childMatchIdAccessor) => {
            const currentMatchId = Solid.createMemo(() => childMatchIdAccessor());
            return (<Solid.Show when={routeId() === rootRouteId} fallback={<Match matchId={currentMatchId()}/>}>
            <Solid.Show when={childRouteId()} keyed>
              {(_routeId) => (<Solid.Loading fallback={childPendingComponent() ? (<Dynamic component={childPendingComponent()}/>) : null}>
                  <Match matchId={currentMatchId()}/>
                </Solid.Loading>)}
            </Solid.Show>
          </Solid.Show>);
        }}
    </Solid.Show>);
};
//# sourceMappingURL=Match.jsx.map