import { AnyPathParams } from "./route";
import { MatchLocation } from "./router";
import { last } from "./utils";

export interface Segment {
	type: "pathname" | "param" | "wildcard";
	value: string;
}

const dynamicParamIndicator = "$";

export function joinPaths(paths: (string | undefined)[]) {
	return cleanPath(paths.filter(Boolean).join("/"));
}

export function cleanPath(path: string) {
	// remove double slashes
	return path.replace(/\/{2,}/g, "/");
}

export function trimPathLeft(path: string) {
	return path === "/" ? path : path.replace(/^\/{1,}/, "");
}

export function trimPathRight(path: string) {
	return path === "/" ? path : path.replace(/\/{1,}$/, "");
}

export function trimPath(path: string) {
	return trimPathRight(trimPathLeft(path));
}

export function resolvePath(basepath: string, base: string, to: string) {
	base = base.replace(new RegExp(`^${basepath}`), "/");
	to = to.replace(new RegExp(`^${basepath}`), "/");

	let baseSegments = parsePathname(base);
	const toSegments = parsePathname(to);

	toSegments.forEach((toSegment, index) => {
		if (toSegment.value === "/") {
			if (!index) {
				// Leading slash
				baseSegments = [toSegment];
			} else if (index === toSegments.length - 1) {
				// Trailing Slash
				baseSegments.push(toSegment);
			} else {
				// ignore inter-slashes
			}
		} else if (toSegment.value === "..") {
			// Extra trailing slash? pop it off
			if (baseSegments.length > 1 && last(baseSegments)?.value === "/") {
				baseSegments.pop();
			}
			baseSegments.pop();
		} else if (toSegment.value === ".") {
			return;
		} else {
			baseSegments.push(toSegment);
		}
	});

	const joined = joinPaths([basepath, ...baseSegments.map((d) => d.value)]);

	return cleanPath(joined);
}

export function parsePathname(pathname?: string): Segment[] {
	if (!pathname) {
		return [];
	}

	pathname = cleanPath(pathname);

	const segments: Segment[] = [];

	if (pathname.slice(0, 1) === "/") {
		pathname = pathname.substring(1);
		segments.push({
			type: "pathname",
			value: "/",
		});
	}

	if (!pathname) {
		return segments;
	}

	// Remove empty segments and '.' segments
	const split = pathname.split("/").filter(Boolean);

	segments.push(
		...split.map((part): Segment => {
			if (part === ":" || part === "*") {
				return {
					type: "wildcard",
					value: part,
				};
			}

			if (part.charAt(0) === ":") {
				return {
					type: "param",
					value: part,
				};
			}

			return {
				type: "pathname",
				value: part,
			};
		}),
	);

	if (pathname.slice(-1) === "/") {
		pathname = pathname.substring(1);
		segments.push({
			type: "pathname",
			value: "/",
		});
	}

	return segments;
}

export function interpolatePath(
	path: string | undefined,
	params: any,
	leaveWildcard?: boolean,
) {
	const interpolatedPathSegments = parsePathname(path);

	return joinPaths(
		interpolatedPathSegments.map((segment) => {
			if ([":", "*"].includes(segment.value) && !leaveWildcard) {
				return "";
			}

			if (segment.type === "param") {
				return params![segment.value.substring(1)] ?? "";
			}

			return segment.value;
		}),
	);
}

export function matchPathname(
	basepath: string,
	currentPathname: string,
	matchLocation: Pick<MatchLocation, "to" | "fuzzy" | "caseSensitive">,
): AnyPathParams | undefined {
	const pathParams = matchByPath(basepath, currentPathname, matchLocation);
	// const searchMatched = matchBySearch(currentLocation.search, matchLocation)

	if (matchLocation.to && !pathParams) {
		return;
	}

	return pathParams ?? {};
}

export function matchByPath(
	basepath: string,
	from: string,
	matchLocation: Pick<MatchLocation, "to" | "caseSensitive" | "fuzzy">,
): Record<string, string> | undefined {
	if (!from.startsWith(basepath)) {
		return undefined;
	}
	from = basepath != "/" ? from.substring(basepath.length) : from;
	const baseSegments = parsePathname(from);
	const to = `${matchLocation.to ?? "$"}`;
	const routeSegments = parsePathname(to);

	if (last(baseSegments)?.value === "/") {
		baseSegments.pop();
	}

	const params: Record<string, string> = {};

	const isMatch = (() => {
		for (
			let i = 0;
			i < Math.max(baseSegments.length, routeSegments.length);
			i++
		) {
			const baseSegment = baseSegments[i];
			const routeSegment = routeSegments[i];

			const isLastRouteSegment = i === routeSegments.length - 1;
			const isLastBaseSegment = i === baseSegments.length - 1;

			if (routeSegment) {
				if (routeSegment.type === "wildcard") {
					if (baseSegment?.value) {
						params["*"] = joinPaths(baseSegments.slice(i).map((d) => d.value));
						return true;
					}
					return false;
				}

				if (routeSegment.type === "pathname") {
					if (routeSegment.value === "/" && !baseSegment?.value) {
						return true;
					}

					if (baseSegment) {
						if (matchLocation.caseSensitive) {
							if (routeSegment.value !== baseSegment.value) {
								return false;
							}
						} else if (
							routeSegment.value.toLowerCase() !==
							baseSegment.value.toLowerCase()
						) {
							return false;
						}
					}
				}

				if (!baseSegment) {
					return false;
				}

				if (routeSegment.type === "param") {
					if (baseSegment?.value === "/") {
						return false;
					}
					if (baseSegment.value.charAt(0) !== "$") {
						params[routeSegment.value.substring(1)] = baseSegment.value;
					}
				}
			}

			if (isLastRouteSegment && !isLastBaseSegment) {
				return !!matchLocation.fuzzy;
			}
		}
		return true;
	})();

	return isMatch ? (params as Record<string, string>) : undefined;
}
