<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Enhanced Directed Graph Visualization</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        body,
        html {
            height: 100%;
            width: 100%;
            margin: 0;
            padding: 0;
            font-family: Arial, sans-serif;
        }

        #mynetwork {
            width: 100%;
            height: 100%;
        }

        .node circle {
            fill: #69b3a2;
            stroke: #333;
            stroke-width: 1.5px;
        }

        .node text {
            font: 12px sans-serif;
            pointer-events: none;
            fill: #555;
        }

        .link {
            fill: none;
            stroke: #999;
            stroke-opacity: 0.6;
            stroke-width: 1.5px;
            marker-end: url(#arrowhead);
        }

        .link.directed {
            stroke: #ff5722;
        }

        .link.highlighted {
            stroke-width: 3px;
            stroke: #ff5722;
        }

        .tooltip {
            position: absolute;
            text-align: center;
            width: auto;
            height: auto;
            padding: 5px;
            font: 12px sans-serif;
            background: lightsteelblue;
            border: 0px;
            border-radius: 8px;
            pointer-events: none;
            opacity: 0;
        }

        #controls {
            position: absolute;
            top: 10px;
            left: 10px;
            z-index: 1;
        }

        #legend {
            position: absolute;
            top: 10px;
            right: 10px;
            background-color: rgba(255, 255, 255, 0.7);
            padding: 10px;
            border-radius: 3px;
            font-size: 12px;
        }

        #legend .legend-item {
            display: flex;
            align-items: center;
        }

        #legend .legend-item span {
            margin-left: 5px;
        }

        #legend .legend-color {
            width: 12px;
            height: 12px;
            border-radius: 50%;
        }

        #search {
            position: absolute;
            top: 50px;
            left: 10px;
            z-index: 1;
        }
    </style>
</head>

<body>
    <div id="controls">
        <button onclick="fitNetwork()">Fit View</button>
        <button onclick="expandAll()">Expand All</button>
        <button onclick="collapseAll()">Collapse All</button>
        <input type="range" id="charge" min="-1000" max="0" value="-300" step="10">
        <label for="charge">Charge Strength</label>
    </div>
    <div id="search">
        <input type="text" id="searchInput" placeholder="Search nodes...">
        <button onclick="searchNode()">Search</button>
    </div>
    <div id="legend">
        <div class="legend-item">
            <div class="legend-color" style="background-color: #69b3a2;"></div>
            <span>Node</span>
        </div>
        <div class="legend-item">
            <div class="legend-color" style="background-color: #ff5722;"></div>
            <span>Directed Link</span>
        </div>
    </div>
    <div id="mynetwork"></div>
    <div class="tooltip"></div>
    <script>
        // Define dimensions
        const width = window.innerWidth;
        const height = window.innerHeight;
    
        // Default graph data
        const defaultGraphData = {
            "nodes": [
                {"id": "1", "label": "Root"},
                {"id": "2", "label": "Child 1"},
                {"id": "3", "label": "Child 2"}
            ],
            "edges": [
                {"from": "1", "to": "2"},
                {"from": "1", "to": "3"}
            ]
        };
    
        // Function to initialize the graph
        function initializeGraph(graphData) {
            // Create the SVG container
            const svg = d3.select("#mynetwork")
                .append("svg")
                .attr("width", width)
                .attr("height", height)
                .call(d3.zoom().on("zoom", function (event) {
                    svg.attr("transform", event.transform);
                }))
                .append("g");
    
            // Create a tooltip
            const tooltip = d3.select("body").append("div")
                .attr("class", "tooltip")
                .style("opacity", 0);
    
            // Define the arrowhead marker
            svg.append("defs").append("marker")
                .attr("id", "arrowhead")
                .attr("viewBox", "0 -5 10 10")
                .attr("refX", 20)
                .attr("refY", 0)
                .attr("markerWidth", 8)
                .attr("markerHeight", 8)
                .attr("orient", "auto")
                .append("path")
                .attr("d", "M 0,-5 L 10,0 L 0,5")
                .attr("fill", "#999");
    
            // Rename edges from "from" and "to" to "source" and "target"
            graphData.edges.forEach(edge => {
                edge.source = edge.from;
                edge.target = edge.to;
                delete edge.from;
                delete edge.to;
            });
    
            // Collect all node IDs
            const nodeIds = new Set(graphData.nodes.map(node => node.id));
    
            // Create a mapping for default nodes
            const defaultNode = {
                id: "default",
                label: "Unknown Node",
                x: width / 2,
                y: height / 2
            };
    
            // Ensure all edges have valid nodes
            graphData.edges.forEach(edge => {
                if (!nodeIds.has(edge.source)) {
                    graphData.nodes.push({ ...defaultNode, id: edge.source });
                    nodeIds.add(edge.source);
                }
                if (!nodeIds.has(edge.target)) {
                    graphData.nodes.push({ ...defaultNode, id: edge.target });
                    nodeIds.add(edge.target);
                }
            });
    
            // Create the simulation
            const simulation = d3.forceSimulation(graphData.nodes)
                .force("link", d3.forceLink(graphData.edges).id(d => d.id).distance(100))
                .force("charge", d3.forceManyBody().strength(-300))
                .force("center", d3.forceCenter(width / 2, height / 2))
                .force("collision", d3.forceCollide().radius(50));
    
            // Create links
            const link = svg.append("g")
                .attr("class", "links")
                .selectAll("line")
                .data(graphData.edges)
                .enter().append("line")
                .attr("class", "link directed");
    
            // Create nodes
            const node = svg.append("g")
                .attr("class", "nodes")
                .selectAll("g")
                .data(graphData.nodes)
                .enter().append("g")
                .attr("class", "node")
                .call(d3.drag()
                    .on("start", dragstarted)
                    .on("drag", dragged)
                    .on("end", dragended));
    
            node.append("circle")
                .attr("r", 15)
                .attr("fill", "#69b3a2");
    
            node.append("text")
                .attr("x", 18)
                .attr("y", 5)
                .text(d => d.label)
                .attr("font-size", "12px")
                .attr("fill", "#555");
    
            // Add tooltips and highlighting
            node.on("mouseover", function (event, d) {
                highlightConnections(d);
                showTooltip(event, d);
            }).on("mouseout", function () {
                unhighlightConnections();
                hideTooltip();
            });
    
            // Update positions on tick
            simulation.on("tick", () => {
                link
                    .attr("x1", d => d.source.x)
                    .attr("y1", d => d.source.y)
                    .attr("x2", d => d.target.x)
                    .attr("y2", d => d.target.y);
    
                node
                    .attr("transform", d => `translate(${d.x},${d.y})`);
            });
    
            function dragstarted(event, d) {
                if (!event.active) simulation.alphaTarget(0.3).restart();
                d.fx = d.x;
                d.fy = d.y;
            }
    
            function dragged(event, d) {
                d.fx = event.x;
                d.fy = event.y;
            }
    
            function dragended(event, d) {
                if (!event.active) simulation.alphaTarget(0);
                d.fx = null;
                d.fy = null;
            }
    
            function highlightConnections(d) {
                node.style("opacity", n => n === d || graphData.edges.some(l => (l.source === d && l.target === n) || (l.target === d && l.source === n)) ? 1 : 0.1);
                link.style("opacity", l => l.source === d || l.target === d ? 1 : 0.1)
                    .classed("highlighted", l => l.source === d || l.target === d);
            }
    
            function unhighlightConnections() {
                node.style("opacity", 1);
                link.style("opacity", 1).classed("highlighted", false);
            }
    
            function showTooltip(event, d) {
                tooltip.transition().duration(200).style("opacity", .9);
                tooltip.html(`
                    <strong>${d.label}</strong><br/>
                    ID: ${d.id}<br/>
                    Connections: ${graphData.edges.filter(l => l.source === d || l.target === d).length}
                `)
                    .style("left", (event.pageX + 10) + "px")
                    .style("top", (event.pageY - 28) + "px");
            }
    
            function hideTooltip() {
                tooltip.transition().duration(500).style("opacity", 0);
            }
    
            window.fitNetwork = () => {
                const bounds = svg.node().getBBox();
                const parent = svg.node().parentElement;
                const fullWidth = parent.clientWidth;
                const fullHeight = parent.clientHeight;
                const width = bounds.width;
                const height = bounds.height;
                const midX = bounds.x + width / 2;
                const midY = bounds.y + height / 2;
                if (width === 0 || height === 0) return; // nothing to fit
                const scale = 0.8 / Math.max(width / fullWidth, height / fullHeight);
                const translate = [fullWidth / 2 - scale * midX, fullHeight / 2 - scale * midY];
    
                svg.transition()
                    .duration(750)
                    .call(
                        d3.zoom().transform,
                        d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale)
                    );
            };
    
            window.expandAll = () => {
                node.style("display", "block");
                link.style("display", "block");
                simulation.alpha(1).restart();
            };
    
            window.collapseAll = () => {
                node.style("display", d => d.id === 'root' ? "block" : "none");
                link.style("display", "none");
                simulation.alpha(1).restart();
            };
    
            d3.select("#charge").on("input", function () {
                simulation.force("charge").strength(+this.value);
                simulation.alpha(1).restart();
            });
    
            window.searchNode = () => {
                const searchTerm = document.getElementById("searchInput").value.toLowerCase();
                node.style("opacity", d => d.label.toLowerCase().includes(searchTerm) ? 1 : 0.1);
                link.style("opacity", 0.1);
            };
        }
    
        // Function to load graph data and initialize
        function loadAndInitializeGraph(graphDataOrUrl) {
            if (typeof graphDataOrUrl === 'string') {
                // If it's a URL, fetch the data
                d3.json(graphDataOrUrl).then(data => {
                    initializeGraph(data);
                }).catch(error => {
                    console.error("Error loading the JSON file:", error);
                    initializeGraph(defaultGraphData);
                });
            } else if (typeof graphDataOrUrl === 'object') {
                // If it's an object, use it directly
                initializeGraph(graphDataOrUrl);
            } else {
                // If neither, use the default data
                initializeGraph(defaultGraphData);
            }
        }
    
        // Usage:
        // loadAndInitializeGraph(someJsonObject); // To use a JSON object
        // loadAndInitializeGraph(); // To use default data
        
        // Initialize with default data
        loadAndInitializeGraph("../memory/graph_data.json"); // To load from a file
    </script>
</body>

</html>