"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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidationOptions = exports.FalsyOptions = exports.ValidationError = void 0;
exports.validateRecursively = validateRecursively;
exports.processInput = processInput;
const HotHttpError_1 = require("./HotHttpError");
const HotStaq_1 = require("./HotStaq");
/**
 * A validation error.
 */
class ValidationError extends Error {
    constructor(message, validType) {
        super(message);
        this.name = "HttpError";
        this.validType = validType;
    }
}
exports.ValidationError = ValidationError;
/**
 * The options for when a falsy is triggered.
 */
var FalsyOptions;
(function (FalsyOptions) {
    /**
     * Throw an error message if its triggered.
     */
    FalsyOptions[FalsyOptions["ThrowError"] = 0] = "ThrowError";
    /**
     * Delete the key/value from the object.
     */
    FalsyOptions[FalsyOptions["Delete"] = 1] = "Delete";
    /**
     * Continue on to the next key for validation.
     */
    FalsyOptions[FalsyOptions["Continue"] = 2] = "Continue";
})(FalsyOptions || (exports.FalsyOptions = FalsyOptions = {}));
/**
 * The validation options available.
 */
class ValidationOptions {
    constructor(strictInput = false) {
        this.strictInput = strictInput;
        this.falsy = null;
        this.falsyOptions = FalsyOptions.Continue;
    }
}
exports.ValidationOptions = ValidationOptions;
// If a nested parameter is provided as a string, we assume a default parameter expecting text.
function resolveParameters(nested) {
    return __awaiter(this, void 0, void 0, function* () {
        const result = {};
        for (const key in nested) {
            const val = nested[key];
            if (typeof val === "string")
                result[key] = { validations: [{ type: "Text" }] };
            else if (typeof (val) === "function")
                result[key] = yield val();
            else
                result[key] = val;
        }
        return result;
    });
}
// Recursive helper that validates a single value through its entire validation chain.
function validateRecursively(options, key, input, request, validation) {
    return __awaiter(this, void 0, void 0, function* () {
        const value = input[key];
        if (HotStaq_1.HotStaq.preValidate != null) {
            const result = yield HotStaq_1.HotStaq.preValidate(options, key, validation, value, request);
            if (result != null) {
                if (result.type == null)
                    result.type = HotStaq_1.HotValidReturnType.Return;
                if (result.type === HotStaq_1.HotValidReturnType.Delete)
                    return result;
                if (result.type === HotStaq_1.HotValidReturnType.Return)
                    return result;
                if (result.validationType != null)
                    validation.type = result.validationType;
            }
        }
        if (!validation)
            return { value: value };
        if (options.falsy != null) {
            if (options.falsy(value) === true) {
                if (options.falsyOptions === FalsyOptions.ThrowError)
                    throw new Error(`Falsy "${key}" triggered by value ${value}.`);
                if (options.falsyOptions === FalsyOptions.Delete)
                    return ({ type: HotStaq_1.HotValidReturnType.Delete });
                if (options.falsyOptions === FalsyOptions.Continue)
                    return ({ type: HotStaq_1.HotValidReturnType.Ignore });
            }
        }
        const validType = validation.type;
        if (validType === "Ignore")
            return { value: value };
        if (validType === "Delete")
            return { type: HotStaq_1.HotValidReturnType.Delete };
        const valid = HotStaq_1.HotStaq.valids[validType];
        if (valid == null)
            throw new ValidationError(`Validation '${validType}' not found.`, validType);
        if (validation.defaultValue != null) {
            if (value == null)
                return { value: validation.defaultValue };
        }
        const validCheck = (value2) => __awaiter(this, void 0, void 0, function* () {
            let newResult2 = null;
            if (valid instanceof Function) {
                newResult2 = yield valid(options, key, validation, value2, request);
                if (newResult2 != null) {
                    if (newResult2.type == null)
                        newResult2.type = HotStaq_1.HotValidReturnType.Return;
                    if (newResult2.type === HotStaq_1.HotValidReturnType.Delete)
                        return { type: HotStaq_1.HotValidReturnType.Delete };
                }
            }
            else {
                let processResult;
                // If the parameter map contains the extraction key and we're not
                // processing array elements, pass the full input object so
                // processInput can match the key, then extract just that key's result.
                if (key in valid && !(value instanceof Array)) {
                    processResult = yield processInput(options, valid, input, request, key);
                    processResult = processResult[key];
                }
                else {
                    processResult = yield processInput(options, valid, value2, request, key);
                }
                newResult2 = { type: HotStaq_1.HotValidReturnType.Return, value: processResult };
            }
            return (newResult2);
        });
        let newResult = { type: HotStaq_1.HotValidReturnType.Return };
        if (value instanceof Array) {
            const resultValues = [];
            for (let iIdx = 0; iIdx < value.length; iIdx++) {
                const valueElm = value[iIdx];
                const newResult3 = yield validCheck(valueElm);
                if (newResult3 != null) {
                    if (newResult3.type === HotStaq_1.HotValidReturnType.Delete)
                        continue;
                }
                resultValues.push(newResult3.value);
            }
            newResult.value = resultValues;
        }
        else
            newResult = yield validCheck(value);
        if (HotStaq_1.HotStaq.postValidate != null) {
            newResult = yield HotStaq_1.HotStaq.postValidate(options, key, validation, newResult.value, request);
            if (newResult != null) {
                if (newResult.type === HotStaq_1.HotValidReturnType.Delete)
                    return { type: HotStaq_1.HotValidReturnType.Delete };
                if (newResult.type === HotStaq_1.HotValidReturnType.Return)
                    return { value: newResult.value };
            }
        }
        if (newResult.type === HotStaq_1.HotValidReturnType.Return)
            return { value: newResult.value };
        return validateRecursively(options, key, newResult.value, request, validation.next);
    });
}
/**
 * Main function that validates an input object against a parameters map,
 * including nested properties defined via the `parameters` property.
 * Unfortunately this is a shallow check, so if you have an object with
 * deeply nested properties, issues may arise. This feature is still
 * considered experimental.
 *
 * @experimental
 */
function processInput(options_1, params_1, input_1, request_1) {
    return __awaiter(this, arguments, void 0, function* (options, params, input, request, parentKey = "") {
        const validatedInput = input;
        if (!(input instanceof Object))
            throw new Error(`Parameter '${parentKey}' must be an object.`);
        if (options.strictInput === true) {
            // Check that every key in input exists in params.
            // @fixme This is only a shallow check....
            for (const key in input) {
                if (!(key in params)) {
                    throw new Error(`Unexpected parameter '${key}' provided.`);
                }
            }
        }
        // Process each key defined in the parameters.
        for (const key in params) {
            let paramDef;
            const param = params[key];
            if (typeof param === "function") {
                paramDef = yield param();
            }
            else if (typeof param === "string") {
                paramDef = { validations: [{ type: param }] };
            }
            else {
                paramDef = param;
            }
            // Only validate if the key is present in the input.
            if (key in input) {
                let value = input[key];
                let validReturnType = null;
                // Validate the value using its validation chain, if provided.
                if (paramDef.validations) {
                    let errMsg = null;
                    let isRequired = paramDef.required;
                    for (let iIdx = 0; iIdx < paramDef.validations.length; iIdx++) {
                        const validation = paramDef.validations[iIdx];
                        try {
                            let validValue = yield validateRecursively(options, key, input, request, validation);
                            validReturnType = validValue.type;
                            value = validValue.value;
                            errMsg = null;
                            break;
                        }
                        catch (err) {
                            // ValidationErrors should always be thrown no matter what.
                            if (err instanceof ValidationError)
                                throw err;
                            errMsg = err;
                        }
                    }
                    if (errMsg != null)
                        throw errMsg;
                }
                if (validReturnType != null) {
                    if (validReturnType === HotStaq_1.HotValidReturnType.Delete) {
                        delete validatedInput[key];
                        continue;
                    }
                }
                // If nested parameters exist, ensure the value is an object and validate recursively.
                if (paramDef.parameters) {
                    if (typeof value !== "object" || value === null) {
                        throw new Error(`Parameter '${key}' must be an object.`);
                    }
                    let resolvedParams = yield resolveParameters(paramDef.parameters);
                    value = yield processInput(options, resolvedParams, value, request, key);
                }
                validatedInput[key] = value;
            }
            else {
                if (paramDef.required === true) {
                    let parentKeyStr = "";
                    if (parentKey !== "")
                        parentKeyStr = ` Parent Key: '${parentKey}'`;
                    throw new HotHttpError_1.HttpError(`Parameter '${key}' is required but not provided.${parentKeyStr}`, 400);
                }
            }
        }
        return validatedInput;
    });
}
//# sourceMappingURL=HotProcessInput.js.map