import { parsePathTemplate } from "../contracts/index.js";

export type CompiledPath = {
  keys: string[];
  pattern: RegExp;
  segments: ReturnType<typeof parsePathTemplate>["segments"];
  normalizedPath: string;
  shapeKey: string;
};

export class PathDecodeError extends Error {
  constructor() {
    super("Malformed URL path");
    this.name = "PathDecodeError";
  }
}

function encodeStaticSegment(value: string): string {
  return encodeURI(value).replace(
    /[?#]/g,
    (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,
  );
}

function matchPercentEncodingCase(value: string): string {
  return value.replace(/%([0-9A-F]{2})/g, (_match, encoded: string) => {
    const digits = [...encoded]
      .map((digit) =>
        /[A-F]/.test(digit) ? `[${digit}${digit.toLowerCase()}]` : digit,
      )
      .join("");
    return `%${digits}`;
  });
}

export function compilePath(path: string): CompiledPath {
  const parsed = parsePathTemplate(path);
  const regexParts = parsed.segments.map((segment) =>
    segment.kind === "dynamic"
      ? "([^/]+)"
      : matchPercentEncodingCase(
          encodeStaticSegment(segment.value).replace(
            /[.*+?^${}()|[\]\\]/g,
            "\\$&",
          ),
        ),
  );
  const pattern = new RegExp(`^/${regexParts.join("/")}$`);
  return { ...parsed, pattern };
}

export function decodeMatchedParams(
  keys: string[],
  match: RegExpExecArray,
): Record<string, string> {
  const params: Record<string, string> = {};
  try {
    keys.forEach((key, index) => {
      params[key] = decodeURIComponent(match[index + 1]);
    });
  } catch (error) {
    if (error instanceof URIError) {
      throw new PathDecodeError();
    }
    throw error;
  }
  return params;
}

export function compareRouteSpecificity(
  a: CompiledPath,
  b: CompiledPath,
): number {
  const maxLength = Math.max(a.segments.length, b.segments.length);
  for (let index = 0; index < maxLength; index++) {
    const aSegment = a.segments[index];
    const bSegment = b.segments[index];

    if (!aSegment) return 1;
    if (!bSegment) return -1;

    if (aSegment.kind === bSegment.kind) continue;
    return aSegment.kind === "static" ? -1 : 1;
  }

  return 0;
}
