import { readFile } from "node:fs/promises";
import { resolve } from "node:path";

export async function loadJsonSource(source) {
  if (/^https?:\/\//u.test(source)) {
    const response = await fetch(source, {
      headers: { accept: "application/json" },
      signal: AbortSignal.timeout(15_000),
    });
    if (!response.ok) {
      throw new Error(
        `Unable to load OpenAPI document: ${response.status} ${response.statusText}`,
      );
    }
    return response.json();
  }

  const content = await readFile(resolve(source), "utf8");
  return JSON.parse(content);
}

export function findMissingOperations(contract, openApiDocument) {
  const openApiPaths = openApiDocument?.paths;
  if (!openApiPaths || typeof openApiPaths !== "object") {
    throw new Error("OpenAPI document does not contain a paths object");
  }

  const normalizedPaths = new Map(
    Object.entries(openApiPaths).map(([path, pathItem]) => [
      normalizePath(path),
      pathItem,
    ]),
  );

  return Object.entries(contract).flatMap(([operationId, operation]) => {
    const method = operation.method.toLowerCase();
    const pathItem = normalizedPaths.get(normalizePath(operation.path));
    if (!pathItem) {
      return [{ operationId, ...operation, reason: "path is missing" }];
    }
    if (!pathItem[method]) {
      return [{ operationId, ...operation, reason: "method is missing" }];
    }
    return [];
  });
}

export function normalizePath(path) {
  const withoutTrailingSlash = path.length > 1 ? path.replace(/\/+$/u, "") : path;
  return withoutTrailingSlash.replace(/\{[^/{}]+\}/gu, "{}");
}
