import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";

const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const contractPath = path.join(rootDir, "contracts/admin-openapi.json");
const outputPath = path.join(rootDir, "src/shared/api/generated/endpoints.ts");
const methods = ["get", "post", "put", "patch", "delete"];

const contract = JSON.parse(await readFile(contractPath, "utf8"));
const serverUrl = contract.servers?.[0]?.url || "";
const apiPathPrefix = serverUrl.startsWith("/api") ? serverUrl.slice("/api".length) : serverUrl;
const endpoints = [];

for (const [openapiPath, pathItem] of Object.entries(contract.paths || {})) {
  for (const method of methods) {
    const operation = pathItem[method];
    if (!operation?.operationId) {
      continue;
    }
    const permissions = Array.isArray(operation["x-permissions"])
      ? operation["x-permissions"]
      : operation["x-permission"]
        ? [operation["x-permission"]]
        : [];

    endpoints.push({
      method: method.toUpperCase(),
      operationId: operation.operationId,
      path: `${apiPathPrefix}${openapiPath}`,
      permission: operation["x-permission"] || permissions[0],
      permissions
    });
  }
}

endpoints.sort((left, right) => left.operationId.localeCompare(right.operationId));

const operationLines = endpoints.map((endpoint) => {
  return `  ${endpoint.operationId}: ${JSON.stringify(endpoint.operationId)}`;
});

const endpointLines = endpoints.map((endpoint) => {
  const permission = endpoint.permission ? `,\n    permission: ${JSON.stringify(endpoint.permission)}` : "";
  const permissions = endpoint.permissions.length ? `,\n    permissions: ${JSON.stringify(endpoint.permissions)}` : "";
  return `  ${endpoint.operationId}: {
    method: ${JSON.stringify(endpoint.method)},
    operationId: API_OPERATIONS.${endpoint.operationId},
    path: ${JSON.stringify(endpoint.path)}${permission}${permissions}
  }`;
});

const output = `/* eslint-disable */
// Generated by scripts/generate-api-client.mjs. Do not edit manually.
import type { PermissionCode } from "@/app/permissions";

export type ApiHttpMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT";

export interface ApiEndpoint {
  method: ApiHttpMethod;
  operationId: string;
  path: string;
  permission?: PermissionCode;
  permissions?: PermissionCode[];
}

export const API_OPERATIONS = {
${operationLines.join(",\n")}
} as const;

export type ApiOperationId = (typeof API_OPERATIONS)[keyof typeof API_OPERATIONS];

export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
${endpointLines.join(",\n")}
};

export const API_OPERATION_IDS = Object.values(API_OPERATIONS);

export const API_PERMISSION_CODES = Array.from(
  new Set(
    Object.values(API_ENDPOINTS)
      .flatMap((endpoint) => endpoint.permissions || (endpoint.permission ? [endpoint.permission] : []))
      .filter((permission): permission is PermissionCode => Boolean(permission))
  )
);

export type ApiPathParams = Record<string, number | string>;

export function apiEndpointPath(operationId: ApiOperationId, params: ApiPathParams = {}) {
  let endpointPath = API_ENDPOINTS[operationId].path;

  for (const [key, value] of Object.entries(params)) {
    endpointPath = endpointPath.replace(\`{\${key}}\`, encodeURIComponent(String(value)));
  }

  return endpointPath;
}
`;

await mkdir(path.dirname(outputPath), { recursive: true });
await writeFile(outputPath, output);
console.log(`Generated ${path.relative(rootDir, outputPath)} from ${path.relative(rootDir, contractPath)}`);
