"use strict";
/**
 * Smart FK/PK Detection Algorithms
 * Phase 1: Foundation Architecture - Relationship Detection Engine
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.RelationshipDetector = void 0;
const types_1 = require("./types");
class RelationshipDetector {
    confidenceThreshold;
    enableFuzzyMatching;
    enableSemanticAnalysis;
    constructor(config = {}) {
        this.confidenceThreshold = config.confidenceThreshold ?? 0.5; // Lowered from 0.7 to 0.5 
        this.enableFuzzyMatching = config.enableFuzzyMatching ?? true;
        this.enableSemanticAnalysis = config.enableSemanticAnalysis ?? true;
    }
    /**
     * Detect foreign key relationships across multiple tables
     */
    async inferForeignKeys(tables) {
        const candidates = [];
        for (let i = 0; i < tables.length; i++) {
            for (let j = 0; j < tables.length; j++) {
                if (i === j)
                    continue;
                const fkCandidates = await this.detectForeignKeysBetweenTables(tables[i], tables[j]);
                candidates.push(...fkCandidates);
            }
        }
        // Return both high-confidence and suggested joins
        const highConfidence = candidates.filter(candidate => candidate.confidence >= this.confidenceThreshold);
        const suggestedJoins = candidates.filter(candidate => candidate.confidence >= 0.3 && candidate.confidence < this.confidenceThreshold);
        return [...highConfidence, ...suggestedJoins]
            .sort((a, b) => b.confidence - a.confidence);
    }
    /**
     * Build comprehensive dependency graph showing table relationships
     */
    async buildDependencyGraph(tables) {
        const foreignKeys = await this.inferForeignKeys(tables);
        const nodes = tables.map(table => ({
            table,
            level: 0,
            children: [],
            parents: [],
            isRoot: false,
            isLeaf: false
        }));
        const edges = [];
        const nodeMap = new Map(nodes.map(node => [node.table.tableName, node]));
        // Build edges from foreign key relationships
        for (const fk of foreignKeys) {
            const fromNode = nodeMap.get(fk.table);
            const toNode = nodeMap.get(fk.referencedTable);
            if (fromNode && toNode) {
                fromNode.parents.push(fk.referencedTable);
                toNode.children.push(fk.table);
                edges.push({
                    from: fk.table,
                    to: fk.referencedTable,
                    columns: [{
                            fromColumn: fk.column,
                            toColumn: fk.referencedColumn,
                            similarity: fk.confidence,
                            cardinality: this.inferCardinality(fk)
                        }],
                    strength: fk.confidence,
                    type: 'FK'
                });
            }
        }
        // Calculate levels and identify roots/leaves
        this.calculateNodeLevels(nodes, edges);
        const cycles = this.detectCycles(nodes, edges);
        return {
            nodes,
            edges,
            cycles,
            depth: Math.max(...nodes.map(n => n.level), 0)
        };
    }
    /**
     * Infer business relationships from table structures and naming patterns
     */
    async inferBusinessRelationships(graph) {
        const rules = [];
        // Common business patterns
        const businessPatterns = [
            {
                name: 'Customer-Order Pattern',
                tables: ['customer', 'order'],
                description: 'Customers place orders',
                conditions: ['orders.customer_id references customers.id']
            },
            {
                name: 'Order-Item Pattern',
                tables: ['order', 'item', 'product'],
                description: 'Orders contain items which reference products',
                conditions: ['items.order_id references orders.id', 'items.product_id references products.id']
            },
            {
                name: 'User-Profile Pattern',
                tables: ['user', 'profile'],
                description: 'Users have profiles (1:1 relationship)',
                conditions: ['profiles.user_id references users.id']
            }
        ];
        for (const pattern of businessPatterns) {
            const matchingTables = graph.nodes.filter(node => pattern.tables.some(patternTable => node.table.tableName.toLowerCase().includes(patternTable)));
            if (matchingTables.length >= pattern.tables.length) {
                rules.push({
                    name: pattern.name,
                    description: pattern.description,
                    tables: matchingTables.map(t => t.table.tableName),
                    conditions: pattern.conditions,
                    confidence: this.calculateBusinessRuleConfidence(matchingTables, pattern),
                    source: 'INFERRED'
                });
            }
        }
        return rules.filter(rule => rule.confidence >= this.confidenceThreshold);
    }
    /**
     * Detect temporal relationships for time-series joins
     */
    async detectTemporalRelationships(tables) {
        const temporalJoins = [];
        const timeColumns = this.identifyTimeColumns(tables);
        for (let i = 0; i < tables.length; i++) {
            for (let j = i + 1; j < tables.length; j++) {
                const leftTimeColumns = timeColumns.get(tables[i].tableName) || [];
                const rightTimeColumns = timeColumns.get(tables[j].tableName) || [];
                for (const leftCol of leftTimeColumns) {
                    for (const rightCol of rightTimeColumns) {
                        temporalJoins.push({
                            leftTable: tables[i].tableName,
                            rightTable: tables[j].tableName,
                            leftTimeColumn: leftCol,
                            rightTimeColumn: rightCol,
                            strategy: 'EXACT' // Default strategy
                        });
                    }
                }
            }
        }
        return temporalJoins;
    }
    /**
     * Validate referential integrity across identified relationships
     */
    async validateIntegrity(foreignKeys) {
        const validJoins = [];
        const brokenRelationships = [];
        const orphanedRecords = [];
        const recommendations = [];
        for (const fk of foreignKeys) {
            if (fk.violations === 0) {
                validJoins.push(fk);
            }
            else {
                brokenRelationships.push({
                    fromTable: fk.table,
                    toTable: fk.referencedTable,
                    fromColumn: fk.column,
                    toColumn: fk.referencedColumn,
                    violationCount: fk.violations,
                    violationExamples: [] // Would be populated with actual data
                });
                recommendations.push(`Consider cleaning data in ${fk.table}.${fk.column} - ${fk.violations} referential integrity violations found`);
            }
        }
        return {
            validJoins: [], // Will be populated with actual JoinCandidate objects
            brokenRelationships,
            orphanedRecords,
            circularDependencies: [],
            recommendations
        };
    }
    /**
     * Calculate semantic similarity between column names
     */
    semanticSimilarity(col1, col2) {
        if (!this.enableSemanticAnalysis)
            return 0;
        // Exact match
        if (col1.toLowerCase() === col2.toLowerCase())
            return 1.0;
        // Remove common prefixes/suffixes
        const clean1 = this.cleanColumnName(col1);
        const clean2 = this.cleanColumnName(col2);
        if (clean1 === clean2)
            return 0.95;
        // Common patterns
        const patterns = [
            ['id', 'identifier', 'key'],
            ['name', 'title', 'label'],
            ['date', 'time', 'timestamp'],
            ['email', 'mail', 'email_address'],
            ['phone', 'telephone', 'mobile']
        ];
        for (const pattern of patterns) {
            if (pattern.includes(clean1) && pattern.includes(clean2)) {
                return 0.8;
            }
        }
        // Levenshtein distance
        return this.enableFuzzyMatching ?
            1 - (this.levenshteinDistance(clean1, clean2) / Math.max(clean1.length, clean2.length)) :
            0;
    }
    /**
     * Analyze value distribution similarity between columns
     */
    distributionSimilarity(data1, data2) {
        const stats1 = this.calculateDistributionStats(data1);
        const stats2 = this.calculateDistributionStats(data2);
        const rangeSimilarity = this.compareRanges(stats1, stats2);
        const patternSimilarity = this.comparePatterns(stats1, stats2);
        const typeSimilarity = this.compareTypes(stats1, stats2);
        return {
            overall: (rangeSimilarity + patternSimilarity + typeSimilarity) / 3,
            semantic: 0, // Would be calculated separately
            statistical: rangeSimilarity,
            structural: typeSimilarity,
            domain: patternSimilarity
        };
    }
    // Private helper methods
    async detectForeignKeysBetweenTables(table1, table2) {
        const candidates = [];
        for (const col1 of table1.schema) {
            for (const col2 of table2.schema) {
                const similarity = this.semanticSimilarity(col1.name, col2.name);
                if (similarity >= this.confidenceThreshold) {
                    const candidate = {
                        table: table1.tableName,
                        column: col1.name,
                        referencedTable: table2.tableName,
                        referencedColumn: col2.name,
                        confidence: similarity,
                        matchingRows: 0, // Would be calculated from actual data
                        totalRows: table1.rowCount,
                        violations: 0
                    };
                    candidates.push(candidate);
                }
            }
        }
        return candidates;
    }
    inferCardinality(fk) {
        const matchRatio = fk.matchingRows / fk.totalRows;
        if (matchRatio > 0.95) {
            return types_1.CardinalityType.MANY_TO_ONE;
        }
        else if (matchRatio > 0.5) {
            return types_1.CardinalityType.ONE_TO_MANY;
        }
        else {
            return types_1.CardinalityType.MANY_TO_MANY;
        }
    }
    calculateNodeLevels(nodes, edges) {
        // Identify root nodes (no parents)
        const roots = nodes.filter(node => node.parents.length === 0);
        roots.forEach(node => {
            node.isRoot = true;
            node.level = 0;
        });
        // BFS to calculate levels
        const queue = [...roots];
        const visited = new Set();
        while (queue.length > 0) {
            const current = queue.shift();
            if (visited.has(current.table.tableName))
                continue;
            visited.add(current.table.tableName);
            for (const childName of current.children) {
                const child = nodes.find(n => n.table.tableName === childName);
                if (child && !visited.has(childName)) {
                    child.level = Math.max(child.level, current.level + 1);
                    queue.push(child);
                }
            }
        }
        // Identify leaf nodes
        nodes.forEach(node => {
            node.isLeaf = node.children.length === 0;
        });
    }
    detectCycles(nodes, edges) {
        // Simple cycle detection - would be enhanced for production
        return [];
    }
    calculateBusinessRuleConfidence(tables, pattern) {
        // Calculate confidence based on naming patterns and relationships
        return 0.8; // Simplified for now
    }
    identifyTimeColumns(tables) {
        const timeColumns = new Map();
        for (const table of tables) {
            const timeColumnNames = table.schema
                .filter(col => col.type === types_1.DataType.DATE ||
                col.type === types_1.DataType.DATETIME ||
                col.name.toLowerCase().includes('time') ||
                col.name.toLowerCase().includes('date'))
                .map(col => col.name);
            if (timeColumnNames.length > 0) {
                timeColumns.set(table.tableName, timeColumnNames);
            }
        }
        return timeColumns;
    }
    cleanColumnName(name) {
        return name.toLowerCase()
            .replace(/^(tbl_|table_|tb_)/, '')
            .replace(/_(id|key|fk|pk)$/, '')
            .replace(/[_\s]+/g, '_')
            .trim();
    }
    levenshteinDistance(str1, str2) {
        const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
        for (let i = 0; i <= str1.length; i++)
            matrix[0][i] = i;
        for (let j = 0; j <= str2.length; j++)
            matrix[j][0] = j;
        for (let j = 1; j <= str2.length; j++) {
            for (let i = 1; i <= str1.length; i++) {
                const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
                matrix[j][i] = Math.min(matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + indicator);
            }
        }
        return matrix[str2.length][str1.length];
    }
    calculateDistributionStats(data) {
        // Calculate statistical properties of the data
        return {
            min: Math.min(...data.filter(d => typeof d === 'number')),
            max: Math.max(...data.filter(d => typeof d === 'number')),
            uniqueValues: new Set(data).size,
            nullCount: data.filter(d => d == null).length,
            patterns: this.extractPatterns(data)
        };
    }
    compareRanges(stats1, stats2) {
        if (typeof stats1.min !== 'number' || typeof stats2.min !== 'number') {
            return 0;
        }
        const range1 = stats1.max - stats1.min;
        const range2 = stats2.max - stats2.min;
        const overlap = Math.max(0, Math.min(stats1.max, stats2.max) - Math.max(stats1.min, stats2.min));
        return overlap / Math.max(range1, range2, 1);
    }
    comparePatterns(stats1, stats2) {
        const patterns1 = new Set(stats1.patterns);
        const patterns2 = new Set(stats2.patterns);
        const intersection = new Set([...patterns1].filter(p => patterns2.has(p)));
        return intersection.size / Math.max(patterns1.size, patterns2.size, 1);
    }
    compareTypes(stats1, stats2) {
        // Compare data type compatibility
        return 0.8; // Simplified for now
    }
    extractPatterns(data) {
        const patterns = [];
        const stringData = data.filter(d => typeof d === 'string').slice(0, 100);
        // Email pattern
        if (stringData.some(s => /\S+@\S+\.\S+/.test(s))) {
            patterns.push('email');
        }
        // Phone pattern
        if (stringData.some(s => /^\+?[\d\s\-\(\)]+$/.test(s))) {
            patterns.push('phone');
        }
        // UUID pattern
        if (stringData.some(s => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s))) {
            patterns.push('uuid');
        }
        return patterns;
    }
}
exports.RelationshipDetector = RelationshipDetector;
//# sourceMappingURL=relationship-detector.js.map