// ═══════════════════════════════════════════════════════════════
// Graph Visualization — vis-network wrapper for Code Knowledge Graph
// Obsidian Control theme
// ═══════════════════════════════════════════════════════════════

(function () {
  "use strict";

  // Color palette matching Obsidian Control
  const NODE_COLORS = {
    function: { bg: "#00c8ff", border: "#00a0cc", font: "#060a13" },
    method: { bg: "#00c8ff", border: "#00a0cc", font: "#060a13" },
    struct: { bg: "#e040fb", border: "#b030d0", font: "#060a13" },
    class: { bg: "#e040fb", border: "#b030d0", font: "#060a13" },
    enum: { bg: "#ffb020", border: "#cc8c18", font: "#060a13" },
    interface: { bg: "#7c4dff", border: "#5c38c0", font: "#ffffff" },
    trait: { bg: "#7c4dff", border: "#5c38c0", font: "#ffffff" },
    module: { bg: "#ff8a3d", border: "#cc6e30", font: "#060a13" },
    file: { bg: "#00e676", border: "#00b85e", font: "#060a13" },
  };

  const EDGE_COLORS = {
    calls: "#00c8ff",
    imports: "#ffb020",
    inherits: "#e040fb",
    implements: "#7c4dff",
    contains: "rgba(94, 114, 145, 0.6)",
    type_ref: "rgba(94, 114, 145, 0.5)",
  };

  const DEFAULT_NODE_COLOR = { bg: "#5e7291", border: "#3d506b", font: "#e4eaf4" };
  const DEFAULT_EDGE_COLOR = "rgba(94, 114, 145, 0.3)";

  let network = null;
  let nodesDataset = null;
  let edgesDataset = null;
  let rawNodes = [];
  let rawEdges = [];

  function getNodeColor(kind) {
    return NODE_COLORS[kind] || DEFAULT_NODE_COLOR;
  }

  function getEdgeColor(kind) {
    return EDGE_COLORS[kind] || DEFAULT_EDGE_COLOR;
  }

  // Build a vis-network node from raw graph data
  function toVisNode(node, edgeCounts) {
    const color = getNodeColor(node.kind);
    const connections = edgeCounts[node.qualified_name] || 0;
    const size = Math.max(8, Math.min(35, 8 + connections * 2));

    return {
      id: node.qualified_name,
      label: node.name,
      title: `${node.kind}: ${node.qualified_name}\nFile: ${node.file_path}:${node.start_line}`,
      size: size,
      color: {
        background: color.bg,
        border: color.border,
        highlight: { background: color.bg, border: "#ffffff" },
        hover: { background: color.bg, border: "#ffffff" },
      },
      font: {
        color: color.font,
        size: 10,
        face: "'JetBrains Mono', monospace",
        strokeWidth: 2,
        strokeColor: "#060a13",
      },
      borderWidth: 1,
      borderWidthSelected: 3,
      shape: node.kind === "file" ? "diamond" : node.kind === "module" ? "square" : "dot",
      // Store raw data for click handler
      _raw: node,
    };
  }

  function toVisEdge(edge) {
    const color = getEdgeColor(edge.kind);
    const isDim = edge.kind === "contains" || edge.kind === "type_ref";

    return {
      from: edge.source,
      to: edge.target,
      color: {
        color: color,
        highlight: "#ffffff",
        hover: color,
      },
      width: isDim ? 1 : 2,
      arrows: {
        to: {
          enabled: edge.kind !== "contains",
          scaleFactor: 0.5,
        },
      },
      smooth: {
        enabled: true,
        type: "continuous",
        roundness: 0.3,
      },
      title: `${edge.kind}: ${edge.source} → ${edge.target}`,
      _raw: edge,
    };
  }

  /**
   * Initialize or reload the graph visualization.
   * Called from Rust via eval().
   */
  window.__loadGraph = function (nodes, edges) {
    var container = document.getElementById("graph-canvas");
    if (!container) {
      console.error("[graph-viz] #graph-canvas not found");
      return;
    }

    // Count edges per node for sizing
    const edgeCounts = {};
    edges.forEach(function (e) {
      edgeCounts[e.source] = (edgeCounts[e.source] || 0) + 1;
      edgeCounts[e.target] = (edgeCounts[e.target] || 0) + 1;
    });

    // Deduplicate nodes by qualified_name (keep first occurrence)
    var seenNodes = {};
    var uniqueNodes = [];
    nodes.forEach(function (n) {
      if (!seenNodes[n.qualified_name]) {
        seenNodes[n.qualified_name] = true;
        uniqueNodes.push(n);
      }
    });

    // Deduplicate edges by source+target+kind
    var seenEdges = {};
    var uniqueEdges = [];
    edges.forEach(function (e) {
      var key = e.source + "|" + e.target + "|" + e.kind;
      if (!seenEdges[key]) {
        seenEdges[key] = true;
        uniqueEdges.push(e);
      }
    });

    rawNodes = uniqueNodes;
    rawEdges = uniqueEdges;

    var visNodes = uniqueNodes.map(function (n) {
      return toVisNode(n, edgeCounts);
    });
    var visEdges = uniqueEdges.map(toVisEdge);

    nodesDataset = new vis.DataSet(visNodes);
    edgesDataset = new vis.DataSet(visEdges);

    const options = {
      nodes: {
        font: { color: "#e4eaf4", size: 10 },
        scaling: { min: 8, max: 35 },
      },
      edges: {
        font: { color: "#5e7291", size: 9, strokeWidth: 0 },
        selectionWidth: 3,
      },
      physics: {
        enabled: true,
        solver: "forceAtlas2Based",
        forceAtlas2Based: {
          gravitationalConstant: -80,
          centralGravity: 0.005,
          springLength: 120,
          springConstant: 0.04,
          damping: 0.5,
          avoidOverlap: 0.6,
        },
        stabilization: {
          enabled: true,
          iterations: 1500,
          updateInterval: 25,
        },
        maxVelocity: 50,
        minVelocity: 0.75,
      },
      interaction: {
        hover: true,
        tooltipDelay: 200,
        hideEdgesOnDrag: false,
        hideEdgesOnZoom: false,
        multiselect: false,
        navigationButtons: false,
        keyboard: { enabled: true },
      },
      layout: {
        improvedLayout: uniqueNodes.length < 300,
      },
    };

    // Destroy previous instance
    if (network) {
      network.destroy();
    }

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

    // Click handler — sends data to Rust
    network.on("click", function (params) {
      if (params.nodes.length > 0) {
        const nodeId = params.nodes[0];
        const visNode = nodesDataset.get(nodeId);
        if (visNode && visNode._raw && window.__onNodeClick) {
          window.__onNodeClick(JSON.stringify(visNode._raw));
        }
      }
    });

    // Stabilization overlay
    var overlay = document.getElementById("graph-stabilization-overlay");
    var progressFill = document.getElementById("graph-stab-progress-fill");
    var pctLabel = document.getElementById("graph-stab-pct");

    // Show the overlay
    if (overlay) {
      overlay.classList.remove("graph-stab-fade-out");
      overlay.style.display = "flex";
    }

    network.on("stabilizationProgress", function (params) {
      var pct = Math.round((params.iterations / params.total) * 100);
      if (progressFill) {
        progressFill.style.width = pct + "%";
      }
      if (pctLabel) {
        pctLabel.textContent = pct + "% — computing layout";
      }
    });

    network.on("stabilizationIterationsDone", function () {
      if (progressFill) progressFill.style.width = "100%";
      if (pctLabel) pctLabel.textContent = "Complete";

      // Fade out overlay
      if (overlay) {
        overlay.classList.add("graph-stab-fade-out");
        setTimeout(function () {
          overlay.style.display = "none";
        }, 900);
      }
      // Keep physics running so nodes float and respond to dragging,
      // but reduce forces for a calm, settled feel
      network.setOptions({
        physics: {
          enabled: true,
          solver: "forceAtlas2Based",
          forceAtlas2Based: {
            gravitationalConstant: -40,
            centralGravity: 0.003,
            springLength: 120,
            springConstant: 0.03,
            damping: 0.7,
            avoidOverlap: 0.6,
          },
          maxVelocity: 20,
          minVelocity: 0.75,
        },
      });
    });

    console.log(
      "[graph-viz] Loaded " + nodes.length + " nodes, " + edges.length + " edges"
    );
  };

  /**
   * Highlight a node by qualified name. Focuses + selects it.
   */
  window.__highlightNode = function (qualifiedName) {
    if (!network || !nodesDataset) return;

    const node = nodesDataset.get(qualifiedName);
    if (!node) return;

    network.selectNodes([qualifiedName]);
    network.focus(qualifiedName, {
      scale: 1.5,
      animation: { duration: 500, easingFunction: "easeInOutQuad" },
    });

    // Trigger click callback too
    if (node._raw && window.__onNodeClick) {
      window.__onNodeClick(JSON.stringify(node._raw));
    }
  };

  /**
   * Highlight multiple nodes (e.g., search results or file nodes).
   */
  window.__highlightNodes = function (qualifiedNames) {
    if (!network || !nodesDataset) return;
    network.selectNodes(qualifiedNames);
    if (qualifiedNames.length > 0) {
      network.fit({
        nodes: qualifiedNames,
        animation: { duration: 500, easingFunction: "easeInOutQuad" },
      });
    }
  };

  /**
   * Highlight all nodes belonging to a specific file path.
   */
  window.__highlightFileNodes = function (filePath) {
    if (!network || !nodesDataset) return;
    var matching = [];
    rawNodes.forEach(function (n) {
      if (n.file_path === filePath) {
        matching.push(n.qualified_name);
      }
    });
    if (matching.length > 0) {
      network.selectNodes(matching);
      network.fit({
        nodes: matching,
        animation: { duration: 500, easingFunction: "easeInOutQuad" },
      });
    }
  };

  /**
   * Clear selection.
   */
  window.__clearGraphSelection = function () {
    if (!network) return;
    network.unselectAll();
  };

  /**
   * Fit entire graph in view.
   */
  window.__fitGraph = function () {
    if (!network) return;
    network.fit({
      animation: { duration: 400, easingFunction: "easeInOutQuad" },
    });
  };
})();
