(function() {
    // Parse data and configuration
    const data = vizData;
    const config = vizConfig;
    const maxIteration = config.maxIteration;
    
    // Create SVG based on container size
    const container = document.getElementById('visualization-container');
    const width = container.clientWidth;
    const height = container.clientHeight;
    
    const svg = d3.select("#visualization-container")
        .append("svg")
        .attr("width", width)
        .attr("height", height);
    
    // Add arrow markers for the links
    svg.append("defs").append("marker")
        .attr("id", "arrow")
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 15)
        .attr("refY", 0)
        .attr("markerWidth", 6)
        .attr("markerHeight", 6)
        .attr("orient", "auto")
        .append("path")
        .attr("d", "M0,-5L10,0L0,5")
        .attr("fill", "#4285f4");
    
    // Create a main group for zoom/pan
    const mainGroup = svg.append("g");
    
    // Create minimal tooltip
    const tooltip = d3.select("body")
        .append("div")
        .attr("class", "tooltip");
    
    // Add simple zoom and pan behavior
    const zoom = d3.zoom()
        .scaleExtent([0.1, 3])
        .on("zoom", (event) => {
            mainGroup.attr("transform", event.transform);
        });
    
    svg.call(zoom);
    
    // Add simple navigation controls
    const controls = svg.append("g")
        .attr("class", "controls")
        .attr("transform", "translate(10, 10)");
    
    controls.append("rect")
        .attr("width", 100)
        .attr("height", 30)
        .attr("rx", 5)
        .attr("fill", "white")
        .attr("opacity", 0.8);
    
    // Zoom in button
    controls.append("text")
        .attr("x", 20)
        .attr("y", 20)
        .text("🔍+")
        .attr("cursor", "pointer")
        .on("click", () => {
            svg.transition().duration(300).call(zoom.scaleBy, 1.3);
        });
    
    // Zoom out button
    controls.append("text")
        .attr("x", 50)
        .attr("y", 20)
        .text("🔍-")
        .attr("cursor", "pointer")
        .on("click", () => {
            svg.transition().duration(300).call(zoom.scaleBy, 0.7);
        });
    
    // Reset button
    controls.append("text")
        .attr("x", 80)
        .attr("y", 20)
        .text("🏠")
        .attr("cursor", "pointer")
        .on("click", () => {
            svg.transition().duration(300).call(zoom.transform, d3.zoomIdentity);
        });
    
    // Organize nodes by iteration and population
    const nodesByIterAndPop = {};
    data.nodes.forEach(node => {
        const key = `${node.iteration}_${node.population}`;
        if (!nodesByIterAndPop[key]) {
            nodesByIterAndPop[key] = [];
        }
        nodesByIterAndPop[key].push(node);
    });

    // Calculate layout metrics dynamically
    const iterationSpacing = Math.min(width / (maxIteration + 2), 200); // Cap to prevent excessive spacing
    
    // Find max population count per iteration to distribute vertically
    const populationCountByIter = {};
    for (let i = 0; i <= maxIteration; i++) {
        populationCountByIter[i] = new Set(
            data.nodes
                .filter(n => n.iteration === i && n.type === "parent")
                .map(n => n.population)
        ).size;
    }
    
    // Find max number of mutations per parent to calculate vertical padding
    const maxMutationsPerParent = Math.max(
        1, // Ensure minimum padding
        ...Object.values(nodesByIterAndPop).map(nodes => 
            nodes.filter(n => n.type === "mutation").length
        )
    );
    
    // Dynamically calculate vertical spacing based on available height and number of populations
    const maxPopulationsPerIter = Math.max(...Object.values(populationCountByIter));
    const verticalSpacing = Math.min(
        height / (maxPopulationsPerIter + 1),
        80 // Cap to prevent excessive spacing
    );
    
    // Calculate mutation padding based on available space
    const nodePadding = Math.min(
        verticalSpacing / (maxMutationsPerParent + 1),
        15 // Cap to prevent excessive spacing
    );
    
    // Set static positions for all nodes
    data.nodes.forEach(node => {
        // X position based on iteration
        node.x = (node.iteration + 1) * iterationSpacing;
        
        // Y position calculation
        if (node.type === "parent") {
            // Get index of this population within the iteration
            const populationsInIter = Array.from(new Set(
                data.nodes
                    .filter(n => n.iteration === node.iteration && n.type === "parent")
                    .map(n => n.population)
            )).sort((a, b) => a - b);
            
            const populationIndex = populationsInIter.indexOf(node.population);
            const totalPopulations = populationsInIter.length;
            
            // Center vertically and space evenly
            node.y = height/2 + (populationIndex - (totalPopulations-1)/2) * verticalSpacing;
        } else {
            // Mutations are positioned relative to their parent
            const parentNode = data.nodes.find(n => 
                n.iteration === node.iteration && 
                n.population === node.population && 
                n.type === "parent");
            
            if (parentNode) {
                // Count mutations for this parent for spacing
                const mutations = nodesByIterAndPop[`${node.iteration}_${node.population}`]
                    .filter(n => n.type === "mutation");
                const mutationCount = mutations.length;
                const mutationIndex = mutations.findIndex(n => n.child === node.child);
                
                // Position mutations to the right of parent
                const mutationXOffset = iterationSpacing * 0.4; // Dynamic X offset based on iteration spacing
                node.x = parentNode.x + mutationXOffset;
                node.y = parentNode.y - (mutationCount * nodePadding)/2 + mutationIndex * nodePadding;
            } else {
                node.y = height/2;
            }
        }
    });
    
    // Create a node lookup map for links to use
    const nodeMap = {};
    data.nodes.forEach(node => {
        nodeMap[node.id] = node;
    });
    
    // Process links to reference actual node objects instead of string IDs
    data.links.forEach(link => {
        // Convert string ID references to actual node objects
        if (typeof link.source === 'string') {
            link.source = nodeMap[link.source];
        }
        if (typeof link.target === 'string') {
            link.target = nodeMap[link.target];
        }
    });
    
    // Draw links with proper arrows
    mainGroup.append("g")
        .selectAll("path")
        .data(data.links)
        .enter()
        .append("path")
        .attr("class", d => `link ${d.type}`)
        .attr("d", d => {
            // Check if source and target have x and y coordinates
            if (!d.source || !d.target || 
                typeof d.source.x === 'undefined' || 
                typeof d.target.x === 'undefined') {
                console.error("Invalid link:", d);
                return "";
            }
            return `M${d.source.x},${d.source.y} L${d.target.x},${d.target.y}`;
        })
        .attr("stroke", d => d.type === "mutation" ? "#fbbc05" : "#4285f4")
        .attr("stroke-width", 1.5)
        .attr("stroke-dasharray", d => d.type === "mutation" ? "3,3" : "none")
        .attr("marker-end", d => d.type === "evolution" ? "url(#arrow)" : "");
    
    // Setup drag behavior (improved)
    const drag = d3.drag()
        .on("start", dragStarted)
        .on("drag", dragging)
        .on("end", dragEnded);
    
    function dragStarted(event, d) {
        // Store original positions for potential reset
        d.fx = d.x;
        d.fy = d.y;
        
        // Make the node appear "active" during drag
        d3.select(this).classed("dragging", true);
    }
    
    function dragging(event, d) {
        // Update node position during drag
        d.x = event.x;
        d.y = event.y;
        
        // Update this node's position
        d3.select(this).attr("transform", `translate(${d.x},${d.y})`);
        
        // Update all links connected to this node
        mainGroup.selectAll(".link")
            .filter(link => link.source === d || link.target === d)
            .attr("d", link => {
                return `M${link.source.x},${link.source.y} L${link.target.x},${link.target.y}`;
            });
    }
    
    function dragEnded(event, d) {
        // Clear fixed position indicators
        d.fx = null;
        d.fy = null;
        
        // Remove the "active" styling
        d3.select(this).classed("dragging", false);
        
        // Final position update (should be already set in dragging, but just to be safe)
        d.x = event.x;
        d.y = event.y;
        
        // Make sure all links are updated one final time
        mainGroup.selectAll(".link")
            .filter(link => link.source === d || link.target === d)
            .attr("d", link => {
                return `M${link.source.x},${link.source.y} L${link.target.x},${link.target.y}`;
            });
    }
    
    // Draw nodes
    const nodeGroups = mainGroup.append("g")
        .selectAll("g")
        .data(data.nodes)
        .enter()
        .append("g")
        .attr("class", d => `node ${d.type}`)
        .attr("transform", d => `translate(${d.x},${d.y})`)
        .call(drag) // Apply drag behavior
        .on("mouseover", function(event, d) {
            // Simple tooltip
            tooltip.transition()
                .duration(200)
                .style("opacity", .9);
                
            // Find parent if this is a mutation
            let parentNode = null;
            if (d.type === "mutation") {
                parentNode = data.nodes.find(
                    n => n.iteration === d.iteration && 
                         n.population === d.population && 
                         n.child === 0
                );
            }
            
            tooltip.html(`
                <div><strong>${d.type === "parent" ? "Parent" : "Mutation"}</strong></div>
                <div><strong>Text:</strong> ${d.text}</div>
                ${d.type === "mutation" && d.changes ? 
                  `<div><strong>Changes:</strong> ${d.changes}</div>` : ''}
                <div><strong>Iter:</strong> ${d.iteration}, <strong>Pop:</strong> ${d.population}, <strong>Child:</strong> ${d.child}</div>
                <div><strong>Target:</strong> ${d.target.toFixed(4)}, <strong>X-entropy:</strong> ${d.xentropy.toFixed(4)}</div>
                ${d.type === "parent" && d.x_weight !== undefined ? 
                  `<div><strong>X Weight:</strong> ${d.x_weight.toFixed(4)}</div>` : ''}
            `)
            .style("left", (event.pageX + 10) + "px")
            .style("top", (event.pageY - 28) + "px");
        })
        .on("mouseout", function() {
            // Hide tooltip
            tooltip.transition()
                .duration(500)
                .style("opacity", 0);
        });
    
    // Add circles to nodes
    nodeGroups.append("circle")
        .attr("r", d => d.type === "parent" ? 8 : 5)
        .attr("fill", d => d.type === "parent" ? "#4285f4" : "#fbbc05");
    
    // Add minimal text labels
    nodeGroups.append("text")
        .attr("dx", 10)
        .attr("dy", 4)
        .text(d => d.text.length > 15 ? d.text.substring(0, 15) + "..." : d.text)
        .attr("font-size", "10px");
    
    // Add iteration labels
    mainGroup.append("g")
        .selectAll("text")
        .data([...Array(maxIteration + 1).keys()])
        .enter()
        .append("text")
        .attr("x", d => (d + 1) * iterationSpacing)
        .attr("y", 20)
        .attr("text-anchor", "middle")
        .attr("font-size", "12px")
        .text(d => `Iteration ${d}`);
    
    // Legend (minimal)
    const legend = svg.append("g")
        .attr("transform", "translate(20, 50)")
        .attr("class", "legend");
        
    legend.append("rect")
        .attr("width", 85)
        .attr("height", 55)
        .attr("rx", 5)
        .attr("fill", "white")
        .attr("opacity", 0.8);
        
    legend.append("circle")
        .attr("r", 6)
        .attr("cx", 10)
        .attr("cy", 15)
        .attr("fill", "#4285f4");
        
    legend.append("text")
        .attr("x", 20)
        .attr("y", 19)
        .text("Parent")
        .attr("font-size", "12px");
        
    legend.append("circle")
        .attr("r", 6)
        .attr("cx", 10)
        .attr("cy", 35)
        .attr("fill", "#fbbc05");
        
    legend.append("text")
        .attr("x", 20)
        .attr("y", 39)
        .text("Mutation")
        .attr("font-size", "12px");

    // Calculate ideal initial zoom scale to fit all content
    const contentWidth = (maxIteration + 2) * iterationSpacing;
    const contentHeight = maxPopulationsPerIter * verticalSpacing;
    const scaleX = width / contentWidth;
    const scaleY = height / contentHeight;
    const scale = Math.min(1, scaleX, scaleY) * 0.9; // 90% of perfect fit for some margin
    
    // Initial zoom to fit everything
    svg.call(zoom.transform, d3.zoomIdentity
        .scale(scale)
        .translate(width / (2 * scale) - contentWidth / 2, height / (2 * scale) - contentHeight / 2));
})(); 