"use strict";
/**
 * useContextVisualization Hook - Semantic relationship and context visualization
 * Features: Topic clustering, semantic mapping, conversation flow analysis
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.useContextVisualization = void 0;
const react_1 = require("react");
// Mock word embeddings (in production, use actual embeddings from a service)
const generateMockEmbeddings = (text) => {
    const words = text.toLowerCase().split(/\s+/);
    const vector = new Array(384).fill(0); // Typical embedding dimension
    words.forEach((word, index) => {
        const hash = simpleHash(word);
        for (let i = 0; i < 384; i++) {
            vector[i] += Math.sin(hash + i) * 0.1;
        }
    });
    // Normalize vector
    const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
    return vector.map(val => val / magnitude);
};
// Simple hash function for consistent embeddings
const simpleHash = (str) => {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
        const char = str.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
        hash = hash & hash; // Convert to 32-bit integer
    }
    return Math.abs(hash);
};
// Calculate cosine similarity between embeddings
const cosineSimilarity = (a, b) => {
    const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
    const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
    const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
    return dotProduct / (magnitudeA * magnitudeB);
};
// K-means clustering implementation
const kMeansClustering = (nodes, k) => {
    if (nodes.length < k)
        k = nodes.length;
    // Initialize centroids randomly
    const centroids = Array.from({ length: k }, (_, i) => ({
        id: `cluster-${i}`,
        embeddings: nodes[i % nodes.length].embeddings.slice(),
        position: { x: Math.random() * 500, y: Math.random() * 300 }
    }));
    const clusters = [];
    const maxIterations = 50;
    for (let iter = 0; iter < maxIterations; iter++) {
        // Assign nodes to clusters
        const assignments = new Map();
        centroids.forEach(centroid => {
            assignments.set(centroid.id, []);
        });
        nodes.forEach(node => {
            let bestCluster = centroids[0].id;
            let bestSimilarity = cosineSimilarity(node.embeddings, centroids[0].embeddings);
            centroids.forEach(centroid => {
                const similarity = cosineSimilarity(node.embeddings, centroid.embeddings);
                if (similarity > bestSimilarity) {
                    bestSimilarity = similarity;
                    bestCluster = centroid.id;
                }
            });
            assignments.get(bestCluster)?.push(node);
        });
        // Update centroids
        centroids.forEach(centroid => {
            const clusterNodes = assignments.get(centroid.id) || [];
            if (clusterNodes.length > 0) {
                const newEmbeddings = new Array(centroid.embeddings.length).fill(0);
                clusterNodes.forEach(node => {
                    node.embeddings.forEach((val, i) => {
                        newEmbeddings[i] += val;
                    });
                });
                centroid.embeddings = newEmbeddings.map(val => val / clusterNodes.length);
            }
        });
    }
    // Create final clusters
    centroids.forEach((centroid, index) => {
        const clusterNodes = Array.from(assignments.get(centroid.id) || []);
        const keywords = extractKeywords(clusterNodes);
        clusters.push({
            id: centroid.id,
            name: generateClusterName(keywords),
            weight: clusterNodes.length / nodes.length,
            keywords,
            messages: clusterNodes.map(node => node.messageId),
            relationships: [],
            centroid: centroid.position,
            color: generateClusterColor(index)
        });
    });
    return clusters;
};
// Extract keywords from cluster nodes
const extractKeywords = (nodes) => {
    const words = new Map();
    nodes.forEach(node => {
        const nodeWords = node.text.toLowerCase()
            .split(/\s+/)
            .filter(word => word.length > 3)
            .filter(word => !['this', 'that', 'with', 'have', 'will', 'from', 'they', 'been', 'said', 'each', 'which'].includes(word));
        nodeWords.forEach(word => {
            words.set(word, (words.get(word) || 0) + 1);
        });
    });
    return Array.from(words.entries())
        .sort(([, a], [, b]) => b - a)
        .slice(0, 5)
        .map(([word]) => word);
};
// Generate cluster name from keywords
const generateClusterName = (keywords) => {
    if (keywords.length === 0)
        return 'Misc Topic';
    if (keywords.length === 1)
        return keywords[0];
    return keywords.slice(0, 2).join(' & ');
};
// Generate consistent colors for clusters
const generateClusterColor = (index) => {
    const colors = [
        '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
        '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9'
    ];
    return colors[index % colors.length];
};
const useContextVisualization = ({ enabled = true, messages, maxClusters = 5, clusteringAlgorithm = 'kmeans', semanticSimilarityThreshold = 0.6, updateInterval = 5000, enableRealTimeUpdates = true, visualizationMode = '2d', onTopicDetected, onContextShift }) => {
    const [contextGraph, setContextGraph] = (0, react_1.useState)(null);
    const [isAnalyzing, setIsAnalyzing] = (0, react_1.useState)(false);
    const [analysisProgress, setAnalysisProgress] = (0, react_1.useState)(0);
    const [currentTopic, setCurrentTopic] = (0, react_1.useState)(null);
    const [contextFlow, setContextFlow] = (0, react_1.useState)([]);
    // Process messages into semantic nodes
    const processMessages = (0, react_1.useCallback)(async (messages) => {
        return messages.map((message, index) => {
            const embeddings = generateMockEmbeddings(message.content);
            return {
                id: `node-${message.id}`,
                text: message.content,
                messageId: message.id,
                embeddings,
                position: {
                    x: (index * 100) % 500,
                    y: Math.floor(index / 5) * 80,
                    z: visualizationMode === '3d' ? Math.random() * 200 : undefined
                },
                connections: [],
                importance: calculateImportance(message, messages),
                category: message.role
            };
        });
    }, [visualizationMode]);
    // Calculate message importance
    const calculateImportance = (0, react_1.useCallback)((message, allMessages) => {
        let importance = 0.5; // Base importance
        // Longer messages tend to be more important
        importance += Math.min(message.content.length / 1000, 0.3);
        // Questions are often important
        if (message.content.includes('?'))
            importance += 0.2;
        // Messages with metadata (AI responses) might be important
        if (message.metadata)
            importance += 0.1;
        // Messages with attachments are important
        if (message.attachments?.length)
            importance += 0.2;
        return Math.min(importance, 1.0);
    }, []);
    // Analyze semantic similarity
    const analyzeSemanticSimilarity = (0, react_1.useCallback)(async (text1, text2) => {
        const embeddings1 = generateMockEmbeddings(text1);
        const embeddings2 = generateMockEmbeddings(text2);
        return cosineSimilarity(embeddings1, embeddings2);
    }, []);
    // Extract topics from messages
    const extractTopics = (0, react_1.useCallback)(async (messages) => {
        const nodes = await processMessages(messages);
        if (nodes.length === 0)
            return [];
        const clusters = kMeansClustering(nodes, Math.min(maxClusters, nodes.length));
        // Calculate relationships between clusters
        clusters.forEach(cluster => {
            clusters.forEach(otherCluster => {
                if (cluster.id !== otherCluster.id) {
                    const clusterNodes = nodes.filter(node => cluster.messages.includes(node.messageId));
                    const otherNodes = nodes.filter(node => otherCluster.messages.includes(node.messageId));
                    let totalSimilarity = 0;
                    let comparisons = 0;
                    clusterNodes.forEach(node1 => {
                        otherNodes.forEach(node2 => {
                            totalSimilarity += cosineSimilarity(node1.embeddings, node2.embeddings);
                            comparisons++;
                        });
                    });
                    if (comparisons > 0) {
                        const avgSimilarity = totalSimilarity / comparisons;
                        if (avgSimilarity > semanticSimilarityThreshold) {
                            cluster.relationships.push({
                                targetId: otherCluster.id,
                                strength: avgSimilarity,
                                type: 'semantic'
                            });
                        }
                    }
                }
            });
        });
        return clusters;
    }, [processMessages, maxClusters, semanticSimilarityThreshold]);
    // Update context analysis
    const updateContext = (0, react_1.useCallback)(async (newMessages) => {
        if (!enabled || newMessages.length === 0)
            return;
        setIsAnalyzing(true);
        setAnalysisProgress(0);
        try {
            // Process messages
            setAnalysisProgress(0.2);
            const nodes = await processMessages(newMessages);
            // Extract topics
            setAnalysisProgress(0.5);
            const clusters = await extractTopics(newMessages);
            // Build context graph
            setAnalysisProgress(0.8);
            const edges = buildContextEdges(nodes, newMessages);
            const timeline = buildTimeline(newMessages, clusters);
            const newContextGraph = {
                nodes,
                edges,
                clusters,
                timeline
            };
            setContextGraph(newContextGraph);
            // Detect topic changes
            if (clusters.length > 0) {
                const latestCluster = findCurrentTopic(clusters, newMessages);
                if (latestCluster && latestCluster.id !== currentTopic?.id) {
                    setCurrentTopic(latestCluster);
                    onTopicDetected?.(latestCluster);
                    if (currentTopic) {
                        onContextShift?.({
                            from: currentTopic.name,
                            to: latestCluster.name,
                            confidence: 0.8
                        });
                    }
                }
            }
            // Update context flow
            const flow = buildContextFlow(newMessages, clusters);
            setContextFlow(flow);
            setAnalysisProgress(1.0);
        }
        catch (error) {
            console.error('Context analysis failed:', error);
        }
        finally {
            setIsAnalyzing(false);
        }
    }, [enabled, processMessages, extractTopics, currentTopic, onTopicDetected, onContextShift]);
    // Build context edges between messages
    const buildContextEdges = (0, react_1.useCallback)((nodes, messages) => {
        const edges = [];
        for (let i = 1; i < messages.length; i++) {
            const currentMessage = messages[i];
            const previousMessage = messages[i - 1];
            let edgeType = 'follow-up';
            // Detect question-answer patterns
            if (previousMessage.content.includes('?') && currentMessage.role === 'assistant') {
                edgeType = 'question-answer';
            }
            // Detect clarifications
            else if (currentMessage.content.toLowerCase().includes('clarify') ||
                currentMessage.content.toLowerCase().includes('explain')) {
                edgeType = 'clarification';
            }
            // Detect topic shifts (low semantic similarity)
            else {
                const similarity = cosineSimilarity(generateMockEmbeddings(currentMessage.content), generateMockEmbeddings(previousMessage.content));
                if (similarity < 0.3) {
                    edgeType = 'topic-shift';
                }
            }
            edges.push({
                source: `node-${previousMessage.id}`,
                target: `node-${currentMessage.id}`,
                weight: 1.0,
                type: edgeType,
                metadata: {
                    timestamp: currentMessage.timestamp,
                    roles: [previousMessage.role, currentMessage.role]
                }
            });
        }
        return edges;
    }, []);
    // Build conversation timeline
    const buildTimeline = (0, react_1.useCallback)((messages, clusters) => {
        const timeline = [];
        let currentTopicId = null;
        messages.forEach((message, index) => {
            // Find which cluster this message belongs to
            const messageCluster = clusters.find(cluster => cluster.messages.includes(message.id));
            if (messageCluster && messageCluster.id !== currentTopicId) {
                if (currentTopicId) {
                    timeline.push({
                        timestamp: message.timestamp,
                        event: 'topic-end',
                        metadata: { topicId: currentTopicId }
                    });
                }
                timeline.push({
                    timestamp: message.timestamp,
                    event: 'topic-start',
                    metadata: { topicId: messageCluster.id, topicName: messageCluster.name }
                });
                currentTopicId = messageCluster.id;
            }
        });
        return timeline;
    }, []);
    // Find current topic based on recent messages
    const findCurrentTopic = (0, react_1.useCallback)((clusters, messages) => {
        if (messages.length === 0 || clusters.length === 0)
            return null;
        const recentMessages = messages.slice(-3); // Look at last 3 messages
        for (const cluster of clusters) {
            const hasRecentMessage = cluster.messages.some(messageId => recentMessages.some(msg => msg.id === messageId));
            if (hasRecentMessage) {
                return cluster;
            }
        }
        return clusters[0]; // Fallback to first cluster
    }, []);
    // Build context flow
    const buildContextFlow = (0, react_1.useCallback)((messages, clusters) => {
        const flow = [];
        let previousTopicId = null;
        messages.forEach(message => {
            const messageCluster = clusters.find(cluster => cluster.messages.includes(message.id));
            if (messageCluster) {
                let transitionType = 'continuation';
                if (previousTopicId && previousTopicId !== messageCluster.id) {
                    // Check if we're returning to a previous topic
                    const previousMessages = flow.filter(f => f.topicId === messageCluster.id);
                    transitionType = previousMessages.length > 0 ? 'return' : 'shift';
                }
                flow.push({
                    messageId: message.id,
                    topicId: messageCluster.id,
                    transitionType,
                    confidence: 0.8 // Simplified confidence
                });
                previousTopicId = messageCluster.id;
            }
        });
        return flow;
    }, []);
    // Generate context summary
    const generateContextSummary = (0, react_1.useCallback)(() => {
        if (!contextGraph)
            return 'No context analysis available.';
        const { clusters, timeline } = contextGraph;
        let summary = `Conversation covers ${clusters.length} main topics:\n`;
        clusters.forEach((cluster, index) => {
            summary += `${index + 1}. ${cluster.name} (${(cluster.weight * 100).toFixed(1)}%)\n`;
        });
        const topicShifts = timeline.filter(event => event.event === 'topic-start').length;
        summary += `\nConversation had ${topicShifts} topic transitions.`;
        if (currentTopic) {
            summary += ` Currently discussing: ${currentTopic.name}.`;
        }
        return summary;
    }, [contextGraph, currentTopic]);
    // Export visualization
    const exportVisualization = (0, react_1.useCallback)(async (format) => {
        if (!contextGraph)
            throw new Error('No visualization data available');
        switch (format) {
            case 'json':
                return JSON.stringify(contextGraph, null, 2);
            case 'svg':
                // Generate SVG representation
                return generateSVGVisualization(contextGraph);
            case 'png':
                // Generate PNG (simplified - would use canvas)
                const canvas = document.createElement('canvas');
                canvas.width = 800;
                canvas.height = 600;
                const ctx = canvas.getContext('2d');
                // Draw nodes and edges
                drawVisualization(ctx, contextGraph);
                return new Promise(resolve => {
                    canvas.toBlob(blob => resolve(blob), 'image/png');
                });
            default:
                throw new Error(`Unsupported export format: ${format}`);
        }
    }, [contextGraph]);
    // Reset visualization
    const resetVisualization = (0, react_1.useCallback)(() => {
        setContextGraph(null);
        setCurrentTopic(null);
        setContextFlow([]);
        setAnalysisProgress(0);
    }, []);
    // Auto-update context when messages change
    (0, react_1.useEffect)(() => {
        if (enableRealTimeUpdates && messages.length > 0) {
            const timeoutId = setTimeout(() => {
                updateContext(messages);
            }, updateInterval);
            return () => clearTimeout(timeoutId);
        }
    }, [messages, enableRealTimeUpdates, updateInterval, updateContext]);
    // Memoized derived data
    const topicClusters = (0, react_1.useMemo)(() => contextGraph?.clusters || [], [contextGraph]);
    const semanticMap = (0, react_1.useMemo)(() => contextGraph?.nodes || [], [contextGraph]);
    return {
        contextGraph,
        topicClusters,
        semanticMap,
        isAnalyzing,
        analysisProgress,
        currentTopic,
        contextFlow,
        updateContext,
        analyzeSemanticSimilarity,
        extractTopics,
        generateContextSummary,
        exportVisualization,
        resetVisualization
    };
};
exports.useContextVisualization = useContextVisualization;
// Helper functions for visualization export
function generateSVGVisualization(graph) {
    const width = 800;
    const height = 600;
    let svg = `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">`;
    // Draw edges
    graph.edges.forEach(edge => {
        const sourceNode = graph.nodes.find(n => n.id === edge.source);
        const targetNode = graph.nodes.find(n => n.id === edge.target);
        if (sourceNode && targetNode) {
            svg += `<line x1="${sourceNode.position.x}" y1="${sourceNode.position.y}" `;
            svg += `x2="${targetNode.position.x}" y2="${targetNode.position.y}" `;
            svg += `stroke="#999" stroke-width="${edge.weight}" opacity="0.6"/>`;
        }
    });
    // Draw nodes
    graph.nodes.forEach(node => {
        const radius = 5 + (node.importance * 10);
        svg += `<circle cx="${node.position.x}" cy="${node.position.y}" r="${radius}" `;
        svg += `fill="#4ECDC4" stroke="#333" stroke-width="1" opacity="0.8"/>`;
        svg += `<text x="${node.position.x}" y="${node.position.y + radius + 15}" `;
        svg += `text-anchor="middle" font-size="10" fill="#333">${node.text.slice(0, 20)}...</text>`;
    });
    svg += '</svg>';
    return svg;
}
function drawVisualization(ctx, graph) {
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    // Draw edges
    ctx.strokeStyle = '#999';
    ctx.globalAlpha = 0.6;
    graph.edges.forEach(edge => {
        const sourceNode = graph.nodes.find(n => n.id === edge.source);
        const targetNode = graph.nodes.find(n => n.id === edge.target);
        if (sourceNode && targetNode) {
            ctx.lineWidth = edge.weight;
            ctx.beginPath();
            ctx.moveTo(sourceNode.position.x, sourceNode.position.y);
            ctx.lineTo(targetNode.position.x, targetNode.position.y);
            ctx.stroke();
        }
    });
    // Draw nodes
    ctx.globalAlpha = 0.8;
    graph.nodes.forEach(node => {
        const radius = 5 + (node.importance * 10);
        // Draw node
        ctx.fillStyle = '#4ECDC4';
        ctx.beginPath();
        ctx.arc(node.position.x, node.position.y, radius, 0, 2 * Math.PI);
        ctx.fill();
        ctx.strokeStyle = '#333';
        ctx.lineWidth = 1;
        ctx.stroke();
        // Draw label
        ctx.fillStyle = '#333';
        ctx.font = '10px Arial';
        ctx.textAlign = 'center';
        ctx.fillText(node.text.slice(0, 20) + '...', node.position.x, node.position.y + radius + 15);
    });
}
exports.default = exports.useContextVisualization;
//# sourceMappingURL=useContextVisualization.js.map