import * as Solid from 'solid-js';
import { rootRouteId } from '@tanstack/router-core';
import { isServer } from '@tanstack/router-core/isServer';
import { Dynamic } from 'solid-js/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';
// 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.byRoute.get(props.routeId).get());
    const nearestMatch = [() => props.routeId, currentMatch];
    const route = router.routesById[props.routeId];
    // Lazy route option mutations become observable with the next client match
    // publication. Server stores are non-reactive and options load before render.
    const routeOptions = (isServer ?? router.isServer)
        ? () => route.options
        : () => {
            currentMatch();
            return route.options;
        };
    const resolvePendingComponent = () => routeOptions().pendingComponent ?? router.options.defaultPendingComponent;
    const routeErrorComponent = () => routeOptions().errorComponent ?? router.options.defaultErrorComponent;
    const routeNotFoundComponent = () => route.isRoot
        ? // If it's the root route, use the _notFound option, with fallback to the notFoundRoute's component
            (routeOptions().notFoundComponent ??
                router.options.notFoundRoute?.options.component)
        : routeOptions().notFoundComponent;
    const resolvedNoSsr = () => currentMatch().ssr === false || currentMatch().ssr === 'data-only';
    const shouldSkipSuspenseFallback = () => (isServer ?? router.isServer)
        ? resolvedNoSsr()
        : currentMatch().ssr === 'data-only';
    const ShellComponent = route.isRoot
        ? (route.options.shellComponent ?? SafeFragment)
        : SafeFragment;
    const MatchContent = () => (<Solid.Show when={currentMatch().status !== 'pending'} fallback={(() => {
            if (process.env.NODE_ENV !== 'production') {
                return renderInNonRouteComponentContext(() => <Dynamic component={resolvePendingComponent()}/>, 'pendingComponent');
            }
            return <Dynamic component={resolvePendingComponent()}/>;
        })()}>
      <MatchInner />
    </Solid.Show>);
    return (<ShellComponent>
      <nearestMatchContext.Provider value={nearestMatch}>
        <Solid.Suspense fallback={(() => {
            // Data-only SSR renders the inner fallback on the server, so
            // avoid adding an extra suspense fallback on the client.
            if (shouldSkipSuspenseFallback()) {
                return undefined;
            }
            if (process.env.NODE_ENV !== 'production') {
                return renderInNonRouteComponentContext(() => <Dynamic component={resolvePendingComponent()}/>, 'pendingComponent');
            }
            return <Dynamic component={resolvePendingComponent()}/>;
        })()}>
          <Dynamic component={routeErrorComponent() ? CatchBoundary : SafeFragment} getResetKey={currentMatch} errorComponent={routeErrorComponent()} 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 = currentMatch().routeId);
                throw notFoundError;
            }
            if (process.env.NODE_ENV !== 'production') {
                console.warn(`Warning: Error in route match: ${currentMatch().routeId}`);
            }
            ;
            (route.options.onCatch ?? router.options.defaultOnCatch)?.(error);
        }}>
            <Dynamic component={routeNotFoundComponent() ? CatchNotFound : SafeFragment} fallback={(error) => {
            const notFoundError = getNotFound(error) ?? error;
            notFoundError.routeId ?? (notFoundError.routeId = currentMatch().routeId);
            if (notFoundError.routeId !== currentMatch().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>
          </Dynamic>
        </Solid.Suspense>
      </nearestMatchContext.Provider>

      {renderScrollRestoration?.(router, route)}
    </ShellComponent>);
};
export const MatchInner = () => {
    const router = useRouter();
    const nearestMatch = Solid.useContext(nearestMatchContext);
    const match = nearestMatch[1 /* match */];
    const routeId = nearestMatch[0 /* route id */];
    const route = router.routesById[routeId()];
    const currentMatch = () => match();
    const componentKey = () => {
        const current = currentMatch();
        const remount = route.options.remountDeps ?? router.options.defaultRemountDeps;
        if (!remount) {
            return routeId();
        }
        const deps = remount({
            routeId: routeId(),
            loaderDeps: current.loaderDeps,
            params: current._strictParams,
            search: current._strictSearch,
        });
        return JSON.stringify(deps) ?? routeId();
    };
    const out = () => {
        const Comp = route.options.component ?? router.options.defaultComponent;
        if (Comp) {
            return <Comp />;
        }
        return <Outlet />;
    };
    const keyedOut = () => (<Solid.Show when={componentKey()} keyed>
      {(_key) => out()}
    </Solid.Show>);
    return (<Solid.Switch>
      <Solid.Match when={currentMatch().status === 'notFound'}>
        {(_) => renderRouteNotFound(router, route, currentMatch().error)}
      </Solid.Match>
      <Solid.Match when={currentMatch().status === 'error'}>
        {(_) => {
            if (isServer ?? router.isServer) {
                const RouteErrorComponent = (route.options.errorComponent ??
                    router.options.defaultErrorComponent) ||
                    ErrorComponent;
                return process.env.NODE_ENV !== 'production' ? (renderInNonRouteComponentContext(() => (<RouteErrorComponent error={currentMatch().error} info={{
                        componentStack: '',
                    }}/>), 'errorComponent')) : (<RouteErrorComponent error={currentMatch().error} info={{
                        componentStack: '',
                    }}/>);
            }
            throw currentMatch().error;
        }}
      </Solid.Match>
      <Solid.Match when={currentMatch().status === 'success'}>
        {keyedOut()}
      </Solid.Match>
    </Solid.Switch>);
};
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 = router.routesById[routeId()];
    const childRouteId = () => {
        if (parentMatch()._notFound) {
            return;
        }
        const ids = router.stores.ids.get();
        return ids[ids.indexOf(routeId()) + 1];
    };
    return (<Solid.Show when={childRouteId()} keyed fallback={parentMatch()._notFound
            ? renderRouteNotFound(router, route, parentMatch().error)
            : undefined}>
      {(_routeKey) => {
            return (<Solid.Show when={routeId() === rootRouteId} fallback={<Match routeId={childRouteId()}/>}>
            <Solid.Suspense fallback={(() => {
                    if (process.env.NODE_ENV !== 'production') {
                        return renderInNonRouteComponentContext(() => (<Dynamic component={router.options.defaultPendingComponent}/>), 'pendingComponent');
                    }
                    return (<Dynamic component={router.options.defaultPendingComponent}/>);
                })()}>
              <Match routeId={childRouteId()}/>
            </Solid.Suspense>
          </Solid.Show>);
        }}
    </Solid.Show>);
};
//# sourceMappingURL=Match.jsx.map