"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.readEntitiesSchema = readEntitiesSchema;
function __assignType(fn, args) {
    fn.__type = args;
    return fn;
}
/*@ts-ignore*/
var { __ΩTypeIntersection, __ΩTypeEnum, __ΩTypeClass } = require('@deepkit/type');
const type_1 = require("@deepkit/type");
const common_1 = require("../../common");
/*@ts-ignore*/
var { __ΩDialect, __ΩDatabaseSchema, __ΩTableSchema, __ΩColumnSchema, __ΩForeignKeySchema } = require('./schema-model');
const schema_model_1 = require("./schema-model");
function readEntitiesSchema(db, dialect) {
    const schema = new Map();
    const entities = db.entityRegistry.all();
    for (const entity of entities) {
        const reflection = type_1.ReflectionClass.from(entity);
        const tableName = reflection.getCollectionName() || reflection.name || '';
        if (!tableName)
            continue;
        if (schema_model_1.INTERNAL_TABLES.has(tableName))
            continue;
        const table = readTableSchema(reflection, tableName, dialect, db);
        schema.set(tableName, table);
    }
    return schema;
}
readEntitiesSchema.__type = [() => common_1.BaseDatabase, 'db', () => __ΩDialect, 'dialect', () => __ΩDatabaseSchema, 'readEntitiesSchema', 'PP7!2"n#2$n%/&'];
function readTableSchema(reflection, tableName, dialect, db) {
    const columns = [];
    const indexes = [];
    const foreignKeys = [];
    const pkColumns = [];
    const skippedColumns = (Set.Ω = [['&']], new Set());
    const properties = reflection.getProperties();
    let ordinal = 0;
    for (const prop of properties) {
        if (prop.isBackReference())
            continue;
        // For references, we handle the FK column, not the object
        if (prop.isReference()) {
            const col = readReferenceColumn(prop, ++ordinal, dialect, db, tableName);
            if (col) {
                columns.push(col);
                if (col.isPrimaryKey)
                    pkColumns.push(col.name);
                // Create FK
                const fk = readForeignKey(prop, tableName, dialect, db);
                if (fk)
                    foreignKeys.push(fk);
            }
            else {
                skippedColumns.add(prop.name);
            }
            continue;
        }
        const col = readColumn(prop, ++ordinal, dialect, tableName);
        if (col) {
            columns.push(col);
            if (col.isPrimaryKey)
                pkColumns.push(col.name);
            // Single-column index from property
            const indexInfo = prop.getIndex();
            if (indexInfo) {
                indexes.push({
                    name: indexInfo.name || `idx_${tableName}_${col.name}`,
                    columns: [col.name],
                    unique: indexInfo.unique || false,
                    spatial: false
                });
            }
        }
        else {
            skippedColumns.add(prop.name);
            console.warn(`migration:create: Skipping column '${prop.name}' in '${tableName}' — unsupported type`);
        }
    }
    // Multi-column indexes from entity-level indexes
    if (reflection.indexes) {
        for (const idx of reflection.indexes) {
            indexes.push({
                name: idx.options.name || `idx_${tableName}_${idx.names.join('_')}`,
                columns: idx.names,
                unique: idx.options.unique || false,
                spatial: dialect === 'mysql' ? idx.options.spatial || false : false
            });
        }
    }
    // Deduplicate indexes by name (Deepkit stores property-level indexes
    // both on the property and in reflection.indexes)
    const seen = (Set.Ω = [['&']], new Set());
    const dedupedIndexes = indexes.filter(__assignType(idx => {
        if (seen.has(idx.name))
            return false;
        seen.add(idx.name);
        return true;
    }, ['idx', '', 'P"2!"/"']));
    return { name: tableName, columns, indexes: dedupedIndexes, foreignKeys, skippedColumns };
}
readTableSchema.__type = [() => type_1.ReflectionClass, 'reflection', 'tableName', () => __ΩDialect, 'dialect', () => common_1.BaseDatabase, 'db', () => __ΩTableSchema, 'readTableSchema', 'PP#7!2"&2#n$2%P7&2\'n(/)'];
function readColumn(prop, ordinal, dialect, tableName) {
    const type = prop.type;
    const resolved = resolveColumnType(type, prop.name, dialect, tableName);
    if (!resolved)
        return null;
    const col = {
        name: prop.name,
        type: resolved.type,
        size: resolved.size,
        scale: resolved.scale,
        unsigned: resolved.unsigned || false,
        nullable: prop.isOptional() || prop.isNullable(),
        autoIncrement: prop.isAutoIncrement(),
        isPrimaryKey: prop.isPrimaryKey(),
        defaultValue: resolved.defaultValue,
        defaultExpression: resolved.defaultExpression,
        enumValues: resolved.enumValues,
        enumTypeName: resolved.enumTypeName,
        ordinalPosition: ordinal
    };
    // ON UPDATE expression (MySQL only)
    if (dialect === 'mysql') {
        const onUpdateAnnotation = type_1.typeAnnotation.getType(type, 'dksf:onUpdate');
        if (onUpdateAnnotation?.kind === type_1.ReflectionKind.literal && typeof onUpdateAnnotation.literal === 'string') {
            col.onUpdateExpression = onUpdateAnnotation.literal.toUpperCase();
        }
    }
    return col;
}
readColumn.__type = [() => type_1.ReflectionProperty, 'prop', 'ordinal', () => __ΩDialect, 'dialect', 'tableName', () => __ΩColumnSchema, 'readColumn', 'PP7!2"\'2#n$2%&2&8Pn\',J/('];
function readReferenceColumn(prop, ordinal, dialect, _db, tableName) {
    // A reference property stores the PK of the referenced entity
    const refClass = prop.getResolvedReflectionClass();
    // Guard: only single-column PKs are supported for references
    const refPkProps = refClass.getProperties().filter(__assignType(p => p.isPrimaryKey(), ['p', '', 'P"2!"/"']));
    if (refPkProps.length !== 1) {
        console.warn(`migration:create: Skipping reference '${prop.name}' in '${tableName ?? '?'}' — ` +
            `referenced entity '${refClass.name}' has ${refPkProps.length === 0 ? 'no' : 'composite'} primary key (unsupported)`);
        return null;
    }
    const refPk = refClass.getPrimary();
    if (!refPk)
        return null;
    // Resolve the PK column type of the referenced entity
    const resolved = resolveColumnType(refPk.type, prop.name, dialect, tableName);
    if (!resolved)
        return null;
    return {
        name: prop.name,
        type: resolved.type,
        size: resolved.size,
        scale: resolved.scale,
        unsigned: resolved.unsigned || false,
        nullable: prop.isOptional() || prop.isNullable(),
        autoIncrement: false,
        isPrimaryKey: prop.isPrimaryKey(),
        ordinalPosition: ordinal
    };
}
readReferenceColumn.__type = [() => type_1.ReflectionProperty, 'prop', 'ordinal', () => __ΩDialect, 'dialect', () => common_1.BaseDatabase, '_db', 'tableName', () => __ΩColumnSchema, 'readReferenceColumn', 'PP7!2"\'2#n$2%P7&2\'&2(8Pn),J/*'];
function readForeignKey(prop, tableName, _dialect, _db) {
    const refClass = prop.getResolvedReflectionClass();
    const refTableName = refClass.getCollectionName() || refClass.name || '';
    // Guard: only single-column PKs are supported for FK generation
    const refPkProps = refClass.getProperties().filter(__assignType(p => p.isPrimaryKey(), ['p', '', 'P"2!"/"']));
    if (refPkProps.length !== 1) {
        // Warning already emitted by readReferenceColumn
        return null;
    }
    const refPk = refClass.getPrimary();
    if (!refPk || !refTableName)
        return null;
    const ref = prop.getReference();
    const onDelete = ref?.onDelete ?? 'CASCADE';
    // Deepkit's ReferenceOptions does not expose onUpdate; future-proof with dynamic check.
    // Default is CASCADE to match Deepkit's internal FK default (see @deepkit/sql ForeignKey class).
    const onUpdate = ref?.onUpdate ? String(ref.onUpdate) : 'CASCADE';
    return {
        name: `fk_${tableName}_${prop.name}`,
        columns: [prop.name],
        referencedTable: refTableName,
        referencedColumns: [refPk.name],
        onDelete: onDelete.toUpperCase(),
        onUpdate: onUpdate.toUpperCase()
    };
}
readForeignKey.__type = [() => type_1.ReflectionProperty, 'prop', 'tableName', () => __ΩDialect, '_dialect', () => common_1.BaseDatabase, '_db', () => __ΩForeignKeySchema, 'readForeignKey', 'PP7!2"&2#n$2%P7&2\'Pn(,J/)'];
const __ΩResolvedType = ['type', 'size', 'scale', 'unsigned', 'defaultValue', 'defaultExpression', 'enumValues', 'enumTypeName', 'ResolvedType', 'P&4!\'4"8\'4#8)4$8#4%8&4&8&F4\'8&4(8Mw)y'];
function resolveColumnType(type, columnName, dialect, parentTableName) {
    // Unwrap unions: filter out undefined/null (they only affect nullable)
    if (type.kind === type_1.ReflectionKind.union) {
        const nonNull = type.types.filter(__assignType(t => t.kind !== type_1.ReflectionKind.undefined && t.kind !== type_1.ReflectionKind.null && !(t.kind === type_1.ReflectionKind.literal && t.literal === null), ['t', '', 'P"2!"/"']));
        if (nonNull.length === 1) {
            return resolveColumnType(nonNull[0], columnName, dialect, parentTableName);
        }
        // Multiple non-null union members -- treat as the first (lossy; warn)
        if (nonNull.length > 0) {
            console.warn(`migration:create: Column '${columnName}'${parentTableName ? ` in '${parentTableName}'` : ''} ` +
                `has a union type with ${nonNull.length} non-null members — using the first member only`);
            return resolveColumnType(nonNull[0], columnName, dialect, parentTableName);
        }
        return null;
    }
    // Unwrap intersections: look for base type + annotations
    if (type.kind === type_1.ReflectionKind.intersection) {
        return resolveIntersectionType(type, columnName, dialect, parentTableName);
    }
    // Check dksf:type annotation
    const dksfType = type_1.typeAnnotation.getType(type, 'dksf:type');
    if (dksfType?.kind === type_1.ReflectionKind.literal) {
        const result = resolveDksfType(dksfType.literal, type, dialect);
        if (result)
            return result;
    }
    // Check dialect-specific database annotation
    const dbAnnotation = (type_1.databaseAnnotation.getDatabase.Ω = [['type', 'P&4!8M']], type_1.databaseAnnotation.getDatabase(type, dialect));
    if (dbAnnotation?.type) {
        return { type: dbAnnotation.type.toLowerCase() };
    }
    // Check generic database annotation
    const genericDbAnnotation = (type_1.databaseAnnotation.getDatabase.Ω = [['type', 'P&4!8M']], type_1.databaseAnnotation.getDatabase(type, '*'));
    if (genericDbAnnotation?.type) {
        return { type: genericDbAnnotation.type.toLowerCase() };
    }
    // Check dksf:length annotation
    const lengthAnnotation = type_1.typeAnnotation.getType(type, 'dksf:length');
    if (lengthAnnotation?.kind === type_1.ReflectionKind.literal && typeof lengthAnnotation.literal === 'number') {
        return { type: 'char', size: lengthAnnotation.literal };
    }
    // Check validation annotations for MaxLength
    const maxLength = getMaxLength(type);
    if (maxLength !== undefined) {
        return { type: 'varchar', size: maxLength };
    }
    // Enum types
    if (type.kind === type_1.ReflectionKind.enum) {
        return resolveEnumType(type, columnName, dialect, parentTableName);
    }
    // Primitive type map
    return resolvePrimitiveType(type, dialect);
}
resolveColumnType.__type = ['Type', 'type', 'columnName', () => __ΩDialect, 'dialect', 'parentTableName', () => __ΩResolvedType, 'resolveColumnType', 'P"w!2"&2#n$2%&2&8Pn\',J/('];
function resolveIntersectionType(type, columnName, dialect, parentTableName) {
    // Priority 1: Check for dialect-specific database annotation on the intersection
    const dbAnnotation = (type_1.databaseAnnotation.getDatabase.Ω = [['type', 'P&4!8M']], type_1.databaseAnnotation.getDatabase(type, dialect));
    if (dbAnnotation?.type) {
        return { type: dbAnnotation.type.toLowerCase() };
    }
    // Priority 2: Check for generic database annotation
    const genericDbAnnotation = (type_1.databaseAnnotation.getDatabase.Ω = [['type', 'P&4!8M']], type_1.databaseAnnotation.getDatabase(type, '*'));
    if (genericDbAnnotation?.type) {
        return { type: genericDbAnnotation.type.toLowerCase() };
    }
    // Priority 3: dksf:type annotation
    const dksfType = type_1.typeAnnotation.getType(type, 'dksf:type');
    if (dksfType?.kind === type_1.ReflectionKind.literal) {
        const result = resolveDksfType(dksfType.literal, type, dialect);
        if (result)
            return result;
    }
    // Priority 4: dksf:length annotation
    const lengthAnnotation = type_1.typeAnnotation.getType(type, 'dksf:length');
    if (lengthAnnotation?.kind === type_1.ReflectionKind.literal && typeof lengthAnnotation.literal === 'number') {
        return { type: 'char', size: lengthAnnotation.literal };
    }
    // Priority 5: MaxLength validation
    const maxLength = getMaxLength(type);
    if (maxLength !== undefined) {
        return { type: 'varchar', size: maxLength };
    }
    // Priority 6: Find the base type in the intersection members
    for (const member of type.types) {
        // Skip annotation-only types (PrimaryKey, AutoIncrement, Index, etc.)
        if (member.kind === type_1.ReflectionKind.class ||
            member.kind === type_1.ReflectionKind.string ||
            member.kind === type_1.ReflectionKind.number ||
            member.kind === type_1.ReflectionKind.boolean ||
            member.kind === type_1.ReflectionKind.bigint ||
            member.kind === type_1.ReflectionKind.enum) {
            const result = resolveColumnType(member, columnName, dialect, parentTableName);
            if (result)
                return result;
        }
    }
    return null;
}
resolveIntersectionType.__type = [() => __ΩTypeIntersection, 'type', 'columnName', () => __ΩDialect, 'dialect', 'parentTableName', () => __ΩResolvedType, 'resolveIntersectionType', 'Pn!2"&2#n$2%&2&8Pn\',J/('];
function resolveDksfType(dksfType, _type, _dialect) {
    switch (dksfType) {
        case 'uuid':
            return { type: 'char', size: 36 };
        case 'date':
            return { type: 'date' };
        case 'phone':
        case 'phoneNanp':
            return { type: 'varchar', size: 20 };
        default:
            return null;
    }
}
resolveDksfType.__type = ['dksfType', 'Type', '_type', () => __ΩDialect, '_dialect', () => __ΩResolvedType, 'resolveDksfType', 'P&2!"w"2#n$2%Pn&,J/\''];
function resolveEnumType(type, columnName, dialect, parentTableName) {
    const values = type.values.filter(__assignType(v => v != null, ['v', '', 'P"2!"/"']));
    if (values.length === 0)
        return null;
    // String enum
    if (typeof values[0] === 'string') {
        const stringValues = values.filter(__assignType((v) => typeof v === 'string', ['v', '', 'P"2!!/"']));
        if (dialect === 'postgres') {
            const typeName = parentTableName ? `${parentTableName}_${columnName}` : columnName;
            return {
                type: 'enum',
                enumValues: stringValues,
                enumTypeName: typeName
            };
        }
        return {
            type: 'enum',
            enumValues: stringValues
        };
    }
    // Numeric enum
    const numValues = values.filter(__assignType((v) => typeof v === 'number', ['v', '', 'P"2!!/"']));
    const minVal = Math.min(...numValues);
    const maxVal = Math.max(...numValues);
    if (dialect === 'mysql') {
        if (minVal >= 0 && maxVal <= 255)
            return { type: 'tinyint', unsigned: true };
        if (minVal >= -128 && maxVal <= 127)
            return { type: 'tinyint' };
        if (minVal >= 0 && maxVal <= 65535)
            return { type: 'smallint', unsigned: true };
        return { type: 'int' };
    }
    if (minVal >= -32768 && maxVal <= 32767)
        return { type: 'smallint' };
    return { type: 'int' };
}
resolveEnumType.__type = [() => __ΩTypeEnum, 'type', 'columnName', () => __ΩDialect, 'dialect', 'parentTableName', () => __ΩResolvedType, 'resolveEnumType', 'Pn!2"&2#n$2%&2&8Pn\',J/('];
function getMaxLength(type) {
    // Check for Deepkit's MaxLength validation annotation
    try {
        const annotations = type_1.validationAnnotation.getAnnotations(type);
        if (annotations) {
            for (const annotation of annotations) {
                if (annotation.name === 'maxLength') {
                    const arg = annotation.args?.[0];
                    if (arg && arg.kind === type_1.ReflectionKind.literal && typeof arg.literal === 'number') {
                        return arg.literal;
                    }
                }
            }
        }
    }
    catch {
        // validationAnnotation might not be available
    }
    return undefined;
}
getMaxLength.__type = ['Type', 'type', 'getMaxLength', 'P"w!2"P\'-J/#'];
function resolvePrimitiveType(type, dialect) {
    switch (type.kind) {
        case type_1.ReflectionKind.string:
            return { type: 'varchar', size: 255 };
        case type_1.ReflectionKind.number:
            return dialect === 'mysql' ? { type: 'double' } : { type: 'double precision' };
        case type_1.ReflectionKind.boolean:
            return dialect === 'mysql' ? { type: 'tinyint', size: 1 } : { type: 'boolean' };
        case type_1.ReflectionKind.bigint:
            return { type: 'bigint' };
        case type_1.ReflectionKind.class:
            return resolveClassType(type, dialect);
        case type_1.ReflectionKind.objectLiteral:
        case type_1.ReflectionKind.array:
            return dialect === 'mysql' ? { type: 'json' } : { type: 'jsonb' };
        case type_1.ReflectionKind.any:
            return dialect === 'mysql' ? { type: 'json' } : { type: 'jsonb' };
        case type_1.ReflectionKind.literal: {
            // literal string → varchar, literal number → int, etc.
            const literal = type.literal;
            if (typeof literal === 'string')
                return { type: 'varchar', size: 255 };
            if (typeof literal === 'number')
                return { type: 'int' };
            if (typeof literal === 'boolean') {
                return dialect === 'mysql' ? { type: 'tinyint', size: 1 } : { type: 'boolean' };
            }
            return null;
        }
        default:
            return null;
    }
}
resolvePrimitiveType.__type = ['Type', 'type', () => __ΩDialect, 'dialect', () => __ΩResolvedType, 'resolvePrimitiveType', 'P"w!2"n#2$Pn%,J/&'];
function resolveClassType(type, dialect) {
    const className = type.classType?.name;
    switch (className) {
        case 'Date':
            return dialect === 'mysql' ? { type: 'datetime' } : { type: 'timestamp' };
        case 'Coordinate':
            if (dialect === 'mysql') {
                return { type: 'point' };
            }
            // PG doesn't support POINT in this framework
            return null;
        case 'ArrayBuffer':
        case 'Uint8Array':
            return dialect === 'mysql' ? { type: 'blob' } : { type: 'bytea' };
        default:
            // Unknown class → JSON
            return dialect === 'mysql' ? { type: 'json' } : { type: 'jsonb' };
    }
}
resolveClassType.__type = [() => __ΩTypeClass, 'type', () => __ΩDialect, 'dialect', () => __ΩResolvedType, 'resolveClassType', 'Pn!2"n#2$Pn%,J/&'];
//# sourceMappingURL=entity-reader.js.map