// Convert https://openapi-ts.dev/introduction generated OpenAPI TypeScript types to contracts
// Example: pnpx openapi-typescript https://developer.clickup.com/openapi/ClickUp_PUBLIC_API_V3.yaml -o clickup.d.ts --export-type

import type { ContractGuard } from "./contract-guard.js";
import type { Contract, ContractResponse, CreateContractRouteDetails, HasProps, InterfaceToZod } from "./contract.js";
import type {
    EHttpStatusCodeClientError,
    EHttpStatusCodeServerError,
    EHttpStatusCodeSuccess,
} from "./http-status-code.js";
import type { ERouteHttpVerb } from "./http-verb.js";
import type { ZodVoid, z } from "zod";

type OpenApiHttpMethod = "get" | "post" | "put" | "patch" | "delete";
type KnownHttpStatusCode = EHttpStatusCodeSuccess | EHttpStatusCodeClientError | EHttpStatusCodeServerError | "default";
type OpenApiMethodToRouteMethod<M extends OpenApiHttpMethod> = M extends "get"
    ? ERouteHttpVerb.GET
    : M extends "post"
      ? ERouteHttpVerb.POST
      : M extends "put"
        ? ERouteHttpVerb.PUT
        : M extends "patch"
          ? ERouteHttpVerb.PATCH
          : M extends "delete"
            ? ERouteHttpVerb.DELETE
            : never;

type OmitNeverProps<T> = {
    [K in keyof T as [T[K]] extends [never] ? never : K]: T[K];
};

type OpenApiJsonContent<T> = T extends { content: infer Content }
    ? Content extends { "application/json": infer Json }
        ? Json
        : Content extends Record<string, infer Fallback>
          ? Fallback
          : void
    : void;

type OpenApiRequestBody<Operation> = Operation extends { requestBody: infer RequestBody }
    ? OpenApiJsonContent<RequestBody>
    : void;

type OpenApiParameters<Operation, Key extends "path" | "query" | "header"> = Operation extends {
    parameters: infer Parameters;
}
    ? Parameters extends Record<string, unknown>
        ? Key extends keyof Parameters
            ? Parameters[Key] extends never
                ? object
                : Parameters[Key]
            : object
        : object
    : object;

type OpenApiParams<Operation> = OmitNeverProps<
    OpenApiParameters<Operation, "path"> & OpenApiParameters<Operation, "query">
>;
type OpenApiHeaders<Operation> = OmitNeverProps<OpenApiParameters<Operation, "header">>;

type OpenApiResponses<Operation> = Operation extends { responses: infer Responses }
    ? {
          // We want to have stable KnownHttpStatusCode enum value types instead of numbers.
          // Therefore, iterate all keys of KnownHttpStatusCode and check if the status code is available in the operation
          [Status in KnownHttpStatusCode as Status extends keyof Responses
              ? Status
              : `${Status}` extends keyof Responses
                ? Status
                : never]: Status extends keyof Responses
              ? ContractResponse<OpenApiJsonContent<Responses[Status]>>
              : `${Status}` extends keyof Responses
                ? ContractResponse<OpenApiJsonContent<Responses[`${Status}`]>>
                : never;
      }
    : Record<never, never>;

type OpenApiContractFromOperation<Path extends string, Method extends OpenApiHttpMethod, Operation> = Contract<
    Readonly<{ path: Path; method: OpenApiMethodToRouteMethod<Method> }>,
    HasProps<OpenApiRequestBody<Operation>> extends true ? InterfaceToZod<OpenApiRequestBody<Operation>> : ZodVoid,
    HasProps<OpenApiParams<Operation>> extends true ? InterfaceToZod<OpenApiParams<Operation>> : ZodVoid,
    HasProps<OpenApiHeaders<Operation>> extends true ? InterfaceToZod<OpenApiHeaders<Operation>> : ZodVoid,
    OpenApiResponses<Operation>,
    Record<string, ContractGuard>,
    CreateContractRouteDetails,
    typeof z,
    null[]
>;

type ReplaceCurlyBrackets<T> = T extends `${infer Head}{${infer Inner}}${infer Tail}`
    ? `${Head}:${ReplaceCurlyBrackets<Inner>}${ReplaceCurlyBrackets<Tail>}`
    : T;

type ContractFromOpenApiPathsUnion<Paths extends Record<string, Record<string, unknown>>> = {
    [Path in keyof Paths]: {
        [Method in Extract<keyof Paths[Path], OpenApiHttpMethod>]: [Paths[Path][Method]] extends [never]
            ? never
            : OpenApiContractFromOperation<ReplaceCurlyBrackets<Extract<Path, string>>, Method, Paths[Path][Method]>;
    }[Extract<keyof Paths[Path], OpenApiHttpMethod>];
}[keyof Paths];

/**
 * Allows to convert a set of OpenAPI paths to a set of contracts. You can generate the paths from a OpenAPI specification file
 * via the following command:
 *
 * ```sh
 * pnpx openapi-typescript https://developer.clickup.com/openapi/ClickUp_PUBLIC_API_V3.yaml -o clickup.d.ts --export-type
 * ```
 *
 * *Performance tip*: When you only need a few paths, you can pass a whitelist of paths to the function to avoid generating all paths
 * and save memory in TypeScript.
 *
 * @see https://openapi-ts.dev/introduction
 */
type ContractFromOpenApiPaths<
    Paths extends Record<string, Record<string, unknown>>,
    WhitelistPaths extends keyof Paths & string = keyof Paths & string,
> = Readonly<Record<string, ContractFromOpenApiPathsUnion<Pick<Paths, WhitelistPaths>>>>;

export type { ContractFromOpenApiPaths };
