import { performance } from "node:perf_hooks";
import type { Readable } from "node:stream";
import { createConfig } from "@redocly/openapi-core";
import type ts from "typescript";
import { validateAndBundle } from "./lib/redoc.js";
import { debug, resolveRef, scanDiscriminators } from "./lib/utils.js";
import transformSchema from "./transform/index.js";
import type { GlobalContext, OpenAPI3, OpenAPITSOptions } from "./types.js";

export * from "./lib/ts.js";
export * from "./lib/utils.js";
export { default as transformComponentsObject } from "./transform/components-object.js";
export { default as transformHeaderObject } from "./transform/header-object.js";
export { default as transformSchema } from "./transform/index.js";
export { default as transformMediaTypeObject } from "./transform/media-type-object.js";
export * from "./transform/operation-object.js";
export { default as transformOperationObject } from "./transform/operation-object.js";
export { default as transformParameterObject } from "./transform/parameter-object.js";
export * from "./transform/path-item-object.js";
export { default as transformPathItemObject } from "./transform/path-item-object.js";
export { default as transformPathsObject } from "./transform/paths-object.js";
export { default as transformRequestBodyObject } from "./transform/request-body-object.js";
export { default as transformResponseObject } from "./transform/response-object.js";
export { default as transformResponsesObject } from "./transform/responses-object.js";
export * from "./transform/schema-object.js";
export { default as transformSchemaObject } from "./transform/schema-object.js";
export * from "./types.js";

export const COMMENT_HEADER = `/**
 * This file was auto-generated by openapi-typescript.
 * Do not make direct changes to the file.
 */

`;

/**
 * Convert an OpenAPI schema to TypesScript AST
 * @param {string|URL|object|Readable} source OpenAPI schema source:
 *   - YAML: string
 *   - JSON: parsed object
 *   - URL: URL to a YAML or JSON file (local or remote)
 *   - Readable: Readable stream of YAML or JSON
 */
export default async function openapiTS(
  source: string | URL | OpenAPI3 | Buffer | Readable,
  options: OpenAPITSOptions = {} as Partial<OpenAPITSOptions>,
): Promise<ts.Node[]> {
  if (!source) {
    throw new Error("Empty schema. Please specify a URL, file path, or Redocly Config");
  }

  const redoc =
    options.redocly ??
    (await createConfig(
      {
        rules: {
          "operation-operationId-unique": { severity: "error" }, // throw error on duplicate operationIDs
        },
      },
      { extends: ["minimal"] },
    ));

  const schema = await validateAndBundle(source, {
    redoc,
    cwd: options.cwd instanceof URL ? options.cwd : new URL(`file://${options.cwd ?? process.cwd()}/`),
    silent: options.silent ?? false,
  });

  const ctx: GlobalContext = {
    additionalProperties: options.additionalProperties ?? false,
    alphabetize: options.alphabetize ?? false,
    arrayLength: options.arrayLength ?? false,
    defaultNonNullable: options.defaultNonNullable ?? true,
    discriminators: scanDiscriminators(schema, options),
    emptyObjectsUnknown: options.emptyObjectsUnknown ?? false,
    enum: options.enum ?? false,
    enumValues: options.enumValues ?? false,
    dedupeEnums: options.dedupeEnums ?? false,
    excludeDeprecated: options.excludeDeprecated ?? false,
    exportType: options.exportType ?? false,
    immutable: options.immutable ?? false,
    rootTypes: options.rootTypes ?? false,
    rootTypesNoSchemaPrefix: options.rootTypesNoSchemaPrefix ?? false,
    injectFooter: [],
    pathParamsAsTypes: options.pathParamsAsTypes ?? false,
    postTransform: typeof options.postTransform === "function" ? options.postTransform : undefined,
    propertiesRequiredByDefault: options.propertiesRequiredByDefault ?? false,
    redoc,
    silent: options.silent ?? false,
    inject: options.inject ?? undefined,
    transform: typeof options.transform === "function" ? options.transform : undefined,
    transformProperty: typeof options.transformProperty === "function" ? options.transformProperty : undefined,
    makePathsEnum: options.makePathsEnum ?? false,
    generatePathParams: options.generatePathParams ?? false,
    resolve($ref) {
      return resolveRef(schema, $ref, { silent: options.silent ?? false });
    },
  };

  const transformT = performance.now();
  const result = transformSchema(schema, ctx);
  debug("Completed AST transformation for entire document", "ts", performance.now() - transformT);

  return result;
}
