var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
    if (decorator = decorators[i])
      result = (kind ? decorator(target, key, result) : decorator(result)) || result;
  if (kind && result) __defProp(target, key, result);
  return result;
};
/**
 * @license
 * Copyright 2025-2026 Open Home Foundation
 * SPDX-License-Identifier: Apache-2.0
 */
import { LitElement, css } from "lit";
import { property, state } from "lit/decorators.js";
import { DataSet, Network } from "vis-network/standalone";
import { getCssVar } from "../../util/shared-styles.js";
import { ThemeService } from "../../util/theme-service.js";
class BaseNetworkGraph extends LitElement {
  constructor() {
    super(...arguments);
    this.nodes = {};
    this._selectedNodeId = null;
    this._physicsEnabled = true;
    /** Whether auto-freeze has already been applied (to avoid re-freezing after user unfreezes) */
    this._autoFreezeApplied = false;
    /** Whether initial fit has been done (to preserve user's zoom/pan after first load) */
    this._initialFitDone = false;
    /** Store original edge colors for restoration after highlighting */
    this._originalEdgeColors = /* @__PURE__ */ new Map();
  }
  _getFontColor() {
    return getCssVar("--graph-font-color", ThemeService.effectiveTheme === "dark" ? "#e0e0e0" : "#333333");
  }
  _getDimmedEdgeColor() {
    return getCssVar("--graph-edge-dimmed", ThemeService.effectiveTheme === "dark" ? "#555555" : "#cccccc");
  }
  /**
   * Returns physics options for the network. Override in subclasses for different behavior.
   */
  _getPhysicsOptions() {
    return {
      enabled: true,
      solver: "forceAtlas2Based",
      forceAtlas2Based: {
        gravitationalConstant: -70,
        centralGravity: 5e-3,
        springLength: 130,
        springConstant: 0.08,
        damping: 0.4,
        avoidOverlap: 0.6
      },
      stabilization: {
        enabled: true,
        iterations: 250,
        updateInterval: 25
      }
    };
  }
  updated(changedProperties) {
    super.updated(changedProperties);
    if (!this._container && !this._resizeObserver) {
      this._tryAttachContainer();
    }
    if (changedProperties.has("nodes")) {
      this._debouncedUpdateGraph();
    }
  }
  /**
   * Try to find and attach the graph container if it wasn't available before.
   * This handles the case where empty state was rendered initially.
   */
  _tryAttachContainer() {
    const container = this.shadowRoot?.querySelector(".graph-container");
    if (container) {
      this._container = container;
      this._resizeObserver = new ResizeObserver((entries) => {
        const entry = entries[0];
        if (entry && entry.contentRect.width > 0 && entry.contentRect.height > 0) {
          if (!this._network) {
            this._initializeNetwork();
          } else {
            this._network.setSize(`${entry.contentRect.width}px`, `${entry.contentRect.height}px`);
            this._network.redraw();
          }
        }
      });
      this._resizeObserver.observe(this._container);
    }
  }
  /** Debounced graph update to avoid excessive redraws */
  _debouncedUpdateGraph() {
    if (this._updateDebounceTimer) {
      clearTimeout(this._updateDebounceTimer);
    }
    this._updateDebounceTimer = setTimeout(() => {
      this._updateGraph();
    }, 100);
  }
  firstUpdated() {
    this._container = this.shadowRoot?.querySelector(".graph-container");
    if (this._container) {
      this._resizeObserver = new ResizeObserver((entries) => {
        const entry = entries[0];
        if (entry && entry.contentRect.width > 0 && entry.contentRect.height > 0) {
          if (!this._network) {
            this._initializeNetwork();
          } else {
            this._network.setSize(`${entry.contentRect.width}px`, `${entry.contentRect.height}px`);
            this._network.redraw();
          }
        }
      });
      this._resizeObserver.observe(this._container);
    }
    this._themeUnsubscribe = ThemeService.subscribe(() => {
      if (this._network && this._nodesDataSet) {
        const fontColor = this._getFontColor();
        this._network.setOptions({
          nodes: {
            font: {
              color: fontColor
            }
          }
        });
        this._updateGraph();
        const allNodes = this._nodesDataSet.get();
        const nodeUpdates = allNodes.map((node) => ({
          id: node.id,
          font: { color: fontColor }
        }));
        this._nodesDataSet.update(nodeUpdates);
        this._network.redraw();
      }
    });
  }
  disconnectedCallback() {
    super.disconnectedCallback();
    this._resizeObserver?.disconnect();
    this._themeUnsubscribe?.();
    this._cancelAutoFreezeTimer();
    if (this._updateDebounceTimer) {
      clearTimeout(this._updateDebounceTimer);
    }
    this._nodesDataSet?.clear();
    this._edgesDataSet?.clear();
    this._network?.destroy();
    this._network = void 0;
    this._nodesDataSet = void 0;
    this._edgesDataSet = void 0;
    this._originalEdgeColors.clear();
  }
  _initializeNetwork() {
    if (!this._container) return;
    const rect = this._container.getBoundingClientRect();
    if (rect.width === 0 || rect.height === 0) {
      return;
    }
    this._nodesDataSet = new DataSet();
    this._edgesDataSet = new DataSet();
    const options = {
      width: `${rect.width}px`,
      height: `${rect.height}px`,
      autoResize: false,
      // We handle resize manually
      nodes: {
        shape: "image",
        size: 30,
        font: {
          size: 12,
          color: this._getFontColor()
        },
        borderWidth: 2,
        borderWidthSelected: 3
      },
      edges: {
        width: 2,
        smooth: {
          enabled: true,
          type: "continuous",
          roundness: 0.5
        }
      },
      physics: this._getPhysicsOptions(),
      interaction: {
        hover: true,
        tooltipDelay: 200,
        hideEdgesOnDrag: true
      }
    };
    this._network = new Network(
      this._container,
      {
        nodes: this._nodesDataSet,
        edges: this._edgesDataSet
      },
      options
    );
    this._network.on("selectNode", (params) => {
      if (params.nodes.length > 0) {
        this._selectedNodeId = params.nodes[0];
        this._highlightConnections(this._selectedNodeId);
        this._dispatchNodeSelected(this._selectedNodeId);
      }
    });
    this._network.on("deselectNode", () => {
      this._selectedNodeId = null;
      this._clearHighlights();
      this._dispatchNodeSelected(null);
    });
    this._network.on("stabilizationIterationsDone", () => {
      if (!this._initialFitDone) {
        this._network?.fit({
          animation: {
            duration: 500,
            easingFunction: "easeInOutQuad"
          }
        });
        this._initialFitDone = true;
        this._startAutoFreezeTimer();
      }
    });
    this._updateGraph();
  }
  /**
   * Returns true if the graph is initialized and ready for interaction.
   */
  isReady() {
    return this._network !== void 0 && this._nodesDataSet !== void 0;
  }
  /**
   * Fits all nodes into view.
   */
  fit() {
    this._network?.fit({
      animation: {
        duration: 300,
        easingFunction: "easeInOutQuad"
      }
    });
  }
  /**
   * Zooms in by 20%.
   */
  zoomIn() {
    if (!this._network) return;
    const scale = this._network.getScale();
    this._network.moveTo({
      scale: scale * 1.2,
      animation: {
        duration: 200,
        easingFunction: "easeInOutQuad"
      }
    });
  }
  /**
   * Zooms out by 20%.
   */
  zoomOut() {
    if (!this._network) return;
    const scale = this._network.getScale();
    this._network.moveTo({
      scale: scale / 1.2,
      animation: {
        duration: 200,
        easingFunction: "easeInOutQuad"
      }
    });
  }
  /**
   * Returns whether physics simulation is currently enabled.
   */
  get physicsEnabled() {
    return this._physicsEnabled;
  }
  /**
   * Enables or disables physics simulation (node movement/settling).
   * When disabled, nodes freeze in place; when enabled, they resume settling.
   * @param enabled Whether to enable physics
   * @param isManual If true, cancels any pending auto-freeze (user manually toggled)
   */
  setPhysicsEnabled(enabled, isManual = true) {
    if (isManual) {
      this._cancelAutoFreezeTimer();
      this._autoFreezeApplied = true;
    }
    this._physicsEnabled = enabled;
    this._network?.setOptions({
      physics: { enabled }
    });
  }
  /**
   * Starts the auto-freeze timer. Physics will be disabled after 5 seconds
   * unless the user manually toggles physics or the timer is cancelled.
   */
  _startAutoFreezeTimer() {
    if (this._autoFreezeApplied) {
      return;
    }
    this._cancelAutoFreezeTimer();
    this._autoFreezeTimer = setTimeout(() => {
      if (!this._autoFreezeApplied && this._physicsEnabled) {
        this.setPhysicsEnabled(false, false);
        this._autoFreezeApplied = true;
        this.dispatchEvent(
          new CustomEvent("physics-changed", {
            detail: { enabled: false },
            bubbles: true,
            composed: true
          })
        );
      }
    }, 5e3);
  }
  /**
   * Cancels any pending auto-freeze timer.
   */
  _cancelAutoFreezeTimer() {
    if (this._autoFreezeTimer) {
      clearTimeout(this._autoFreezeTimer);
      this._autoFreezeTimer = void 0;
    }
  }
  /**
   * Selects a node by ID and focuses on it.
   */
  selectNode(nodeId) {
    if (!this._network) return;
    this._selectedNodeId = nodeId;
    this._network.selectNodes([nodeId]);
    this._highlightConnections(nodeId);
    this._dispatchNodeSelected(nodeId);
    this._network.focus(nodeId, {
      scale: 1.2,
      animation: {
        duration: 500,
        easingFunction: "easeInOutQuad"
      }
    });
  }
  /**
   * Deselects all nodes, clears highlights, and restores default styling.
   */
  deselectAll() {
    if (!this._network) return;
    this._selectedNodeId = null;
    this._network.unselectAll();
    this._clearHighlights();
  }
  _dispatchNodeSelected(nodeId) {
    this.dispatchEvent(
      new CustomEvent("node-selected", {
        detail: { nodeId },
        bubbles: true,
        composed: true
      })
    );
  }
  /**
   * Highlights edges connected to the selected node and makes neighbor nodes more prominent.
   */
  _highlightConnections(nodeId) {
    if (!this._edgesDataSet || !this._nodesDataSet) return;
    const allEdges = this._edgesDataSet.get();
    for (const edge of allEdges) {
      const edgeId = String(edge.id);
      if (!this._originalEdgeColors.has(edgeId)) {
        const colorObj = edge.color;
        const color = colorObj?.color ?? getCssVar("--graph-node-fallback", "#999999");
        const highlight = colorObj?.highlight ?? color;
        this._originalEdgeColors.set(edgeId, { color, highlight });
      }
    }
    const connectedEdges = this._edgesDataSet.get({
      filter: (edge) => edge.from === nodeId || edge.to === nodeId
    });
    const neighborIds = /* @__PURE__ */ new Set();
    for (const edge of connectedEdges) {
      if (edge.from === nodeId) {
        neighborIds.add(edge.to);
      } else {
        neighborIds.add(edge.from);
      }
    }
    const dimmedColor = this._getDimmedEdgeColor();
    const edgeUpdates = allEdges.map((edge) => {
      const isConnected = edge.from === nodeId || edge.to === nodeId;
      const originalColor = this._originalEdgeColors.get(String(edge.id));
      const connectedColor = originalColor ?? {
        color: getCssVar("--graph-node-fallback", "#999999"),
        highlight: getCssVar("--graph-node-fallback", "#999999")
      };
      return {
        id: edge.id,
        width: isConnected ? 3 : 1,
        color: isConnected ? { color: connectedColor.color, highlight: connectedColor.highlight } : { color: dimmedColor, highlight: dimmedColor }
      };
    });
    this._edgesDataSet.update(edgeUpdates);
    const allNodes = this._nodesDataSet.get();
    const nodeUpdates = allNodes.map((node) => {
      const isNeighbor = neighborIds.has(node.id);
      const isSelected = node.id === nodeId;
      return {
        id: node.id,
        size: isSelected ? 40 : isNeighbor ? 35 : 25,
        font: {
          size: isSelected ? 14 : isNeighbor ? 13 : 11,
          color: this._getFontColor(),
          bold: isSelected || isNeighbor ? { color: this._getFontColor() } : void 0
        },
        opacity: isSelected || isNeighbor ? 1 : 0.5
      };
    });
    this._nodesDataSet.update(nodeUpdates);
  }
  /**
   * Clears all highlights and restores default styling.
   */
  _clearHighlights() {
    if (!this._edgesDataSet || !this._nodesDataSet) return;
    const allEdges = this._edgesDataSet.get();
    const edgeUpdates = allEdges.map((edge) => {
      const originalColor = this._originalEdgeColors.get(String(edge.id));
      return {
        id: edge.id,
        width: 2,
        color: originalColor ?? {
          color: getCssVar("--graph-node-fallback", "#999999"),
          highlight: getCssVar("--graph-node-fallback", "#999999")
        }
      };
    });
    this._edgesDataSet.update(edgeUpdates);
    const allNodes = this._nodesDataSet.get();
    const nodeUpdates = allNodes.map((node) => ({
      id: node.id,
      size: 30,
      font: {
        size: 12,
        color: this._getFontColor(),
        bold: void 0
      },
      opacity: 1
    }));
    this._nodesDataSet.update(nodeUpdates);
  }
  /**
   * Clear stored edge colors when graph is updated (edges are recreated).
   */
  _clearOriginalEdgeColors() {
    this._originalEdgeColors.clear();
  }
  static {
    this.styles = css`
        :host {
            display: block;
            width: 100%;
            height: 100%;
            min-height: 0;
        }

        .graph-container {
            width: 100%;
            height: 100%;
            background-color: var(--md-sys-color-surface, #fff);
            border-radius: 8px;
            border: 1px solid var(--md-sys-color-outline-variant, #ccc);
        }

        .empty-state {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100%;
            color: var(--md-sys-color-on-surface-variant, #666);
            text-align: center;
            padding: 24px;
            box-sizing: border-box;
            background-color: var(--md-sys-color-surface, #fff);
            border-radius: 8px;
            border: 1px solid var(--md-sys-color-outline-variant, #ccc);
        }

        .empty-state p {
            margin: 8px 0;
        }

        .empty-state .hint {
            font-size: 0.875rem;
            opacity: 0.7;
        }
    `;
  }
}
__decorateClass([
  property({ type: Object })
], BaseNetworkGraph.prototype, "nodes", 2);
__decorateClass([
  state()
], BaseNetworkGraph.prototype, "_selectedNodeId", 2);
__decorateClass([
  state()
], BaseNetworkGraph.prototype, "_physicsEnabled", 2);
export {
  BaseNetworkGraph
};
//# sourceMappingURL=base-network-graph.js.map
