import { isNonNullish } from "@fern-api/ui-core-utils";
import { camelCase } from "es-toolkit/compat";
import { UnreachableCaseError } from "ts-essentials";
import { FernRegistry } from "../../../../client/generated";
import { BaseOpenApiV3_1ConverterNode, } from "../../../BaseOpenApiV3_1Converter.node";
import { resolveSchemaReference } from "../../../utils/3.1/resolveSchemaReference";
import { createTypeDefinition } from "../../../utils/createTypeDefinition";
import { maybeSingleValueToArray } from "../../../utils/maybeSingleValueToArray";
import { MediaType } from "../../../utils/MediaType";
import { AvailabilityConverterNode } from "../../extensions/AvailabilityConverter.node";
import { isReferenceObject } from "../../guards/isReferenceObject";
import { SchemaConverterNode } from "../../schemas";
import { ReferenceConverterNode } from "../../schemas/ReferenceConverter.node";
import { GLOBAL_EXAMPLE_NAME } from "../ExampleObjectConverter.node";
import { MultipartFormDataPropertySchemaConverterNode } from "./MultipartFormDataPropertySchemaConverter.node";
export class RequestMediaTypeObjectConverterNode extends BaseOpenApiV3_1ConverterNode {
    description;
    method;
    path;
    // application/json
    schema;
    // application/octet-stream
    isOptional;
    contentType;
    // multipart/form-data
    availability;
    requiredFields;
    fields;
    examples;
    constructor(args) {
        super(args);
        this.method = args.method;
        this.path = args.path;
        this.safeParse(args);
    }
    parse({ contentType }) {
        if (this.input.schema != null) {
            const resolvedSchema = resolveSchemaReference(this.input.schema, this.context.document);
            this.availability = new AvailabilityConverterNode({
                input: this.input.schema,
                context: this.context,
                accessPath: this.accessPath,
                pathId: "availability",
            });
            this.schema = isReferenceObject(this.input.schema)
                ? new ReferenceConverterNode({
                    input: this.input.schema,
                    context: this.context,
                    accessPath: this.accessPath,
                    pathId: "schema",
                    seenSchemas: new Set(),
                    nullable: false,
                    schemaName: "Request Body",
                    description: this.input.schema.description,
                    availability: this.availability,
                })
                : undefined;
            const mediaType = MediaType.parse(contentType);
            // An exhaustive switch cannot be used here, because contentType is an unbounded string
            if (mediaType?.containsJSON()) {
                this.contentType = "json";
                this.schema =
                    this.schema ??
                        new SchemaConverterNode({
                            input: this.input.schema,
                            context: this.context,
                            accessPath: this.accessPath,
                            pathId: "schema",
                            seenSchemas: new Set(),
                            nullable: undefined,
                            schemaName: "Request Body",
                        });
            }
            else if (mediaType?.isOctetStream()) {
                this.contentType = "bytes";
                this.isOptional = resolvedSchema?.required == null;
            }
            else if (mediaType?.isMultiPartFormData()) {
                this.contentType = "form-data";
                this.requiredFields = resolvedSchema?.required;
                this.fields = Object.fromEntries(Object.entries(resolvedSchema?.properties ?? {})
                    .map(([key, property]) => {
                    if (property == null) {
                        return undefined;
                    }
                    return [
                        key,
                        new MultipartFormDataPropertySchemaConverterNode({
                            input: property,
                            context: this.context,
                            accessPath: this.accessPath,
                            pathId: `schema.${key}`,
                            seenSchemas: new Set(),
                            nullable: undefined,
                            schemaName: key,
                        }),
                    ];
                })
                    .filter(isNonNullish));
            }
            else {
                this.context.errors.warning({
                    message: `Expected request body of reference or object with json, streaming or form-data content types. Received: ${contentType}`,
                    path: this.accessPath,
                });
            }
        }
        // This sets examples derived from OpenAPI examples.
        // this.input.example is typed as any
        // this.input.examples is typed as Record<string, OpenAPIV3_1.ReferenceObject | OpenAPIV3.ExampleObject> | undefined
        // In order to create a consistent shape, we add a default string key for an example, which should be treated as a global example
        // If there is no global example, we try to generate an example from underlying schemas, which may have examples, or defaults or fallback values
        const generatedExample = this.schema?.example({
            includeOptionals: true,
            override: undefined,
        });
        this.examples = {
            ...(this.input.example != null
                ? {
                    [GLOBAL_EXAMPLE_NAME]: {
                        value: this.input.example,
                    },
                }
                : {}),
            ...this.input.examples,
        };
        if (generatedExample != null && Object.keys(this.examples).length === 0) {
            this.examples = {
                [GLOBAL_EXAMPLE_NAME]: {
                    value: generatedExample,
                },
            };
        }
    }
    convertJsonLike() {
        const convertedJsonSchema = this.schema?.convert();
        if (convertedJsonSchema == null) {
            return undefined;
        }
        const convertedJsonSchemaArray = maybeSingleValueToArray(convertedJsonSchema);
        return convertedJsonSchemaArray
            ?.map((convertedJsonSchema) => {
            const type = convertedJsonSchema.type;
            switch (type) {
                case "object":
                case "alias":
                    return convertedJsonSchema;
                case "enum":
                case "undiscriminatedUnion":
                case "discriminatedUnion": {
                    const uniqueId = camelCase([this.method, this.path, this.contentType, "request"].join("_"));
                    createTypeDefinition({
                        uniqueId,
                        type: convertedJsonSchema,
                        contextTypes: this.context.generatedTypes,
                        description: this.schema?.description,
                        availability: this.schema?.availability?.convert(),
                    });
                    return {
                        type: "alias",
                        value: {
                            type: "id",
                            id: FernRegistry.TypeId(uniqueId),
                            default: convertedJsonSchema.type === "enum" &&
                                convertedJsonSchema.default != null
                                ? {
                                    type: "enum",
                                    value: convertedJsonSchema.default,
                                }
                                : undefined,
                        },
                    };
                }
                case undefined:
                    return undefined;
                default:
                    new UnreachableCaseError(type);
                    return undefined;
            }
        })
            .filter(isNonNullish);
    }
    convert() {
        if (this.schema instanceof ReferenceConverterNode) {
            return this.schema.convert();
        }
        switch (this.contentType) {
            case "json": {
                return this.convertJsonLike();
            }
            case "bytes":
                return {
                    type: "bytes",
                    isOptional: this.isOptional ?? false,
                    contentType: this.contentType,
                };
            case "form-data": {
                const possibleFields = Object.entries(this.fields ?? {})
                    .map(([key, field]) => {
                    switch (field.multipartType) {
                        case "file":
                            return [
                                {
                                    type: field.multipartType,
                                    key: FernRegistry.PropertyKey(key),
                                    isOptional: this.requiredFields?.includes(key) == null,
                                    contentType: field.contentType,
                                    description: field.description,
                                    availability: field.availability?.convert(),
                                },
                            ];
                        case "files":
                            return [
                                {
                                    type: field.multipartType,
                                    key: FernRegistry.PropertyKey(key),
                                    isOptional: this.requiredFields?.includes(key) == null,
                                    contentType: field.contentType,
                                    description: field.description,
                                    availability: field.availability?.convert(),
                                },
                            ];
                        case "property": {
                            const maybeValueShapes = maybeSingleValueToArray(field.convert());
                            const type = field.multipartType;
                            return maybeValueShapes?.map((valueShape) => ({
                                type,
                                key: FernRegistry.PropertyKey(key),
                                contentType: field.contentType,
                                exploded: undefined,
                                valueShape,
                                description: field.description,
                                availability: field.availability?.convert(),
                            }));
                        }
                        case undefined:
                            return [];
                        default:
                            new UnreachableCaseError(field.multipartType);
                            return [];
                    }
                })
                    .filter(isNonNullish);
                const fieldPermutations = possibleFields.reduce((acc, curr) => {
                    return acc.flatMap((a) => curr.length > 0 ? curr.map((b) => [...a, b]) : [[...a]]);
                }, [[]]);
                return fieldPermutations.map((fields) => ({
                    type: "formData",
                    fields,
                    availability: this.availability?.convert(),
                    description: this.description,
                }));
            }
            case undefined: {
                return this.convertJsonLike();
            }
            default:
                return undefined;
        }
    }
}
