"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = void 0;
exports.resolveColumnType = resolveColumnType;
exports.parseRawSqlType = parseRawSqlType;
exports.canonicalType = canonicalType;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-server-foundation/type-metadata-runtime"));
const reflection_1 = require("../../../reflection");
const type_utils_1 = require("../../../reflection/type-utils");
const type_annotations_1 = require("../../../types/type-annotations");
function resolveColumnType(type, dialect) {
    return applyMinimumUnsigned(type, dialect, resolveColumnTypeBase(type, dialect));
}
function resolveColumnTypeBase(type, dialect) {
    if (hasConflictingTypeAnnotation(type, 'tsf:type'))
        return { type: 'json' };
    const rawType = getDatabaseTypeAnnotation(type, dialect);
    if (rawType)
        return parseRawSqlType(rawType, dialect);
    if (type.kind === reflection_1.ReflectionKind.union) {
        const nonNull = (0, type_utils_1.flattenRuntimeUnionTypes)(type).filter(item => item.kind !== reflection_1.ReflectionKind.null && item.kind !== reflection_1.ReflectionKind.undefined);
        if (nonNull.length === 1)
            return resolveColumnType(nonNull[0], dialect);
        if (nonNull.length > 0 &&
            nonNull.every(item => item.kind === reflection_1.ReflectionKind.literal && typeof item.literal === 'string')) {
            return {
                type: 'enum',
                enumValues: nonNull.map(item => String(item.literal))
            };
        }
        return { type: 'json' };
    }
    const foundationType = getTypeAnnotation(type, 'tsf:type');
    if (foundationType?.kind === reflection_1.ReflectionKind.literal) {
        switch (foundationType.literal) {
            case 'date':
                return { type: 'date' };
            case 'phone':
            case 'phoneNanp':
                return { type: 'varchar', size: 20 };
            case 'uuid':
                return uuidColumnType(dialect);
            case 'uuidString':
                return uuidStringColumnType(dialect);
            case 'integer':
                return { type: 'int' };
        }
    }
    if ((0, reflection_1.isDatabaseUUIDType)(type)) {
        return uuidColumnType(dialect);
    }
    if (isBinaryType(type)) {
        return binaryColumnType(dialect);
    }
    const length = getTypeAnnotation(type, 'tsf:length');
    if (length?.kind === reflection_1.ReflectionKind.literal && typeof length.literal === 'number') {
        return { type: 'char', size: length.literal };
    }
    const maxLength = getMaxLength(type);
    if (maxLength !== undefined) {
        return { type: 'varchar', size: maxLength };
    }
    if (type.kind === reflection_1.ReflectionKind.intersection) {
        const rawIntersectionType = getDatabaseTypeAnnotation(type, dialect);
        if (rawIntersectionType)
            return parseRawSqlType(rawIntersectionType, dialect);
        const foundationIntersectionType = getTypeAnnotation(type, 'tsf:type');
        if (foundationIntersectionType?.kind === reflection_1.ReflectionKind.literal) {
            switch (foundationIntersectionType.literal) {
                case 'date':
                    return { type: 'date' };
                case 'phone':
                case 'phoneNanp':
                    return { type: 'varchar', size: 20 };
                case 'uuid':
                    return uuidColumnType(dialect);
                case 'uuidString':
                    return uuidStringColumnType(dialect);
                case 'integer':
                    return { type: 'int' };
            }
        }
        if ((0, reflection_1.isDatabaseUUIDType)(type)) {
            return uuidColumnType(dialect);
        }
        if (isBinaryType(type)) {
            return binaryColumnType(dialect);
        }
        const intersectionLength = getTypeAnnotation(type, 'tsf:length');
        if (intersectionLength?.kind === reflection_1.ReflectionKind.literal && typeof intersectionLength.literal === 'number') {
            return { type: 'char', size: intersectionLength.literal };
        }
        const intersectionMaxLength = getMaxLength(type);
        if (intersectionMaxLength !== undefined) {
            return { type: 'varchar', size: intersectionMaxLength };
        }
        const base = type.types.find(item => [
            reflection_1.ReflectionKind.string,
            reflection_1.ReflectionKind.number,
            reflection_1.ReflectionKind.boolean,
            reflection_1.ReflectionKind.bigint,
            reflection_1.ReflectionKind.enum,
            reflection_1.ReflectionKind.union,
            reflection_1.ReflectionKind.class,
            reflection_1.ReflectionKind.literal
        ].includes(item.kind));
        if (base)
            return resolveColumnType(base, dialect);
    }
    if (type.kind === reflection_1.ReflectionKind.enum) {
        const values = type.values.filter(value => value !== null && value !== undefined);
        if (values.length && values.every(value => typeof value === 'string')) {
            return { type: 'enum', enumValues: values.map(String) };
        }
        return { type: 'int' };
    }
    if (type.kind === reflection_1.ReflectionKind.literal) {
        switch (typeof type.literal) {
            case 'string':
                return { type: 'varchar', size: 255 };
            case 'number':
                return { type: 'double' };
            case 'boolean':
                return dialect === 'postgres' ? { type: 'boolean' } : { type: 'tinyint', size: 1, unsigned: true };
        }
    }
    switch (type.kind) {
        case reflection_1.ReflectionKind.string:
            return { type: 'varchar', size: 255 };
        case reflection_1.ReflectionKind.number:
            return { type: 'double' };
        case reflection_1.ReflectionKind.bigint:
            return { type: 'bigint' };
        case reflection_1.ReflectionKind.boolean:
            return dialect === 'postgres' ? { type: 'boolean' } : { type: 'tinyint', size: 1, unsigned: true };
        case reflection_1.ReflectionKind.class: {
            const classType = (0, type_utils_1.tryResolveClassType)(type.classType);
            if (classType === Date)
                return dialect === 'postgres' ? { type: 'timestamp' } : { type: 'datetime' };
            if (dialect === 'mysql' && classType === type_annotations_1.Coordinate)
                return { type: 'point' };
            if (isBinaryType(type))
                return binaryColumnType(dialect);
            return { type: 'json' };
        }
        case reflection_1.ReflectionKind.objectLiteral:
        case reflection_1.ReflectionKind.array:
        case reflection_1.ReflectionKind.any:
            return { type: 'json' };
        default:
            return { type: 'json' };
    }
}
function applyMinimumUnsigned(type, dialect, resolved) {
    if (dialect !== 'mysql' || resolved.unsigned === true || !supportsUnsigned(resolved))
        return resolved;
    const minimum = getMinimum(type);
    return minimum !== undefined && minimum >= 0 ? { ...resolved, unsigned: true } : resolved;
}
function supportsUnsigned(resolved) {
    return ['bigint', 'decimal', 'double', 'float', 'int', 'mediumint', 'smallint', 'tinyint'].includes(resolved.type);
}
function uuidColumnType(dialect) {
    return dialect === 'postgres' ? { type: 'uuid' } : { type: 'binary', size: 16 };
}
function uuidStringColumnType(dialect) {
    return dialect === 'postgres' ? { type: 'uuid' } : { type: 'char', size: 36 };
}
function binaryColumnType(dialect) {
    return dialect === 'postgres' ? { type: 'bytea' } : { type: 'blob' };
}
function isBinaryType(type) {
    const typeName = type.typeName;
    if (typeName === 'Uint8Array' || typeName === 'ArrayBuffer' || typeName === 'Buffer')
        return true;
    if (type.kind === reflection_1.ReflectionKind.class) {
        const className = type.classType?.name;
        return className === 'Uint8Array' || className === 'ArrayBuffer' || className === 'Buffer';
    }
    if ('types' in type && Array.isArray(type.types))
        return type.types.some(isBinaryType);
    return false;
}
function getTypeAnnotation(type, ...names) {
    for (const name of names) {
        const direct = reflection_1.typeAnnotation.getType(type, name);
        if (direct)
            return direct;
    }
    if ('types' in type && Array.isArray(type.types)) {
        for (const child of type.types) {
            const nested = getTypeAnnotation(child, ...names);
            if (nested)
                return nested;
        }
    }
}
function getMaxLength(type) {
    for (const annotation of reflection_1.validationAnnotation.getAnnotations(type)) {
        if (annotation.name !== 'maxLength')
            continue;
        const arg = annotation.args?.[0];
        if (arg?.kind === reflection_1.ReflectionKind.literal && typeof arg.literal === 'number')
            return arg.literal;
    }
    if ('types' in type && Array.isArray(type.types)) {
        for (const child of type.types) {
            const nested = getMaxLength(child);
            if (nested !== undefined)
                return nested;
        }
    }
}
function getMinimum(type) {
    let minimum;
    for (const annotation of reflection_1.validationAnnotation.getAnnotations(type)) {
        if (annotation.name !== 'minimum' && annotation.name !== 'greaterThan')
            continue;
        const arg = annotation.args?.[0];
        if (arg?.kind !== reflection_1.ReflectionKind.literal || typeof arg.literal !== 'number')
            continue;
        minimum = minimum === undefined ? arg.literal : Math.max(minimum, arg.literal);
    }
    return minimum;
}
function hasConflictingTypeAnnotation(type, name) {
    const values = new Set();
    collectTypeAnnotationLiterals(type, name, values);
    return values.size > 1;
}
function collectTypeAnnotationLiterals(type, name, values) {
    const direct = type.annotations?.[name];
    if (direct?.kind === reflection_1.ReflectionKind.literal)
        values.add(direct.literal);
    if (type.kind === reflection_1.ReflectionKind.intersection || type.kind === reflection_1.ReflectionKind.union) {
        for (const item of type.types)
            collectTypeAnnotationLiterals(item, name, values);
    }
    else if (type.kind === reflection_1.ReflectionKind.array) {
        collectTypeAnnotationLiterals(type.type, name, values);
    }
    else if (type.kind === reflection_1.ReflectionKind.tuple) {
        for (const item of type.types)
            collectTypeAnnotationLiterals(item.type, name, values);
    }
}
function parseRawSqlType(raw, dialect) {
    const normalized = raw.trim().toLowerCase().replace(/\s+/g, ' ');
    const unsigned = dialect === 'mysql' && /\bunsigned\b/.test(normalized);
    const withoutUnsigned = normalized.replace(/\bunsigned\b/g, '').trim();
    const enumMatch = withoutUnsigned.match(/^enum\s*\((.*)\)$/);
    if (enumMatch) {
        return {
            type: 'enum',
            enumValues: enumMatch[1].split(',').map(item => item
                .trim()
                .replace(/^'(.*)'$/, '$1')
                .replace(/''/g, "'"))
        };
    }
    const match = withoutUnsigned.match(/^([a-z ]+?)(?:\((\d+)(?:\s*,\s*(\d+))?\))?$/);
    if (!match)
        return { type: canonicalType(withoutUnsigned), unsigned };
    return {
        type: canonicalType(match[1].trim()),
        size: match[2] === undefined ? undefined : Number(match[2]),
        scale: match[3] === undefined ? undefined : Number(match[3]),
        unsigned
    };
}
function canonicalType(type) {
    switch (type.toLowerCase()) {
        case 'integer':
            return 'int';
        case 'bool':
            return 'boolean';
        case 'numeric':
            return 'decimal';
        case 'character varying':
            return 'varchar';
        case 'timestamp without time zone':
        case 'timestamp with time zone':
            return 'timestamp';
        case 'double precision':
            return 'double';
        default:
            return type.toLowerCase();
    }
}
function getDatabaseTypeAnnotation(type, dialect) {
    return (reflection_1.databaseAnnotation.getDatabase(type, dialect)?.type ?? reflection_1.databaseAnnotation.getDatabase(type, '*')?.type);
}
exports.__tsfTypeAliases = _a.decodeCompactMetadataV1("[1,{\"ResolvedColumnType\":{\"kind\":18,\"typeName\":\"ResolvedColumnType\",\"types\":[{\"kind\":20,\"name\":\"type\",\"type\":{\"kind\":6},\"optional\":false},{\"kind\":20,\"name\":\"size\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"scale\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"unsigned\",\"type\":{\"kind\":12,\"types\":[{\"kind\":8},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"enumValues\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":6}},{\"kind\":4}]},\"optional\":true}]}}]", []);
//# sourceMappingURL=type-mapper.js.map