/*!
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
import { Expression } from './rvx.js';
import { Context } from './rvx.js';
import { Component } from './rvx.js';
import { View } from './rvx.js';

/**
 * Represents URL search parameters.
 */
declare class Query {
	#private;
	constructor(raw: string, params?: URLSearchParams);
	static from(init: QueryInit): Query | undefined;
	get raw(): string;
	get params(): URLSearchParams;
}
type QueryInit = ConstructorParameters<typeof URLSearchParams>[0];
/**
 * Format the specified query for use in a URL.
 *
 * Strings are returned as is.
 */
declare function formatQuery(value: QueryInit): string;

/**
 * Represents a path with optional query parameters that may change over time.
 */
interface Router {
	/**
	 * The root router.
	 */
	get root(): Router;
	/**
	 * The parent of this router if any.
	 */
	get parent(): Router | undefined;
	/**
	 * Reactively get the remaining normalized path in this context.
	 */
	get path(): string;
	/**
	 * Reactively get the search parameters in this context.
	 */
	get query(): Query | undefined;
	/**
	 * Navigate to the specified path within the path this router is mounted in.
	 *
	 * @param path The path. This may not be normalized.
	 * @param query The query part.
	 *
	 * @example
	 * ```tsx
	 * import { ROUTER } from "rvx/router";
	 *
	 * ROUTER.current!.root.push("/home");
	 * ```
	 */
	push(path: string, query?: QueryInit): void;
	/**
	 * Same as {@link push}, but replaces the URL in history if possible.
	 *
	 * @example
	 * ```tsx
	 * import { ROUTER } from "rvx/router";
	 *
	 * ROUTER.current!.root.replace("/home");
	 * ```
	 */
	replace(path: string, query?: QueryInit): void;
}
/**
 * Context for the current router.
 */
declare const ROUTER: Context<Router | undefined>;

/**
 * A router that is located at a specific path and navigates within that path.
 */
declare class ChildRouter implements Router {
	#private;
	/**
	 * Create a new child router.
	 *
	 * @param parent The parent router.
	 * @param mountPath The path this router is mounted at.
	 * @param path An expression to get the normalized rest path.
	 */
	constructor(parent: Router, mountPath: string, path: Expression<string>);
	get root(): Router;
	get parent(): Router | undefined;
	get path(): string;
	get query(): Query | undefined;
	push(path: string, query?: QueryInit): void;
	replace(path: string, query?: QueryInit): void;
}

interface HashRouterOptions {
	/**
	 * The current location is parsed when one of these events occur.
	 *
	 * @default ["hashchange"]
	 */
	parseEvents?: string[];
}
/**
 * A router that uses `location.hash` as the path ignoring the leading `"#"`.
 *
 * Everything after the first `"?"` is treated as query parameters.
 */
declare class HashRouter implements Router {
	#private;
	constructor(options?: HashRouterOptions);
	/**
	 * Called to parse & update this router's state from the current browser location.
	 */
	parse(): void;
	get root(): Router;
	get parent(): Router | undefined;
	get path(): string;
	get query(): Query | undefined;
	push(path: string, query?: QueryInit): void;
	replace(path: string, query?: QueryInit): void;
}

interface HistoryRouterOptions {
	/**
	 * The current location is parsed when one of these events occur.
	 *
	 * @default ["popstate", "rvx:router:update"]
	 */
	parseEvents?: string[];
	/**
	 * The leading base path to ignore when matching routes.
	 *
	 * @default ""
	 */
	basePath?: string;
}
/**
 * A router that uses the history API.
 */
declare class HistoryRouter implements Router {
	#private;
	constructor(options?: HistoryRouterOptions);
	/**
	 * Called to parse & update this router's state from the current browser location.
	 */
	parse(): void;
	get root(): Router;
	get parent(): Router | undefined;
	get path(): string;
	get query(): Query | undefined;
	push(path: string, query?: QueryInit): void;
	replace(path: string, query?: QueryInit): void;
}

interface MemoryRouterOptions {
	/**
	 * The initial path.
	 */
	path?: string;
	/**
	 * The initial query.
	 */
	query?: QueryInit;
	parent?: Router;
}
/**
 * A router that keeps its state in memory instead of the browser location.
 */
declare class MemoryRouter implements Router {
	#private;
	constructor(options?: MemoryRouterOptions);
	get root(): Router;
	get parent(): Router | undefined;
	get path(): string;
	get query(): Query | undefined;
	push(path: string, query?: QueryInit): void;
	replace(path: string, query?: QueryInit): void;
}

/**
 * Normalize a path:
 * + Empty paths are represented as an empty string.
 * + Non-empty paths start with a slash.
 *
 * @param path The path to normalize.
 * @param preserveDir True to keep trailing slashes in non-empty paths. Default is `true`.
 * @returns The normalized path.
 */
declare function normalize(path: string, preserveDir?: boolean): string;
/**
 * Join two paths.
 *
 * Note, that this dosn't handle empty, ".." or "." parts.
 *
 * @param parent The parent path.
 * @param child The child path.
 * @param preserveDir True to keep trailing slashes. Default is `true`.
 * @returns A {@link normalize normalized} path.
 */
declare function join(parent: string, child: string, preserveDir?: boolean): string;
/**
 * Get a normalized relative path.
 *
 * Note, that this dosn't handle empty, ".." or "." parts, but inserts ".." parts if necessary.
 *
 * @param from The path from which the relative path starts.
 * @param to The path to which the relative path points.
 * @param preserveDir True to keep trailing slashes from the `to` path. Trailing slashes from the `from` path are always discarded. Default is `true`.
 * @returns A {@link normalize normalized} path.
 */
declare function relative(from: string, to: string, preserveDir?: boolean): string;

interface RouteMatchResult {
	/**
	 * The normalized matched path.
	 */
	path: string;
	/**
	 * The parameters extracted from the matched path.
	 */
	params?: unknown;
}
interface RouteMatchFn {
	/**
	 * Check if this route matches the specified path.
	 *
	 * @param path The path to match against.
	 * @returns The match result or undefined if the path doesn't match.
	 */
	(path: string): RouteMatchResult | undefined;
}
interface Route {
	/**
	 * The paths this route matches.
	 */
	match?: string | RegExp | RouteMatchFn;
}
interface ParentRouteMatch<T extends Route> {
	/**
	 * The route that has been matched.
	 */
	route: T;
	/**
	 * The normalized matched path.
	 */
	path: string;
	/**
	 * The parameters extracted from the matched path.
	 */
	params?: unknown;
}
interface RouteMatch<T extends Route> extends ParentRouteMatch<T> {
	/**
	 * The normalized remaining rest path.
	 */
	rest: string;
}
/**
 * Match a route against the specified path.
 *
 * @param path The {@link normalize normalized} path to match against. Non normalized paths result in undefined behavior.
 * @param route The route to match.
 * @returns A match or undefined if the route doesn't match.
 */
declare function matchRoute<T extends Route>(path: string, route: T): RouteMatch<T> | undefined;
interface WatchedRoutes<T extends Route> {
	match: () => ParentRouteMatch<T> | undefined;
	rest: () => string;
}
/**
 * Watch and match routes against an expression.
 *
 * @param path The normalized path.
 * @param routes The routes to watch.
 * @returns An object with individually watchable route match and the unmatched rest path.
 */
declare function watchRoutes<T extends Route>(path: Expression<string>, routes: Expression<Iterable<T>>): WatchedRoutes<T>;
/**
 * Props passed to the root component of a {@link ComponentRoute}.
 */
interface RouteProps<P = unknown> {
	/** Matched route parameters. */
	params: P;
}
/**
 * A route where the content is a component to render.
 */
interface ComponentRoute<P = unknown> extends Route {
	content: Component<RouteProps<P>>;
}
/**
 * Match and render routes in the current context.
 *
 * A {@link ChildRouter} is provided as a replacement for the current router when rendering route content.
 */
declare function routes(routes: Expression<Iterable<ComponentRoute<any>>>): View;
/**
 * Match and render routes in the current context.
 *
 * A {@link ChildRouter} is provided as a replacement for the current router when rendering route content.
 */
declare function Routes(props: {
	/**
	 * The routes to match.
	 */
	routes: Expression<Iterable<ComponentRoute<any>>>;
}): View;
/**
 * Check if the specified pattern matches the current router's rest path.
 */
declare function isCurrent(match: Route["match"], router?: Router): boolean;

export { ChildRouter, HashRouter, HistoryRouter, MemoryRouter, Query, ROUTER, Routes, formatQuery, isCurrent, join, matchRoute, normalize, relative, routes, watchRoutes };
export type { ComponentRoute, HashRouterOptions, HistoryRouterOptions, MemoryRouterOptions, ParentRouteMatch, QueryInit, Route, RouteMatch, RouteMatchFn, RouteMatchResult, RouteProps, Router, WatchedRoutes };
