"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = void 0;
exports.readEntitiesSchema = readEntitiesSchema;
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 identifiers_1 = require("../../identifiers");
const metadata_1 = require("../../metadata");
const type_mapper_1 = require("./type-mapper");
const schema_model_1 = require("./schema-model");
function readEntitiesSchema(db, options = {}) {
    const schema = new Map();
    const allowedTableNames = options.tableNames ? new Set(options.tableNames) : undefined;
    for (const entity of db.entityRegistry) {
        const reflection = reflection_1.ReflectionClass.from(entity);
        if (reflection.isDatabaseMigrationSkipped(db.driver.dialect))
            continue;
        const tableName = getEntityTableName(entity);
        if (shouldSkipEntityTable(tableName, allowedTableNames))
            continue;
        const metadata = (0, metadata_1.getEntityMetadata)(entity);
        const defaultInstance = new entity();
        const columnsByProperty = new Map(metadata.columns.map(column => [column.propertyName, column]));
        const columnNameForProperty = (propertyName) => columnsByProperty.get(String(propertyName))?.columnName ?? String(propertyName);
        const table = {
            name: metadata.tableName,
            columns: metadata.columns.map((column, index) => {
                const property = reflection.getProperty(column.propertyName);
                const isReference = property.isReference();
                const defaultValue = defaultInstance[column.propertyName];
                const type = isReference
                    ? property.getResolvedReflectionClass().getPrimary().getType()
                    : typeWithDefaultInitializer(column.type, defaultValue);
                const resolved = (0, type_mapper_1.resolveColumnType)(type, db.driver.dialect);
                if (resolved.type === 'double' && (column.primaryKey || column.autoIncrement || isReference))
                    resolved.type = 'int';
                const schema = {
                    name: column.columnName,
                    ...resolved,
                    enumTypeName: db.driver.dialect === 'postgres' && resolved.type === 'enum' && resolved.enumValues?.length
                        ? `${metadata.tableName}_${column.columnName}_enum`
                        : undefined,
                    unsigned: resolved.unsigned ?? false,
                    nullable: column.nullable,
                    autoIncrement: column.autoIncrement,
                    primaryKey: column.primaryKey,
                    ordinalPosition: index + 1
                };
                applyDefaultInitializer(schema, defaultValue, db.driver.dialect);
                return schema;
            }),
            indexes: readEntityIndexes(metadata.tableName, reflection, columnNameForProperty, db.driver.dialect),
            foreignKeys: readEntityForeignKeys(metadata.tableName, reflection, columnNameForProperty, db.driver.dialect),
            primaryKeyColumns: metadata.columns.filter(column => column.primaryKey).map(column => column.columnName)
        };
        for (const foreignKey of table.foreignKeys)
            ensureForeignKeyIndex(table.indexes, table.name, foreignKey.localColumns, db.driver.dialect);
        schema.set(table.name, table);
    }
    return schema;
}
function shouldSkipEntityTable(tableName, allowedTableNames) {
    if (allowedTableNames && !allowedTableNames.has(tableName))
        return true;
    return schema_model_1.INTERNAL_TABLES.has(tableName) && !allowedTableNames?.has(tableName);
}
function typeWithDefaultInitializer(type, value) {
    if (type.kind !== reflection_1.ReflectionKind.unknown || value === undefined || value === null)
        return type;
    if (typeof value === 'string')
        return { kind: reflection_1.ReflectionKind.string };
    if (typeof value === 'number')
        return { kind: reflection_1.ReflectionKind.number };
    if (typeof value === 'boolean')
        return { kind: reflection_1.ReflectionKind.boolean };
    if (value instanceof Date)
        return { kind: reflection_1.ReflectionKind.class, classType: Date };
    return type;
}
function applyDefaultInitializer(column, value, dialect) {
    if (column.autoIncrement || value === undefined || value === null)
        return;
    if (value instanceof Date) {
        column.defaultExpression = 'CURRENT_TIMESTAMP';
    }
    else if (typeof value === 'boolean') {
        column.defaultValue = dialect === 'mysql' ? (value ? '1' : '0') : value;
    }
    else if (typeof value === 'number' || typeof value === 'string') {
        column.defaultValue = value;
    }
}
function getEntityTableName(entity) {
    const reflection = reflection_1.ReflectionClass.from(entity);
    return reflection.getCollectionName() || reflection.name || entity.name;
}
function readEntityIndexes(tableName, reflection, columnNameForProperty, dialect) {
    const indexes = [];
    for (const index of reflection.indexes) {
        const columns = index.names.map(columnNameForProperty);
        if (!columns.length)
            continue;
        const unique = index.options.unique === true;
        const schema = {
            name: index.options.name || (0, identifiers_1.defaultEntityIndexName)(tableName, columns, dialect),
            columns,
            unique,
            size: index.options.size
        };
        mergeOrPushIndex(indexes, schema);
    }
    return indexes;
}
function readEntityForeignKeys(tableName, reflection, columnNameForProperty, dialect) {
    const foreignKeys = [];
    for (const property of reflection.getProperties()) {
        if (!property.isReference() || property.isDatabaseMigrationSkipped('*'))
            continue;
        const localColumn = columnNameForProperty(property.name);
        const foreignReflection = property.getResolvedReflectionClass();
        const foreignMetadata = (0, metadata_1.getEntityMetadata)(foreignReflection.getClassType());
        const foreignColumns = foreignMetadata.columns.filter(column => column.primaryKey).map(column => column.columnName);
        if (!foreignColumns.length)
            continue;
        const reference = property.getReference();
        foreignKeys.push({
            name: (0, identifiers_1.defaultEntityForeignKeyName)(tableName, [localColumn], dialect),
            localColumns: [localColumn],
            foreignTable: foreignMetadata.tableName,
            foreignColumns,
            onDelete: reference?.onDelete ?? 'CASCADE',
            onUpdate: reference?.onUpdate ?? 'CASCADE'
        });
    }
    return foreignKeys;
}
function ensureForeignKeyIndex(indexes, tableName, columns, dialect) {
    if (indexes.some(index => startsWithColumns(index.columns, columns)))
        return;
    indexes.push({
        name: (0, identifiers_1.defaultEntityIndexName)(tableName, columns, dialect),
        columns,
        unique: false
    });
}
function startsWithColumns(indexColumns, columns) {
    return columns.every((column, index) => indexColumns[index] === column);
}
function mergeOrPushIndex(indexes, schema) {
    const sameName = indexes.find(existing => existing.name === schema.name);
    if (sameName) {
        if (arraysEqual(sameName.columns, schema.columns)) {
            sameName.unique = sameName.unique || schema.unique;
            sameName.size ??= schema.size;
        }
        return;
    }
    if (!indexes.some(existing => sameIndexShape(existing, schema)))
        indexes.push(schema);
}
function sameIndexShape(a, b) {
    if (a.unique !== b.unique || !arraysEqual(a.columns, b.columns))
        return false;
    return a.columns.every(column => (a.columnSizes?.[column] ?? a.size) === (b.columnSizes?.[column] ?? b.size));
}
function arraysEqual(a, b) {
    return a.length === b.length && a.every((value, index) => Object.is(value, b[index]));
}
exports.__tsfTypeAliases = _a.decodeCompactMetadataV1("[1,{\"ReadEntitiesSchemaOptions\":{\"kind\":18,\"typeName\":\"ReadEntitiesSchemaOptions\",\"types\":[{\"kind\":20,\"name\":\"tableNames\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":6}},{\"kind\":4}]},\"optional\":true}]}}]", []);
//# sourceMappingURL=entity-reader.js.map