/* ═══════════════════════════════════════════════════════════════════════════════
   MGraph HTML Graph - Render UI - vis.js Renderer Component
   v0.2.0 - Consolidated from v0.1.x

   Uses vis.js Network for interactive graph visualization.
   Parses DOT output and converts to vis.js format client-side.

   CDN: https://unpkg.com/vis-network@9.1.6/standalone/umd/vis-network.min.js
   ═══════════════════════════════════════════════════════════════════════════════ */

class VisRenderer extends HTMLElement {
    constructor() {
        super();
        this.network = null;
        this.targetCanvas = null;
        this.isLoaded = false;
    }

    connectedCallback() {
        this.loadVisJs();
    }

    /**
     * Load vis.js from CDN
     */
    async loadVisJs() {
        if (window.vis) {
            this.isLoaded = true;
            //console.log('vis.js already loaded');
            return;
        }

        try {
            await this.loadScript('https://unpkg.com/vis-network@9.1.6/standalone/umd/vis-network.min.js');
            this.isLoaded = true;
            //console.log('vis.js loaded successfully');
        } catch (error) {
            console.error('Failed to load vis.js:', error);
        }
    }

    loadScript(src) {
        return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = src;
            script.onload = resolve;
            script.onerror = reject;
            document.head.appendChild(script);
        });
    }

    /**
     * Set the target canvas element
     */
    setTargetCanvas(canvasElement) {
        this.targetCanvas = canvasElement;
    }

    /**
     * Render DOT code using vis.js
     * Parses DOT and converts to vis.js Network format
     */
    async renderDot(dotCode) {
        if (!this.isLoaded) {
            await this.loadVisJs();
        }

        if (!this.targetCanvas) {
            throw new Error('No target canvas set for vis.js renderer');
        }

        // Parse DOT to extract nodes and edges
        const { nodes, edges } = this.parseDot(dotCode);

        // Clear previous network
        if (this.network) {
            this.network.destroy();
            this.network = null;
        }

        // Create container
        this.targetCanvas.innerHTML = '<div id="vis-container" style="width: 100%; height: 100%;"></div>';
        const container = this.targetCanvas.querySelector('#vis-container');

        // Create vis.js datasets
        const nodesDataset = new vis.DataSet(nodes);
        const edgesDataset = new vis.DataSet(edges);

        // Network options
        const options = {
            nodes: {
                shape: 'box',
                margin: 10,
                font: {
                    size: 12,
                    face: 'system-ui, sans-serif'
                },
                borderWidth: 2,
                shadow: true
            },
            edges: {
                arrows: {
                    to: { enabled: true, scaleFactor: 0.5 }
                },
                smooth: {
                    type: 'cubicBezier',
                    forceDirection: 'vertical',
                    roundness: 0.4
                },
                color: {
                    color: '#888',
                    highlight: '#6366f1'
                }
            },
            layout: {
                hierarchical: {
                    enabled: true,
                    direction: 'UD',  // Up-Down
                    sortMethod: 'directed',
                    levelSeparation: 80,
                    nodeSpacing: 120,
                    treeSpacing: 200
                }
            },
            physics: {
                enabled: false  // Disable physics for hierarchical layout
            },
            interaction: {
                dragNodes: true,
                dragView: true,
                zoomView: true,
                hover: true,
                tooltipDelay: 200
            }
        };

        // Create network
        this.network = new vis.Network(container, { nodes: nodesDataset, edges: edgesDataset }, options);

        // Fit to view after stabilization
        this.network.once('stabilized', () => {
            this.network.fit({ animation: { duration: 300, easingFunction: 'easeInOutQuad' } });
        });

        return { nodes: nodes.length, edges: edges.length };
    }

    /**
     * Parse DOT code to extract nodes and edges
     * This is a simplified parser for the DOT format generated by our backend
     */
    parseDot(dotCode) {
        const nodes = [];
        const edges = [];
        const nodeMap = new Map();

        // Extract node definitions
        // Pattern: "nodeId" [shape="box", label="<text>", fillcolor="#FFFFFF", ...]
        const nodeRegex = /"([^"]+)"\s*\[([^\]]+)\]/g;
        let match;

        while ((match = nodeRegex.exec(dotCode)) !== null) {
            const nodeId = match[1];
            const attrs = match[2];

            // Skip if this looks like an edge definition
            if (attrs.includes('->')) continue;

            // Parse attributes
            const label = this.extractAttr(attrs, 'label') || nodeId;
            const fillcolor = this.extractAttr(attrs, 'fillcolor') || '#f5f5f5';
            const fontcolor = this.extractAttr(attrs, 'fontcolor') || '#333333';
            const shape = this.extractAttr(attrs, 'shape') || 'box';

            // Map DOT shapes to vis.js shapes
            const visShape = this.mapShape(shape);

            // Determine node type from color/label
            const nodeType = this.detectNodeType(label, fillcolor);

            nodes.push({
                id: nodeId,
                label: this.cleanLabel(label),
                color: {
                    background: fillcolor,
                    border: this.darkenColor(fillcolor),
                    highlight: {
                        background: this.lightenColor(fillcolor),
                        border: '#6366f1'
                    }
                },
                font: {
                    color: fontcolor
                },
                shape: visShape,
                title: this.cleanLabel(label),  // Tooltip
                nodeType: nodeType
            });

            nodeMap.set(nodeId, true);
        }

        // Extract edges
        // Pattern: "nodeId1" -> "nodeId2" [style=dashed, color="#888888"]
        const edgeRegex = /"([^"]+)"\s*->\s*"([^"]+)"(?:\s*\[([^\]]*)\])?/g;

        while ((match = edgeRegex.exec(dotCode)) !== null) {
            const from = match[1];
            const to = match[2];
            const attrs = match[3] || '';

            // Only add edge if both nodes exist
            if (nodeMap.has(from) && nodeMap.has(to)) {
                const style = this.extractAttr(attrs, 'style');
                const color = this.extractAttr(attrs, 'color') || '#888888';

                edges.push({
                    from: from,
                    to: to,
                    dashes: style === 'dashed',
                    color: { color: color }
                });
            }
        }

        return { nodes, edges };
    }

    /**
     * Extract attribute value from DOT attribute string
     */
    extractAttr(attrString, attrName) {
        // Handle both quoted and unquoted values
        const patterns = [
            new RegExp(`${attrName}="([^"]*)"`, 'i'),
            new RegExp(`${attrName}=([^,\\]\\s]+)`, 'i')
        ];

        for (const pattern of patterns) {
            const match = attrString.match(pattern);
            if (match) {
                return match[1].replace(/\\"/g, '"').replace(/\\n/g, '\n');
            }
        }
        return null;
    }

    /**
     * Map DOT shapes to vis.js shapes
     */
    mapShape(dotShape) {
        const shapeMap = {
            'box': 'box',
            'ellipse': 'ellipse',
            'circle': 'circle',
            'diamond': 'diamond',
            'triangle': 'triangle',
            'star': 'star',
            'hexagon': 'hexagon',
            'note': 'box',
            'tab': 'box',
            'folder': 'box',
            'component': 'box'
        };
        return shapeMap[dotShape] || 'box';
    }

    /**
     * Detect node type from label and color
     */
    detectNodeType(label, color) {
        if (label.startsWith('<') && label.endsWith('>')) return 'tag';
        if (label.includes('=')) return 'attribute';
        if (color === '#FFFACD' || color === '#fffacd') return 'text';
        return 'element';
    }

    /**
     * Clean label text
     */
    cleanLabel(label) {
        return label
            .replace(/\\n/g, '\n')
            .replace(/\\"/g, '"')
            .replace(/^"(.*)"$/, '$1');
    }

    /**
     * Darken a hex color
     */
    darkenColor(hex) {
        const color = hex.replace('#', '');
        const r = Math.max(0, parseInt(color.substr(0, 2), 16) - 30);
        const g = Math.max(0, parseInt(color.substr(2, 2), 16) - 30);
        const b = Math.max(0, parseInt(color.substr(4, 2), 16) - 30);
        return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
    }

    /**
     * Lighten a hex color
     */
    lightenColor(hex) {
        const color = hex.replace('#', '');
        const r = Math.min(255, parseInt(color.substr(0, 2), 16) + 30);
        const g = Math.min(255, parseInt(color.substr(2, 2), 16) + 30);
        const b = Math.min(255, parseInt(color.substr(4, 2), 16) + 30);
        return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
    }

    /**
     * Fit graph to view
     */
    fitToView() {
        if (this.network) {
            this.network.fit({ animation: { duration: 300, easingFunction: 'easeInOutQuad' } });
        }
    }

    /**
     * Export as PNG
     */
    async exportPng() {
        if (!this.network) return null;

        const canvas = this.targetCanvas.querySelector('canvas');
        if (canvas) {
            return canvas.toDataURL('image/png');
        }
        return null;
    }
}

customElements.define('vis-renderer', VisRenderer);
