import { ParsePathParams } from "./link";
import { AnyRouter, Router } from "./router";
import {
	IsAny,
	NoInfer,
	PickRequired,
	PickUnsafe,
	UnionToIntersection,
} from "./utils";
import invariant from "tiny-invariant";
import { joinPaths, trimPath, trimPathRight } from "./path";
import { AnyRoutesInfo, DefaultRoutesInfo } from "./routeInfo";
import { RouteMatch } from "./routeMatch";

export const rootRouteId = "__root__" as const;
export type RootRouteId = typeof rootRouteId;
export type AnyPathParams = {};
export type AnySearchSchema = {};
export type AnyContext = {};
export interface RouteMeta {}
export interface RouteContext {}

export type RouteOptionsBase<TCustomId, TPath> =
	| {
			path: TPath;
	  }
	| {
			id: TCustomId;
	  };

export type RouteOptionsBaseIntersection<TCustomId, TPath> =
	UnionToIntersection<RouteOptionsBase<TCustomId, TPath>>;

export type MetaOptions = keyof PickRequired<RouteMeta> extends never
	? {
			meta?: RouteMeta;
	  }
	: {
			meta: RouteMeta;
	  };

type GetContextFn<
	TParentRoute,
	TAllParams,
	TFullSearchSchema,
	TParentContext,
	TAllParentContext,
	TRouteContext,
> = (
	opts: {
		params: TAllParams;
		search: TFullSearchSchema;
	} & (TParentRoute extends undefined
		? {
				context?: TAllParentContext;
				parentContext?: TParentContext;
		  }
		: {
				context: TAllParentContext;
				parentContext: TParentContext;
		  }),
) => TRouteContext;

export type ContextOptions<
	TParentRoute,
	TAllParams,
	TFullSearchSchema,
	TParentContext,
	TAllParentContext,
	TRouteContext,
> = keyof PickRequired<RouteContext> extends never
	? {
			getContext?: GetContextFn<
				TParentRoute,
				TAllParams,
				TFullSearchSchema,
				TParentContext,
				TAllParentContext,
				TRouteContext
			>;
	  }
	: {
			getContext: GetContextFn<
				TParentRoute,
				TAllParams,
				TFullSearchSchema,
				TParentContext,
				TAllParentContext,
				TRouteContext
			>;
	  };

export type RouteProps<
	TParentRoute extends AnyRoute,
	TPath extends string,
	TFullPath extends ResolveFullPath<
		TParentRoute,
		TPath,
		RoutePrefix<TParentRoute["fullPath"], TPath>
	>,
	TCustomId extends string,
	TId extends ResolveId<TParentRoute, TCustomId, TPath>,
	TLoader,
	TSearchSchema extends AnySearchSchema,
	TFullSearchSchema extends AnySearchSchema,
	TAllParams,
	TParentContext,
	TAllParentContext extends IsAny<
		TParentRoute["__types"]["allParams"],
		TParentContext,
		TParentRoute["__types"]["allParams"] & TParentContext
	>,
	TRouteContext,
	TContext,
	TRouterContext extends AnyContext,
> = {
	useMatch: () => RouteMatch<
		AnyRoutesInfo,
		Route<
			TParentRoute,
			TPath,
			TFullPath,
			TCustomId,
			TId,
			TLoader,
			TSearchSchema,
			TFullSearchSchema,
			TParentContext,
			TAllParentContext,
			TRouteContext,
			TContext,
			TRouterContext,
			any,
			any
		>
	>;
	useLoader: () => TLoader;
	useSearch: <
		TStrict extends boolean = true,
		TSearch = TFullSearchSchema,
		TSelected = TSearch,
	>(opts?: {
		strict?: TStrict;
		track?: (search: TSearch) => TSelected;
	}) => TStrict extends true ? TSelected : TSelected | undefined;
	useParams: <
		TDefaultSelected = TAllParams,
		TSelected = TDefaultSelected,
	>(opts?: {
		track?: (search: TDefaultSelected) => TSelected;
	}) => TSelected;
	useContext: () => TContext;
	// navigate: <T extends TFullPath, TTo extends string = ''>(
	//   opts?: MakeLinkOptions<T, TTo>,
	// ) => Promise<void>
};

export type TypedRouteOptions<
	TParentRoute extends AnyRoute = AnyRoute,
	TPath extends string = string,
	TSearchSchema extends AnySearchSchema = {},
	TParams = Record<ParsePathParams<TPath>, any>,
	TLoader = unknown,
	TCustomId extends string = string,
	TParentSearchSchema extends {} = {},
	TFullSearchSchema extends AnySearchSchema = TSearchSchema,
	TParentParams extends AnyPathParams = {},
	TAllParams = TParams,
	TParentContext extends AnyContext = AnyContext,
	TAllParentContext extends IsAny<
		TParentRoute["__types"]["allParams"],
		TParentContext,
		TParentRoute["__types"]["allParams"] & TParentContext
	> = IsAny<
		TParentRoute["__types"]["allParams"],
		TParentContext,
		TParentRoute["__types"]["allParams"] & TParentContext
	>,
	TRouteContext extends RouteContext = RouteContext,
	TContext extends MergeFromParent<
		TAllParentContext,
		TRouteContext
	> = MergeFromParent<TAllParentContext, TRouteContext>,
> = Omit<
	RouteOptions<
		TParentRoute,
		TCustomId,
		TPath,
		TLoader,
		TParentSearchSchema,
		TSearchSchema,
		TFullSearchSchema,
		TParentParams,
		TParams,
		TAllParams,
		TParentContext,
		TAllParentContext,
		TRouteContext
	>,
	"id" | "path" | "getParentRoute"
>;

export type RouteOptions<
	TParentRoute extends AnyRoute = AnyRoute,
	TCustomId extends string = string,
	TPath extends string = string,
	TLoader = unknown,
	TParentSearchSchema extends {} = {},
	TSearchSchema extends AnySearchSchema = {},
	TFullSearchSchema extends AnySearchSchema = TSearchSchema,
	TParentParams extends AnyPathParams = {},
	TParams = Record<ParsePathParams<TPath>, any>,
	TAllParams = TParams,
	TParentContext extends AnyContext = AnyContext,
	TAllParentContext extends IsAny<
		TParentRoute["__types"]["allParams"],
		TParentContext,
		TParentRoute["__types"]["allParams"] & TParentContext
	> = IsAny<
		TParentRoute["__types"]["allParams"],
		TParentContext,
		TParentRoute["__types"]["allParams"] & TParentContext
	>,
	TRouteContext extends RouteContext = RouteContext,
	TContext extends MergeFromParent<
		TAllParentContext,
		TRouteContext
	> = MergeFromParent<TAllParentContext, TRouteContext>,
> = RouteOptionsBase<TCustomId, TPath> & {
	getParentRoute: () => TParentRoute;
	// If true, this route will be matched as case-sensitive
	caseSensitive?: boolean;
	// Filter functions that can manipulate search params *before* they are passed to links and navigate
	// calls that match this route.
	preSearchFilters?: SearchFilter<TFullSearchSchema>[];
	// Filter functions that can manipulate search params *after* they are passed to links and navigate
	// calls that match this route.
	postSearchFilters?: SearchFilter<TFullSearchSchema>[];
	// The content to be rendered when the route is matched. If no component is provided, defaults to `<Outlet />`
	// component?: RouteComponent<
	// 	RouteProps<
	// 		TParentRoute,
	// 		TPath,
	// 		ResolveFullPath<TParentRoute, TPath>,
	// 		TCustomId,
	// 		ResolveId<TParentRoute, TCustomId, TPath>,
	// 		TLoader,
	// 		TSearchSchema,
	// 		TFullSearchSchema,
	// 		TAllParams,
	// 		TParentContext,
	// 		TAllParentContext,
	// 		TRouteContext,
	// 		TContext,
	// 		NoInfer<TRouteContext>
	// 	>
	// >; //
	// // The content to be rendered when the route encounters an error
	// errorComponent?: RouteComponent<{
	// 	error: Error;
	// 	info: { componentStack: string };
	// }>; //
	// // If supported by your framework, the content to be rendered as the fallback content until the route is ready to render
	// pendingComponent?: RouteComponent<
	// 	RouteProps<
	// 		TParentRoute,
	// 		TPath,
	// 		ResolveFullPath<TParentRoute, TPath>,
	// 		TCustomId,
	// 		ResolveId<TParentRoute, TCustomId, TPath>,
	// 		TLoader,
	// 		TSearchSchema,
	// 		TFullSearchSchema,
	// 		TAllParams,
	// 		TParentContext,
	// 		TAllParentContext,
	// 		TRouteContext,
	// 		TContext,
	// 		NoInfer<TRouteContext>
	// 	>
	// >; //
	wrapInSuspense?: boolean;

	// This async function is called before a route is loaded.
	// If an error is thrown here, the route's loader will not be called.
	// If thrown during a navigation, the navigation will be cancelled and the error will be passed to the `onLoadError` function.
	// If thrown during a preload event, the error will be logged to the console.
	beforeLoad?: (opts: {
		router: AnyRouter;
		match: RouteMatch;
	}) => Promise<void> | void;
	// This function will be called if the route's loader throws an error **during an attempted navigation**.
	// If you want to redirect due to an error, call `router.navigate()` from within this function.
	onBeforeLoadError?: (err: any) => void;
	validateSearch?: SearchSchemaValidator<TSearchSchema, TParentSearchSchema>;
	// This function will be called if the route's validateSearch option throws an error **during an attempted validation**.
	// If you want to redirect due to an error, call `router.navigate()` from within this function.
	// If you want to display the errorComponent, rethrow the error
	onValidateSearchError?: (err: any) => void;
	// An asynchronous function responsible for preparing or fetching data for the route before it is rendered
	loader?: OnLoadFn<
		TLoader,
		TSearchSchema,
		TFullSearchSchema,
		TAllParams,
		NoInfer<TRouteContext>,
		TContext
	>;
	onLoadError?: (err: any) => void;
	onError?: (err: any) => void;
	// This function is called
	// when moving from an inactive state to an active one. Likewise, when moving from
	// an active to an inactive state, the return function (if provided) is called.
	onLoaded?: (matchContext: {
		params: TAllParams;
		search: TFullSearchSchema;
	}) =>
		| void
		| undefined
		| ((match: { params: TAllParams; search: TFullSearchSchema }) => void);
	// This function is called when the route remains active from one transition to the next.
	onTransition?: (match: {
		params: TAllParams;
		search: TFullSearchSchema;
	}) => void;
} & MetaOptions &
	ContextOptions<
		TParentRoute,
		TAllParams,
		TFullSearchSchema,
		TParentContext,
		TAllParentContext,
		TRouteContext
	> &
	(
		| {
				// Parse params optionally receives path params as strings and returns them in a parsed format (like a number or boolean)
				parseParams?: (
					rawParams: IsAny<TPath, any, Record<ParsePathParams<TPath>, string>>,
				) => TParams extends Record<ParsePathParams<TPath>, any>
					? TParams
					: "parseParams must return an object";
				stringifyParams?: (
					params: NoInfer<TParams>,
				) => Record<ParsePathParams<TPath>, string>;
		  }
		| {
				stringifyParams?: never;
				parseParams?: never;
		  }
	) &
	(PickUnsafe<TParentParams, ParsePathParams<TPath>> extends never // Detect if an existing path param is being redefined
		? { test?: PickUnsafe<TParentParams, ParsePathParams<TPath>> }
		: "Cannot redefined path params in child routes!");

// The parse type here allows a zod schema to be passed directly to the validator
export type SearchSchemaValidator<TReturn, TParentSchema> =
	| SearchSchemaValidatorObj<TReturn, TParentSchema>
	| SearchSchemaValidatorFn<TReturn, TParentSchema>;

export type SearchSchemaValidatorObj<TReturn, TParentSchema> = {
	parse?: SearchSchemaValidatorFn<TReturn, TParentSchema>;
};

export type SearchSchemaValidatorFn<TReturn, TParentSchema> = (
	searchObj: Record<string, unknown>,
) => {} extends TParentSchema
	? TReturn
	: keyof TReturn extends keyof TParentSchema
	? {
			error: "Top level search params cannot be redefined by child routes!";
			keys: keyof TReturn & keyof TParentSchema;
	  }
	: TReturn;

export type DefinedPathParamWarning =
	"Path params cannot be redefined by child routes!";

export type ParentParams<TParentParams> = AnyPathParams extends TParentParams
	? {}
	: {
			[Key in keyof TParentParams]?: DefinedPathParamWarning;
	  };

export type OnLoadFn<
	TLoader = unknown,
	TSearchSchema extends AnySearchSchema = {},
	TFullSearchSchema extends AnySearchSchema = {},
	TAllParams = {},
	TContext extends AnyContext = AnyContext,
	TAllContext extends AnyContext = AnyContext,
> = (
	loaderContext: LoaderContext<
		TSearchSchema,
		TFullSearchSchema,
		TAllParams,
		TContext,
		TAllContext
	>,
) => Promise<TLoader> | TLoader;

export interface LoaderContext<
	TSearchSchema extends AnySearchSchema = {},
	TFullSearchSchema extends AnySearchSchema = {},
	TAllParams = {},
	TContext extends AnyContext = AnyContext,
	TAllContext extends AnyContext = AnyContext,
> {
	params: TAllParams;
	routeSearch: TSearchSchema;
	search: TFullSearchSchema;
	signal?: AbortSignal;
	preload: boolean;
	routeContext: TContext;
	context: TAllContext;
}

export type UnloaderFn<TPath extends string> = (
	routeMatch: RouteMatch<any, Route>,
) => void;

export type SearchFilter<T, U = T> = (prev: T) => U;

type ResolveId<
	TParentRoute,
	TCustomId extends string,
	TPath extends string,
> = TParentRoute extends { id: infer TParentId extends string }
	? RoutePrefix<TParentId, string extends TCustomId ? TPath : TCustomId>
	: RootRouteId;

export type InferFullSearchSchema<TRoute> = TRoute extends {
	isRoot: true;
	__types: {
		searchSchema: infer TSearchSchema;
	};
}
	? TSearchSchema
	: TRoute extends {
			__types: {
				fullSearchSchema: infer TFullSearchSchema;
			};
	  }
	? TFullSearchSchema
	: {};

export type ResolveFullSearchSchema<TParentRoute, TSearchSchema> =
	InferFullSearchSchema<TParentRoute> & TSearchSchema;

export type AnyRoute = Route<
	any,
	any,
	any,
	any,
	any,
	any,
	any,
	any,
	any,
	any,
	any,
	any,
	any,
	any,
	any,
	any,
	any
>;

type MergeFromParent<T, U> = IsAny<T, U, T & U>;

export type TypedRoute<
	TParentRoute extends AnyRoute = AnyRoute,
	TPath extends string = string,
	TSearchSchema extends AnySearchSchema = {},
	TParams extends Record<ParsePathParams<TPath>, any> = Record<
		ParsePathParams<TPath>,
		string
	>,
	TChildren extends unknown = unknown,
	TFullPath extends ResolveFullPath<TParentRoute, TPath> = ResolveFullPath<
		TParentRoute,
		TPath
	>,
	TCustomId extends string = string,
	TId extends ResolveId<TParentRoute, TCustomId, TPath> = ResolveId<
		TParentRoute,
		TCustomId,
		TPath
	>,
	TLoader = unknown,
	TFullSearchSchema extends AnySearchSchema = ResolveFullSearchSchema<
		TParentRoute,
		TSearchSchema
	>,
	TAllParams extends MergeFromParent<
		TParentRoute["__types"]["allParams"],
		TParams
	> = MergeFromParent<TParentRoute["__types"]["allParams"], TParams>,
	TParentContext extends TParentRoute["__types"]["routeContext"] = TParentRoute["__types"]["routeContext"],
	TAllParentContext extends TParentRoute["__types"]["context"] = TParentRoute["__types"]["context"],
	TRouteContext extends RouteContext = RouteContext,
	TContext extends MergeFromParent<
		TParentRoute["__types"]["context"],
		TRouteContext
	> = MergeFromParent<TParentRoute["__types"]["context"], TRouteContext>,
	TRouterContext extends AnyContext = AnyContext,
	TRoutesInfo extends DefaultRoutesInfo = DefaultRoutesInfo,
> = Route<
	TParentRoute,
	TPath,
	TFullPath,
	TCustomId,
	TId,
	TLoader,
	TSearchSchema,
	TFullSearchSchema,
	TParams,
	TAllParams,
	TParentContext,
	TAllParentContext,
	TRouteContext,
	TContext,
	TRouterContext,
	TChildren,
	TRoutesInfo
>;

export class Route<
	TParentRoute extends AnyRoute = AnyRoute,
	TPath extends string = string,
	TFullPath extends ResolveFullPath<TParentRoute, TPath> = ResolveFullPath<
		TParentRoute,
		TPath
	>,
	TCustomId extends string = string,
	TId extends ResolveId<TParentRoute, TCustomId, TPath> = ResolveId<
		TParentRoute,
		TCustomId,
		TPath
	>,
	TLoader = unknown,
	TSearchSchema extends AnySearchSchema = {},
	TFullSearchSchema extends AnySearchSchema = ResolveFullSearchSchema<
		TParentRoute,
		TSearchSchema
	>,
	TParams extends Record<ParsePathParams<TPath>, any> = Record<
		ParsePathParams<TPath>,
		string
	>,
	TAllParams extends MergeFromParent<
		TParentRoute["__types"]["allParams"],
		TParams
	> = MergeFromParent<TParentRoute["__types"]["allParams"], TParams>,
	TParentContext extends TParentRoute["__types"]["routeContext"] = TParentRoute["__types"]["routeContext"],
	TAllParentContext extends TParentRoute["__types"]["context"] = TParentRoute["__types"]["context"],
	TRouteContext extends RouteContext = RouteContext,
	TContext extends MergeFromParent<
		TParentRoute["__types"]["context"],
		TRouteContext
	> = MergeFromParent<TParentRoute["__types"]["context"], TRouteContext>,
	TRouterContext extends AnyContext = AnyContext,
	TChildren extends unknown = unknown,
	TRoutesInfo extends DefaultRoutesInfo = DefaultRoutesInfo,
> {
	__types!: {
		parentRoute: TParentRoute;
		path: TPath;
		fullPath: TFullPath;
		customId: TCustomId;
		id: TId;
		loader: TLoader;
		searchSchema: TSearchSchema;
		fullSearchSchema: TFullSearchSchema;
		params: TParams;
		allParams: TAllParams;
		parentContext: TParentContext;
		allParentContext: TAllParentContext;
		routeContext: TRouteContext;
		context: TContext;
		children: TChildren;
		routesInfo: TRoutesInfo;
		routerContext: TRouterContext;
	};
	isRoot: TParentRoute extends Route<any> ? true : false;
	options: RouteOptions<
		TParentRoute,
		TCustomId,
		TPath,
		TLoader,
		InferFullSearchSchema<TParentRoute>,
		TSearchSchema,
		InferFullSearchSchema<TParentRoute> & TSearchSchema,
		TParentRoute["__types"]["allParams"],
		TParams,
		TAllParams,
		TParentContext,
		TAllParentContext,
		TRouteContext,
		TContext
	>;

	// Set up in this.init()
	parentRoute!: TParentRoute;
	id!: TId;
	// customId!: TCustomId
	path!: TPath;
	fullPath!: TFullPath;

	// Optional
	children?: TChildren;
	originalIndex?: number;
	router?: Router<TRoutesInfo["routeTree"], TRoutesInfo>;

	constructor(
		options: RouteOptions<
			TParentRoute,
			TCustomId,
			TPath,
			TLoader,
			InferFullSearchSchema<TParentRoute>,
			TSearchSchema,
			TFullSearchSchema,
			TParentRoute["__types"]["allParams"],
			TParams,
			TAllParams,
			TParentContext,
			TAllParentContext,
			TRouteContext,
			TContext
		>,
	) {
		this.options = (options as any) || {};
		this.isRoot = !options?.getParentRoute as any;
	}

	init = (opts: { originalIndex: number; router: AnyRouter }) => {
		this.originalIndex = opts.originalIndex;
		this.router = opts.router;

		const allOptions = this.options as RouteOptions<
			TParentRoute,
			TCustomId,
			TPath,
			TLoader,
			InferFullSearchSchema<TParentRoute>,
			TSearchSchema,
			TFullSearchSchema,
			TParentRoute["__types"]["allParams"],
			TParams,
			TAllParams,
			TParentContext,
			TAllParentContext,
			TRouteContext,
			TContext
		> &
			RouteOptionsBaseIntersection<TCustomId, TPath>;

		const isRoot = !allOptions?.path && !allOptions?.id;

		this.parentRoute = this.options?.getParentRoute?.();

		if (isRoot) {
			this.path = rootRouteId as TPath;
		} else {
			invariant(
				this.parentRoute,
				`Child Route instances must pass a 'getParentRoute: () => ParentRoute' option that returns a Route instance.`,
			);
		}

		let path: undefined | string = isRoot ? rootRouteId : allOptions.path;

		// If the path is anything other than an index path, trim it up
		if (path && path !== "/") {
			path = trimPath(path);
		}

		const customId = allOptions?.id || path;

		// Strip the parentId prefix from the first level of children
		let id = isRoot
			? rootRouteId
			: joinPaths([
					(this.parentRoute.id as any) === rootRouteId
						? ""
						: this.parentRoute.id,
					customId,
			  ]);

		if (path === rootRouteId) {
			path = "/";
		}

		if (id !== rootRouteId) {
			id = joinPaths(["/", id]);
		}

		const fullPath =
			id === rootRouteId
				? "/"
				: trimPathRight(joinPaths([this.parentRoute.fullPath, path]));

		this.path = path as TPath;
		this.id = id as TId;
		// this.customId = customId as TCustomId
		this.fullPath = fullPath as TFullPath;
	};

	addChildren = <TNewChildren extends AnyRoute[]>(
		children: TNewChildren,
	): Route<
		TParentRoute,
		TPath,
		TFullPath,
		TCustomId,
		TId,
		TLoader,
		TSearchSchema,
		TFullSearchSchema,
		TParams,
		TAllParams,
		TParentContext,
		TAllParentContext,
		TRouteContext,
		TContext,
		TRouterContext,
		TNewChildren,
		TRoutesInfo
	> => {
		this.children = children as any;
		return this as any;
	};
}

export type AnyRootRoute = RootRoute<any, any, any, any>;

export type RouteWithChildren<
	TRoute extends AnyRoute,
	TNewChildren extends AnyRoute[],
> = Route<
	TRoute["__types"]["parentRoute"],
	TRoute["__types"]["path"],
	TRoute["__types"]["fullPath"],
	TRoute["__types"]["customId"],
	TRoute["__types"]["id"],
	TRoute["__types"]["loader"],
	TRoute["__types"]["searchSchema"],
	TRoute["__types"]["fullSearchSchema"],
	TRoute["__types"]["params"],
	TRoute["__types"]["allParams"],
	TRoute["__types"]["parentContext"],
	TRoute["__types"]["allParentContext"],
	TRoute["__types"]["routeContext"],
	TRoute["__types"]["context"],
	TRoute["__types"]["routerContext"],
	TNewChildren,
	TRoute["__types"]["routesInfo"]
>;

// export type TypedRoute<
// TRoute extends AnyRoute,
// TPath extends string,
// TSearchSchema extends AnySearchSchema,
// > = Route<
// TRoute,
// TPath,

export class RootRoute<
	TLoader = unknown,
	TSearchSchema extends AnySearchSchema = {},
	TContext extends RouteContext = RouteContext,
	TRouterContext extends {} = {},
> extends Route<
	any,
	"/",
	"/",
	string,
	RootRouteId,
	TLoader,
	TSearchSchema,
	TSearchSchema,
	{},
	{},
	TRouterContext,
	TRouterContext,
	MergeFromParent<TRouterContext, TContext>,
	MergeFromParent<TRouterContext, TContext>,
	TRouterContext,
	any,
	any
> {
	constructor(
		options?: Omit<
			RouteOptions<
				AnyRoute,
				RootRouteId,
				"",
				TLoader,
				{},
				TSearchSchema,
				NoInfer<TSearchSchema>,
				{},
				TRouterContext,
				TRouterContext,
				TContext,
				NoInfer<TContext>
			>,
			| "path"
			| "id"
			| "getParentRoute"
			| "caseSensitive"
			| "parseParams"
			| "stringifyParams"
		>,
	) {
		super(options as any);
	}

	static withRouterContext = <TRouterContext extends {}>() => {
		return <
			TLoader = unknown,
			TSearchSchema extends AnySearchSchema = {},
			TContext extends {} = {},
		>(
			options?: Omit<
				RouteOptions<
					AnyRoute,
					RootRouteId,
					"",
					TLoader,
					{},
					TSearchSchema,
					NoInfer<TSearchSchema>,
					{},
					TRouterContext,
					TRouterContext,
					TContext,
					TRouterContext & TContext
				>,
				"path" | "id" | "getParentRoute" | "caseSensitive"
			>,
		) =>
			new RootRoute<TLoader, TSearchSchema, TContext, TRouterContext>(
				options as any,
			);
	};
}

type ResolveFullPath<
	TParentRoute extends AnyRoute,
	TPath extends string,
	TPrefixed extends RoutePrefix<TParentRoute["fullPath"], TPath> = RoutePrefix<
		TParentRoute["fullPath"],
		TPath
	>,
> = TPrefixed extends RootRouteId ? "/" : TrimPathRight<`${TPrefixed}`>;

type RoutePrefix<
	TPrefix extends string,
	TId extends string,
> = string extends TId
	? RootRouteId
	: TId extends string
	? TPrefix extends RootRouteId
		? TId extends "/"
			? "/"
			: `/${TrimPath<TId>}`
		: `${TPrefix}/${TId}` extends "/"
		? "/"
		: `/${TrimPathLeft<`${TrimPathRight<TPrefix>}/${TrimPath<TId>}`>}`
	: never;

type TrimPath<T extends string> = "" extends T
	? ""
	: TrimPathRight<TrimPathLeft<T>>;

type TrimPathLeft<T extends string> = T extends `${RootRouteId}/${infer U}`
	? TrimPathLeft<U>
	: T extends `/${infer U}`
	? TrimPathLeft<U>
	: T;
type TrimPathRight<T extends string> = T extends "/"
	? "/"
	: T extends `${infer U}/`
	? TrimPathRight<U>
	: T;

// const rootRoute = new RootRoute({
//   validateSearch: () => null as unknown as { root?: boolean },
// })

// const aRoute = new Route({
//   getParentRoute: () => rootRoute,
//   path: 'a',
//   validateSearch: () => null as unknown as { a?: string },
// })

// const bRoute = new Route({
//   getParentRoute: () => aRoute,
//   path: 'b',
// })

// const rootIsRoot = rootRoute.isRoot
// //    ^?
// const aIsRoot = aRoute.isRoot
// //    ^?

// const rId = rootRoute.id
// //    ^?
// const aId = aRoute.id
// //    ^?
// const bId = bRoute.id
// //    ^?

// const rPath = rootRoute.fullPath
// //    ^?
// const aPath = aRoute.fullPath
// //    ^?
// const bPath = bRoute.fullPath
// //    ^?

// const rSearch = rootRoute.__types.fullSearchSchema
// //    ^?
// const aSearch = aRoute.__types.fullSearchSchema
// //    ^?
// const bSearch = bRoute.__types.fullSearchSchema
// //    ^?

// const config = rootRoute.addChildren([aRoute.addChildren([bRoute])])
// //    ^?
