// Events Component - Visualizes websocket events as a graph
class EventComponent extends Component {
    constructor(container, viewModel, eventBus) {
        super(container, viewModel, eventBus);
        
        this.svg = null;
        this.simulation = null;
        this.zoom = null;
        this.width = 0;
        this.height = 0;
        this.isInitialized = false;
        
        // Center node data - will be initialized with proper coordinates later
        this.centerNode = { id: 'center', type: 'center', label: '', x: 0, y: 0 };
        
        // Track nodes for D3
        this.graphNodes = [];
        this.graphLinks = [];
        
        // Track recent events to trigger animations
        this.lastSeenEvents = new Set();
    }

    updateDimensions() {
        const container = this.findElement('#events-graph-container');
        if (!container) {
            logger.error('EventComponent: Container not found!');
            return;
        }
        
        const rect = container.getBoundingClientRect();
        this.width = rect.width || 1400;
        this.height = rect.height || 800;
        
        this.width = Math.max(this.width, 800);
        this.height = Math.max(this.height, 600);
    }

    mount() {
        if (this.isMounted) return;
        
        if (!this.isInitialized) {
            this.initialize();
        }
        
        this.isMounted = true;
        this.setupEventListeners();
        this.setupViewModelListeners();
        this.render();
    }

    initialize() {
        if (this.isInitialized) return;
        
        // Create SVG
        const container = this.findElement('#events-graph-container');
        if (!container) {
            logger.error('EventComponent: Container not found');
            return;
        }
        
        this.updateDimensions();
        
        // Clear container
        container.innerHTML = '';
        
        // Create a wrapper div for relative positioning  
        const wrapper = document.createElement('div');
        wrapper.style.cssText = 'position: relative; width: 100%; height: 100%; overflow: hidden;';
        container.appendChild(wrapper);
        
        // Store wrapper for button placement
        this.wrapper = wrapper;
        
        // Add rearrange button
        this.createRearrangeButton(wrapper);
        
        // Create SVG with D3 - match topology view style
        this.svg = d3.select(wrapper)
            .append('svg')
            .attr('width', '100%')
            .attr('height', '100%')
            .attr('viewBox', `0 0 ${this.width} ${this.height}`)
            .style('border', '1px solid rgba(255, 255, 255, 0.1)')
            .style('border-radius', '12px');
        
        // Create zoom behavior
        this.zoom = d3.zoom()
            .scaleExtent([0.5, 5])
            .on('zoom', (event) => {
                this.svg.select('g').attr('transform', event.transform);
            });
        
        this.svg.call(this.zoom);
        
        // Create main group for graph elements
        const mainGroup = this.svg.append('g');
        
        // Apply initial zoom to show the graph more zoomed in
        mainGroup.attr('transform', 'scale(1.4) translate(-200, -150)');
        
        // Initialize simulation
        this.simulation = d3.forceSimulation()
            .force('link', d3.forceLink().id(d => d.id).distance(100))
            .force('charge', d3.forceManyBody().strength(-300))
            .force('center', d3.forceCenter(this.width / 2, this.height / 2))
            .force('collision', d3.forceCollide().radius(35))
            .alphaDecay(0.0228) // Slower decay to allow simulation to run longer
            .velocityDecay(0.4); // Higher velocity decay for smoother, less jumpy movement
        
        this.isInitialized = true;
    }

    setupEventListeners() {
        // Handle window resize
        this.addEventListener(window, 'resize', () => {
            if (this.resizeTimeout) clearTimeout(this.resizeTimeout);
            this.resizeTimeout = setTimeout(() => {
                this.handleResize();
            }, 250);
        });
    }

    handleResize() {
        const container = this.findElement('#events-graph-container');
        if (container && this.svg) {
            this.updateDimensions();
            this.svg.attr('viewBox', `0 0 ${this.width} ${this.height}`);
            if (this.simulation) {
                this.simulation.force('center', d3.forceCenter(this.width / 2, this.height / 2));
                this.simulation.alpha(0.3).restart();
            }
        }
    }

    setupViewModelListeners() {
        // Listen for events changes
        this.subscribeToProperty('events', (newValue, previousValue) => {
            // Check for new or updated events to animate
            if (newValue && newValue.size > 0 && previousValue) {
                for (const [topic, data] of newValue) {
                    const prevData = previousValue.get(topic);
                    if (!prevData || data.count > prevData.count) {
                        // New event or count increased - trigger animation
                        this.animateEventMessage(topic, data);
                    }
                }
            }
            
            // Remove empty state if it exists
            if (this.svg) {
                const g = this.svg.select('g');
                if (!g.empty()) {
                    g.selectAll('.empty-state').remove();
                }
            }
            this.updateGraph();
        });
        
        // Listen for lastUpdateTime changes
        this.subscribeToProperty('lastUpdateTime', () => {
            this.updateGraph();
        });
    }

    updateGraph() {
        if (!this.isInitialized || !this.svg) {
            return;
        }
        
        const events = this.viewModel.get('events');
        
        // Get existing nodes from simulation to preserve positions
        const existingNodesMap = new Map();
        if (this.simulation && this.simulation.nodes) {
            this.simulation.nodes().forEach(node => {
                existingNodesMap.set(node.id, node);
            });
        }
        
        // Create nodes map with center node (always present)
        const nodesMap = new Map();
        const centerX = this.width / 2;
        const centerY = this.height / 2;
        
        // Check if center node already exists in simulation (has been dragged)
        const existingCenter = existingNodesMap.get('center');
        let centerNodeWithPos;
        
        if (existingCenter && existingCenter.fx !== undefined && existingCenter.fy !== undefined) {
            // Preserve the dragged position
            centerNodeWithPos = {
                ...this.centerNode,
                x: existingCenter.fx !== null ? existingCenter.fx : existingCenter.x,
                y: existingCenter.fy !== null ? existingCenter.fy : existingCenter.y,
                fx: existingCenter.fx,
                fy: existingCenter.fy
            };
        } else {
            // New center node at center, or if it was never dragged
            centerNodeWithPos = {
                ...this.centerNode,
                x: centerX,
                y: centerY,
                fx: null,
                fy: null
            };
        }
        nodesMap.set('center', centerNodeWithPos);
        
        // Only process events if they exist
        if (events && events.size > 0) {
            // First pass: create all unique topic part nodes with proper hierarchy
            for (const [topic, data] of events) {
                data.parts.forEach((part, index) => {
                    if (!nodesMap.has(part)) {
                        const existingNode = existingNodesMap.get(part);
                        
                        if (existingNode && existingNode.x && existingNode.y) {
                            // Preserve existing position
                            nodesMap.set(part, {
                                id: part,
                                type: 'topic',
                                label: part,
                                count: 0,
                                x: existingNode.x,
                                y: existingNode.y,
                                fx: existingNode.fx,
                                fy: existingNode.fy
                            });
                        } else {
                            // Determine where to place this new node based on hierarchy
                            let parentPart;
                            let refPos;
                            
                            if (index === 0) {
                                // First part connects to center
                                parentPart = 'center';
                            } else {
                                // Subsequent parts connect to previous part in this topic
                                parentPart = data.parts[index - 1];
                            }
                            
                            // Get parent position (should exist since we process in order)
                            refPos = nodesMap.get(parentPart);
                            
                            // Fallback to center if parent doesn't exist yet
                            if (!refPos) {
                                refPos = nodesMap.get('center');
                            }
                            
                            // Initialize new node very close to its parent
                            const angle = Math.random() * Math.PI * 2;
                            const distance = 60 + Math.random() * 30;
                            nodesMap.set(part, {
                                id: part,
                                type: 'topic',
                                label: part,
                                count: 0,
                                x: refPos.x + Math.cos(angle) * distance,
                                y: refPos.y + Math.sin(angle) * distance,
                                fx: null,
                                fy: null
                            });
                        }
                    }
                });
            }
            
            // Second pass: count occurrences of each part
            for (const [topic, data] of events) {
                data.parts.forEach(part => {
                    const node = nodesMap.get(part);
                    if (node) {
                        node.count += data.count;
                    }
                });
            }
        }
        
        this.graphNodes = Array.from(nodesMap.values());
        
        // Build links as chains for each topic
        // For "cluster/update", create: center -> cluster -> update
        this.graphLinks = [];
        const linkSet = new Set(); // Track links to avoid duplicates
        
        if (events && events.size > 0) {
            for (const [topic, data] of events) {
                const parts = data.parts;
                
                if (parts.length === 0) continue;
                
                // Connect center to first part
                const firstPart = parts[0];
                const centerToFirst = `center-${firstPart}`;
                if (!linkSet.has(centerToFirst)) {
                    this.graphLinks.push({
                        source: 'center',
                        target: firstPart,
                        type: 'center-link',
                        count: data.count,
                        topic: topic
                    });
                    linkSet.add(centerToFirst);
                } else {
                    // Update count for existing link
                    const existingLink = this.graphLinks.find(l => 
                        `${l.source}-${l.target}` === centerToFirst
                    );
                    if (existingLink) {
                        existingLink.count += data.count;
                    }
                }
                
                // Connect each part to the next (creating a chain)
                for (let i = 0; i < parts.length - 1; i++) {
                    const source = parts[i];
                    const target = parts[i + 1];
                    const linkKey = `${source}-${target}`;
                    
                    if (!linkSet.has(linkKey)) {
                        this.graphLinks.push({
                            source: source,
                            target: target,
                            type: 'topic-link',
                            topic: topic,
                            count: data.count
                        });
                        linkSet.add(linkKey);
                    } else {
                        // Update count for existing link
                        const existingLink = this.graphLinks.find(l => 
                            `${l.source}-${l.target}` === linkKey
                        );
                        if (existingLink) {
                            existingLink.count += data.count;
                        }
                    }
                }
            }
        }
        
        // Update D3 simulation
        this.updateSimulation();
    }

    updateSimulation() {
        const g = this.svg.select('g');
        if (!g || g.empty()) {
            return;
        }
        
        // Update links
        const links = g.selectAll('.link')
            .data(this.graphLinks, d => `${d.source}-${d.target}`);
        
        // Remove old links
        links.exit()
            .transition()
            .duration(300)
            .style('opacity', 0)
            .remove();
        
        // Add new links
        const linksEnter = links.enter()
            .append('line')
            .attr('class', d => `link ${d.type}`)
            .attr('stroke-opacity', 0);

        // Merge and create linksUpdate
        const linksUpdate = linksEnter.merge(links);
        
        // Add hover interactions to all links (existing ones will be updated)
        linksUpdate
            .on('mouseover', function(event, d) {
                d3.select(event.currentTarget)
                    .attr('stroke-width', d => d.type === 'topic-link' ? Math.max(3, Math.min(5, Math.sqrt(d.count) + 2)) : 3.5)
                    .attr('stroke-opacity', 0.9);
            })
            .on('mouseout', function(event, d) {
                d3.select(event.currentTarget)
                    .attr('stroke-width', d => d.type === 'topic-link' ? Math.max(1.5, Math.min(4, Math.sqrt(d.count) + 1)) : 2.5)
                    .attr('stroke-opacity', 0.7);
            });
        
        // Update link attributes directly (no transitions to avoid event conflicts)
        linksUpdate
            .attr('stroke', d => {
                if (d.type === 'center-link') return '#3498db';
                return '#10b981';
            })
            .attr('stroke-opacity', 0.7)
            .attr('stroke-width', d => {
                if (d.type === 'topic-link') return Math.max(1.5, Math.min(4, Math.sqrt(d.count) + 1));
                return 2.5;
            });
        
        // Update nodes
        const nodes = g.selectAll('.node')
            .data(this.graphNodes, d => d.id);
        
        // Remove old nodes
        nodes.exit()
            .transition()
            .duration(300)
            .style('opacity', 0)
            .remove();
        
        // Add new nodes
        const nodesEnter = nodes.enter()
            .append('g')
            .attr('class', d => `node ${d.type}`)
            .style('opacity', 0)
            .call(this.drag());
        
        // Add circle
        nodesEnter.append('circle')
            .attr('r', d => d.type === 'center' ? 18 : 14)
            .attr('fill', d => {
                if (d.type === 'center') return '#3498db';
                return '#10b981';
            })
            .attr('stroke', '#fff')
            .attr('stroke-width', 2.5);
        
        // Add text labels
        nodesEnter.append('text')
            .attr('dy', d => d.type === 'center' ? -28 : -20)
            .attr('text-anchor', 'middle')
            .attr('font-size', d => d.type === 'center' ? '16px' : '13px')
            .attr('fill', 'var(--text-primary)')
            .attr('font-weight', '600')
            .text(d => d.label);
        
        // Merge and update all nodes
        const nodesUpdate = nodesEnter.merge(nodes);
        
        // Update node visibility
        nodesUpdate.style('opacity', 1);
        
        // Update circles
        nodesUpdate.select('circle')
            .attr('r', d => d.type === 'center' ? 18 : 14)
            .attr('fill', d => {
                if (d.type === 'center') return '#3498db';
                return '#10b981';
            });
        
        // Update text labels
        nodesUpdate.select('text')
            .text(d => d.label);
        
        // Remove any existing count badges
        nodesUpdate.selectAll('.count-badge').remove();
        
        // Update simulation
        this.simulation.nodes(this.graphNodes);
        
        // Only add links force if there are links
        if (this.graphLinks.length > 0) {
            const linkForce = this.simulation.force('link');
            if (linkForce) {
                linkForce.links(this.graphLinks);
            } else {
                // Create the link force if it doesn't exist
                this.simulation.force('link', d3.forceLink().id(d => d.id).distance(100));
                this.simulation.force('link').links(this.graphLinks);
            }
        } else {
            // Remove link force if no links
            this.simulation.force('link', null);
        }
        
        // Update positions on tick
        this.simulation.on('tick', () => {
            if (linksUpdate && this.graphLinks.length > 0) {
                linksUpdate.each(function(d) {
                    const link = d3.select(this);
                    
                    // Calculate direction from source to target
                    const dx = d.target.x - d.source.x;
                    const dy = d.target.y - d.source.y;
                    const length = Math.sqrt(dx * dx + dy * dy);
                    
                    if (length > 0) {
                        // Normalize direction vector
                        const nx = dx / length;
                        const ny = dy / length;
                        
                        // Edge radius based on target node type
                        const targetRadius = d.target.type === 'center' ? 18 : 14;
                        const sourceRadius = d.source.type === 'center' ? 18 : 14;
                        
                        // Start from edge of source node
                        const x1 = d.source.x + nx * sourceRadius;
                        const y1 = d.source.y + ny * sourceRadius;
                        
                        // End at edge of target node
                        const x2 = d.target.x - nx * targetRadius;
                        const y2 = d.target.y - ny * targetRadius;
                        
                        link.attr('x1', x1)
                            .attr('y1', y1)
                            .attr('x2', x2)
                            .attr('y2', y2);
                    }
                });
            }
            
            nodesUpdate
                .attr('transform', d => `translate(${d.x},${d.y})`);
        });
        
        // Use lower alpha to avoid jumping - just tweak positions gently
        this.simulation.alpha(0.3).restart();
    }

    drag() {
        const simulation = this.simulation;
        const component = this;
        
        let initialCenterX = 0;
        let initialCenterY = 0;
        
        function dragstarted(event, d) {
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
            
            // If dragging the center node, store initial position and get all nodes
            if (d.type === 'center') {
                initialCenterX = d.x;
                initialCenterY = d.y;
            }
        }
        
        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
            
            // If dragging the center node, move all other nodes by the same offset
            if (d.type === 'center') {
                const dx = event.x - initialCenterX;
                const dy = event.y - initialCenterY;
                
                // Apply the same offset to all other nodes
                simulation.nodes().forEach(node => {
                    if (node.id !== 'center') {
                        node.fx = node.x + dx;
                        node.fy = node.y + dy;
                    }
                });
                
                // Update initial position for next iteration
                initialCenterX = event.x;
                initialCenterY = event.y;
            }
        }
        
        function dragended(event, d) {
            if (!event.active) simulation.alphaTarget(0);
            // Keep positions fixed after dragging
            d.fx = d.x;
            d.fy = d.y;
            
            // If dragging the center node, preserve all node positions
            if (d.type === 'center') {
                simulation.nodes().forEach(node => {
                    node.fx = node.x;
                    node.fy = node.y;
                });
            }
        }
        
        return d3.drag()
            .on('start', dragstarted)
            .on('drag', dragged)
            .on('end', dragended);
    }

    render() {
        if (!this.isInitialized) {
            this.initialize();
        }
        
        const events = this.viewModel.get('events');
        
        // If no events exist yet, show an empty state message
        if (!events || events.size === 0) {
            this.showEmptyState();
        } else {
            // Remove empty state if it exists
            const g = this.svg.select('g');
            if (!g.empty()) {
                g.selectAll('.empty-state').remove();
            }
        }
        
        // Always call updateGraph to show at least the center node
        this.updateGraph();
    }
    
    showEmptyState() {
        if (!this.isInitialized || !this.svg) return;
        
        const g = this.svg.select('g');
        if (g.empty()) return;
        
        // Remove any existing message
        g.selectAll('.empty-state').remove();
        
        // Account for the initial zoom transform (scale(1.4) translate(-200, -150))
        // We need to place the text in the center of the transformed coordinate space
        const transformX = -200;
        const transformY = -150;
        const scale = 1.4;
        
        // Calculate centered position in transformed space
        const x = ((this.width / 2) - transformX) / scale;
        const y = ((this.height / 2) - transformY) / scale;
        
        const emptyMsg = g.append('text')
            .attr('class', 'empty-state')
            .attr('x', x)
            .attr('y', y)
            .attr('text-anchor', 'middle')
            .attr('font-size', '18px')
            .attr('fill', 'var(--text-secondary)')
            .attr('font-weight', '500')
            .text('Waiting for websocket events...');
    }

    onPause() {
        if (this.simulation) {
            this.simulation.stop();
        }
    }

    onResume() {
        if (this.simulation) {
            this.simulation.alpha(0.3).restart();
        }
    }

    shouldRenderOnResume() {
        return false;
    }
    
    createRearrangeButton(container) {
        const button = document.createElement('button');
        button.className = 'topology-rearrange-btn';
        button.innerHTML = `
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <circle cx="12" cy="5" r="1.5"/>
                <circle cx="12" cy="12" r="1.5"/>
                <circle cx="12" cy="19" r="1.5"/>
                <circle cx="5" cy="12" r="1.5"/>
                <circle cx="19" cy="12" r="1.5"/>
                <path d="M12 7v3m0 2v3m-5-3h3m2 0h3"/>
            </svg>
            <span>Rearrange</span>
        `;
        button.title = 'Rearrange nodes into a clean layout';
        button.style.cssText = `
            position: absolute;
            left: 16px;
            top: 16px;
            z-index: 1000;
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 8px 14px;
            background: var(--card-background, rgba(30, 30, 30, 0.95));
            border: 1px solid var(--border-color, rgba(255, 255, 255, 0.1));
            border-radius: 8px;
            color: var(--text-primary, #ecf0f1);
            font-size: 13px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s ease;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
        `;

        // Add hover effect
        button.addEventListener('mouseenter', () => {
            button.style.background = 'var(--card-hover, rgba(40, 40, 40, 0.95))';
            button.style.borderColor = 'var(--primary-color, #3498db)';
            button.style.transform = 'translateY(-1px)';
            button.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)';
        });

        button.addEventListener('mouseleave', () => {
            button.style.background = 'var(--card-background, rgba(30, 30, 30, 0.95))';
            button.style.borderColor = 'var(--border-color, rgba(255, 255, 255, 0.1))';
            button.style.transform = 'translateY(0)';
            button.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.2)';
        });

        // Add click handler
        button.addEventListener('click', () => {
            this.rearrangeNodes();
        });

        container.appendChild(button);
    }
    
    rearrangeNodes() {
        if (!this.simulation) {
            return;
        }
        
        // Clear fixed positions for all nodes except center
        this.graphNodes.forEach(node => {
            if (node.type !== 'center') {
                node.fx = null;
                node.fy = null;
            }
        });
        
        // Restart simulation with higher energy
        this.simulation.alpha(1).restart();
    }
    
    animateEventMessage(topic, data) {
        const g = this.svg.select('g');
        if (g.empty()) return;
        
        const parts = data.parts || topic.split('/').filter(p => p);
        if (parts.length === 0) return;
        
        // Wait for the next tick to ensure nodes exist
        setTimeout(() => {
            // Build the chain: center -> first -> second -> ... -> last
            const chain = ['center', ...parts];
            
            // Animate along each segment of the chain
            let delay = 0;
            for (let i = 0; i < chain.length - 1; i++) {
                const sourceId = chain[i];
                const targetId = chain[i + 1];
                
                const sourceNode = this.graphNodes.find(n => n.id === sourceId);
                const targetNode = this.graphNodes.find(n => n.id === targetId);
                
                if (!sourceNode || !targetNode) continue;
                
                setTimeout(() => {
                    this.animateDotAlongLink(sourceNode, targetNode, g);
                }, delay);
                
                // Add delay between segments (staggered animation)
                delay += 400; // Duration (300ms) + small gap (100ms)
            }
        }, 100);
    }
    
    animateDotAlongLink(sourceNode, targetNode, g) {
        if (!sourceNode || !targetNode || !g) return;
        
        // Calculate positions
        const dx = targetNode.x - sourceNode.x;
        const dy = targetNode.y - sourceNode.y;
        const length = Math.sqrt(dx * dx + dy * dy);
        
        if (length === 0) return;
        
        // Normalize direction
        const nx = dx / length;
        const ny = dy / length;
        
        // Calculate node radii
        const sourceRadius = sourceNode.type === 'center' ? 18 : 14;
        const targetRadius = targetNode.type === 'center' ? 18 : 14;
        
        // Start from edge of source node
        const startX = sourceNode.x + nx * sourceRadius;
        const startY = sourceNode.y + ny * sourceRadius;
        
        // End at edge of target node
        const endX = targetNode.x - nx * targetRadius;
        const endY = targetNode.y - ny * targetRadius;
        
        // Create animation dot
        const animationGroup = g.append('g').attr('class', 'animation-group');
        
        const dot = animationGroup.append('circle')
            .attr('r', 4)
            .attr('fill', '#FFD700')
            .attr('stroke', '#fff')
            .attr('stroke-width', 2)
            .attr('cx', startX)
            .attr('cy', startY);
        
        // Animate the dot along the path
        dot.transition()
            .duration(300)
            .ease(d3.easeLinear)
            .attr('cx', endX)
            .attr('cy', endY)
            .on('end', function() {
                // Fade out after reaching destination
                dot.transition()
                    .duration(100)
                    .style('opacity', 0)
                    .remove();
                animationGroup.remove();
            });
    }
}

window.EventComponent = EventComponent;
