import { isNonNullish } from "@fern-api/ui-core-utils";
import { FernRegistry } from "../../../client/generated";
import { BaseOpenApiV3_1ConverterNode, } from "../../BaseOpenApiV3_1Converter.node";
import { coalesceServers } from "../../utils/3.1/coalesceServers";
import { resolveParameterReference } from "../../utils/3.1/resolveParameterReference";
import { getEndpointId } from "../../utils/getEndpointId";
import { getExampleName } from "../../utils/getExampleName";
import { maybeSingleValueToArray } from "../../utils/maybeSingleValueToArray";
import { mergeSnippets } from "../../utils/mergeSnippets";
import { mergeXFernAndResponseExamples } from "../../utils/mergeXFernAndResponsesExamples";
import { SecurityRequirementObjectConverterNode } from "../auth/SecurityRequirementObjectConverter.node";
import { AvailabilityConverterNode } from "../extensions/AvailabilityConverter.node";
import { XFernEndpointExampleConverterNode } from "../extensions/XFernEndpointExampleConverter.node";
import { XFernGroupNameConverterNode } from "../extensions/XFernGroupNameConverter.node";
import { XFernSdkMethodNameConverterNode } from "../extensions/XFernSdkMethodNameConverter.node";
import { XFernWebhookConverterNode } from "../extensions/XFernWebhookConverter.node";
import { RedocExampleConverterNode } from "../extensions/examples/RedocExampleConverter.node";
import { X_FERN_GROUP_NAME } from "../extensions/fernExtension.consts";
import { isReferenceObject } from "../guards/isReferenceObject";
import { ExampleObjectConverterNode } from "./ExampleObjectConverter.node";
import { ParameterBaseObjectConverterNode, convertOperationObjectProperties, } from "./parameters/ParameterBaseObjectConverter.node";
import { RequestBodyObjectConverterNode } from "./request/RequestBodyObjectConverter.node";
import { ResponsesObjectConverterNode } from "./response/ResponsesObjectConverter.node";
export class OperationObjectConverterNode extends BaseOpenApiV3_1ConverterNode {
    servers;
    globalAuth;
    path;
    method;
    basePath;
    isWebhook;
    endpointIds;
    description;
    displayName;
    operationId;
    pathParameters;
    queryParameters;
    requestHeaders;
    requests;
    responses;
    availability;
    auth;
    namespaces;
    xFernExamplesNode;
    redocExamplesNode;
    emptyResponseExamples;
    constructor(args) {
        super(args);
        this.servers = args.servers;
        this.globalAuth = args.globalAuth;
        this.path = args.path;
        this.method = args.method;
        this.basePath = args.basePath;
        this.isWebhook = args.isWebhook;
        this.safeParse();
    }
    pushEmptyResponseExample(requestExample, requestBody, requestExampleName) {
        this.emptyResponseExamples ??= [];
        this.emptyResponseExamples.push(new ExampleObjectConverterNode({
            input: {
                requestExample: requestExample,
                responseExample: undefined,
            },
            context: this.context,
            accessPath: this.accessPath,
            pathId: "examples",
        }, this.path, 
        // Since there is no response, we can use any status code, so we use 200
        200, getExampleName(requestExampleName, undefined), {
            requestBody,
            pathParameters: this.pathParameters,
            queryParameters: this.queryParameters,
            requestHeaders: this.requestHeaders,
        }));
    }
    parse() {
        if (this.isWebhook && this.method !== "POST" && this.method !== "GET") {
            this.context.errors.error({
                message: `Webhook method must be POST or GET. Received: ${this.method}`,
                path: this.accessPath,
            });
            return;
        }
        this.description = this.input.description;
        this.displayName = this.input.summary;
        this.operationId = this.input.operationId;
        this.availability = new AvailabilityConverterNode({
            input: this.input,
            context: this.context,
            accessPath: this.accessPath,
            pathId: "x-fern-availability",
        });
        this.servers = coalesceServers(this.servers, this.input.servers, this.context, this.accessPath);
        this.isWebhook =
            new XFernWebhookConverterNode({
                input: this.input,
                context: this.context,
                accessPath: this.accessPath,
                pathId: this.pathId,
            }).isWebhook || this.isWebhook;
        this.input.parameters?.map((parameter, index) => {
            if (isReferenceObject(parameter)) {
                const resolvedParameter = resolveParameterReference(parameter, this.context.document);
                if (resolvedParameter != null) {
                    parameter = resolvedParameter;
                }
                else {
                    this.context.errors.warning({
                        message: `Expected parameter reference to resolve to an object. Received undefined reference: ${parameter.$ref}`,
                        path: [...this.accessPath, `parameters[${index}]`],
                    });
                    return undefined;
                }
            }
            if (parameter.in === "path") {
                if (parameter.schema != null) {
                    this.pathParameters ??= {};
                    this.pathParameters[parameter.name] =
                        new ParameterBaseObjectConverterNode({
                            input: parameter,
                            context: this.context,
                            accessPath: this.accessPath,
                            pathId: ["parameters", `${index}`],
                            parameterName: parameter.name,
                        });
                }
                // this.pathParameters.push(parameter.name);
            }
            else if (parameter.in === "query") {
                if (parameter.schema != null) {
                    this.queryParameters ??= {};
                    this.queryParameters[parameter.name] =
                        new ParameterBaseObjectConverterNode({
                            input: parameter,
                            context: this.context,
                            accessPath: this.accessPath,
                            pathId: ["parameters", `${index}`],
                            parameterName: parameter.name,
                        });
                }
            }
            else if (parameter.in === "header") {
                if (parameter.schema != null) {
                    this.requestHeaders ??= {};
                    this.requestHeaders[parameter.name] =
                        new ParameterBaseObjectConverterNode({
                            input: parameter,
                            context: this.context,
                            accessPath: this.accessPath,
                            pathId: ["parameters", `${index}`],
                            parameterName: parameter.name,
                        });
                }
            }
        });
        for (const pathParameterId of this.extractPathParameterIds() ?? []) {
            if (this.pathParameters?.[pathParameterId] == null) {
                this.context.errors.warning({
                    message: `Path parameter not defined: ${pathParameterId}`,
                    path: [...this.accessPath, "parameters"],
                });
            }
        }
        this.redocExamplesNode = new RedocExampleConverterNode({
            input: this.input,
            context: this.context,
            accessPath: this.accessPath,
            pathId: "x-code-samples",
        });
        this.requests =
            this.input.requestBody != null
                ? new RequestBodyObjectConverterNode({
                    input: this.input.requestBody,
                    context: this.context,
                    accessPath: this.accessPath,
                    pathId: "requestBody",
                    method: this.method,
                    path: this.path,
                })
                : undefined;
        this.responses =
            this.input.responses != null
                ? new ResponsesObjectConverterNode({
                    input: this.input.responses,
                    context: this.context,
                    accessPath: this.accessPath,
                    pathId: "responses",
                }, this.path, this.method, Object.values(this.requests?.requestBodiesByContentType ?? {}), {
                    pathParameters: this.pathParameters,
                    queryParameters: this.queryParameters,
                    requestHeaders: this.requestHeaders,
                })
                : undefined;
        if (this.responses == null) {
            const requestBodiesByContent = this.requests?.requestBodiesByContentType;
            if (requestBodiesByContent == null ||
                Object.keys(requestBodiesByContent).length === 0) {
                // Push an example
                this.pushEmptyResponseExample(undefined, undefined, undefined);
            }
            else {
                Object.values(requestBodiesByContent).forEach((requestBody) => {
                    if (Object.keys(requestBody.examples ?? {}).length === 0) {
                        const example = requestBody.schema?.example({
                            includeOptionals: false,
                            override: undefined,
                        });
                        if (example != null) {
                            this.pushEmptyResponseExample({ value: example }, requestBody, undefined);
                        }
                    }
                    else {
                        Object.entries(requestBody.examples ?? {}).forEach(([requestExampleName, requestExample]) => {
                            this.pushEmptyResponseExample(requestExample, requestBody, requestExampleName);
                        });
                    }
                });
            }
        }
        // TODO: pass appropriate status codes for examples
        let responseStatusCode = 200;
        if (this.responses?.responsesByStatusCode != null) {
            responseStatusCode = Number(Object.keys(this.responses.responsesByStatusCode)?.filter((statusCode) => Number(statusCode) >= 200 && Number(statusCode) < 300)[0]);
        }
        if (this.globalAuth != null) {
            this.auth = this.globalAuth;
        }
        if (this.input.security != null) {
            this.auth = new SecurityRequirementObjectConverterNode({
                input: this.input.security,
                context: this.context,
                accessPath: this.accessPath,
                pathId: "security",
            });
        }
        this.namespaces = [
            new XFernGroupNameConverterNode({
                input: this.input,
                context: this.context,
                accessPath: this.accessPath,
                pathId: "x-fern-group-name",
            }),
        ];
        if (this.namespaces?.every((namespace) => namespace.groupName == null) &&
            this.input.tags != null) {
            // TODO: review if tags multiplex endpoint definitions, or if there should
            // be some cascading behavior
            this.namespaces = this.input.tags.slice(0, 1).map((tag, index) => {
                const newNamespaceNode = new XFernGroupNameConverterNode({
                    input: { [X_FERN_GROUP_NAME]: tag },
                    context: this.context,
                    accessPath: this.accessPath,
                    pathId: ["tags", `${index}`],
                });
                return newNamespaceNode;
            });
        }
        const sdkMethodName = new XFernSdkMethodNameConverterNode({
            input: this.input,
            context: this.context,
            accessPath: this.accessPath,
            pathId: "x-fern-sdk-method-name",
        });
        this.endpointIds = this.namespaces
            .map((namespace) => getEndpointId({
            namespace: maybeSingleValueToArray(namespace?.groupName),
            path: this.path,
            method: this.method,
            sdkMethodName: sdkMethodName.sdkMethodName,
            operationId: this.input.operationId,
            displayName: this.displayName,
            isWebhook: this.isWebhook,
        }))
            .filter(isNonNullish);
        // TODO: figure out how to merge user specified examples with success response
        this.xFernExamplesNode = new XFernEndpointExampleConverterNode({
            input: this.input,
            context: this.context,
            accessPath: this.accessPath,
            pathId: "x-fern-examples",
        }, this.path, responseStatusCode, this.requests?.requestBodiesByContentType, this.responses?.responsesByStatusCode?.[responseStatusCode]?.responses);
    }
    extractPathParameterIds() {
        if (this.path == null) {
            return undefined;
        }
        return this.path
            .split("/")
            .map((part) => {
            if (part.startsWith("{") && part.endsWith("}")) {
                return part.slice(1, -1).trim();
            }
            return undefined;
        })
            .filter(isNonNullish);
    }
    convertPathToPathParts() {
        if (this.path === undefined) {
            return undefined;
        }
        const path = this.path.startsWith("/") ? this.path.slice(1) : this.path;
        const basePath = this.basePath?.convert();
        const pathParts = basePath
            ? [basePath, ...path.split("/")]
            : path.split("/");
        return pathParts.reduce((acc, part) => {
            acc.push({
                type: "literal",
                value: "/",
            });
            if (part.startsWith("{") && part.endsWith("}")) {
                acc.push({
                    type: "pathParameter",
                    value: FernRegistry.PropertyKey(part.slice(1, -1).trim()),
                });
            }
            else {
                acc.push({
                    type: "literal",
                    value: part,
                });
            }
            return acc;
        }, []);
    }
    convert() {
        if (this.path == null) {
            return undefined;
        }
        if (this.endpointIds == null) {
            return undefined;
        }
        const { responses, errors } = this.responses?.convert() ?? {
            responses: undefined,
            errors: undefined,
        };
        const emptyResponseExamples = this.emptyResponseExamples
            ?.map((example) => example.convert())
            .filter(isNonNullish);
        console.log(this.xFernExamplesNode?.convert()?.length, emptyResponseExamples?.length, responses?.flatMap((response) => response.examples)?.length);
        const examples = mergeXFernAndResponseExamples(this.xFernExamplesNode?.convert(), [
            ...(emptyResponseExamples ?? []),
            ...(responses?.flatMap((response) => response.examples) ?? []),
        ])?.map((example) => {
            return {
                ...example,
                snippets: mergeSnippets(example.snippets, this.redocExamplesNode?.convert()),
            };
        });
        if (this.isWebhook) {
            return this.endpointIds
                .map((endpointId, index) => {
                if (this.method !== "POST" && this.method !== "GET") {
                    return undefined;
                }
                return {
                    id: FernRegistry.WebhookId(endpointId),
                    description: this.description,
                    availability: this.availability?.convert(),
                    displayName: this.displayName,
                    operationId: this.operationId,
                    namespace: this.namespaces?.[index]?.convert(),
                    method: this.method,
                    path: this.convertPathToPathParts()?.map((part) => part.value.toString()) ?? [],
                    queryParameters: convertOperationObjectProperties(this.queryParameters)?.flat(),
                    headers: convertOperationObjectProperties(this.requestHeaders)?.flat(),
                    payloads: this.requests?.convertToWebhookPayload(),
                    examples: [this.requests?.webhookExample()].filter(isNonNullish),
                };
            })
                .filter(isNonNullish);
        }
        const environments = this.servers
            ?.map((server) => server.convert())
            .filter(isNonNullish);
        const pathParts = this.convertPathToPathParts();
        if (pathParts == null) {
            return undefined;
        }
        let authIds;
        const auth = this.auth?.convert();
        if (auth != null) {
            authIds = Object.keys(auth);
        }
        const responseHeaders = responses
            ?.flatMap((response) => response.headers)
            .filter(isNonNullish);
        return this.endpointIds.map((endpointId, index) => ({
            id: FernRegistry.EndpointId(endpointId),
            description: this.description,
            availability: this.availability?.convert(),
            namespace: this.namespaces?.[index]
                ?.convert()
                ?.map((id) => FernRegistry.api.v1.SubpackageId(id)),
            displayName: this.displayName,
            operationId: this.operationId,
            method: this.method,
            path: pathParts,
            auth: authIds?.map((id) => FernRegistry.api.latest.AuthSchemeId(id)),
            defaultEnvironment: environments?.[0]?.id,
            environments,
            pathParameters: convertOperationObjectProperties(this.pathParameters)?.flat(),
            queryParameters: convertOperationObjectProperties(this.queryParameters)?.flat(),
            requestHeaders: convertOperationObjectProperties(this.requestHeaders)?.flat(),
            responseHeaders: responseHeaders != null && responseHeaders.length > 0
                ? responseHeaders
                : undefined,
            requests: this.requests?.convert(),
            responses: responses?.map((response) => response.response),
            errors,
            examples,
            snippetTemplates: undefined,
            protocol: {
                type: "rest",
            },
        }));
    }
}
