/**
 * Route compilation + matching.
 *
 * A "route" is a URL pattern compiled to a RegExp. We support static segments
 * and dynamic `[param]` segments, e.g. `/users/[id]` -> `{ id }`.
 */

export interface Route {
  /** The human-readable route pattern, e.g. `/users/[id]`. */
  raw: string;
  /** Absolute path to the module that handles this route. */
  file: string;
  /** Compiled matcher. */
  regex: RegExp;
  /** Ordered names of dynamic params captured by `regex`. */
  paramNames: string[];
}

export interface RouteMatch {
  route: Route;
  params: Record<string, string>;
}

const ESCAPE_RE = /[.*+?^${}()|[\]\\]/g;

/** Compile a `/users/[id]` style pattern into a RegExp + param names. */
export function compileRoutePattern(raw: string): Pick<Route, "regex" | "paramNames"> {
  if (raw === "/") {
    return { regex: /^\/$/, paramNames: [] };
  }

  const paramNames: string[] = [];
  const parts = raw
    .split("/")
    .filter(Boolean)
    .map((segment) => {
      const dynamic = segment.match(/^\[(.+)\]$/);
      if (dynamic) {
        paramNames.push(dynamic[1]!);
        return "([^/]+)";
      }
      return segment.replace(ESCAPE_RE, "\\$&");
    });

  // Allow an optional trailing slash.
  const regex = new RegExp("^/" + parts.join("/") + "/?$");
  return { regex, paramNames };
}

/**
 * Order routes so that static routes win over dynamic ones, and longer/more
 * specific routes win over shorter ones. Sorting once keeps matching simple.
 */
export function sortRoutes(routes: Route[]): Route[] {
  return [...routes].sort((a, b) => {
    if (a.paramNames.length !== b.paramNames.length) {
      return a.paramNames.length - b.paramNames.length; // fewer params first
    }
    return b.raw.length - a.raw.length; // longer/more specific first
  });
}

/** Find the first route whose pattern matches `pathname`. */
export function matchRoute(routes: Route[], pathname: string): RouteMatch | null {
  for (const route of routes) {
    const m = route.regex.exec(pathname);
    if (!m) continue;
    const params: Record<string, string> = {};
    try {
      route.paramNames.forEach((name, i) => {
        params[name] = decodeURIComponent(m[i + 1]!);
      });
    } catch {
      // A malformed percent-encoded path is not a valid route match. Treat it
      // as a 404 instead of allowing decodeURIComponent to become a 500.
      return null;
    }
    return { route, params };
  }
  return null;
}
