/**
 * Phase 4 Pipeline — Stage 3: ERP-Native SQL Schema Generator
 *
 * Generates domain-aware CREATE TABLE DDL statements in the SQL dialect of the
 * detected ERP system's database engine. Reads the DDD domain model to extract
 * aggregates, entities, and value objects, then generates columns specific to
 * each bounded context rather than cookie-cutter templates.
 *
 * Supported dialects:
 *   hana-sql     — SAP HANA COLUMN TABLE syntax
 *   tsql         — Microsoft SQL Server / Azure SQL / Azure Synapse T-SQL syntax
 *   oracle-sql   — Oracle Database syntax (VARCHAR2, NUMBER, SYSTIMESTAMP)
 *   db2-sql      — IBM Db2 syntax
 *   postgresql   — PostgreSQL / Cloud SQL / Supabase / Aurora PostgreSQL
 *   mysql        — MySQL / Aurora MySQL / PlanetScale / MariaDB
 *   bigquery-sql — Google BigQuery (STRING, INT64, no FK/indexes)
 *   sqlite       — SQLite / Turso / libSQL
 *
 * Error codes:
 *   ECLI-P4-004  Schema file write failure
 */
// Generated by Phase 4 pipeline — do not edit manually
import { mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { createSpan, endSpan, emitSpan } from '../../phase2/telemetry.js';
// ============================================================================
// Constants
// ============================================================================
const DIR_MODE = 0o700;
const FILE_MODE = 0o600;
// ============================================================================
// Name Helpers
// ============================================================================
function toTableSlug(name) {
    return name
        .replace(/Context$/i, '')
        .replace(/Aggregate$/i, '')
        .replace(/([a-z])([A-Z])/g, '$1_$2')
        .replace(/[^a-zA-Z0-9]+/g, '_')
        .replace(/^_|_$/g, '')
        .toLowerCase();
}
function toDirSlug(name) {
    return toTableSlug(name);
}
const AUDIT_COLUMNS = [
    { name: 'correlation_id', type: 'VARCHAR(64)', nullable: true },
    { name: 'created_by', type: 'VARCHAR(128)', nullable: false },
    { name: 'updated_by', type: 'VARCHAR(128)', nullable: false },
    { name: 'created_at', type: 'TIMESTAMP_DEFAULT', nullable: false },
    { name: 'updated_at', type: 'TIMESTAMP_DEFAULT', nullable: false },
];
function withAudit(cols) {
    return [...cols, ...AUDIT_COLUMNS];
}
const DOMAIN_TABLE_SPECS = [
    // ── Facilities (reference table — no domain-specific context, shared) ──
    {
        contextPattern: /\b(?:facility|facilities|transfer)\b/,
        tables: [
            {
                tableName: 'facilities',
                sequenceName: 'facilities_seq',
                columns: withAudit([
                    { name: 'facility_id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'facility_name', type: 'VARCHAR(255)', nullable: false },
                    { name: 'facility_type', type: 'VARCHAR(32)', nullable: false, checkConstraint: "facility_type IN ('hospital','clinic','warehouse','distribution_center','pharmacy')" },
                    { name: 'address', type: 'VARCHAR(512)', nullable: true },
                    { name: 'region', type: 'VARCHAR(128)', nullable: true },
                    { name: 'bed_count', type: 'INTEGER', nullable: true },
                    { name: 'active', type: 'BOOLEAN', nullable: false, defaultExpr: 'DEFAULT_TRUE' },
                ]),
                indexes: [
                    { name: 'idx_facilities_region', columns: ['region'] },
                    { name: 'idx_facilities_type', columns: ['facility_type'] },
                ],
                foreignKeys: [],
            },
            {
                tableName: 'transfer_orders',
                sequenceName: 'transfer_orders_seq',
                columns: withAudit([
                    { name: 'transfer_id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'from_facility_id', type: 'VARCHAR(64)', nullable: false },
                    { name: 'to_facility_id', type: 'VARCHAR(64)', nullable: false },
                    { name: 'sku', type: 'VARCHAR(64)', nullable: false },
                    { name: 'quantity', type: 'INTEGER', nullable: false },
                    { name: 'priority', type: 'VARCHAR(16)', nullable: false, checkConstraint: "priority IN ('low','normal','high','urgent')", defaultExpr: "'normal'" },
                    { name: 'status', type: 'VARCHAR(32)', nullable: false, checkConstraint: "status IN ('pending','approved','in_transit','received','cancelled')", defaultExpr: "'pending'" },
                    { name: 'requested_by', type: 'VARCHAR(128)', nullable: false },
                    { name: 'approved_by', type: 'VARCHAR(128)', nullable: true },
                    { name: 'netsuite_record_id', type: 'VARCHAR(64)', nullable: true },
                    { name: 'completed_at', type: 'TIMESTAMP', nullable: true },
                ]),
                indexes: [
                    { name: 'idx_transfer_from_facility', columns: ['from_facility_id', 'sku'] },
                    { name: 'idx_transfer_to_facility', columns: ['to_facility_id', 'sku'] },
                    { name: 'idx_transfer_status', columns: ['status'] },
                ],
                foreignKeys: [
                    { column: 'from_facility_id', refTable: 'facilities', refColumn: 'facility_id' },
                    { column: 'to_facility_id', refTable: 'facilities', refColumn: 'facility_id' },
                ],
            },
        ],
    },
    // ── Inventory ──
    {
        contextPattern: /\b(?:inventory|stock)\b/,
        tables: [
            {
                tableName: 'inventory_levels',
                sequenceName: 'inventory_levels_seq',
                columns: withAudit([
                    { name: 'id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'facility_id', type: 'VARCHAR(64)', nullable: false },
                    { name: 'sku', type: 'VARCHAR(64)', nullable: false },
                    { name: 'product_name', type: 'VARCHAR(255)', nullable: false },
                    { name: 'category', type: 'VARCHAR(128)', nullable: true },
                    { name: 'quantity_on_hand', type: 'INTEGER', nullable: false, defaultExpr: '0' },
                    { name: 'reorder_point', type: 'INTEGER', nullable: false },
                    { name: 'reorder_quantity', type: 'INTEGER', nullable: false },
                    { name: 'unit_of_measure', type: 'VARCHAR(32)', nullable: false, defaultExpr: "'EA'" },
                    { name: 'last_counted_at', type: 'TIMESTAMP', nullable: true },
                    { name: 'storage_location', type: 'VARCHAR(128)', nullable: true },
                    { name: 'lot_number', type: 'VARCHAR(64)', nullable: true },
                    { name: 'expiration_date', type: 'DATE', nullable: true },
                ]),
                indexes: [
                    { name: 'idx_inv_facility_sku', columns: ['facility_id', 'sku'], unique: true },
                    { name: 'idx_inv_category', columns: ['category'] },
                    { name: 'idx_inv_reorder', columns: ['quantity_on_hand', 'reorder_point'] },
                ],
                foreignKeys: [
                    { column: 'facility_id', refTable: 'facilities', refColumn: 'facility_id' },
                ],
            },
        ],
    },
    // ── Demand Forecasting ──
    {
        contextPattern: /\b(?:demand|forecast)\b/,
        tables: [
            {
                tableName: 'demand_forecasts',
                sequenceName: 'demand_forecasts_seq',
                columns: withAudit([
                    { name: 'id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'facility_id', type: 'VARCHAR(64)', nullable: false },
                    { name: 'sku', type: 'VARCHAR(64)', nullable: false },
                    { name: 'forecast_date', type: 'DATE', nullable: false },
                    { name: 'forecast_horizon_days', type: 'INTEGER', nullable: false },
                    { name: 'predicted_quantity', type: 'DECIMAL(18,4)', nullable: false },
                    { name: 'confidence_lower', type: 'DECIMAL(18,4)', nullable: true },
                    { name: 'confidence_upper', type: 'DECIMAL(18,4)', nullable: true },
                    { name: 'model_version', type: 'VARCHAR(64)', nullable: false },
                    { name: 'patient_volume_input', type: 'INTEGER', nullable: true },
                    { name: 'seasonal_factor', type: 'DECIMAL(10,4)', nullable: true },
                    { name: 'data_sources_used', type: 'TEXT', nullable: true },
                ]),
                indexes: [
                    { name: 'idx_forecast_facility_sku', columns: ['facility_id', 'sku'] },
                    { name: 'idx_forecast_date', columns: ['forecast_date'] },
                    { name: 'idx_forecast_model', columns: ['model_version'] },
                ],
                foreignKeys: [
                    { column: 'facility_id', refTable: 'facilities', refColumn: 'facility_id' },
                ],
            },
        ],
    },
    // ── Supply Risk Detection ──
    {
        contextPattern: /\b(?:supply[\s_-]?risk|risk[\s_-]?detect|shortage|surplus)\b/,
        tables: [
            {
                tableName: 'supply_risk_alerts',
                sequenceName: 'supply_risk_alerts_seq',
                columns: withAudit([
                    { name: 'alert_id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'facility_id', type: 'VARCHAR(64)', nullable: false },
                    { name: 'sku', type: 'VARCHAR(64)', nullable: false },
                    { name: 'risk_type', type: 'VARCHAR(16)', nullable: false, checkConstraint: "risk_type IN ('shortage','surplus')" },
                    { name: 'severity', type: 'VARCHAR(16)', nullable: false, checkConstraint: "severity IN ('critical','warning','info')" },
                    { name: 'current_quantity', type: 'INTEGER', nullable: false },
                    { name: 'threshold_quantity', type: 'INTEGER', nullable: false },
                    { name: 'predicted_stockout_date', type: 'DATE', nullable: true },
                    { name: 'recommended_action', type: 'VARCHAR(512)', nullable: true },
                    { name: 'resolved_at', type: 'TIMESTAMP', nullable: true },
                    { name: 'resolution_notes', type: 'TEXT', nullable: true },
                    { name: 'source_forecast_id', type: 'VARCHAR(64)', nullable: true },
                ]),
                indexes: [
                    { name: 'idx_risk_facility_sku', columns: ['facility_id', 'sku'] },
                    { name: 'idx_risk_severity', columns: ['severity'] },
                    { name: 'idx_risk_type', columns: ['risk_type'] },
                    { name: 'idx_risk_unresolved', columns: ['resolved_at'] },
                ],
                foreignKeys: [
                    { column: 'facility_id', refTable: 'facilities', refColumn: 'facility_id' },
                ],
            },
        ],
    },
    // ── Procurement Automation ──
    {
        contextPattern: /\b(?:procurement|sourcing|purchas)\b/,
        tables: [
            {
                tableName: 'procurement_orders',
                sequenceName: 'procurement_orders_seq',
                columns: withAudit([
                    { name: 'order_id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'facility_id', type: 'VARCHAR(64)', nullable: false },
                    { name: 'sku', type: 'VARCHAR(64)', nullable: false },
                    { name: 'quantity', type: 'INTEGER', nullable: false },
                    { name: 'vendor_id', type: 'VARCHAR(64)', nullable: false },
                    { name: 'unit_cost', type: 'DECIMAL(18,4)', nullable: true },
                    { name: 'total_cost', type: 'DECIMAL(18,4)', nullable: true },
                    { name: 'status', type: 'VARCHAR(32)', nullable: false, checkConstraint: "status IN ('draft','submitted','approved','ordered','received','cancelled')", defaultExpr: "'draft'" },
                    { name: 'netsuite_po_id', type: 'VARCHAR(64)', nullable: true },
                    { name: 'lead_time_days', type: 'INTEGER', nullable: true },
                    { name: 'expected_delivery_date', type: 'DATE', nullable: true },
                    { name: 'actual_delivery_date', type: 'DATE', nullable: true },
                    { name: 'triggering_alert_id', type: 'VARCHAR(64)', nullable: true },
                ]),
                indexes: [
                    { name: 'idx_proc_facility_sku', columns: ['facility_id', 'sku'] },
                    { name: 'idx_proc_vendor', columns: ['vendor_id'] },
                    { name: 'idx_proc_status', columns: ['status'] },
                    { name: 'idx_proc_netsuite', columns: ['netsuite_po_id'] },
                ],
                foreignKeys: [
                    { column: 'facility_id', refTable: 'facilities', refColumn: 'facility_id' },
                ],
            },
            {
                tableName: 'vendors',
                sequenceName: 'vendors_seq',
                columns: withAudit([
                    { name: 'vendor_id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'vendor_name', type: 'VARCHAR(255)', nullable: false },
                    { name: 'contact_email', type: 'VARCHAR(512)', nullable: true },
                    { name: 'lead_time_days', type: 'INTEGER', nullable: true },
                    { name: 'rating', type: 'DECIMAL(3,2)', nullable: true },
                    { name: 'netsuite_vendor_id', type: 'VARCHAR(64)', nullable: true },
                    { name: 'active', type: 'BOOLEAN', nullable: false, defaultExpr: 'DEFAULT_TRUE' },
                ]),
                indexes: [
                    { name: 'idx_vendor_name', columns: ['vendor_name'] },
                    { name: 'idx_vendor_netsuite', columns: ['netsuite_vendor_id'] },
                ],
                foreignKeys: [],
            },
        ],
    },
    // ── Compliance Auditing ──
    {
        contextPattern: /\b(?:compliance|audit)\b/,
        tables: [
            {
                tableName: 'audit_log',
                sequenceName: 'audit_log_seq',
                columns: [
                    { name: 'audit_id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'table_name', type: 'VARCHAR(128)', nullable: false },
                    { name: 'record_id', type: 'VARCHAR(64)', nullable: false },
                    { name: 'operation', type: 'VARCHAR(8)', nullable: false, checkConstraint: "operation IN ('INSERT','UPDATE','DELETE')" },
                    { name: 'old_values', type: 'CLOB', nullable: true },
                    { name: 'new_values', type: 'CLOB', nullable: true },
                    { name: 'performed_by', type: 'VARCHAR(128)', nullable: false },
                    { name: 'performed_at', type: 'TIMESTAMP_DEFAULT', nullable: false },
                    { name: 'correlation_id', type: 'VARCHAR(64)', nullable: true },
                ],
                indexes: [
                    { name: 'idx_audit_table_record', columns: ['table_name', 'record_id'] },
                    { name: 'idx_audit_performed_at', columns: ['performed_at'] },
                    { name: 'idx_audit_operation', columns: ['operation'] },
                    { name: 'idx_audit_performed_by', columns: ['performed_by'] },
                ],
                foreignKeys: [],
            },
            {
                tableName: 'compliance_checks',
                sequenceName: 'compliance_checks_seq',
                columns: withAudit([
                    { name: 'check_id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'check_type', type: 'VARCHAR(128)', nullable: false },
                    { name: 'target_table', type: 'VARCHAR(128)', nullable: false },
                    { name: 'target_record_id', type: 'VARCHAR(64)', nullable: true },
                    { name: 'status', type: 'VARCHAR(16)', nullable: false, checkConstraint: "status IN ('pass','fail','warning','pending')" },
                    { name: 'findings', type: 'TEXT', nullable: true },
                    { name: 'checked_at', type: 'TIMESTAMP_DEFAULT', nullable: false },
                ]),
                indexes: [
                    { name: 'idx_compliance_type', columns: ['check_type'] },
                    { name: 'idx_compliance_status', columns: ['status'] },
                ],
                foreignKeys: [],
            },
        ],
    },
    // ── Analytics / Reporting ──
    {
        contextPattern: /\b(?:analytics|reporting|dashboard)\b/,
        tables: [
            {
                tableName: 'report_snapshots',
                sequenceName: 'report_snapshots_seq',
                columns: withAudit([
                    { name: 'snapshot_id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'report_type', type: 'VARCHAR(128)', nullable: false },
                    { name: 'facility_id', type: 'VARCHAR(64)', nullable: true },
                    { name: 'snapshot_date', type: 'DATE', nullable: false },
                    { name: 'payload', type: 'CLOB', nullable: false },
                    { name: 'generated_by', type: 'VARCHAR(128)', nullable: false },
                ]),
                indexes: [
                    { name: 'idx_snapshot_type_date', columns: ['report_type', 'snapshot_date'] },
                    { name: 'idx_snapshot_facility', columns: ['facility_id'] },
                ],
                foreignKeys: [],
            },
        ],
    },
    // ── Supply Chain Coordination ──
    {
        contextPattern: /\b(?:supply[\s_-]?chain[\s_-]?coord|coordination|orchestrat)\b/,
        tables: [
            {
                tableName: 'supply_chain_events',
                sequenceName: 'supply_chain_events_seq',
                columns: withAudit([
                    { name: 'event_id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
                    { name: 'event_type', type: 'VARCHAR(128)', nullable: false },
                    { name: 'source_context', type: 'VARCHAR(128)', nullable: false },
                    { name: 'facility_id', type: 'VARCHAR(64)', nullable: true },
                    { name: 'sku', type: 'VARCHAR(64)', nullable: true },
                    { name: 'payload', type: 'CLOB', nullable: false },
                    { name: 'processed', type: 'BOOLEAN', nullable: false, defaultExpr: 'DEFAULT_FALSE' },
                    { name: 'processed_at', type: 'TIMESTAMP', nullable: true },
                ]),
                indexes: [
                    { name: 'idx_sce_type', columns: ['event_type'] },
                    { name: 'idx_sce_facility_sku', columns: ['facility_id', 'sku'] },
                    { name: 'idx_sce_unprocessed', columns: ['processed'] },
                ],
                foreignKeys: [],
            },
        ],
    },
];
// ============================================================================
// Fallback Column Builder (for contexts not in the domain registry)
// ============================================================================
function inferSQLType(fieldName) {
    const lower = fieldName.toLowerCase();
    if (/amount|price|cost|fee|rate|total|balance|value/.test(lower))
        return 'DECIMAL(18,4)';
    if (/score|rating|rank|weight|percentage|pct/.test(lower))
        return 'DECIMAL(10,4)';
    if (/quantity|count|number|version|level|priority/.test(lower))
        return 'INTEGER';
    if (/date|deadline|expires|timestamp/.test(lower))
        return 'TIMESTAMP_DEFAULT';
    if (/^is[A-Z]|^has[A-Z]|enabled|active|valid|flag|approved|completed/.test(fieldName))
        return 'BOOLEAN';
    if (/description|note|comment|text|summary|content|body|markdown|json/.test(lower))
        return 'TEXT';
    if (/email|url|link|uri|endpoint/.test(lower))
        return 'VARCHAR(512)';
    if (/name|title|label|status|state|type|kind|category|currency|code/.test(lower))
        return 'VARCHAR(128)';
    if (/id$|ref$|uuid|key/.test(lower))
        return 'VARCHAR(64)';
    return 'VARCHAR(255)';
}
/**
 * Extract plausible field names from a DDD description string.
 * Splits on common separators, filters noise words, and returns
 * camelCase tokens that look like data fields.
 */
function extractFieldsFromDescription(description) {
    if (!description)
        return [];
    const noiseWords = new Set([
        'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
        'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
        'should', 'may', 'might', 'shall', 'can', 'must', 'need', 'to', 'of',
        'in', 'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through',
        'during', 'before', 'after', 'above', 'below', 'between', 'under',
        'again', 'further', 'then', 'once', 'and', 'but', 'or', 'nor', 'not',
        'so', 'yet', 'both', 'either', 'neither', 'each', 'every', 'all',
        'any', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'only',
        'own', 'same', 'than', 'too', 'very', 'just', 'because', 'if', 'when',
        'where', 'how', 'what', 'which', 'who', 'whom', 'this', 'that', 'these',
        'those', 'it', 'its', 'also', 'like', 'represents', 'contains', 'includes',
        'defines', 'tracks', 'stores', 'holds', 'manages', 'provides', 'used',
        'using', 'etc', 'e.g', 'i.e', 'value', 'object', 'entity', 'aggregate',
    ]);
    // Split on non-alphanumeric, camelCase boundaries, and common separators
    const tokens = description
        .replace(/([a-z])([A-Z])/g, '$1 $2')
        .split(/[\s,;:.()\[\]{}"'\/\-_]+/)
        .map(t => t.trim().toLowerCase())
        .filter(t => t.length > 2 && !noiseWords.has(t) && !/^\d+$/.test(t));
    return [...new Set(tokens)];
}
/**
 * Expand a value object name into constituent field names using common
 * domain patterns (e.g., Address → street, city, zip; Money → amount, currency).
 */
function expandVOFields(voName) {
    const lower = voName.toLowerCase();
    // Common VO decompositions
    if (/address/.test(lower))
        return [`${voName}_street`, `${voName}_city`, `${voName}_state`, `${voName}_zip`, `${voName}_country`];
    if (/money|price|amount|cost/.test(lower))
        return [`${voName}_amount`, `${voName}_currency`];
    if (/coordinates?|location|position|geo/.test(lower))
        return [`${voName}_latitude`, `${voName}_longitude`];
    if (/date.?range|period|time.?range|interval/.test(lower))
        return [`${voName}_start`, `${voName}_end`];
    if (/name|full.?name|person.?name/.test(lower) && lower !== 'name')
        return [`${voName}_first`, `${voName}_last`];
    if (/measurement|dimension/.test(lower))
        return [`${voName}_value`, `${voName}_unit`];
    // Default: keep VO as a single column
    return [voName];
}
/**
 * Parse a query.returns string for field names.
 * Handles patterns like "{ flightNumber, gateId, delay }" or "list of Delay objects with id, minutes".
 */
function extractFieldsFromReturns(returns) {
    if (!returns)
        return [];
    // Try to find { field1, field2 } patterns
    const braceMatch = returns.match(/\{([^}]+)\}/);
    if (braceMatch) {
        return braceMatch[1]
            .split(/[,;]/)
            .map(f => f.trim().split(/[\s:]+/)[0])
            .filter(f => f.length > 1 && !/^[A-Z]/.test(f)); // skip type names
    }
    // Fall back to extracting field-like tokens
    return extractFieldsFromDescription(returns);
}
function buildFallbackTable(agg, ctx) {
    const tableName = toTableSlug(agg.name);
    const fieldNames = [];
    // 1. Entity fields: FK id + fields extracted from description
    for (const entity of agg.entities) {
        fieldNames.push(`${entity.name}_id`);
        for (const field of extractFieldsFromDescription(entity.description)) {
            fieldNames.push(`${entity.name}_${field}`);
        }
    }
    // 2. Value object properties: expand VOs into constituent fields + description fields
    for (const vo of agg.valueObjects) {
        fieldNames.push(...expandVOFields(vo.name));
        for (const field of extractFieldsFromDescription(vo.description)) {
            fieldNames.push(`${vo.name}_${field}`);
        }
    }
    // 3. Command inputs: ALL commands in the bounded context (relaxed matching)
    for (const cmd of ctx.commands) {
        fieldNames.push(...cmd.input);
    }
    // 4. Domain event payload fields
    for (const event of ctx.domainEvents) {
        fieldNames.push(...event.payload);
    }
    // 5. Query return fields + query parameters
    for (const query of ctx.queries) {
        fieldNames.push(...query.parameters);
        fieldNames.push(...extractFieldsFromReturns(query.returns));
    }
    // Deduplicate, normalize to table slugs, and filter out 'id' (added separately)
    const seen = new Set();
    seen.add('id');
    seen.add('status');
    seen.add('version');
    const uniqueFields = [];
    for (const field of fieldNames) {
        const slug = toTableSlug(field);
        if (slug && !seen.has(slug)) {
            seen.add(slug);
            uniqueFields.push(field);
        }
    }
    const columns = [
        { name: 'id', type: 'VARCHAR(64)', nullable: false, primaryKey: true },
        ...uniqueFields.map((field) => ({
            name: toTableSlug(field),
            type: inferSQLType(field),
            nullable: true,
        })),
        { name: 'status', type: 'VARCHAR(64)', nullable: false },
        { name: 'version', type: 'INTEGER', nullable: false },
        ...AUDIT_COLUMNS,
    ];
    return {
        tableName,
        columns,
        indexes: [],
        foreignKeys: [],
    };
}
// ============================================================================
// Dialect-specific DDL generators
// ============================================================================
function resolveType(dialect, placeholder) {
    switch (dialect) {
        case 'oracle-sql':
            if (placeholder === 'TIMESTAMP_DEFAULT')
                return 'TIMESTAMP DEFAULT SYSTIMESTAMP';
            if (placeholder === 'TIMESTAMP')
                return 'TIMESTAMP';
            if (placeholder === 'DATE')
                return 'DATE';
            if (placeholder === 'BOOLEAN')
                return 'NUMBER(1)';
            if (placeholder === 'INTEGER')
                return 'NUMBER(10)';
            if (placeholder === 'TEXT')
                return 'CLOB';
            if (placeholder === 'CLOB')
                return 'CLOB';
            if (placeholder.startsWith('VARCHAR'))
                return placeholder.replace('VARCHAR', 'VARCHAR2');
            if (placeholder.startsWith('DECIMAL'))
                return placeholder.replace('DECIMAL', 'NUMBER');
            return placeholder;
        case 'hana-sql':
            if (placeholder === 'TIMESTAMP_DEFAULT')
                return 'TIMESTAMP DEFAULT CURRENT_UTCTIMESTAMP';
            if (placeholder === 'TIMESTAMP')
                return 'TIMESTAMP';
            if (placeholder === 'DATE')
                return 'DATE';
            if (placeholder === 'BOOLEAN')
                return 'BOOLEAN';
            if (placeholder === 'TEXT')
                return 'NCLOB';
            if (placeholder === 'CLOB')
                return 'NCLOB';
            if (placeholder.startsWith('VARCHAR'))
                return placeholder.replace('VARCHAR', 'NVARCHAR');
            return placeholder;
        case 'tsql':
            if (placeholder === 'TIMESTAMP_DEFAULT')
                return 'DATETIME2 DEFAULT GETUTCDATE()';
            if (placeholder === 'TIMESTAMP')
                return 'DATETIME2';
            if (placeholder === 'DATE')
                return 'DATE';
            if (placeholder === 'BOOLEAN')
                return 'BIT';
            if (placeholder === 'TEXT')
                return 'NVARCHAR(MAX)';
            if (placeholder === 'CLOB')
                return 'NVARCHAR(MAX)';
            if (placeholder.startsWith('VARCHAR'))
                return placeholder.replace('VARCHAR', 'NVARCHAR');
            return placeholder;
        case 'db2-sql':
            if (placeholder === 'TIMESTAMP_DEFAULT')
                return 'TIMESTAMP DEFAULT CURRENT TIMESTAMP';
            if (placeholder === 'TIMESTAMP')
                return 'TIMESTAMP';
            if (placeholder === 'DATE')
                return 'DATE';
            if (placeholder === 'BOOLEAN')
                return 'SMALLINT';
            if (placeholder === 'TEXT')
                return 'CLOB';
            if (placeholder === 'CLOB')
                return 'CLOB';
            return placeholder;
        case 'postgresql':
            if (placeholder === 'TIMESTAMP_DEFAULT')
                return 'TIMESTAMPTZ DEFAULT NOW()';
            if (placeholder === 'TIMESTAMP')
                return 'TIMESTAMPTZ';
            if (placeholder === 'DATE')
                return 'DATE';
            if (placeholder === 'BOOLEAN')
                return 'BOOLEAN';
            if (placeholder === 'INTEGER')
                return 'INTEGER';
            if (placeholder === 'TEXT')
                return 'TEXT';
            if (placeholder === 'CLOB')
                return 'TEXT';
            return placeholder;
        case 'mysql':
            if (placeholder === 'TIMESTAMP_DEFAULT')
                return 'TIMESTAMP DEFAULT NOW()';
            if (placeholder === 'TIMESTAMP')
                return 'TIMESTAMP';
            if (placeholder === 'DATE')
                return 'DATE';
            if (placeholder === 'BOOLEAN')
                return 'TINYINT(1)';
            if (placeholder === 'INTEGER')
                return 'INT';
            if (placeholder === 'TEXT')
                return 'TEXT';
            if (placeholder === 'CLOB')
                return 'LONGTEXT';
            return placeholder;
        case 'bigquery-sql':
            if (placeholder === 'TIMESTAMP_DEFAULT')
                return 'TIMESTAMP';
            if (placeholder === 'TIMESTAMP')
                return 'TIMESTAMP';
            if (placeholder === 'DATE')
                return 'DATE';
            if (placeholder === 'BOOLEAN')
                return 'BOOL';
            if (placeholder === 'INTEGER')
                return 'INT64';
            if (placeholder === 'TEXT')
                return 'STRING';
            if (placeholder === 'CLOB')
                return 'STRING';
            if (placeholder.startsWith('VARCHAR'))
                return 'STRING';
            if (placeholder.startsWith('DECIMAL'))
                return placeholder.replace('DECIMAL', 'NUMERIC');
            return placeholder;
        case 'sqlite':
            if (placeholder === 'TIMESTAMP_DEFAULT')
                return "TEXT DEFAULT (datetime('now'))";
            if (placeholder === 'TIMESTAMP')
                return 'TEXT';
            if (placeholder === 'DATE')
                return 'TEXT';
            if (placeholder === 'BOOLEAN')
                return 'INTEGER';
            if (placeholder === 'INTEGER')
                return 'INTEGER';
            if (placeholder === 'TEXT')
                return 'TEXT';
            if (placeholder === 'CLOB')
                return 'TEXT';
            if (placeholder.startsWith('VARCHAR'))
                return 'TEXT';
            if (placeholder.startsWith('DECIMAL'))
                return 'REAL';
            return placeholder;
        case 'redshift-sql':
            if (placeholder === 'TIMESTAMP_DEFAULT')
                return 'TIMESTAMPTZ DEFAULT GETDATE()';
            if (placeholder === 'TIMESTAMP')
                return 'TIMESTAMPTZ';
            if (placeholder === 'DATE')
                return 'DATE';
            if (placeholder === 'BOOLEAN')
                return 'BOOLEAN';
            if (placeholder === 'INTEGER')
                return 'INTEGER';
            if (placeholder === 'TEXT')
                return 'VARCHAR(MAX)';
            if (placeholder === 'CLOB')
                return 'VARCHAR(MAX)';
            return placeholder;
    }
}
function resolveDefault(dialect, defaultExpr) {
    if (!defaultExpr)
        return undefined;
    if (defaultExpr === 'DEFAULT_TRUE') {
        switch (dialect) {
            case 'oracle-sql': return '1';
            case 'hana-sql': return 'TRUE';
            case 'tsql': return '1';
            case 'db2-sql': return '1';
            case 'postgresql': return 'TRUE';
            case 'mysql': return '1';
            case 'bigquery-sql': return 'TRUE';
            case 'sqlite': return '1';
            case 'redshift-sql': return 'TRUE';
        }
    }
    if (defaultExpr === 'DEFAULT_FALSE') {
        switch (dialect) {
            case 'oracle-sql': return '0';
            case 'hana-sql': return 'FALSE';
            case 'tsql': return '0';
            case 'db2-sql': return '0';
            case 'postgresql': return 'FALSE';
            case 'mysql': return '0';
            case 'bigquery-sql': return 'FALSE';
            case 'sqlite': return '0';
            case 'redshift-sql': return 'FALSE';
        }
    }
    return defaultExpr;
}
function generateTableDDL(dialect, table) {
    const lines = [];
    lines.push(`-- Generated by Phase 4 pipeline — do not edit manually`);
    lines.push(`-- Table: ${table.tableName}`);
    lines.push(`-- Dialect: ${dialect}`);
    lines.push('');
    const isHana = dialect === 'hana-sql';
    const isTSQL = dialect === 'tsql';
    const isBigQuery = dialect === 'bigquery-sql';
    const isMySQL = dialect === 'mysql';
    const isSQLite = dialect === 'sqlite';
    const isRedshift = dialect === 'redshift-sql';
    const isPg = dialect === 'postgresql' || isRedshift;
    // Sequence for Oracle
    if (dialect === 'oracle-sql' && table.sequenceName) {
        lines.push(`CREATE SEQUENCE ${table.sequenceName} START WITH 1 INCREMENT BY 1 NOCACHE;`);
        lines.push('');
    }
    // CREATE TABLE with dialect-appropriate IF NOT EXISTS
    let prefix;
    if (isHana) {
        prefix = 'CREATE COLUMN TABLE';
    }
    else if (isPg || isMySQL || isSQLite) {
        prefix = 'CREATE TABLE IF NOT EXISTS';
    }
    else {
        prefix = 'CREATE TABLE';
    }
    const tblRef = isTSQL ? `[dbo].[${table.tableName}]` : table.tableName;
    const colDefs = [];
    for (const col of table.columns) {
        const resolvedType = resolveType(dialect, col.type);
        const parts = [];
        const colRef = isTSQL ? `[${col.name}]` : col.name;
        parts.push(`  ${colRef} ${resolvedType}`);
        const defaultVal = resolveDefault(dialect, col.defaultExpr);
        if (defaultVal && !resolvedType.includes('DEFAULT')) {
            parts.push(`DEFAULT ${defaultVal}`);
        }
        if (!col.nullable && !isBigQuery)
            parts.push('NOT NULL');
        if (col.primaryKey && !isBigQuery)
            parts.push('PRIMARY KEY');
        colDefs.push(parts.join(' '));
    }
    // CHECK constraints (not supported in BigQuery or SQLite CHECK is limited)
    if (!isBigQuery) {
        for (const col of table.columns) {
            if (col.checkConstraint) {
                const constraintName = `chk_${table.tableName}_${col.name}`;
                colDefs.push(`  CONSTRAINT ${constraintName} CHECK (${col.checkConstraint})`);
            }
        }
    }
    // Foreign keys (not supported in BigQuery)
    if (!isBigQuery) {
        for (const fk of table.foreignKeys) {
            const fkName = `fk_${table.tableName}_${fk.column}`;
            colDefs.push(`  CONSTRAINT ${fkName} FOREIGN KEY (${fk.column}) REFERENCES ${fk.refTable}(${fk.refColumn})`);
        }
    }
    lines.push(`${prefix} ${tblRef} (`);
    lines.push(colDefs.join(',\n'));
    // MySQL ENGINE clause
    if (isMySQL) {
        lines.push(`) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`);
    }
    else {
        lines.push(`);`);
    }
    lines.push('');
    // Indexes (BigQuery doesn't support CREATE INDEX)
    if (!isBigQuery) {
        for (const idx of table.indexes) {
            const idxType = idx.unique ? 'CREATE UNIQUE INDEX' : 'CREATE INDEX';
            const idxCols = idx.columns.join(', ');
            const tblName = isTSQL ? `[dbo].[${table.tableName}]` : table.tableName;
            const ifNotExists = (isPg || isMySQL || isSQLite) ? ' IF NOT EXISTS' : '';
            lines.push(`${idxType}${ifNotExists} ${idx.name} ON ${tblName} (${idxCols});`);
        }
        if (table.indexes.length > 0)
            lines.push('');
    }
    return lines.join('\n');
}
// ============================================================================
// Migration Order
// ============================================================================
/**
 * Table dependency order: tables that are referenced by foreign keys
 * must be created first. This defines the canonical migration order.
 */
const MIGRATION_ORDER = [
    'facilities',
    'vendors',
    'inventory_levels',
    'demand_forecasts',
    'supply_risk_alerts',
    'procurement_orders',
    'transfer_orders',
    'supply_chain_events',
    'report_snapshots',
    'compliance_checks',
    'audit_log',
];
function migrationRank(tableName) {
    const idx = MIGRATION_ORDER.indexOf(tableName);
    return idx >= 0 ? idx : MIGRATION_ORDER.length;
}
// ============================================================================
// Master Migration Generator
// ============================================================================
function generateMasterMigration(dialect, contextFiles) {
    const sorted = [...contextFiles].sort((a, b) => migrationRank(a.tableName) - migrationRank(b.tableName));
    const lines = [];
    lines.push(`-- Master migration — run files in this order`);
    lines.push(`-- Generated by Phase 4 pipeline — do not edit manually`);
    lines.push(`-- Dialect: ${dialect}`);
    lines.push('');
    lines.push(`-- Dependency order ensures referenced tables exist before foreign keys.`);
    lines.push('');
    for (let i = 0; i < sorted.length; i++) {
        const entry = sorted[i];
        lines.push(`-- ${i + 1}. ${entry.contextSlug}/${entry.fileName}`);
    }
    lines.push('');
    if (dialect === 'oracle-sql') {
        lines.push(`-- Oracle SQL*Plus execution:`);
        for (const entry of sorted) {
            lines.push(`@@${entry.contextSlug}/${entry.fileName}`);
        }
    }
    else if (dialect === 'postgresql' || dialect === 'redshift-sql') {
        lines.push(`-- ${dialect === 'redshift-sql' ? 'Redshift' : 'PostgreSQL'} psql execution:`);
        for (const entry of sorted) {
            lines.push(`\\i ${entry.contextSlug}/${entry.fileName}`);
        }
    }
    else if (dialect === 'mysql') {
        lines.push(`-- MySQL execution:`);
        for (const entry of sorted) {
            lines.push(`SOURCE ${entry.contextSlug}/${entry.fileName};`);
        }
    }
    else if (dialect === 'sqlite') {
        lines.push(`-- SQLite execution:`);
        for (const entry of sorted) {
            lines.push(`.read ${entry.contextSlug}/${entry.fileName}`);
        }
    }
    else if (dialect === 'bigquery-sql') {
        lines.push(`-- BigQuery: execute each file as a separate query in the BigQuery console.`);
        for (const entry of sorted) {
            lines.push(`-- ${entry.contextSlug}/${entry.fileName}`);
        }
    }
    else {
        lines.push(`-- Execute each file in sequence against your database.`);
        for (const entry of sorted) {
            lines.push(`-- :r ${entry.contextSlug}/${entry.fileName}`);
        }
    }
    lines.push('');
    return lines.join('\n');
}
// ============================================================================
// Main: generateSchemas
// ============================================================================
/**
 * Stage 3: Generate ERP-native SQL schema files for all DDD bounded contexts.
 *
 * Reads the ERPResolution from the previous stage and the Phase 2 domain model
 * from context.phase3Artifacts. For each bounded context:
 *   1. Checks the domain table registry for context-specific schemas
 *   2. Falls back to generating tables from DDD aggregates/entities/VOs
 *
 * Generates per-context migration files plus a master migration.
 *
 * @param context - The Phase 4 pipeline context
 * @returns Stage output describing success, skip, or failure
 */
export function generateSchemas(context) {
    const rootSpan = createSpan('schema-generation', 'generate-schemas', {
        phase4Dir: context.phase4Dir,
    });
    try {
        // -------------------------------------------------------------------------
        // Step 1: Retrieve ERP resolution from previous stage
        // -------------------------------------------------------------------------
        const erpStage = context.stageOutputs.get('erp-target-resolution');
        if (!erpStage || erpStage.status !== 'completed') {
            throw new Error('ECLI-P4-004: ERP target resolution not available on context. ' +
                'Ensure the erp-target-resolution stage completed successfully.');
        }
        const erpResolution = erpStage.data;
        // -------------------------------------------------------------------------
        // Step 2: Skip for API-only ERPs (e.g. Workday)
        // -------------------------------------------------------------------------
        if (erpResolution.apiOnly) {
            const skippedSpan = endSpan(rootSpan, 'ok', { skipped: true, reason: 'api-only' });
            emitSpan(skippedSpan);
            context.telemetrySpans.push(skippedSpan);
            const timing = skippedSpan.durationMs ?? 0;
            const skipOutput = {
                stage: 'schema-generation',
                status: 'completed',
                data: {
                    skipped: true,
                    skipReason: 'API-only ERP (Workday) — no database schema generation',
                    dialect: null,
                    files: [],
                    tableCount: 0,
                },
                artifacts: [],
                timing,
                summary: `  [SKIP] Schema generation skipped: API-only ERP (${erpResolution.erpSystem}) in ${timing}ms`,
            };
            context.stageOutputs.set('schema-generation', skipOutput);
            return skipOutput;
        }
        const dialect = erpResolution.sqlDialect;
        // -------------------------------------------------------------------------
        // Step 3: Retrieve Phase 2 domain model
        // -------------------------------------------------------------------------
        const domainModel = context.phase3Artifacts.phase2Artifacts.domainModel;
        const generatedFiles = [];
        const writtenPaths = [];
        let totalTableCount = 0;
        // Track all generated context files for master migration
        const migrationEntries = [];
        // Track which domain specs have been used (to avoid duplicates)
        const usedDomainSpecs = new Set();
        // -------------------------------------------------------------------------
        // Step 4: Generate schema files per bounded context
        // -------------------------------------------------------------------------
        for (const ctx of domainModel.contexts) {
            const ctxSlug = toDirSlug(ctx.name);
            const ctxDir = join(context.phase4Dir, 'database', ctxSlug);
            const ctxSpan = createSpan('schema-generation', `generate-context-${ctxSlug}`, { context: ctx.name, aggregates: ctx.aggregates.length }, rootSpan.spanId);
            try {
                mkdirSync(ctxDir, { recursive: true, mode: DIR_MODE });
                // Find matching domain spec for this context
                const matchingSpec = DOMAIN_TABLE_SPECS.find((spec) => spec.contextPattern.test(ctx.name) && !usedDomainSpecs.has(spec));
                let tables;
                if (matchingSpec) {
                    usedDomainSpecs.add(matchingSpec);
                    tables = matchingSpec.tables;
                }
                else {
                    // Fallback: generate from DDD aggregates
                    tables = ctx.aggregates.map((agg) => buildFallbackTable(agg, ctx));
                }
                for (const table of tables) {
                    const ddl = generateTableDDL(dialect, table);
                    totalTableCount += 1;
                    const fileName = `${table.tableName}.sql`;
                    const relPath = `database/${ctxSlug}/${fileName}`;
                    const absPath = join(ctxDir, fileName);
                    writeFileSync(absPath, ddl, { encoding: 'utf-8', mode: FILE_MODE });
                    writtenPaths.push(absPath);
                    migrationEntries.push({ contextSlug: ctxSlug, fileName, tableName: table.tableName });
                    generatedFiles.push({
                        relativePath: relPath,
                        content: ddl,
                        kind: 'schema',
                        dialect,
                        boundedContext: ctx.name,
                        tableCount: 1,
                    });
                }
                const finalCtxSpan = endSpan(ctxSpan, 'ok', {
                    context: ctx.name,
                    tablesGenerated: tables.length,
                    domainSpecific: !!matchingSpec,
                });
                emitSpan(finalCtxSpan);
                context.telemetrySpans.push(finalCtxSpan);
            }
            catch (err) {
                const finalCtxSpan = endSpan(ctxSpan, 'error', {
                    context: ctx.name,
                    error: err instanceof Error ? err.message : String(err),
                });
                emitSpan(finalCtxSpan);
                context.telemetrySpans.push(finalCtxSpan);
                throw err;
            }
        }
        // -------------------------------------------------------------------------
        // Step 5: Generate master migration file
        // -------------------------------------------------------------------------
        const masterMigration = generateMasterMigration(dialect, migrationEntries);
        const masterPath = join(context.phase4Dir, 'database', 'migrate.sql');
        mkdirSync(join(context.phase4Dir, 'database'), { recursive: true, mode: DIR_MODE });
        writeFileSync(masterPath, masterMigration, { encoding: 'utf-8', mode: FILE_MODE });
        writtenPaths.push(masterPath);
        generatedFiles.push({
            relativePath: 'database/migrate.sql',
            content: masterMigration,
            kind: 'migration',
            dialect,
            boundedContext: '__master__',
            tableCount: 0,
        });
        // -------------------------------------------------------------------------
        // Step 6: Build stage output
        // -------------------------------------------------------------------------
        const schemaGenOutput = {
            dialect,
            files: generatedFiles,
            tableCount: totalTableCount,
            skipped: false,
        };
        const finalRootSpan = endSpan(rootSpan, 'ok', {
            dialect,
            totalFiles: generatedFiles.length,
            totalTables: totalTableCount,
            contexts: domainModel.contexts.length,
            domainSpecificTables: [...usedDomainSpecs].reduce((n, s) => n + s.tables.length, 0),
        });
        emitSpan(finalRootSpan);
        context.telemetrySpans.push(finalRootSpan);
        const timing = finalRootSpan.durationMs ?? 0;
        const output = {
            stage: 'schema-generation',
            status: 'completed',
            data: schemaGenOutput,
            artifacts: writtenPaths,
            timing,
            summary: `  [OK] Generated ${generatedFiles.length} SQL schema files ` +
                `(${totalTableCount} tables, dialect=${dialect}) ` +
                `across ${domainModel.contexts.length} bounded contexts in ${timing}ms`,
        };
        context.stageOutputs.set('schema-generation', output);
        return output;
    }
    catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        const finalRootSpan = endSpan(rootSpan, 'error', { error: errorMessage });
        emitSpan(finalRootSpan);
        context.telemetrySpans.push(finalRootSpan);
        const timing = finalRootSpan.durationMs ?? 0;
        const output = {
            stage: 'schema-generation',
            status: 'failed',
            data: { error: errorMessage },
            artifacts: [],
            timing,
            summary: `  [FAIL] Schema generation failed: ${errorMessage}`,
        };
        context.stageOutputs.set('schema-generation', output);
        return output;
    }
}
//# sourceMappingURL=schema-generator.js.map