/**
 * Parsed path segment from a Beignet route template.
 */
export type PathTemplateSegment =
  | { kind: "static"; value: string }
  | { kind: "dynamic"; name: string; raw: string };

/**
 * Parsed representation of a Beignet route path template.
 */
export interface ParsedPathTemplate {
  /**
   * Dynamic parameter names in declaration order.
   */
  keys: string[];
  /**
   * Static and dynamic path segments.
   */
  segments: PathTemplateSegment[];
  /**
   * Normalized Beignet path using `:param` dynamic syntax.
   */
  normalizedPath: string;
  /**
   * Route shape used for ambiguity detection, ignoring dynamic parameter names.
   */
  shapeKey: string;
  /**
   * OpenAPI-compatible path using `{param}` syntax.
   */
  openApiPath: string;
}

const PARAM_NAME = "[A-Za-z0-9_-]+";
const COLON_PARAM = new RegExp(`^:(${PARAM_NAME})$`);
const BRACKET_PARAM = new RegExp(`^\\[(${PARAM_NAME})\\]$`);

function createPathTemplateError(path: string, segment: string): Error {
  return new Error(
    `Unsupported path template segment "${segment}" in "${path}". ` +
      "Use single-segment params like :id or [id]. Contract catch-all params such as [...path] and partial-segment params are not supported. Expose a platform catch-all route to the Beignet server, but keep individual contracts on concrete paths.",
  );
}

function parsePathSegment(path: string, segment: string): PathTemplateSegment {
  const colonMatch = segment.match(COLON_PARAM);
  if (colonMatch) {
    return { kind: "dynamic", name: colonMatch[1], raw: segment };
  }

  const bracketMatch = segment.match(BRACKET_PARAM);
  if (bracketMatch) {
    return { kind: "dynamic", name: bracketMatch[1], raw: segment };
  }

  if (
    segment.startsWith(":") ||
    segment.startsWith("[") ||
    segment.endsWith("]")
  ) {
    throw createPathTemplateError(path, segment);
  }

  return { kind: "static", value: segment };
}

/**
 * Parse a Beignet route path template.
 *
 * Paths must start with `/`. Dynamic segments may use `:id` or `[id]`.
 * Contract catch-all segments and partial dynamic segments are intentionally rejected.
 */
export function parsePathTemplate(path: string): ParsedPathTemplate {
  if (!path.startsWith("/")) {
    throw new Error(
      `Invalid path template "${path}". Paths must start with "/".`,
    );
  }

  const segments = path
    .split("/")
    .filter(Boolean)
    .map((segment) => parsePathSegment(path, segment));

  const keys = segments
    .filter(
      (segment): segment is Extract<PathTemplateSegment, { kind: "dynamic" }> =>
        segment.kind === "dynamic",
    )
    .map((segment) => segment.name);

  const duplicateKeys = keys.filter(
    (key, index) => keys.indexOf(key) !== index,
  );
  if (duplicateKeys.length) {
    throw new Error(
      `Invalid path template "${path}". Path parameter names must be unique; duplicate parameter "${duplicateKeys[0]}" was found.`,
    );
  }

  const normalizedPath = `/${segments
    .map((segment) =>
      segment.kind === "static" ? segment.value : `:${segment.name}`,
    )
    .join("/")}`;

  const shapeKey = `/${segments
    .map((segment) => (segment.kind === "static" ? segment.value : ":"))
    .join("/")}`;

  const openApiPath = `/${segments
    .map((segment) =>
      segment.kind === "static" ? segment.value : `{${segment.name}}`,
    )
    .join("/")}`;

  return { keys, segments, normalizedPath, shapeKey, openApiPath };
}
