import path from "path";
import { TESTKIT_COVERAGE_GRAPH_VERSION } from "@elench/testkit-protocol";

export const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
export const HTTP_WRAPPER_METHODS = {
  getJson: "GET",
  postJson: "POST",
  putJson: "PUT",
  patchJson: "PATCH",
  deleteJson: "DELETE",
};
export const DYNAMIC_SEGMENT_TOKEN = "__TESTKIT_DYNAMIC_SEGMENT__";

export function createEmptyGraph() {
  return {
    schemaVersion: TESTKIT_COVERAGE_GRAPH_VERSION,
    nodes: [],
    edges: [],
    evidence: [],
    diagnostics: [],
  };
}

export function appendGraph(graph, fragment) {
  const nodeIds = new Set(graph.nodes.map((node) => node.id));
  for (const node of fragment.nodes) {
    if (nodeIds.has(node.id)) continue;
    nodeIds.add(node.id);
    graph.nodes.push(node);
  }
  const edgeIds = new Set(graph.edges.map((edge) => edge.id));
  for (const edge of fragment.edges) {
    if (edgeIds.has(edge.id)) continue;
    edgeIds.add(edge.id);
    graph.edges.push(edge);
  }
}

export function createTestFileNode(graph, entry) {
  const nodeId = `test_file:${entry.serviceName}:${entry.filePath}`;
  if (!graph.nodes.some((node) => node.id === nodeId)) {
    graph.nodes.push({
      id: nodeId,
      kind: "test_file",
      service: entry.serviceName,
      label: path.basename(entry.filePath),
      filePath: entry.filePath,
      metadata: {
        suiteName: entry.suiteName,
        framework: entry.framework,
        type: toSelectionType(entry.type, entry.framework),
      },
    });
  }
  return nodeId;
}

export function apiRouteLookupKey(method, route) {
  return `${method}:${route}`;
}

export function toApiRequestPath(route) {
  return normalizeRoute(`/api${route === "/" ? "" : route}`);
}

export function toSelectionType(type, framework) {
  if (framework === "playwright") return "pw";
  if (type === "integration") return "int";
  return type;
}

export function pageLabelFromRoute(route) {
  if (route === "/") return "Home";
  return route
    .split("/")
    .filter(Boolean)
    .map((segment) => {
      if (segment.startsWith("[") && segment.endsWith("]")) {
        return segment.slice(1, -1);
      }
      return segment;
    })
    .map((segment) => segment.replace(/[-_]+/gu, " "))
    .map((segment) => segment.replace(/^\w/u, (char) => char.toUpperCase()))
    .join(" ");
}

export function normalizeRoute(value) {
  const trimmed = String(value || "/").trim();
  if (!trimmed || trimmed === "/") return "/";
  const withLeadingSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
  return withLeadingSlash.length > 1 ? withLeadingSlash.replace(/\/+$/u, "") : withLeadingSlash;
}

export function normalizePath(filePath) {
  return filePath.split(path.sep).join("/");
}

export function modulePathKey(filePath) {
  const normalized = normalizePath(filePath);
  const ext = path.extname(normalized);
  if (path.basename(normalized, ext) === "index") {
    return normalizePath(path.dirname(normalized));
  }
  return normalized.slice(0, normalized.length - ext.length);
}

export function escapeRegExp(value) {
  return String(value).replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
}

export function isServerActionFile(content) {
  const trimmed = content.trimStart();
  return trimmed.startsWith('"use server"') || trimmed.startsWith("'use server'");
}

export function isBackendSpecifier(specifier) {
  return (
    specifier.startsWith("@/backend/server/") ||
    specifier.includes("/backend/server/") ||
    specifier.startsWith("./backend/server/") ||
    specifier.startsWith("../backend/server/")
  );
}

export function isDataSpecifier(specifier) {
  return (
    specifier.startsWith("@/backend/data/") ||
    specifier.startsWith("@/backend/dal/") ||
    specifier.includes("/backend/data/") ||
    specifier.includes("/backend/dal/") ||
    specifier.includes("/db/") ||
    specifier.includes("/repository/") ||
    specifier.includes("/repositories/")
  );
}

export function hasWord(content, word) {
  if (!content || !word) return false;
  return new RegExp(`\\b${escapeRegExp(word)}\\b`, "u").test(content);
}

export function dedupeRequests(requests) {
  const seen = new Set();
  return requests.filter((entry) => {
    const key = `${entry.method}:${entry.path}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

export function dedupeNodes(nodes) {
  const seen = new Set();
  return nodes.filter((node) => {
    if (seen.has(node.id)) return false;
    seen.add(node.id);
    return true;
  });
}

export function dedupeEdges(edges) {
  const seen = new Set();
  return edges.filter((edge) => {
    if (seen.has(edge.id)) return false;
    seen.add(edge.id);
    return true;
  });
}

export function dedupeTargets(targets) {
  const seen = new Set();
  return targets.filter((target) => {
    const key = `${target.kind}:${target.value}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

export function dedupeBackendImports(entries) {
  const seen = new Set();
  return entries.filter((entry) => {
    const key = `${entry.importName}:${entry.node.filePath || ""}:${entry.node.label}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

export function dedupeDataImports(entries) {
  const seen = new Set();
  return entries.filter((entry) => {
    const key = `${entry.importName}:${entry.node.filePath || ""}:${entry.node.label}`;
    if (seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

export function findMatchingApiRouteEntry(method, candidatePath, routeEntries = []) {
  const normalizedMethod = String(method || "").trim().toUpperCase();
  const normalizedCandidate = normalizeRequestPathCandidate(candidatePath);
  if (!normalizedMethod || !normalizedCandidate) return null;

  return [...routeEntries]
    .filter((entry) => entry.method === normalizedMethod)
    .sort(compareRouteSpecificity)
    .find((entry) => routePatternMatchesCandidate(entry.requestPath, normalizedCandidate)) || null;
}

export function findMatchingRouteValue(candidateRoute, routeValues = []) {
  const normalizedCandidate = normalizeRequestPathCandidate(candidateRoute);
  if (!normalizedCandidate) return null;

  return [...routeValues]
    .sort((left, right) => compareRouteSpecificity({ requestPath: left }, { requestPath: right }))
    .find((routeValue) => routePatternMatchesCandidate(routeValue, normalizedCandidate)) || null;
}

export function normalizeRequestPathCandidate(value) {
  if (typeof value !== "string") return null;
  const [withoutHash] = value.split("#");
  const [withoutQuery] = withoutHash.split("?");
  return normalizeRoute(withoutQuery || "/");
}

export function routePatternMatchesCandidate(patternPath, candidatePath) {
  const normalizedPattern = normalizeRequestPathCandidate(patternPath);
  const normalizedCandidate = normalizeRequestPathCandidate(candidatePath);
  if (!normalizedPattern || !normalizedCandidate) return false;

  const patternSegments = splitRouteSegments(normalizedPattern);
  const candidateSegments = splitRouteSegments(normalizedCandidate);
  if (patternSegments.length !== candidateSegments.length) return false;

  for (let index = 0; index < patternSegments.length; index += 1) {
    const patternSegment = patternSegments[index];
    const candidateSegment = candidateSegments[index];
    if (patternSegment === candidateSegment) continue;
    if (isDynamicRouteSegment(patternSegment) && candidateSegment.length > 0) continue;
    if (candidateSegment === DYNAMIC_SEGMENT_TOKEN && isDynamicRouteSegment(patternSegment)) continue;
    return false;
  }

  return true;
}

export function isDynamicRouteSegment(segment) {
  return /^\[[^/]+\]$/u.test(segment) || /^\[\.\.\.[^/]+\]$/u.test(segment) || /^\[\[\.\.\.[^/]+\]\]$/u.test(segment);
}

function splitRouteSegments(value) {
  if (value === "/") return [];
  return value.split("/").filter(Boolean);
}

function compareRouteSpecificity(left, right) {
  const leftSegments = splitRouteSegments(left.requestPath || "");
  const rightSegments = splitRouteSegments(right.requestPath || "");
  const leftStaticSegments = leftSegments.filter((segment) => !isDynamicRouteSegment(segment)).length;
  const rightStaticSegments = rightSegments.filter((segment) => !isDynamicRouteSegment(segment)).length;
  if (leftStaticSegments !== rightStaticSegments) return rightStaticSegments - leftStaticSegments;
  return rightSegments.length - leftSegments.length;
}
