import { generatePath } from "react-router";

import type { AgentContext } from "@phoenix/agent/context/agentContextTypes";

import type { RouteCatalogEntry, RouteInfoMatch } from "./types";

/**
 * Finds dynamic route parameters in a React Router path pattern.
 *
 * @param path - Route path pattern such as `/projects/:projectId/traces`.
 */
function getRequiredParams(path: string): string[] {
  return Array.from(path.matchAll(/:([A-Za-z0-9_]+)/g)).map(
    ([, paramName]) => paramName ?? ""
  );
}

/**
 * Restores only encoded `=` padding in generated route links.
 *
 * React Router percent-encodes dynamic params, but Phoenix's existing internal
 * links render Relay-style IDs with raw `=` padding. Keep generated assistant
 * links consistent with that convention without broadly decoding escapes like
 * `%2F`, `%3F`, or `%23`, which would change the URL structure.
 *
 * @param path - Path generated by React Router.
 */
function restoreRouteParamPadding(path: string): string {
  return path.replace(/%3D/gi, "=");
}

/**
 * Derives route parameter values from the active PXI contexts.
 *
 * @param contexts - Active PXI contexts selected in the chat session.
 */
export function buildParamsFromContexts(
  contexts: AgentContext[]
): Partial<Record<string, string>> {
  // The map is sparse because the current chat may only know about one entity
  // type. Callers must check for missing values before generating a link.
  const params: Partial<Record<string, string>> = {};
  for (const context of contexts) {
    if (context.type === "project") {
      params.projectId = context.projectNodeId;
    } else if (context.type === "trace") {
      params.projectId = context.projectNodeId;
      params.traceId = context.otelTraceId;
    } else if (context.type === "session") {
      params.projectId = context.projectNodeId;
      params.sessionId = context.sessionNodeId;
    } else if (context.type === "span" && context.spanNodeId) {
      params.spanId = context.spanNodeId;
      if (context.projectNodeId) {
        params.projectId = context.projectNodeId;
      }
    } else if (context.type === "dataset") {
      params.datasetId = context.datasetNodeId;
    } else if (context.type === "prompt") {
      params.promptId = context.promptNodeId;
    } else if (context.type === "prompt_version") {
      params.promptId = context.promptNodeId;
      params.versionId = context.promptVersionNodeId;
    }
  }
  return params;
}

/**
 * Converts a catalog entry into the tool response shape and generates a link
 * when all dynamic route params are known.
 *
 * @param params - Match construction inputs.
 * @param params.entry - Catalog entry selected by search or path lookup.
 * @param params.currentParams - Route params derived from active PXI contexts.
 */
export function buildMatch({
  entry,
  currentParams,
}: {
  entry: RouteCatalogEntry;
  currentParams: Partial<Record<string, string>>;
}): RouteInfoMatch {
  const requiredParams = getRequiredParams(entry.path);
  const missingParams = requiredParams.filter(
    (paramName) => currentParams[paramName] == null
  );
  // `generatePath` expects every named param to be present. The placeholder
  // values are never used when any param is missing because `link` becomes null.
  const generateParams = Object.fromEntries(
    requiredParams.map((paramName) => [
      paramName,
      currentParams[paramName] ?? "",
    ])
  );
  const link =
    missingParams.length === 0
      ? restoreRouteParamPadding(generatePath(entry.path, generateParams))
      : null;
  return {
    path: entry.path,
    label: entry.metadata.label,
    description: entry.metadata.description,
    link,
    missingParams,
  };
}
