import { isNonNullish } from "@fern-api/ui-core-utils";
import { UnreachableCaseError } from "ts-essentials";
import { FernRegistry } from "../../../../client/generated";
import { BaseOpenApiV3_1ConverterNode, } from "../../../BaseOpenApiV3_1Converter.node";
import { resolveReference } from "../../../utils/3.1/resolveReference";
import { maybeSingleValueToArray } from "../../../utils/maybeSingleValueToArray";
import { MediaType } from "../../../utils/MediaType";
import { AvailabilityConverterNode } from "../../extensions/AvailabilityConverter.node";
import { isObjectSchema } from "../../guards/isObjectSchema";
import { isReferenceObject } from "../../guards/isReferenceObject";
import { ObjectConverterNode } from "../../schemas/ObjectConverter.node";
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;
    // application/json
    schema;
    // application/octet-stream
    isOptional;
    contentType;
    // multipart/form-data
    availability;
    requiredFields;
    fields;
    resolvedSchema;
    examples;
    constructor(args, contentType) {
        super(args);
        this.safeParse(contentType);
    }
    parse(contentType) {
        // 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
        this.examples = {
            ...(this.input.example != null || this.schema?.example() != null
                ? {
                    [GLOBAL_EXAMPLE_NAME]: {
                        value: this.input.example ?? this.schema?.example(),
                    },
                }
                : {}),
            ...this.input.examples,
        };
        if (this.input.schema != null) {
            if (isReferenceObject(this.input.schema)) {
                this.resolvedSchema = resolveReference(this.input.schema, this.context.document, undefined);
                this.schema = new ReferenceConverterNode({
                    input: this.input.schema,
                    context: this.context,
                    accessPath: this.accessPath,
                    pathId: "schema",
                });
            }
            else if (isObjectSchema(this.input.schema)) {
                this.resolvedSchema = this.input.schema;
                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 = new ObjectConverterNode({
                        input: this.input.schema,
                        context: this.context,
                        accessPath: this.accessPath,
                        pathId: "schema",
                        seenSchemas: new Set(),
                    });
                }
                else if (mediaType?.isOctetStream()) {
                    this.contentType = "bytes";
                    this.isOptional = this.input.schema.required == null;
                }
                else if (mediaType?.isMultiPartFormData()) {
                    this.contentType = "form-data";
                    this.description = this.input.schema.description;
                    this.availability = new AvailabilityConverterNode({
                        input: this.input.schema,
                        context: this.context,
                        accessPath: this.accessPath,
                        pathId: "availability",
                    });
                    this.requiredFields = this.input.schema.required;
                    this.fields = Object.fromEntries(Object.entries(this.input.schema?.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(),
                            }),
                        ];
                    })
                        .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,
                    });
                }
            }
        }
        else {
            this.context.errors.warning({
                message: "Expected media type schema or reference.",
                path: this.accessPath,
            });
        }
    }
    convert() {
        switch (this.contentType) {
            case "json":
                return this.schema?.convert();
            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,
                                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.schema?.convert();
            default:
                return undefined;
        }
    }
}
