import { getRouteParams, type Route } from "./match.ts";

export interface NamedRoute extends Route {
  name: string;
  metadata?: Record<string, unknown>;
}

export interface RouteManifestEntry {
  name: string;
  path: string;
  file: string;
  params: ReturnType<typeof getRouteParams>;
  metadata?: Record<string, unknown>;
}

export function routeName(raw: string): string {
  const value = raw.replace(/^\/+|\/+$/g, "");
  return value
    ? value.replace(/\[\[?\.\.\.|\[\[?|\]\]?/g, "").replace(/[^A-Za-z0-9]+/g, ".")
    : "index";
}

export function nameRoutes(
  routes: readonly Route[],
  metadata: Record<string, Record<string, unknown>> = {},
): NamedRoute[] {
  const used = new Map<string, number>();
  return routes.map((route) => {
    const base = routeName(route.raw);
    const count = (used.get(base) ?? 0) + 1;
    used.set(base, count);
    const name = count === 1 ? base : `${base}.${count}`;
    return { ...route, name, metadata: metadata[name] };
  });
}

export function createRouteManifest(routes: readonly NamedRoute[]): RouteManifestEntry[] {
  return routes.map((route) => ({
    name: route.name,
    path: route.raw,
    file: route.file,
    params: route.paramMeta ?? getRouteParams(route.raw),
    metadata: route.metadata,
  }));
}

export function routeUrl(
  route: Pick<NamedRoute, "raw" | "paramMeta">,
  params: Record<string, string | number | Array<string | number> | null | undefined> = {},
  query?: Record<string, string | number | boolean | null | undefined>,
): string {
  const meta = route.paramMeta ?? getRouteParams(route.raw);
  let output = route.raw;
  for (const param of meta) {
    const value = params[param.name];
    if (value === undefined || value === null || value === "") {
      if (!param.optional) throw new Error(`WRN-ROUTE-MISSING-PARAM: ${param.name}`);
      output = output.replace(new RegExp(`/\\[\\[?(?:\\.\\.\\.)?${param.name}\\??\\]\\]?`), "");
      continue;
    }
    const encoded = Array.isArray(value)
      ? value.map((part) => encodeURIComponent(String(part))).join("/")
      : encodeURIComponent(String(value));
    output = output.replace(new RegExp(`\\[\\[?(?:\\.\\.\\.)?${param.name}\\??\\]\\]?`), encoded);
  }
  output = output.replace(/\/+/g, "/") || "/";
  if (query) {
    const search = new URLSearchParams();
    for (const [key, value] of Object.entries(query))
      if (value !== undefined && value !== null) search.set(key, String(value));
    const text = search.toString();
    if (text) output += `?${text}`;
  }
  return output;
}

export function findNamedRoute(routes: readonly NamedRoute[], name: string): NamedRoute {
  const route = routes.find((candidate) => candidate.name === name);
  if (!route) throw new Error(`WRN-ROUTE-NAME-NOT-FOUND: ${name}`);
  return route;
}
