/**
 * Performance Visualization Components
 *
 * Provides flamegraph and timeline visualization for performance profiling data.
 *
 * @module PerformanceVisualizer
 */

/**
 * Flamegraph Visualizer
 *
 * Renders interactive flamegraph from profiling samples
 */
export class FlamegraphVisualizer {
    constructor(container, options = {}) {
        this.container = typeof container === 'string'
            ? document.querySelector(container)
            : container;

        if (!this.container) {
            throw new Error('Container element not found');
        }

        this.options = {
            width: options.width ?? this.container.clientWidth,
            height: options.height ?? 400,
            barHeight: options.barHeight ?? 20,
            barPadding: options.barPadding ?? 2,
            colorScheme: options.colorScheme ?? 'category', // 'category', 'duration', 'monochrome'
            minWidth: options.minWidth ?? 0.5, // Minimum width in pixels to render
            ...options
        };

        this.data = null;
        this.svg = null;
        this.tooltip = null;
        this.selectedNode = null;

        this.init();
    }

    /**
     * Initialize SVG canvas
     * @private
     */
    init() {
        // Create SVG element
        this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        this.svg.setAttribute('width', this.options.width);
        this.svg.setAttribute('height', this.options.height);
        this.svg.style.fontFamily = 'monospace';
        this.svg.style.fontSize = '12px';

        // Create tooltip
        this.tooltip = document.createElement('div');
        this.tooltip.style.position = 'absolute';
        this.tooltip.style.padding = '8px';
        this.tooltip.style.background = 'rgba(0, 0, 0, 0.8)';
        this.tooltip.style.color = 'white';
        this.tooltip.style.borderRadius = '4px';
        this.tooltip.style.pointerEvents = 'none';
        this.tooltip.style.display = 'none';
        this.tooltip.style.zIndex = '1000';
        this.tooltip.style.fontSize = '12px';

        this.container.style.position = 'relative';
        this.container.appendChild(this.svg);
        this.container.appendChild(this.tooltip);
    }

    /**
     * Render flamegraph from data
     * @param {Object} data - Flamegraph data (hierarchical structure)
     */
    render(data) {
        this.data = data;
        this.clear();

        if (!data) {
            return;
        }

        // Calculate total value for width scaling
        const totalValue = this.calculateTotalValue(data);

        // Render nodes recursively
        this.renderNode(data, 0, 0, this.options.width, totalValue);
    }

    /**
     * Render a single flamegraph node
     * @private
     */
    renderNode(node, x, y, width, totalValue) {
        const { barHeight, barPadding, minWidth } = this.options;

        // Skip if too small to render
        if (width < minWidth) {
            return;
        }

        // Create rectangle
        const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
        rect.setAttribute('x', x);
        rect.setAttribute('y', y);
        rect.setAttribute('width', width);
        rect.setAttribute('height', barHeight - barPadding);
        rect.setAttribute('fill', this.getColor(node));
        rect.setAttribute('stroke', '#fff');
        rect.setAttribute('stroke-width', '0.5');
        rect.style.cursor = 'pointer';

        // Add interactivity
        rect.addEventListener('mouseenter', (e) => this.showTooltip(e, node));
        rect.addEventListener('mouseleave', () => this.hideTooltip());
        rect.addEventListener('click', () => this.selectNode(node));

        this.svg.appendChild(rect);

        // Create text label
        const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
        text.setAttribute('x', x + 4);
        text.setAttribute('y', y + barHeight / 2 + 4);
        text.setAttribute('fill', this.getTextColor(node));
        text.setAttribute('pointer-events', 'none');
        text.textContent = this.truncateText(node.name, width);

        this.svg.appendChild(text);

        // Render children
        if (node.children && node.children.length > 0) {
            let childX = x;
            const childY = y + barHeight;

            for (const child of node.children) {
                const childWidth = (child.value / node.value) * width;
                this.renderNode(child, childX, childY, childWidth, totalValue);
                childX += childWidth;
            }
        }
    }

    /**
     * Calculate total value in tree
     * @private
     */
    calculateTotalValue(node) {
        if (!node.children || node.children.length === 0) {
            return node.value;
        }

        return node.children.reduce((sum, child) => {
            return sum + this.calculateTotalValue(child);
        }, 0);
    }

    /**
     * Get color for node based on color scheme
     * @private
     */
    getColor(node) {
        if (this.options.colorScheme === 'monochrome') {
            return '#3b82f6';
        }

        if (this.options.colorScheme === 'duration') {
            // Color based on node value (duration)
            const intensity = Math.min(node.value / 100, 1);
            const r = Math.floor(255 * intensity);
            const g = Math.floor(255 * (1 - intensity));
            return `rgb(${r}, ${g}, 100)`;
        }

        // Category-based coloring (default)
        const colorMap = {
            'action': '#4CAF50',
            'render': '#2196F3',
            'state': '#FF9800',
            'network': '#9C27B0',
            'dom': '#F44336',
            'compute': '#00BCD4'
        };

        // Determine category from function name
        for (const [keyword, color] of Object.entries(colorMap)) {
            if (node.name.toLowerCase().includes(keyword)) {
                return color;
            }
        }

        // Hash-based color for consistency
        return this.hashColor(node.name);
    }

    /**
     * Get text color for contrast
     * @private
     */
    getTextColor(node) {
        return '#fff'; // White text for all colors (good contrast)
    }

    /**
     * Hash function for consistent colors
     * @private
     */
    hashColor(str) {
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            hash = str.charCodeAt(i) + ((hash << 5) - hash);
        }

        const hue = hash % 360;
        return `hsl(${hue}, 70%, 50%)`;
    }

    /**
     * Truncate text to fit width
     * @private
     */
    truncateText(text, width) {
        const charWidth = 7; // Approximate character width
        const maxChars = Math.floor(width / charWidth) - 2;

        if (text.length <= maxChars) {
            return text;
        }

        return text.substring(0, maxChars) + '…';
    }

    /**
     * Show tooltip with node information
     * @private
     */
    showTooltip(event, node) {
        const percentage = this.data
            ? ((node.value / this.calculateTotalValue(this.data)) * 100).toFixed(2)
            : '0';

        this.tooltip.innerHTML = `
            <strong>${node.name}</strong><br>
            Time: ${node.value.toFixed(2)}ms<br>
            ${percentage}% of total<br>
            ${node.children ? `${node.children.length} child(ren)` : 'Leaf node'}
        `;

        this.tooltip.style.display = 'block';
        this.tooltip.style.left = `${event.pageX + 10}px`;
        this.tooltip.style.top = `${event.pageY + 10}px`;
    }

    /**
     * Hide tooltip
     * @private
     */
    hideTooltip() {
        this.tooltip.style.display = 'none';
    }

    /**
     * Select node (zoom/highlight)
     * @private
     */
    selectNode(node) {
        this.selectedNode = node;
        console.log('[Flamegraph] Selected node:', node);

        // Could implement zoom functionality here
        // this.render(node);
    }

    /**
     * Clear flamegraph
     */
    clear() {
        while (this.svg.firstChild) {
            this.svg.removeChild(this.svg.firstChild);
        }
    }

    /**
     * Export flamegraph as SVG
     * @returns {string} SVG string
     */
    exportSVG() {
        return this.svg.outerHTML;
    }

    /**
     * Export flamegraph as PNG
     * @returns {Promise<Blob>} PNG image blob
     */
    async exportPNG() {
        const svgData = new XMLSerializer().serializeToString(this.svg);
        const canvas = document.createElement('canvas');
        canvas.width = this.options.width;
        canvas.height = this.options.height;

        const ctx = canvas.getContext('2d');
        const img = new Image();

        return new Promise((resolve, reject) => {
            img.onload = () => {
                ctx.drawImage(img, 0, 0);
                canvas.toBlob(resolve, 'image/png');
            };
            img.onerror = reject;
            img.src = 'data:image/svg+xml;base64,' + btoa(svgData);
        });
    }
}

/**
 * Timeline Visualizer
 *
 * Renders interactive timeline from profiling events
 */
export class TimelineVisualizer {
    constructor(container, options = {}) {
        this.container = typeof container === 'string'
            ? document.querySelector(container)
            : container;

        if (!this.container) {
            throw new Error('Container element not found');
        }

        this.options = {
            width: options.width ?? this.container.clientWidth,
            height: options.height ?? 200,
            trackHeight: options.trackHeight ?? 30,
            padding: options.padding ?? { top: 20, right: 20, bottom: 30, left: 60 },
            ...options
        };

        this.events = [];
        this.canvas = null;
        this.ctx = null;
        this.tooltip = null;
        this.scale = { x: 1, y: 1 };
        this.offset = { x: 0, y: 0 };

        this.init();
    }

    /**
     * Initialize canvas
     * @private
     */
    init() {
        // Create canvas
        this.canvas = document.createElement('canvas');
        this.canvas.width = this.options.width;
        this.canvas.height = this.options.height;
        this.canvas.style.cursor = 'crosshair';

        this.ctx = this.canvas.getContext('2d');

        // Create tooltip
        this.tooltip = document.createElement('div');
        this.tooltip.style.position = 'absolute';
        this.tooltip.style.padding = '8px';
        this.tooltip.style.background = 'rgba(0, 0, 0, 0.8)';
        this.tooltip.style.color = 'white';
        this.tooltip.style.borderRadius = '4px';
        this.tooltip.style.pointerEvents = 'none';
        this.tooltip.style.display = 'none';
        this.tooltip.style.zIndex = '1000';
        this.tooltip.style.fontSize = '12px';

        this.container.style.position = 'relative';
        this.container.appendChild(this.canvas);
        this.container.appendChild(this.tooltip);

        // Add mouse events
        this.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
        this.canvas.addEventListener('mouseleave', this.handleMouseLeave.bind(this));
    }

    /**
     * Render timeline from events
     * @param {Array} events - Timeline events
     */
    render(events) {
        this.events = events;
        this.clear();

        if (!events || events.length === 0) {
            return;
        }

        // Calculate time range
        const times = events.map(e => e.timestamp || e.relativeTime || 0);
        const minTime = Math.min(...times);
        const maxTime = Math.max(...times);
        const duration = maxTime - minTime;

        // Calculate scale
        const { padding } = this.options;
        const plotWidth = this.options.width - padding.left - padding.right;
        const plotHeight = this.options.height - padding.top - padding.bottom;

        this.scale.x = plotWidth / duration;

        // Draw axes
        this.drawAxes(minTime, maxTime, duration);

        // Draw events
        const tracks = this.organizeIntoTracks(events);
        this.scale.y = plotHeight / tracks.length;

        tracks.forEach((track, index) => {
            track.forEach(event => {
                this.drawEvent(event, index, minTime);
            });
        });
    }

    /**
     * Organize events into non-overlapping tracks
     * @private
     */
    organizeIntoTracks(events) {
        const tracks = [];
        const sortedEvents = [...events].sort((a, b) => {
            const aTime = a.timestamp || a.relativeTime || 0;
            const bTime = b.timestamp || b.relativeTime || 0;
            return aTime - bTime;
        });

        for (const event of sortedEvents) {
            const eventStart = event.timestamp || event.relativeTime || 0;
            const eventEnd = event.type === 'measure'
                ? eventStart + event.duration
                : eventStart + 0.1;

            // Find track where event fits
            let placed = false;
            for (const track of tracks) {
                const conflicts = track.some(e => {
                    const eStart = e.timestamp || e.relativeTime || 0;
                    const eEnd = e.type === 'measure'
                        ? eStart + e.duration
                        : eStart + 0.1;
                    return eventStart < eEnd && eventEnd > eStart;
                });

                if (!conflicts) {
                    track.push(event);
                    placed = true;
                    break;
                }
            }

            if (!placed) {
                tracks.push([event]);
            }
        }

        return tracks;
    }

    /**
     * Draw axes and grid
     * @private
     */
    drawAxes(minTime, maxTime, duration) {
        const { padding } = this.options;
        const { ctx } = this;

        ctx.strokeStyle = '#ccc';
        ctx.lineWidth = 1;

        // Y-axis
        ctx.beginPath();
        ctx.moveTo(padding.left, padding.top);
        ctx.lineTo(padding.left, this.options.height - padding.bottom);
        ctx.stroke();

        // X-axis
        ctx.beginPath();
        ctx.moveTo(padding.left, this.options.height - padding.bottom);
        ctx.lineTo(this.options.width - padding.right, this.options.height - padding.bottom);
        ctx.stroke();

        // Time labels
        ctx.fillStyle = '#666';
        ctx.font = '10px monospace';
        ctx.textAlign = 'center';

        const numLabels = 5;
        for (let i = 0; i <= numLabels; i++) {
            const time = minTime + (duration / numLabels) * i;
            const x = padding.left + (time - minTime) * this.scale.x;
            const y = this.options.height - padding.bottom + 15;

            ctx.fillText(`${time.toFixed(1)}ms`, x, y);
        }
    }

    /**
     * Draw a single event
     * @private
     */
    drawEvent(event, trackIndex, minTime) {
        const { padding } = this.options;
        const { ctx } = this;

        const startTime = event.timestamp || event.relativeTime || 0;
        const x = padding.left + (startTime - minTime) * this.scale.x;
        const y = padding.top + trackIndex * this.scale.y;
        const height = this.scale.y - 4;

        if (event.type === 'mark') {
            // Draw mark as vertical line
            ctx.strokeStyle = event.color || '#2196F3';
            ctx.lineWidth = 2;
            ctx.beginPath();
            ctx.moveTo(x, y);
            ctx.lineTo(x, y + height);
            ctx.stroke();

            // Draw marker
            ctx.fillStyle = event.color || '#2196F3';
            ctx.beginPath();
            ctx.arc(x, y + height / 2, 4, 0, Math.PI * 2);
            ctx.fill();
        } else if (event.type === 'measure') {
            // Draw measure as rectangle
            const width = Math.max(event.duration * this.scale.x, 2);

            ctx.fillStyle = event.color || '#4CAF50';
            ctx.fillRect(x, y, width, height);

            ctx.strokeStyle = '#fff';
            ctx.lineWidth = 0.5;
            ctx.strokeRect(x, y, width, height);

            // Draw label if wide enough
            if (width > 30) {
                ctx.fillStyle = '#fff';
                ctx.font = '10px monospace';
                ctx.textAlign = 'left';
                ctx.fillText(event.name, x + 4, y + height / 2 + 4);
            }
        }

        // Store event bounds for hover detection
        event._bounds = {
            x,
            y,
            width: event.type === 'measure' ? event.duration * this.scale.x : 8,
            height
        };
    }

    /**
     * Handle mouse move for tooltip
     * @private
     */
    handleMouseMove(e) {
        const rect = this.canvas.getBoundingClientRect();
        const mouseX = e.clientX - rect.left;
        const mouseY = e.clientY - rect.top;

        // Find hovered event
        const hoveredEvent = this.events.find(event => {
            if (!event._bounds) return false;
            const { x, y, width, height } = event._bounds;
            return mouseX >= x && mouseX <= x + width &&
                   mouseY >= y && mouseY <= y + height;
        });

        if (hoveredEvent) {
            this.showTooltip(e, hoveredEvent);
        } else {
            this.hideTooltip();
        }
    }

    /**
     * Handle mouse leave
     * @private
     */
    handleMouseLeave() {
        this.hideTooltip();
    }

    /**
     * Show tooltip with event information
     * @private
     */
    showTooltip(event, timelineEvent) {
        const time = timelineEvent.timestamp || timelineEvent.relativeTime || 0;

        this.tooltip.innerHTML = `
            <strong>${timelineEvent.name}</strong><br>
            Type: ${timelineEvent.type}<br>
            Time: ${time.toFixed(2)}ms<br>
            ${timelineEvent.duration ? `Duration: ${timelineEvent.duration.toFixed(2)}ms` : ''}
        `;

        this.tooltip.style.display = 'block';
        this.tooltip.style.left = `${event.pageX + 10}px`;
        this.tooltip.style.top = `${event.pageY + 10}px`;
    }

    /**
     * Hide tooltip
     * @private
     */
    hideTooltip() {
        this.tooltip.style.display = 'none';
    }

    /**
     * Clear timeline
     */
    clear() {
        this.ctx.clearRect(0, 0, this.options.width, this.options.height);
    }

    /**
     * Export timeline as PNG
     * @returns {Promise<Blob>} PNG image blob
     */
    async exportPNG() {
        return new Promise((resolve) => {
            this.canvas.toBlob(resolve, 'image/png');
        });
    }
}

export default { FlamegraphVisualizer, TimelineVisualizer };
