/**
 * Core graph data structures and operations
 * Manages nodes, relations, and basic graph state
 * 
 * Now uses SimpleGraph for the underlying graph structure.
 */
import { SimpleGraph } from './SimpleGraph.js';

export class GraphData {
  constructor() {
    // Use SimpleGraph for the underlying graph structure
    this.graph = new SimpleGraph({ directed: false });
    
    // Core data structures
    this.nodes = new Map();
    this.nodeIdByKey = new Map();
    this.keyByNodeId = new Map();
    this.nextNodeId = 0;
    this.relations = [];
    
    // Hub settings for distance estimation
    this.hubIds = new Set();
  }

  /**
   * Add a node to the graph
   * @param {string} key - Unique key for the node
   * @param {string} type - Type of the node
   * @param {object} data - Additional node data
   * @returns {number} The assigned node ID
   */
  addNode(key, type, data = {}) {
    if (this.nodeIdByKey.has(key)) {
      return this.nodeIdByKey.get(key);
    }

    const nodeId = this.nextNodeId++;
    const node = {
      id: nodeId,
      key,
      type,
      data,
      createdAt: Date.now()
    };

    this.nodes.set(nodeId, node);
    this.nodeIdByKey.set(key, nodeId);
    this.keyByNodeId.set(nodeId, key);

    return nodeId;
  }

  /**
   * Get a node by its key
   * @param {string} key - Node key
   * @returns {object|null} Node object or null if not found
   */
  getNodeByKey(key) {
    const nodeId = this.nodeIdByKey.get(key);
    return nodeId !== undefined ? this.nodes.get(nodeId) : null;
  }

  /**
   * Get a node by its ID
   * @param {number} nodeId - Node ID
   * @returns {object|null} Node object or null if not found
   */
  getNodeById(nodeId) {
    return this.nodes.get(nodeId) || null;
  }

  /**
   * Get node key by ID
   * @param {number} nodeId - Node ID
   * @returns {string|null} Node key or null if not found
   */
  getNodeKeyById(nodeId) {
    return this.keyByNodeId.get(nodeId) || null;
  }

  /**
   * Get node ID by key
   * @param {string} key - Node key
   * @returns {number|null} Node ID or null if not found
   */
  getNodeIdByKey(key) {
    return this.nodeIdByKey.get(key) || null;
  }

  /**
   * Add a relation to the graph
   * @param {number} srcId - Source node ID
   * @param {string} relation - Relation type
   * @param {number} dstId - Destination node ID
   * @param {object} options - Relation options (possibility, value, etc.)
   * @returns {number} The index of the created relation in the relations array
   */
  addRelation(srcId, relation, dstId, options = {}) {
    const relationObj = {
      src: srcId,
      rel: relation,
      dst: dstId,
      possibility: options.possibility ?? 1.0,
      value: options.value || null,
      createdAt: Date.now(),
      ...options
    };

    this.relations.push(relationObj);
    
    // Also add edge to internal graph
    this.graph.addEdge(srcId, dstId);
    
    return this.relations.length - 1; // Return the index
  }

  /**
   * Remove a relation from the graph
   * @param {number} srcId - Source node ID
   * @param {string} relation - Relation type
   * @param {number} dstId - Destination node ID
   * @returns {boolean} True if relation was removed, false if not found
   */
  removeRelation(srcId, relation, dstId) {
    const index = this.relations.findIndex(r => 
      r.src === srcId && r.rel === relation && r.dst === dstId
    );

    if (index !== -1) {
      this.relations.splice(index, 1);
      
      // Check if there are any other relations between these nodes
      // If not, remove the edge from internal graph
      const hasOtherRelations = this.relations.some(r => 
        (r.src === srcId && r.dst === dstId) || (r.src === dstId && r.dst === srcId)
      );
      if (!hasOtherRelations) {
        this.graph.removeEdge(srcId, dstId);
      }
      
      return true;
    }
    return false;
  }

  /**
   * Find relations matching criteria
   * @param {object} criteria - Search criteria
   * @returns {Array} Array of matching relations
   */
  findRelations(criteria = {}) {
    return this.relations.filter(relation => {
      if (criteria.src !== undefined && relation.src !== criteria.src) return false;
      if (criteria.rel !== undefined && relation.rel !== criteria.rel) return false;
      if (criteria.dst !== undefined && relation.dst !== criteria.dst) return false;
      if (criteria.possibility !== undefined && relation.possibility !== criteria.possibility) return false;
      return true;
    });
  }

  /**
   * Get all relations for a node
   * @param {number} nodeId - Node ID
   * @param {string} direction - 'outgoing', 'incoming', or 'both'
   * @returns {Array} Array of relations
   */
  getNodeRelations(nodeId, direction = 'both') {
    return this.relations.filter(relation => {
      if (direction === 'outgoing') return relation.src === nodeId;
      if (direction === 'incoming') return relation.dst === nodeId;
      return relation.src === nodeId || relation.dst === nodeId;
    });
  }

  /**
   * Get graph statistics
   * @returns {object} Graph statistics
   */
  getStats() {
    return {
      nodeCount: this.nodes.size,
      relationCount: this.relations.length,
      nextNodeId: this.nextNodeId,
      hubCount: this.hubIds.size
    };
  }

  /**
   * Clear all graph data
   */
  clear() {
    this.nodes.clear();
    this.nodeIdByKey.clear();
    this.keyByNodeId.clear();
    this.nextNodeId = 0;
    this.relations = [];
    this.hubIds.clear();
    
    // Clear internal graph by creating a new one
    this.graph = new SimpleGraph({ directed: false });
  }
  
  /**
   * Get the underlying UnifiedGraph instance
   * @returns {SimpleGraph} The internal graph instance
   */
  getGraph() {
    return this.graph;
  }

  /**
   * Add a hub node for distance estimation
   * @param {number} nodeId - Node ID to mark as hub
   */
  addHub(nodeId) {
    this.hubIds.add(nodeId);
  }

  /**
   * Remove a hub node
   * @param {number} nodeId - Node ID to remove from hubs
   */
  removeHub(nodeId) {
    this.hubIds.delete(nodeId);
  }

  /**
   * Check if a node is a hub
   * @param {number} nodeId - Node ID to check
   * @returns {boolean} True if node is a hub
   */
  isHub(nodeId) {
    return this.hubIds.has(nodeId);
  }

  /**
   * Get all hub nodes
   * @returns {Set} Set of hub node IDs
   */
  getHubs() {
    return new Set(this.hubIds);
  }
}
