// engine/migration/db-migrator.ts — Database migration planning (SPEC-028a)
// analyze-db, map-db, plan-db actions
// --- Type mapping knowledge base ---
const POSTGRES_TO_MONGO = [
    { sourceType: 'TEXT', targetType: 'String', dataLossRisk: false },
    { sourceType: 'VARCHAR', targetType: 'String', dataLossRisk: false },
    { sourceType: 'INTEGER', targetType: 'Number', dataLossRisk: false },
    {
        sourceType: 'BIGINT',
        targetType: 'Long (NumberLong)',
        dataLossRisk: true,
        dataLossNote: 'MongoDB Number may lose precision for very large integers',
    },
    { sourceType: 'BOOLEAN', targetType: 'Boolean', dataLossRisk: false },
    { sourceType: 'TIMESTAMP', targetType: 'Date', dataLossRisk: false },
    { sourceType: 'JSON', targetType: 'Object', dataLossRisk: false },
    {
        sourceType: 'UUID',
        targetType: 'String (or ObjectId)',
        dataLossRisk: false,
        transformation: 'Store as string or convert to ObjectId',
    },
    {
        sourceType: 'FOREIGN KEY',
        targetType: 'Embedded document or $ref',
        transformation: 'Decide: embed document vs reference. Embedding denormalizes data.',
        dataLossRisk: true,
        dataLossNote: 'Relational integrity not enforced — denormalized or reference-based',
    },
];
const POSTGRES_TO_MYSQL = [
    { sourceType: 'TEXT', targetType: 'TEXT', dataLossRisk: false },
    {
        sourceType: 'BOOLEAN',
        targetType: 'TINYINT(1)',
        dataLossRisk: false,
        transformation: 'Use TINYINT(1) as BOOLEAN in MySQL',
    },
    {
        sourceType: 'TIMESTAMP WITH TIME ZONE',
        targetType: 'DATETIME',
        dataLossRisk: true,
        dataLossNote: 'MySQL DATETIME does not store timezone — convert to UTC first',
    },
    { sourceType: 'SERIAL', targetType: 'AUTO_INCREMENT', dataLossRisk: false },
    { sourceType: 'UUID', targetType: 'CHAR(36)', dataLossRisk: false },
    {
        sourceType: 'JSONB',
        targetType: 'JSON',
        dataLossRisk: false,
        transformation: 'MySQL JSON does not support GIN indexes',
    },
    {
        sourceType: 'ARRAY',
        targetType: 'JSON',
        dataLossRisk: true,
        dataLossNote: 'PostgreSQL native arrays have no MySQL equivalent — store as JSON',
    },
];
const MYSQL_TO_POSTGRES = [
    { sourceType: 'TINYINT(1)', targetType: 'BOOLEAN', dataLossRisk: false },
    {
        sourceType: 'DATETIME',
        targetType: 'TIMESTAMP WITH TIME ZONE',
        dataLossRisk: false,
        transformation: 'Add timezone info — assume UTC if not specified',
    },
    { sourceType: 'AUTO_INCREMENT', targetType: 'SERIAL / IDENTITY', dataLossRisk: false },
    { sourceType: 'ENUM', targetType: 'custom type or CHECK constraint', dataLossRisk: false },
    { sourceType: 'TEXT', targetType: 'TEXT', dataLossRisk: false },
];
const TYPE_MAPPING_DB = {
    'postgresql-mongodb': POSTGRES_TO_MONGO,
    'postgresql-mysql': POSTGRES_TO_MYSQL,
    'mysql-postgresql': MYSQL_TO_POSTGRES,
};
function getTypeMappings(sourceEngine, targetEngine) {
    const key = `${sourceEngine.toLowerCase()}-${targetEngine.toLowerCase()}`;
    return TYPE_MAPPING_DB[key] ?? [];
}
function detectMigrationType(source, target) {
    const noSqlEngines = new Set([
        'mongodb',
        'dynamodb',
        'firebase',
        'documentdb',
        'cosmosdb',
        'cassandra',
        'scylladb',
        'hbase',
    ]);
    const newSqlEngines = new Set(['cockroachdb', 'spanner', 'tidb', 'alloydb']);
    const cloudManaged = new Set([
        'supabase',
        'aurora',
        'neon',
        'planetscale',
        'alloydb',
    ]);
    const sourceIsNoSql = noSqlEngines.has(source.toLowerCase());
    const targetIsNoSql = noSqlEngines.has(target.toLowerCase());
    const targetIsNewSql = newSqlEngines.has(target.toLowerCase());
    const targetIsCloud = cloudManaged.has(target.toLowerCase());
    if (sourceIsNoSql && !targetIsNoSql) {
        return 'nosql-to-relational';
    }
    if (!sourceIsNoSql && targetIsNoSql) {
        return 'relational-to-nosql';
    }
    if (targetIsNewSql) {
        return 'sql-to-newsql';
    }
    if (targetIsCloud) {
        return 'on-premise-to-cloud';
    }
    return 'relational-to-relational';
}
function buildStructuralTransformations(source, target) {
    const transforms = [];
    const migrationType = detectMigrationType(source, target);
    if (migrationType === 'relational-to-nosql') {
        transforms.push('Convert FK relationships to embedded documents or $lookup references');
        transforms.push('Denormalize frequently accessed related data for query efficiency');
        transforms.push('Remove JOIN-based query patterns — MongoDB uses aggregation pipelines');
    }
    if (migrationType === 'nosql-to-relational') {
        transforms.push('Normalize embedded documents into separate tables with FK relationships');
        transforms.push('Identify unique indexes from MongoDB _id fields');
        transforms.push('Convert array fields to junction tables or JSON columns');
    }
    return transforms;
}
function buildDbMigrationPhases(schema, dualWrite) {
    const phases = [
        {
            order: 1,
            name: 'schema',
            description: 'Create target schema: tables/collections, columns, constraints',
            estimatedMinutes: 5 + schema.tables.length * 2,
            rollbackStrategy: 'Drop target schema entirely — origin untouched',
        },
        {
            order: 2,
            name: 'data-batches',
            description: 'Migrate data in batches to minimize memory pressure and allow progress tracking',
            /* v8 ignore next 1 */
            estimatedMinutes: Math.ceil((schema.estimatedSizeGb ?? 1) * 60),
            rollbackStrategy: 'Truncate target tables/collections — re-run from batch checkpoint',
        },
        {
            order: 3,
            name: 'indexes',
            description: 'Create indexes AFTER data migration for maximum performance',
            estimatedMinutes: 10 + schema.tables.length * 5,
            rollbackStrategy: 'Drop indexes from target — data remains intact',
        },
        {
            order: 4,
            name: 'validation',
            description: 'Run row/document count comparisons and sample data spot checks',
            estimatedMinutes: 15,
            rollbackStrategy: 'No rollback needed — validation only, origin unchanged',
        },
    ];
    if (dualWrite) {
        phases.splice(1, 0, {
            order: 2,
            name: 'data-batches',
            description: 'Enable dual-write: all new writes go to both DBs simultaneously',
            estimatedMinutes: 30,
            rollbackStrategy: 'Disable dual-write flag — all writes revert to origin only',
        });
    }
    return phases;
}
function buildDualWriteStrategy() {
    return {
        description: 'Dual-write pattern: application writes to both databases simultaneously during transition. New database is read-validated but not read by production until cutover.',
        steps: [
            '1. Deploy dual-write code with feature flag disabled',
            '2. Enable dual-write flag — application now writes to both DBs',
            '3. Migrate historical data in background batches',
            '4. Run validation queries to confirm data parity',
            '5. Enable new DB for reads on non-critical paths (canary)',
            '6. Monitor error rates and compare response times',
            '7. Full cutover: disable writes to origin DB',
            '8. Keep origin DB for 30 days as read-only backup',
        ],
        validationQueries: [
            'SELECT COUNT(*) FROM each table — compare origin vs target',
            'Sample 1000 random rows per table — compare field by field',
            'Check latest 100 records are present in both DBs',
        ],
        cutoverCondition: 'Zero validation errors for 24 consecutive hours + error rate on canary reads < 0.1%',
    };
}
// --- Public API ---
export function analyzeDbSchema(params) {
    return {
        engine: params.engine,
        orm: params.orm,
        tables: params.tables ?? [],
        estimatedSizeGb: params.estimatedSizeGb,
    };
}
export function buildDbMigrationMap(sourceEngine, targetEngine) {
    const rawMappings = getTypeMappings(sourceEngine, targetEngine);
    const typeMappings = rawMappings.map((m) => ({
        sourceType: m.sourceType,
        targetType: m.targetType,
        transformation: m.transformation,
        dataLossRisk: m.dataLossRisk,
        dataLossNote: m.dataLossNote,
    }));
    const warnings = typeMappings
        .filter((m) => m.dataLossRisk)
        .map(
    /* v8 ignore next 1 */
    (m) => `WARNING: ${m.sourceType} → ${m.targetType}: ${m.dataLossNote ?? 'potential data loss'}`);
    const structuralTransformations = buildStructuralTransformations(sourceEngine, targetEngine);
    return {
        sourceEngine,
        targetEngine,
        migrationType: detectMigrationType(sourceEngine, targetEngine),
        typeMappings,
        structuralTransformations,
        warnings,
        unmigratableFeatures: [],
        generatedAt: new Date().toISOString(),
    };
}
export function buildDbMigrationPlan(params) {
    const { sourceEngine, targetEngine, schema, zeroDowntime } = params;
    const phases = buildDbMigrationPhases(schema, zeroDowntime);
    const totalMinutes = phases.reduce((sum, p) => sum + p.estimatedMinutes, 0);
    return {
        sourceEngine,
        targetEngine,
        phases,
        estimatedDowntimeMinutes: zeroDowntime ? 0 : Math.ceil(totalMinutes * 0.3),
        rollbackPlan: zeroDowntime
            ? 'Disable dual-write + feature flags — full rollback in < 5 minutes'
            : 'Stop application, restore origin DB from snapshot, restart — downtime ~15 minutes',
        dualWriteStrategy: zeroDowntime ? buildDualWriteStrategy() : undefined,
        unmigratableData: [],
        generatedAt: new Date().toISOString(),
    };
}
//# sourceMappingURL=db-migrator.js.map