"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isLiveQueryOperationDefinitionNode = exports.buildHTTPExecutor = void 0;
const graphql_1 = require("graphql");
const value_or_promise_1 = require("value-or-promise");
const utils_1 = require("@graphql-tools/utils");
const fetch_1 = require("@whatwg-node/fetch");
const createFormDataFromVariables_js_1 = require("./createFormDataFromVariables.js");
const handleEventStreamResponse_js_1 = require("./handleEventStreamResponse.js");
const handleMultipartMixedResponse_js_1 = require("./handleMultipartMixedResponse.js");
const isLiveQueryOperationDefinitionNode_js_1 = require("./isLiveQueryOperationDefinitionNode.js");
Object.defineProperty(exports, "isLiveQueryOperationDefinitionNode", { enumerable: true, get: function () { return isLiveQueryOperationDefinitionNode_js_1.isLiveQueryOperationDefinitionNode; } });
const prepareGETUrl_js_1 = require("./prepareGETUrl.js");
function buildHTTPExecutor(options) {
    const executor = (request) => {
        const fetchFn = request.extensions?.fetch ?? options?.fetch ?? fetch_1.fetch;
        let controller;
        let method = request.extensions?.method || options?.method;
        const operationAst = (0, utils_1.getOperationASTFromRequest)(request);
        const operationType = operationAst.operation;
        if ((options?.useGETForQueries || request.extensions?.useGETForQueries) &&
            operationType === 'query') {
            method = 'GET';
        }
        let accept = 'application/graphql-response+json, application/json, multipart/mixed';
        if (operationType === 'subscription' || (0, isLiveQueryOperationDefinitionNode_js_1.isLiveQueryOperationDefinitionNode)(operationAst)) {
            method ||= 'GET';
            accept = 'text/event-stream';
        }
        else {
            method ||= 'POST';
        }
        const endpoint = request.extensions?.endpoint || options?.endpoint || '/graphql';
        const headers = Object.assign({
            accept,
        }, (typeof options?.headers === 'function' ? options.headers(request) : options?.headers) || {}, request.extensions?.headers || {});
        const query = (0, graphql_1.print)(request.document);
        let timeoutId;
        if (options?.timeout) {
            controller = new AbortController();
            timeoutId = setTimeout(() => {
                if (!controller?.signal.aborted) {
                    controller?.abort('timeout');
                }
            }, options.timeout);
        }
        const responseDetailsForError = {};
        return new value_or_promise_1.ValueOrPromise(() => {
            switch (method) {
                case 'GET': {
                    const finalUrl = (0, prepareGETUrl_js_1.prepareGETUrl)({
                        baseUrl: endpoint,
                        query,
                        variables: request.variables,
                        operationName: request.operationName,
                        extensions: request.extensions,
                    });
                    const fetchOptions = {
                        method: 'GET',
                        headers,
                        signal: controller?.signal,
                    };
                    if (options?.credentials != null) {
                        fetchOptions.credentials = options.credentials;
                    }
                    return fetchFn(finalUrl, fetchOptions, request.context, request.info);
                }
                case 'POST':
                    return new value_or_promise_1.ValueOrPromise(() => (0, createFormDataFromVariables_js_1.createFormDataFromVariables)({
                        query,
                        variables: request.variables,
                        operationName: request.operationName,
                        extensions: request.extensions,
                    }, {
                        File: options?.File,
                        FormData: options?.FormData,
                    }))
                        .then(body => {
                        if (typeof body === 'string') {
                            headers['content-type'] = 'application/json';
                        }
                        const fetchOptions = {
                            method: 'POST',
                            body,
                            headers,
                            signal: controller?.signal,
                        };
                        if (options?.credentials != null) {
                            fetchOptions.credentials = options.credentials;
                        }
                        return fetchFn(endpoint, fetchOptions, request.context, request.info);
                    })
                        .resolve();
            }
        })
            .then((fetchResult) => {
            responseDetailsForError.status = fetchResult.status;
            responseDetailsForError.statusText = fetchResult.statusText;
            if (timeoutId != null) {
                clearTimeout(timeoutId);
            }
            // Retry should respect HTTP Errors
            if (options?.retry != null && !fetchResult.status.toString().startsWith('2')) {
                throw new Error(fetchResult.statusText || `HTTP Error: ${fetchResult.status}`);
            }
            const contentType = fetchResult.headers.get('content-type');
            if (contentType?.includes('text/event-stream')) {
                return (0, handleEventStreamResponse_js_1.handleEventStreamResponse)(fetchResult, controller);
            }
            else if (contentType?.includes('multipart/mixed')) {
                return (0, handleMultipartMixedResponse_js_1.handleMultipartMixedResponse)(fetchResult, controller);
            }
            return fetchResult.text();
        })
            .then(result => {
            if (typeof result === 'string') {
                if (result) {
                    try {
                        return JSON.parse(result);
                    }
                    catch (e) {
                        return {
                            errors: [
                                (0, utils_1.createGraphQLError)(`Unexpected response: ${JSON.stringify(result)}`, {
                                    extensions: {
                                        requestBody: {
                                            query,
                                            operationName: request.operationName,
                                        },
                                        responseDetails: responseDetailsForError,
                                    },
                                    originalError: e,
                                }),
                            ],
                        };
                    }
                }
            }
            else {
                return result;
            }
        })
            .catch((e) => {
            if (typeof e === 'string') {
                return {
                    errors: [
                        (0, utils_1.createGraphQLError)(e, {
                            extensions: {
                                requestBody: {
                                    query,
                                    operationName: request.operationName,
                                },
                                responseDetails: responseDetailsForError,
                            },
                        }),
                    ],
                };
            }
            else if (e.name === 'GraphQLError') {
                return {
                    errors: [e],
                };
            }
            else if (e.name === 'TypeError' && e.message === 'fetch failed') {
                return {
                    errors: [
                        (0, utils_1.createGraphQLError)(`fetch failed to ${endpoint}`, {
                            extensions: {
                                requestBody: {
                                    query,
                                    operationName: request.operationName,
                                },
                                responseDetails: responseDetailsForError,
                            },
                            originalError: e,
                        }),
                    ],
                };
            }
            else if (e.name === 'AbortError' && controller?.signal?.reason) {
                return {
                    errors: [
                        (0, utils_1.createGraphQLError)('The operation was aborted. reason: ' + controller.signal.reason, {
                            extensions: {
                                requestBody: {
                                    query,
                                    operationName: request.operationName,
                                },
                                responseDetails: responseDetailsForError,
                            },
                            originalError: e,
                        }),
                    ],
                };
            }
            else if (e.message) {
                return {
                    errors: [
                        (0, utils_1.createGraphQLError)(e.message, {
                            extensions: {
                                requestBody: {
                                    query,
                                    operationName: request.operationName,
                                },
                                responseDetails: responseDetailsForError,
                            },
                            originalError: e,
                        }),
                    ],
                };
            }
            else {
                return {
                    errors: [
                        (0, utils_1.createGraphQLError)('Unknown error', {
                            extensions: {
                                requestBody: {
                                    query,
                                    operationName: request.operationName,
                                },
                                responseDetails: responseDetailsForError,
                            },
                            originalError: e,
                        }),
                    ],
                };
            }
        })
            .resolve();
    };
    if (options?.retry != null) {
        return function retryExecutor(request) {
            let result;
            let attempt = 0;
            function retryAttempt() {
                attempt++;
                if (attempt > options.retry) {
                    if (result != null) {
                        return result;
                    }
                    return {
                        errors: [(0, utils_1.createGraphQLError)('No response returned from fetch')],
                    };
                }
                return new value_or_promise_1.ValueOrPromise(() => executor(request))
                    .then(res => {
                    result = res;
                    if (result?.errors?.length) {
                        return retryAttempt();
                    }
                    return result;
                })
                    .resolve();
            }
            return retryAttempt();
        };
    }
    return executor;
}
exports.buildHTTPExecutor = buildHTTPExecutor;
