"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.validationRegistry = void 0;
exports.validate = validate;
exports.deserialize = deserialize;
exports.validatedDeserialize = validatedDeserialize;
exports.assert = assert;
exports.is = is;
exports.cast = cast;
exports.resolveReceiveType = resolveReceiveType;
const annotations_1 = require("./annotations");
const errors_1 = require("./errors");
const reflection_class_1 = require("./reflection-class");
const deserializer_1 = require("./deserializer");
const type_utils_1 = require("./type-utils");
const model_1 = require("./model");
const primitive_conversion_1 = require("./primitive-conversion");
const runtimeValidators = new Map();
exports.validationRegistry = {
    register(name, validator) {
        runtimeValidators.set(name, validator);
    },
    get(name) {
        return runtimeValidators.get(name);
    }
};
function validate(value, target) {
    const type = resolveTargetType(value, target);
    if (!type)
        return [];
    return validateValue(value, type);
}
function deserialize(value, type) {
    if (!type)
        throw new Error('deserialize<T>() was not transformed by the metadata compiler');
    (0, type_utils_1.normalizeTypeMetadata)(type);
    return deserializeValue(value, type);
}
function validatedDeserialize(value, _a, _b, _c, type) {
    if (!type)
        throw new Error('validatedDeserialize<T>() requires reflected type metadata');
    (0, type_utils_1.normalizeTypeMetadata)(type);
    const result = deserializeValue(value, type);
    const errors = validateValue(result, type);
    if (errors.length)
        throw errors[0];
    return result;
}
function assert(value, _deserializer, type) {
    if (!type)
        throw new Error('assert<T>() requires explicit reflected type metadata');
    (0, type_utils_1.normalizeTypeMetadata)(type);
    const errors = validateValue(value, type);
    if (errors.length)
        throw errors[0];
}
function is(value, _deserializer, type) {
    if (!type)
        throw new Error('is<T>() requires explicit reflected type metadata');
    (0, type_utils_1.normalizeTypeMetadata)(type);
    return validateValue(value, type).length === 0;
}
function cast(value, _deserializer, _a, _b, type) {
    if (!type)
        throw new Error('cast<T>() requires explicit reflected type metadata');
    (0, type_utils_1.normalizeTypeMetadata)(type);
    return deserializeValue(value, type);
}
function resolveReceiveType(value) {
    if ((0, model_1.isReflectedType)(value))
        return value;
    throw new Error('Unsupported receive type payload');
}
function resolveTargetType(value, target) {
    if ((0, model_1.isReflectedType)(target)) {
        (0, type_utils_1.normalizeTypeMetadata)(target);
        return target;
    }
    if (typeof target === 'function')
        return (0, reflection_class_1.readClassMetadata)(target);
    if (value && typeof value === 'object')
        return (0, reflection_class_1.readClassMetadata)(value.constructor);
}
function validateValue(value, type, path = '') {
    const errors = [];
    const concrete = (0, type_utils_1.unwrapValueType)(type);
    const knownPrimitive = (0, annotations_1.knownPrimitiveKind)(concrete);
    if (value === undefined) {
        if (!(0, type_utils_1.allowsUndefined)(type))
            errors.push(new errors_1.ValidatorError('required', 'The value is required.', path));
        return errors;
    }
    if (value === null) {
        if (!(0, type_utils_1.allowsNull)(type))
            errors.push(new errors_1.ValidatorError('required', 'The value cannot be null.', path));
        return errors;
    }
    if (knownPrimitive === model_1.ReflectionKind.string) {
        if (typeof value !== 'string')
            errors.push(new errors_1.ValidatorError('type', 'The value must be a string.', path));
    }
    else if (knownPrimitive === model_1.ReflectionKind.number) {
        if (typeof value !== 'number' || Number.isNaN(value))
            errors.push(new errors_1.ValidatorError('type', 'The value must be a number.', path));
    }
    else if (knownPrimitive === model_1.ReflectionKind.boolean) {
        if (typeof value !== 'boolean')
            errors.push(new errors_1.ValidatorError('type', 'The value must be a boolean.', path));
    }
    else if (concrete.kind === model_1.ReflectionKind.undefined || concrete.kind === model_1.ReflectionKind.null || concrete.kind === model_1.ReflectionKind.never) {
        errors.push(new errors_1.ValidatorError('type', 'The value does not match the required type.', path));
    }
    else if (concrete.kind === model_1.ReflectionKind.string || concrete.kind === model_1.ReflectionKind.templateLiteral) {
        if (typeof value !== 'string')
            errors.push(new errors_1.ValidatorError('type', 'The value must be a string.', path));
    }
    else if (concrete.kind === model_1.ReflectionKind.number) {
        if (typeof value !== 'number' || Number.isNaN(value))
            errors.push(new errors_1.ValidatorError('type', 'The value must be a number.', path));
    }
    else if (concrete.kind === model_1.ReflectionKind.boolean) {
        if (typeof value !== 'boolean')
            errors.push(new errors_1.ValidatorError('type', 'The value must be a boolean.', path));
    }
    else if (concrete.kind === model_1.ReflectionKind.array) {
        if (!Array.isArray(value)) {
            errors.push(new errors_1.ValidatorError('type', 'The value must be an array.', path));
        }
        else {
            value.forEach((item, index) => {
                errors.push(...validateValue(item, concrete.type, (0, type_utils_1.joinPath)(path, String(index))));
            });
        }
    }
    else if (concrete.kind === model_1.ReflectionKind.tuple) {
        if (!Array.isArray(value)) {
            errors.push(new errors_1.ValidatorError('type', 'The value must be an array.', path));
        }
        else {
            concrete.types.forEach((item, index) => {
                errors.push(...validateValue(value[index], item.type, (0, type_utils_1.joinPath)(path, String(index))));
            });
        }
    }
    else if (concrete.kind === model_1.ReflectionKind.union) {
        const branchErrors = concrete.types.map(item => validateValue(value, item, path));
        const matches = branchErrors.some(item => item.length === 0);
        if (!matches) {
            const best = branchErrors.filter(item => item.length > 0).sort((a, b) => a.length - b.length)[0];
            if (best?.length)
                errors.push(...best);
            else
                errors.push(new errors_1.ValidatorError('type', 'The value does not match any allowed type.', path));
        }
    }
    else if (concrete.kind === model_1.ReflectionKind.objectLiteral) {
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            errors.push(new errors_1.ValidatorError('type', 'The value must be an object.', path));
            return errors;
        }
        const record = value;
        const propertyNames = new Set();
        for (const property of (0, type_utils_1.objectLiteralPropertiesFromType)(concrete)) {
            propertyNames.add(String(property.name));
            const propertyPath = (0, type_utils_1.joinPath)(path, String(property.name));
            if (record[property.name] === undefined && property.optional)
                continue;
            errors.push(...validateValue(record[property.name], property.type, propertyPath));
        }
        const indexType = (0, type_utils_1.objectLiteralIndexType)(concrete);
        if (indexType) {
            for (const [key, item] of Object.entries(record)) {
                if (!propertyNames.has(key))
                    errors.push(...validateValue(item, indexType, (0, type_utils_1.joinPath)(path, key)));
            }
        }
    }
    else if (concrete.kind === model_1.ReflectionKind.literal) {
        if (value !== concrete.literal)
            errors.push(new errors_1.ValidatorError('type', `The value must be ${String(concrete.literal)}.`, path));
    }
    else if (concrete.kind === model_1.ReflectionKind.enum) {
        if (!concrete.values.includes(value))
            errors.push(new errors_1.ValidatorError('type', 'The value does not match any allowed enum value.', path));
    }
    else if (concrete.kind === model_1.ReflectionKind.class) {
        const Target = (0, type_utils_1.tryResolveClassType)(concrete.classType);
        if (!Target) {
            if (!value || typeof value !== 'object' || Array.isArray(value)) {
                errors.push(new errors_1.ValidatorError('type', 'The value must be an object.', path));
            }
            return errors;
        }
        if (Target === Date) {
            // Built-ins can carry validation annotations but do not have project metadata.
        }
        else {
            const reflection = reflection_class_1.ReflectionClass.from(Target);
            const record = value;
            for (const property of reflection.getProperties()) {
                if (record[String(property.name)] === undefined && property.isOptional())
                    continue;
                errors.push(...validateValue(record[String(property.name)], property.getType(), (0, type_utils_1.joinPath)(path, String(property.name))));
            }
        }
    }
    for (const annotation of annotations_1.validationAnnotation.getAnnotations(type)) {
        if (annotation.name === 'minLength' && typeof value === 'string') {
            const min = (0, type_utils_1.literalNumber)(annotation.args[0]);
            if (min !== undefined && value.length < min)
                errors.push(new errors_1.ValidatorError('minLength', `The value must be at least ${min} characters.`, path));
        }
        else if (annotation.name === 'maxLength' && typeof value === 'string') {
            const max = (0, type_utils_1.literalNumber)(annotation.args[0]);
            if (max !== undefined && value.length > max)
                errors.push(new errors_1.ValidatorError('maxLength', `The value must be at most ${max} characters.`, path));
        }
        else if (annotation.name === 'minimum' && typeof value === 'number') {
            const min = (0, type_utils_1.literalNumber)(annotation.args[0]);
            if (min !== undefined && value < min)
                errors.push(new errors_1.ValidatorError('minimum', `The value must be at least ${min}.`, path));
        }
        else if (annotation.name === 'greaterThan' && typeof value === 'number') {
            const min = (0, type_utils_1.literalNumber)(annotation.args[0]);
            if (min !== undefined && value <= min)
                errors.push(new errors_1.ValidatorError('greaterThan', `The value must be greater than ${min}.`, path));
        }
        else if (annotation.name === 'maximum' && typeof value === 'number') {
            const max = (0, type_utils_1.literalNumber)(annotation.args[0]);
            if (max !== undefined && value > max)
                errors.push(new errors_1.ValidatorError('maximum', `The value must be at most ${max}.`, path));
        }
        else if (annotation.name === 'lessThan' && typeof value === 'number') {
            const max = (0, type_utils_1.literalNumber)(annotation.args[0]);
            if (max !== undefined && value >= max)
                errors.push(new errors_1.ValidatorError('lessThan', `The value must be less than ${max}.`, path));
        }
        else if (annotation.name === 'pattern' && typeof value === 'string') {
            const pattern = (0, type_utils_1.resolveRuntimeValue)(annotation.args[0]);
            const regexp = typeof pattern === 'string' ? new RegExp(pattern) : pattern instanceof RegExp ? pattern : undefined;
            if (regexp && !regexp.test(value))
                errors.push(new errors_1.ValidatorError('pattern', 'The value does not match the required pattern.', path));
        }
        else if (annotation.name === 'validate') {
            const validator = (0, type_utils_1.resolveRuntimeValue)(annotation.args[0]);
            if (typeof validator === 'function') {
                const result = validator(value);
                if (result instanceof errors_1.ValidatorError)
                    errors.push(new errors_1.ValidatorError(result.code, result.message, path));
            }
        }
        else if (annotation.name === 'validator') {
            const name = (0, type_utils_1.resolveRuntimeValue)(annotation.args[0]);
            const validator = typeof name === 'string' ? exports.validationRegistry.get(name) : undefined;
            if (validator) {
                const result = validator(value);
                if (result instanceof errors_1.ValidatorError)
                    errors.push(new errors_1.ValidatorError(result.code, result.message, path));
            }
        }
    }
    return errors;
}
function deserializeValue(value, type) {
    if (value === null || value === undefined)
        return value;
    if (type.kind === model_1.ReflectionKind.intersection) {
        const mergedObject = (0, type_utils_1.mergedIntersectionObjectLiteral)(type);
        if (mergedObject && value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
            return deserializer_1.deserializer.apply(type, deserializeValue(value, mergedObject));
        }
        let result = value;
        if (value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
            const output = {};
            let merged = false;
            for (const item of type.types) {
                const part = deserializeValue(value, item);
                if (part && typeof part === 'object' && !Array.isArray(part) && !(part instanceof Date)) {
                    Object.assign(output, part);
                    merged = true;
                }
            }
            result = merged ? output : value;
        }
        else {
            for (const item of type.types)
                result = deserializeValue(result, item);
        }
        return deserializer_1.deserializer.apply(type, result);
    }
    const concrete = (0, type_utils_1.unwrapValueType)(type);
    let result = value;
    if (concrete.kind === model_1.ReflectionKind.number && typeof value === 'string' && value.trim() !== '') {
        result = Number(value);
    }
    else if (concrete.kind === model_1.ReflectionKind.boolean) {
        result = (0, primitive_conversion_1.coerceBooleanValue)(value);
    }
    else if (concrete.kind === model_1.ReflectionKind.enum && typeof value === 'string') {
        const numeric = Number(value);
        if (!Number.isNaN(numeric) && concrete.values.includes(numeric))
            result = numeric;
    }
    else if (concrete.kind === model_1.ReflectionKind.array && Array.isArray(value)) {
        result = value.map(item => deserializeValue(item, concrete.type));
    }
    else if (concrete.kind === model_1.ReflectionKind.tuple && Array.isArray(value)) {
        result = concrete.types.map((item, index) => deserializeValue(value[index], item.type));
    }
    else if (concrete.kind === model_1.ReflectionKind.union) {
        for (const item of concrete.types) {
            const candidate = deserializeValue(value, item);
            if (validateValue(candidate, item).length === 0) {
                result = candidate;
                break;
            }
        }
    }
    else if (concrete.kind === model_1.ReflectionKind.class) {
        const Target = (0, type_utils_1.tryResolveClassType)(concrete.classType);
        if (!Target)
            return deserializer_1.deserializer.apply(type, value);
        if (Target === Date)
            return deserializer_1.deserializer.apply(type, value instanceof Date ? value : new Date(value));
        if (typeof value !== 'object')
            return deserializer_1.deserializer.apply(type, value);
        const output = new Target();
        const input = value;
        for (const property of reflection_class_1.ReflectionClass.from(Target).getProperties()) {
            output[String(property.name)] = deserializeValue(input[String(property.name)], property.getType());
        }
        result = output;
    }
    else if (concrete.kind === model_1.ReflectionKind.objectLiteral && typeof value === 'object') {
        const properties = (0, type_utils_1.objectLiteralPropertiesFromType)(concrete);
        const indexType = (0, type_utils_1.objectLiteralIndexType)(concrete);
        if (properties.length === 0 && !indexType)
            return deserializer_1.deserializer.apply(type, value);
        const input = value;
        const output = {};
        const propertyNames = new Set();
        for (const property of properties) {
            const name = String(property.name);
            propertyNames.add(name);
            output[name] = deserializeValue(input[name], property.type);
        }
        if (indexType) {
            for (const [key, item] of Object.entries(input)) {
                if (!propertyNames.has(key))
                    output[key] = deserializeValue(item, indexType);
            }
        }
        result = output;
    }
    return deserializer_1.deserializer.apply(type, result);
}
exports.__tsfTypeAliases = JSON.parse("[1,{\"RuntimeValidator\":{\"kind\":2,\"typeName\":\"(value: unknown) =\\u003e ValidatorError | undefined | void\",\"typeName\":\"RuntimeValidator\"}}]")[1];
//# sourceMappingURL=conversion.js.map