"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.databaseAnnotation = exports.validationAnnotation = exports.typeAnnotation = void 0;
exports.isUUIDType = isUUIDType;
exports.isDatabaseUUIDType = isDatabaseUUIDType;
exports.knownPrimitiveKind = knownPrimitiveKind;
const type_utils_1 = require("./type-utils");
const model_1 = require("./model");
function isUUIDType(type) {
    const annotation = exports.typeAnnotation.getType(type, 'tsf:type');
    return annotation?.kind === model_1.ReflectionKind.literal && (annotation.literal === 'uuid' || annotation.literal === 'uuidString');
}
function isDatabaseUUIDType(type) {
    const annotation = exports.typeAnnotation.getType(type, 'tsf:type');
    return annotation?.kind === model_1.ReflectionKind.literal && annotation.literal === 'uuid';
}
exports.typeAnnotation = {
    getType(type, name) {
        const direct = type.annotations?.[name];
        if (direct)
            return direct;
        return (0, type_utils_1.findNestedSurface)(type, child => child.annotations?.[name]) ?? findTypiaTagTypeAnnotation(type, name);
    },
    getOption(type, name) {
        return this.getType(type, name);
    }
};
exports.validationAnnotation = {
    getAnnotations(type) {
        const result = [];
        const seen = new Set();
        (0, type_utils_1.visitTypeSurface)(type, child => {
            if (child.validation) {
                for (const annotation of child.validation) {
                    const key = validationAnnotationKey(annotation);
                    if (seen.has(key))
                        continue;
                    seen.add(key);
                    result.push(annotation);
                }
            }
            const typiaAnnotation = typiaTagValidationAnnotation(child);
            if (typiaAnnotation) {
                const key = validationAnnotationKey(typiaAnnotation);
                if (!seen.has(key)) {
                    seen.add(key);
                    result.push(typiaAnnotation);
                }
            }
        });
        return result;
    }
};
exports.databaseAnnotation = {
    getDatabase(type, dialect) {
        const direct = type.database?.[dialect] ?? (dialect !== '*' ? type.database?.['*'] : undefined);
        if (direct)
            return direct;
        if (type.kind === model_1.ReflectionKind.union) {
            const concrete = type.types.filter(item => item.kind !== model_1.ReflectionKind.null && item.kind !== model_1.ReflectionKind.undefined);
            if (concrete.length === 1)
                return this.getDatabase(concrete[0], dialect);
            return undefined;
        }
        const nested = (0, type_utils_1.findNestedSurface)(type, child => child.database?.[dialect] ?? (dialect !== '*' ? child.database?.['*'] : undefined));
        if (nested)
            return nested;
        const typia = findTypiaTagDatabase(type, dialect);
        if (typia)
            return typia;
    }
};
const undefinedType = { kind: model_1.ReflectionKind.undefined };
function literalType(literal) {
    return { kind: model_1.ReflectionKind.literal, literal };
}
function validation(name, ...args) {
    return { name, args };
}
function findTypiaTagTypeAnnotation(type, name) {
    return findSurface(type, child => typiaTagTypeAnnotation(child, name));
}
function typiaTagTypeAnnotation(type, name) {
    const tag = readTypiaTag(type);
    if (!tag || tag.kind !== name)
        return undefined;
    if (!tag.kind.startsWith('tsf:') && !tag.kind.startsWith('openapi:'))
        return undefined;
    return tag.value ?? undefinedType;
}
function typiaTagValidationAnnotation(type) {
    const tag = readTypiaTag(type);
    if (!tag)
        return undefined;
    switch (tag.kind) {
        case 'minLength':
        case 'maxLength':
        case 'minimum':
        case 'greaterThan':
        case 'maximum':
        case 'lessThan':
            return tag.value ? validation(tag.kind, tag.value) : undefined;
        case 'pattern':
            return validation('pattern', tag.value ?? schemaProperty(tag.schema, 'pattern') ?? undefinedType);
        case 'format': {
            const format = literalString(tag.value) ?? literalString(schemaProperty(tag.schema, 'format'));
            const pattern = format ? formatPattern(format) : undefined;
            return pattern ? validation('pattern', literalType(pattern)) : undefined;
        }
        case 'tsf:validator':
            return validation('validator', tag.value ?? undefinedType);
        default:
            return undefined;
    }
}
function findTypiaTagDatabase(type, dialect) {
    return findSurface(type, child => typiaTagDatabase(child, dialect));
}
function typiaTagDatabase(type, dialect) {
    const tag = readTypiaTag(type);
    if (!tag)
        return undefined;
    const payload = plainObjectFromType(tag.schema) ?? plainObjectFromType(tag.value);
    if (!payload)
        return undefined;
    if (tag.kind === 'database:field')
        return payload;
    if (tag.kind === 'database:mysql' && dialect === 'mysql')
        return payload;
}
function readTypiaTag(type) {
    if (type.kind !== model_1.ReflectionKind.objectLiteral)
        return undefined;
    const tagProperty = type.types.find(property => String(property.name) === 'typia.tag');
    if (!tagProperty)
        return undefined;
    const tagType = unwrapOptionalType(tagProperty.type);
    if (tagType.kind !== model_1.ReflectionKind.objectLiteral)
        return undefined;
    const kind = literalString(schemaProperty(tagType, 'kind'));
    if (!kind)
        return undefined;
    return {
        kind,
        value: schemaProperty(tagType, 'value'),
        schema: schemaProperty(tagType, 'schema')
    };
}
function schemaProperty(type, name) {
    const concrete = type ? unwrapOptionalType(type) : undefined;
    if (!concrete || concrete.kind !== model_1.ReflectionKind.objectLiteral)
        return undefined;
    return concrete.types.find(property => String(property.name) === name)?.type;
}
function unwrapOptionalType(type) {
    if (type.kind !== model_1.ReflectionKind.union)
        return type;
    const concrete = type.types.filter(item => item.kind !== model_1.ReflectionKind.undefined && item.kind !== model_1.ReflectionKind.null);
    return concrete.length === 1 ? concrete[0] : type;
}
function literalString(type) {
    return type?.kind === model_1.ReflectionKind.literal && typeof type.literal === 'string' ? type.literal : undefined;
}
function plainObjectFromType(type) {
    const concrete = type ? unwrapOptionalType(type) : undefined;
    if (!concrete || concrete.kind !== model_1.ReflectionKind.objectLiteral)
        return undefined;
    const result = {};
    for (const property of concrete.types) {
        const value = plainValueFromType(property.type);
        if (value !== undefined)
            result[String(property.name)] = value;
    }
    return result;
}
function plainValueFromType(type) {
    const concrete = unwrapOptionalType(type);
    if (concrete.kind === model_1.ReflectionKind.literal)
        return concrete.literal;
    if (concrete.kind === model_1.ReflectionKind.undefined)
        return undefined;
    if (concrete.kind === model_1.ReflectionKind.objectLiteral)
        return plainObjectFromType(concrete);
    return undefined;
}
function findSurface(type, predicate) {
    let result;
    (0, type_utils_1.visitTypeSurface)(type, child => {
        if (result !== undefined)
            return;
        result = predicate(child);
    });
    return result;
}
function formatPattern(format) {
    switch (format) {
        case 'date':
            return '^\\d{4}-\\d{2}-\\d{2}$';
        case 'email':
            return '^[a-zA-Z0-9_+.-]+@[a-zA-Z0-9-.]+\\.[a-zA-Z]+$';
        case 'uuid':
            return '^(?:urn:uuid:)?[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$';
        default:
            return undefined;
    }
}
function validationAnnotationKey(annotation) {
    return `${annotation.name}:${annotation.args.map(validationAnnotationArgKey).join('|')}`;
}
function validationAnnotationArgKey(type) {
    if (type.kind === model_1.ReflectionKind.literal && 'literal' in type)
        return `literal:${String(type.literal)}`;
    const runtime = type.runtime;
    if (runtime)
        return `runtime:${String(runtime)}`;
    return `${type.kind}:${type.typeName ?? ''}`;
}
function knownPrimitiveKind(type) {
    const foundationType = exports.typeAnnotation.getType(type, 'tsf:type');
    if (foundationType?.kind === model_1.ReflectionKind.literal) {
        if (foundationType.literal === 'integer')
            return model_1.ReflectionKind.number;
        if (typeof foundationType.literal === 'string')
            return model_1.ReflectionKind.string;
    }
}
//# sourceMappingURL=annotations.js.map