"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
var qs_1 = __importDefault(require("qs"));
var style_serializer_1 = __importDefault(require("./style-serializer"));
// Certain styles don't support empty values.
function shouldNotStyleEmptyValues(parameter) {
    return ['simple', 'spaceDelimited', 'pipeDelimited', 'deepObject'].includes(parameter.style);
}
function shouldNotStyleReservedHeader(parameter) {
    return ['accept', 'authorization', 'content-type'].includes(parameter.name.toLowerCase());
}
/**
 * Note: This isn't necessarily part of the spec. Behavior for the value 'undefined' is, well,
 * undefined. This code makes our system look better. If we wanted to be more accurate, we might
 * want to remove this, restore the un-fixed behavior for undefined and have our UI pass in empty
 * string instead of undefined.
 */
function removeUndefinedForPath(value) {
    var finalValue = value;
    if (typeof finalValue === 'undefined') {
        return '';
    }
    if (Array.isArray(finalValue)) {
        finalValue = finalValue.filter(function (val) { return (val === undefined ? '' : val); });
        if (finalValue.length === 0) {
            finalValue = '';
        }
    }
    if (typeof finalValue === 'object') {
        Object.keys(finalValue).forEach(function (key) {
            finalValue[key] = finalValue[key] === undefined ? '' : finalValue[key];
        });
    }
    return finalValue;
}
function stylizeValue(value, parameter) {
    var finalValue = value;
    // Some styles don't work with empty values. We catch those there
    if (shouldNotStyleEmptyValues(parameter) && (typeof finalValue === 'undefined' || finalValue === '')) {
        // Paths need return an unstyled empty string instead of undefined so it's ignored in the final
        // path string.
        if (parameter["in"] === 'path') {
            return '';
        }
        // Everything but path should return undefined when unstyled so it's ignored in the final
        // parameter array.
        return undefined;
    }
    // Every style that adds their style to empty values should use emptystring for path parameters
    // instead of undefined to avoid the string `undefined`.
    if (parameter["in"] === 'path') {
        finalValue = removeUndefinedForPath(finalValue);
    }
    /**
     * Eventhough `accept`, `authorization`, and `content-type` headers can be defined as parameters,
     * they should be completely ignored when it comes to serialization.
     *
     *  > If `in` is "header" and the `name` field is "Accept", "Content-Type" or "Authorization", the
     *  > parameter definition SHALL be ignored.
     *
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-10}
     */
    if (parameter["in"] === 'header' && shouldNotStyleReservedHeader(parameter)) {
        return value;
    }
    /**
     * All parameter types have a default `style` format so if they don't have one prescribed we
     * should still conform to what the spec defines.
     *
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-parameterstyle}
     * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-parameterstyle}
     */
    var style = parameter.style;
    if (!style) {
        if (parameter["in"] === 'query') {
            style = 'form';
        }
        else if (parameter["in"] === 'path') {
            style = 'simple';
        }
        else if (parameter["in"] === 'header') {
            style = 'simple';
        }
        else if (parameter["in"] === 'cookie') {
            style = 'form';
        }
    }
    var explode = parameter.explode;
    if (explode === undefined && style === 'form') {
        /**
         * Per the spec if no `explode` is present but `style` is `form` then `explode` should default to `true`.
         *
         * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-parameterexplode}
         * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#user-content-parameterexplode}
         */
        explode = true;
    }
    return (0, style_serializer_1["default"])(__assign({ location: parameter["in"], value: finalValue, key: parameter.name, style: style, explode: explode, 
        /**
         * @todo this parameter is optional to stylize. It defaults to false, and can accept falsy, truthy, or "unsafe".
         *  I do not know if it is correct for query to use this. See style-serializer for more info
         */
        escape: true }, (parameter["in"] === 'query' ? { isAllowedReserved: parameter.allowReserved || false } : {})));
}
function handleDeepObject(value, parameter) {
    return qs_1["default"]
        .stringify(value, {
        // eslint-disable-next-line consistent-return
        encoder: function (str, defaultEncoder, charset, type) {
            if (type === 'key') {
                // `str` will be here as `dog[treats][0]` but because the `qs` library doesn't have any
                // awareness of our OpenAPI parameters we need to rewrite it to slap the `parameter.name`
                // to the top, like `pets[dog][treats][0]`.
                var prefixedKey = str
                    .split(/[[\]]/g)
                    .filter(Boolean)
                    .map(function (k) { return "[".concat(k, "]"); })
                    .join('');
                return "".concat(parameter.name).concat(prefixedKey);
            }
            else if (type === 'value') {
                return stylizeValue(str, parameter);
            }
        }
    })
        .split('&')
        .map(function (item) {
        var split = item.split('=');
        return {
            label: split[0],
            // `qs` will coerce null values into being `undefined` string but we want to preserve them.
            value: split[1] === 'undefined' ? null : split[1]
        };
    });
}
// Explode is handled on its own, because style-serializer doesn't return what we expect for proper
// HAR output.
function handleExplode(value, parameter) {
    var _a;
    // This is to handle the case of arrays of objects in the querystring
    // which is something that's not technically in the spec but since we're
    // using the `qs` module already, it's fairly easy for us to add support
    // for this use case.
    //
    // An example URL would be something like this:
    // https://example.com/?line_items[0][a_string]=abc&line_items[0][quantity]=1&line_items[1][a_string]=def&line_items[1][quantity]=2
    //
    // Some open issues discussing this here:
    // https://github.com/OAI/OpenAPI-Specification/issues/1706
    // https://github.com/OAI/OpenAPI-Specification/issues/1006
    //
    // Link to the spec for this:
    // https://github.com/OAI/OpenAPI-Specification/blob/36a3a67264cc1c4f1eff110cea3ebfe679435108/versions/3.1.0.md#style-examples
    if (Array.isArray(value) &&
        ((_a = parameter.schema) === null || _a === void 0 ? void 0 : _a.type) === 'array' &&
        parameter.style === 'deepObject') {
        var newObj_1 = {};
        var deepObjs = handleDeepObject(value, parameter);
        deepObjs.forEach(function (obj) {
            newObj_1[obj.label] = obj.value;
        });
        return newObj_1;
    }
    if (Array.isArray(value)) {
        return value.map(function (val) {
            return stylizeValue(val, parameter);
        });
    }
    if (typeof value === 'object' && value !== null) {
        var newObj_2 = {};
        Object.keys(value).forEach(function (key) {
            if (parameter.style === 'deepObject') {
                var deepObjs = handleDeepObject(value, parameter);
                deepObjs.forEach(function (obj) {
                    newObj_2[obj.label] = obj.value;
                });
            }
            else {
                newObj_2[key] = stylizeValue(value[key], parameter);
            }
        });
        return newObj_2;
    }
    return stylizeValue(value, parameter);
}
function shouldExplode(parameter) {
    return ((parameter.explode || (parameter.explode !== false && parameter.style === 'form')) &&
        // header and path doesn't explode into separate parameters like query and cookie do
        parameter["in"] !== 'header' &&
        parameter["in"] !== 'path');
}
function formatStyle(value, parameter) {
    // Deep object style only works on objects and arrays, and only works with explode=true.
    if (parameter.style === 'deepObject' && (!value || typeof value !== 'object' || parameter.explode === false)) {
        return undefined;
    }
    // This custom explode logic allows us to bubble up arrays and objects to be handled differently
    // by our HAR transformer. We need this because the `stylizeValue` function assumes we're building
    // strings, not richer data types.
    //
    // The first part of this conditional checks if `explode` is enabled. Explode is disabled for
    // everything by default except for forms.
    //
    // The second part of this conditional bypasses the custom explode logic for headers, because they
    // work differently, and `stylizeValue` is accurate.
    if (shouldExplode(parameter)) {
        return handleExplode(value, parameter);
    }
    return stylizeValue(value, parameter);
}
exports["default"] = formatStyle;
