"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeDefaultKy = exports.http = exports.filterHeaders = exports.wrapResponseClosure = exports.wrapRequestClosure = void 0;
const error_1 = require("./error");
const ky_universal_1 = __importDefault(require("ky-universal"));
const stream_1 = require("./stream");
const merge_1 = require("./merge");
const type_1 = require("./type");
const DEFAULT_KY_CONFIG = {
    headers: {
        accept: 'application/json, text/plain, */*',
        'user-agent': `bee-js`,
    },
};
function isHttpError(e) {
    return (0, type_1.isObject)(e) && typeof e.response !== 'undefined';
}
function isHttpRequestError(e) {
    return (0, type_1.isObject)(e) && typeof e.request !== 'undefined';
}
function headersToObject(header) {
    return [...header.entries()].reduce((obj, [key, val]) => {
        obj[key] = val;
        return obj;
    }, {});
}
function wrapRequest(request) {
    return {
        url: request.url,
        method: request.method.toUpperCase(),
        headers: headersToObject(request.headers),
    };
}
function wrapRequestClosure(cb) {
    return (request) => __awaiter(this, void 0, void 0, function* () {
        yield cb(wrapRequest(request));
    });
}
exports.wrapRequestClosure = wrapRequestClosure;
function wrapResponseClosure(cb) {
    return (request, options, response) => __awaiter(this, void 0, void 0, function* () {
        yield cb({
            headers: headersToObject(response.headers),
            status: response.status,
            statusText: response.statusText,
            request: wrapRequest(request),
        });
    });
}
exports.wrapResponseClosure = wrapResponseClosure;
/**
 * Filters out entries that has undefined value from headers object.
 * Modifies the original object!
 *
 * @param obj
 */
// eslint-disable-next-line @typescript-eslint/ban-types
function filterHeaders(obj) {
    if (obj === undefined) {
        return undefined;
    }
    (0, type_1.isStrictlyObject)(obj);
    const typedObj = obj;
    for (const key in typedObj) {
        if (typedObj[key] === undefined) {
            delete typedObj[key];
        }
    }
    if (Object.keys(typedObj).length === 0) {
        return undefined;
    }
    return typedObj;
}
exports.filterHeaders = filterHeaders;
/**
 * Main utility function to make HTTP requests.
 * @param ky
 * @param config
 */
function http(ky, config) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            const { path, responseType } = config, kyConfig = __rest(config, ["path", "responseType"]);
            const response = (yield ky(path, Object.assign(Object.assign({}, kyConfig), { searchParams: filterHeaders(kyConfig.searchParams) })));
            switch (responseType) {
                case 'stream':
                    if (!response.body) {
                        throw new error_1.BeeError('Response was expected to get data but did not get any!');
                    }
                    response.data = (0, stream_1.normalizeToReadableStream)(response.body);
                    break;
                case 'arraybuffer':
                    response.data = (yield response.arrayBuffer());
                    break;
                case 'json':
                    try {
                        response.data = (yield response.json());
                    }
                    catch (e) {
                        throw new error_1.BeeNotAJsonError();
                    }
                    break;
                default:
                    break; // If responseType is not set, then no data are expected
            }
            return response;
        }
        catch (e) {
            // Passthrough thrown errors
            if (e instanceof error_1.BeeNotAJsonError) {
                throw e;
            }
            if (isHttpError(e)) {
                let message;
                // We store the response body here as it can be read only once in Response's lifecycle so to make it exposed
                // to the user in the BeeResponseError, for further analysis.
                const body = yield e.response.text();
                try {
                    // The response can be Bee's JSON with structure `{code, message}` lets try to parse it
                    message = JSON.parse(body).message;
                }
                catch (e) { }
                if (message) {
                    throw new error_1.BeeResponseError(e.response.status, e.response, body, config, `${e.response.statusText}: ${message}`);
                }
                else {
                    throw new error_1.BeeResponseError(e.response.status, e.response, body, config, e.response.statusText);
                }
            }
            else if (isHttpRequestError(e)) {
                throw new error_1.BeeRequestError(e.message, config);
            }
            else {
                // Node 18 has native `fetch` implementation called Undici. Errors from this implementation have top level generic
                // message "fetch failed" with the more specific error placed into `cause` property. Instead of "fetch failed" we
                // expose the underlying problem.
                if (e.cause) {
                    throw new error_1.BeeError(e.cause.message);
                }
                throw new error_1.BeeError(e.message);
            }
        }
    });
}
exports.http = http;
function makeDefaultKy(kyConfig) {
    return ky_universal_1.default.create((0, merge_1.deepMerge)(DEFAULT_KY_CONFIG, kyConfig));
}
exports.makeDefaultKy = makeDefaultKy;
