import * as Solid from 'solid-js';
import { 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';
import { ClientOnly } from './ClientOnly';
import { nonRouteComponentContext, renderInNonRouteComponentContext, } from './nonRouteComponentContext';
const NearestMatchContext = nearestMatchContext;
// Keep the client constant undefined so the server-only script can be dropped.
const renderScrollRestoration = isServer === false
    ? undefined
    : (router, route) => (isServer ?? router.isServer) &&
        route.parentRoute?.id === rootRouteId &&
        router.options.scrollRestoration ? (<ScrollRestoration />) : null;
export const Match = (props) => {
    const router = useRouter();
    const currentMatch = Solid.createMemo(() => router.stores.getMatchStore(props.routeId).get());
    const matchState = Solid.createMemo(() => {
        const match = currentMatch();
        if (!match) {
            return null;
        }
        return {
            routeId: match.routeId,
            ssr: match.ssr,
            status: match.status,
        };
    });
    const nearestMatch = [() => props.routeId, currentMatch];
    return (<Solid.Show when={matchState()}>
      {(currentMatchState) => {
            const route = router.routesById[props.routeId];
            const routeOptions = () => {
                // Lazy route options become observable with the next publication.
                currentMatchState();
                return route.options;
            };
            const resolvePendingComponent = Solid.createMemo(() => routeOptions().pendingComponent ??
                router.options.defaultPendingComponent);
            const routeErrorComponent = Solid.createMemo(() => routeOptions().errorComponent ??
                router.options.defaultErrorComponent);
            const routeNotFoundComponent = Solid.createMemo(() => route.isRoot
                ? (routeOptions().notFoundComponent ??
                    router.options.notFoundRoute?.options.component ??
                    router.options.defaultNotFoundComponent)
                : (routeOptions().notFoundComponent ??
                    router.options.defaultNotFoundComponent));
            const resolvedNoSsr = Solid.createMemo(() => currentMatchState().ssr === false ||
                currentMatchState().ssr === 'data-only');
            const ResolvedLoadingBoundary = Solid.createMemo(() => resolvedNoSsr() ? SafeFragment : Solid.Loading);
            const shouldSkipLoadingFallback = Solid.createMemo(() => (isServer ?? router.isServer)
                ? resolvedNoSsr()
                : currentMatchState().ssr === 'data-only');
            const ResolvedNotFoundBoundary = Solid.createMemo(() => routeNotFoundComponent() ? CatchNotFound : SafeFragment);
            const ShellComponent = Solid.createMemo(() => route.isRoot
                ? (route.options.shellComponent ??
                    SafeFragment)
                : SafeFragment);
            const MatchContent = () => (<Solid.Show when={currentMatchState().status !== 'pending'} fallback={(() => {
                    if (process.env.NODE_ENV !== 'production') {
                        return renderInNonRouteComponentContext(() => <Dynamic component={resolvePendingComponent()}/>, 'pendingComponent');
                    }
                    return <Dynamic component={resolvePendingComponent()}/>;
                })()}>
            <MatchInner />
          </Solid.Show>);
            const RouteContent = () => (<Dynamic component={ResolvedNotFoundBoundary()} fallback={(error) => {
                    const notFoundError = getNotFound(error) ?? error;
                    notFoundError.routeId ?? (notFoundError.routeId = currentMatchState().routeId);
                    if (notFoundError.routeId !== currentMatchState().routeId) {
                        throw notFoundError;
                    }
                    return process.env.NODE_ENV !== 'production' ? (renderInNonRouteComponentContext(() => (<Dynamic component={routeNotFoundComponent()} {...notFoundError}/>), 'notFoundComponent')) : (<Dynamic component={routeNotFoundComponent()} {...notFoundError}/>);
                }}>
            <Solid.Switch>
              <Solid.Match when={resolvedNoSsr()}>
                <ClientOnly fallback={(() => {
                    if (process.env.NODE_ENV !== 'production') {
                        return renderInNonRouteComponentContext(() => <Dynamic component={resolvePendingComponent()}/>, 'pendingComponent');
                    }
                    return <Dynamic component={resolvePendingComponent()}/>;
                })()}>
                  <MatchContent />
                </ClientOnly>
              </Solid.Match>
              <Solid.Match when={!resolvedNoSsr()}>
                <MatchContent />
              </Solid.Match>
            </Solid.Switch>
          </Dynamic>);
            return (<Dynamic component={ShellComponent()}>
            <NearestMatchContext value={nearestMatch}>
              <Dynamic component={ResolvedLoadingBoundary()} fallback={(() => {
                    // Data-only SSR renders the inner fallback on the server, so
                    // avoid adding an extra loading fallback on the client.
                    if (shouldSkipLoadingFallback()) {
                        return undefined;
                    }
                    if (process.env.NODE_ENV !== 'production') {
                        return renderInNonRouteComponentContext(() => <Dynamic component={resolvePendingComponent()}/>, 'pendingComponent');
                    }
                    return <Dynamic component={resolvePendingComponent()}/>;
                })()}>
                <Solid.Show when={routeErrorComponent()} fallback={<RouteContent />}>
                  {(errorComponent) => (<CatchBoundary 
                // Scope the reset key to this match and its
                // descendants (whose errors bubble here when they
                // have no errorComponent of their own): resetting on
                // unrelated matches' transitions just re-throws the
                // still-stale error and recreates the retried
                // subtree mid-settle.
                getResetKey={() => {
                        const matches = router.stores.matches.get();
                        const index = matches.findIndex((match) => match.routeId === props.routeId);
                        if (index === -1) {
                            return '';
                        }
                        let key = '';
                        for (let i = index; i < matches.length; i++) {
                            const match = matches[i];
                            key += `${match.status}|${match.updatedAt},`;
                        }
                        return key;
                    }} errorComponent={errorComponent()} onCatch={(error) => {
                        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}`);
                        }
                        ;
                        (routeOptions().onCatch ??
                            router.options.defaultOnCatch)?.(error);
                    }} render={RouteContent}/>)}
                </Solid.Show>
              </Dynamic>
            </NearestMatchContext>

            {renderScrollRestoration?.(router, route)}
          </Dynamic>);
        }}
    </Solid.Show>);
};
export const MatchInner = () => {
    const router = useRouter();
    const nearestMatch = Solid.useContext(nearestMatchContext);
    const match = nearestMatch[1 /* match */];
    const routeId = nearestMatch[0 /* route id */];
    const matchState = Solid.createMemo(() => {
        const currentMatch = match();
        const currentRouteId = routeId();
        if (!currentMatch || !currentRouteId) {
            return null;
        }
        const route = router.routesById[currentRouteId];
        const remount = route.options.remountDeps ?? router.options.defaultRemountDeps;
        let componentKey;
        if (!remount) {
            componentKey = currentRouteId;
        }
        else {
            const deps = remount({
                routeId: currentRouteId,
                loaderDeps: currentMatch.loaderDeps,
                params: currentMatch._strictParams,
                search: currentMatch._strictSearch,
            });
            componentKey = JSON.stringify(deps) ?? currentRouteId;
        }
        return {
            route,
            routeId: currentRouteId,
            match: {
                id: currentMatch.id,
                status: currentMatch.status,
                error: currentMatch.error,
            },
            componentKey,
        };
    });
    return (<Solid.Show when={matchState()}>
      {(currentMatchState) => {
            const route = Solid.createMemo(() => currentMatchState().route);
            const currentMatch = Solid.createMemo(() => currentMatchState().match);
            const componentKey = Solid.createMemo(() => currentMatchState().componentKey);
            const OutComponent = Solid.createMemo(() => route().options.component ??
                router.options.defaultComponent ??
                Outlet);
            const keyedOut = () => (<Solid.Show when={componentKey()} keyed>
            {(_key) => <Dynamic component={OutComponent()}/>}
          </Solid.Show>);
            return (<Solid.Switch>
            <Solid.Match when={currentMatch().status === 'notFound'}>
              {(_) => Solid.untrack(() => renderRouteNotFound(router, route(), currentMatch().error))}
            </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 process.env.NODE_ENV !== 'production' ? (renderInNonRouteComponentContext(() => (<RouteErrorComponent error={matchError} reset={undefined} info={{ componentStack: '' }}/>), 'errorComponent')) : (<RouteErrorComponent error={matchError} reset={undefined} info={{ componentStack: '' }}/>);
                    }
                    throw matchError;
                }}
            </Solid.Match>
            <Solid.Match when={currentMatch().status === 'success'}>
              {keyedOut()}
            </Solid.Match>
          </Solid.Switch>);
        }}
    </Solid.Show>);
};
export const Outlet = () => {
    if (process.env.NODE_ENV !== 'production') {
        const nonRouteComponent = Solid.useContext(nonRouteComponentContext);
        if (nonRouteComponent) {
            console.warn(`Warning: An <Outlet /> was rendered inside a ${nonRouteComponent}. <Outlet /> should only be rendered inside a route component.`);
        }
    }
    const router = useRouter();
    const nearestParentMatch = Solid.useContext(nearestMatchContext);
    const parentMatch = nearestParentMatch[1 /* match */];
    const routeId = nearestParentMatch[0 /* route id */];
    const route = Solid.createMemo(() => {
        const currentRouteId = routeId();
        return currentRouteId
            ? router.routesById[currentRouteId]
            : undefined;
    });
    const parentNotFound = Solid.createMemo(() => parentMatch()?._notFound);
    const parentNotFoundError = Solid.createMemo(() => parentMatch()?.error);
    const childRouteId = Solid.createMemo(() => {
        if (parentNotFound()) {
            return undefined;
        }
        const currentRouteId = routeId();
        if (!currentRouteId) {
            return undefined;
        }
        const ids = router.stores.ids.get();
        return ids[ids.indexOf(currentRouteId) + 1];
    });
    const childPendingComponent = Solid.createMemo(() => {
        const childId = childRouteId();
        return childId
            ? (router.routesById[childId].options.pendingComponent ??
                router.options.defaultPendingComponent)
            : router.options.defaultPendingComponent;
    });
    return (<Solid.Show when={childRouteId()} keyed fallback={<Solid.Show when={parentNotFound() && route()}>
          {(resolvedRoute) => Solid.untrack(() => renderRouteNotFound(router, resolvedRoute(), parentNotFoundError()))}
        </Solid.Show>}>
      {(currentChildRouteId) => {
            const nextMatch = () => <Match routeId={currentChildRouteId}/>;
            return (<Solid.Show when={routeId() === rootRouteId} fallback={nextMatch()}>
            <Solid.Loading fallback={(() => {
                    if (!childPendingComponent()) {
                        return null;
                    }
                    if (process.env.NODE_ENV !== 'production') {
                        return renderInNonRouteComponentContext(() => <Dynamic component={childPendingComponent()}/>, 'pendingComponent');
                    }
                    return <Dynamic component={childPendingComponent()}/>;
                })()}>
              {nextMatch()}
            </Solid.Loading>
          </Solid.Show>);
        }}
    </Solid.Show>);
};
//# sourceMappingURL=Match.jsx.map