import Graph, { UndirectedGraph } from 'graphology'
import { useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { errorMessage } from '@/lib/utils'
import * as Constants from '@/lib/constants'
import { useGraphStore, RawGraph, RawNodeType, RawEdgeType } from '@/stores/graph'
import { toast } from 'sonner'
import { queryGraphs } from '@/api/lightrag'
import { useBackendState } from '@/stores/state'
import { useSettingsStore } from '@/stores/settings'
import { useTenantState } from '@/stores/tenant'

import seedrandom from 'seedrandom'

const TYPE_SYNONYMS: Record<string, string> = {
  'unknown': 'unknown',
  '未知': 'unknown',

  'other': 'other',
  '其它': 'other',

  'concept': 'concept',
  'object': 'concept',
  'type': 'concept',
  'category': 'concept',
  'model': 'concept',
  'project': 'concept',
  'condition': 'concept',
  'rule': 'concept',
  'regulation': 'concept',
  'article': 'concept',
  'law': 'concept',
  'legalclause': 'concept',
  'policy': 'concept',
  'disease': 'concept',
  '概念': 'concept',
  '对象': 'concept',
  '类别': 'concept',
  '分类': 'concept',
  '模型': 'concept',
  '项目': 'concept',
  '条件': 'concept',
  '规则': 'concept',
  '法律': 'concept',
  '法律条款': 'concept',
  '条文': 'concept',
  '政策': 'policy',
  '疾病': 'concept',

  'method': 'method',
  'process': 'method',
  '方法': 'method',
  '过程': 'method',

  'artifact': 'artifact',
  'technology': 'artifact',
  'tech': 'artifact',
  'product': 'artifact',
  'equipment': 'artifact',
  'device': 'artifact',
  'stuff': 'artifact',
  'component': 'artifact',
  'material': 'artifact',
  'chemical': 'artifact',
  'drug': 'artifact',
  'medicine': 'artifact',
  'food': 'artifact',
  'weapon': 'artifact',
  'arms': 'artifact',
  '人工制品': 'artifact',
  '人造物品': 'artifact',
  '技术': 'technology',
  '科技': 'technology',
  '产品': 'artifact',
  '设备': 'artifact',
  '装备': 'artifact',
  '物品': 'artifact',
  '材料': 'artifact',
  '化学': 'artifact',
  '药物': 'artifact',
  '食品': 'artifact',
  '武器': 'artifact',
  '军火': 'artifact',

  'naturalobject': 'naturalobject',
  'natural': 'naturalobject',
  'phenomena': 'naturalobject',
  'substance': 'naturalobject',
  'plant': 'naturalobject',
  '自然对象': 'naturalobject',
  '自然物体': 'naturalobject',
  '自然现象': 'naturalobject',
  '物质': 'naturalobject',
  '物体': 'naturalobject',

  'data': 'data',
  'figure': 'data',
  'value': 'data',
  '数据': 'data',
  '数字': 'data',
  '数值': 'data',

  'content': 'content',
  'book': 'content',
  'video': 'content',
  '内容': 'content',
  '作品': 'content',
  '书籍': 'content',
  '视频': 'content',

  'organization': 'organization',
  'org': 'organization',
  'company': 'organization',
  '组织': 'organization',
  '公司': 'organization',
  '机构': 'organization',
  '组织机构': 'organization',

  'event': 'event',
  '事件': 'event',
  'activity': 'event',
  '活动': 'event',

  'person': 'person',
  'people': 'person',
  'human': 'person',
  'role': 'person',
  '人物': 'person',
  '人类': 'person',
  '人': 'person',
  '角色': 'person',

  'creature': 'creature',
  'animal': 'creature',
  'beings': 'creature',
  'being': 'creature',
  'alien': 'creature',
  'ghost': 'creature',
  '动物': 'creature',
  '生物': 'creature',
  '神仙': 'creature',
  '鬼怪': 'creature',
  '妖怪': 'creature',

  'location': 'location',
  'geography': 'location',
  'geo': 'location',
  'place': 'location',
  'address': 'location',
  '地点': 'location',
  '位置': 'location',
  '地址': 'location',
  '地理': 'location',
  '地域': 'location',
};

// node type to color mapping
const NODE_TYPE_COLORS: Record<string, string> = {
  'person': '#4169E1', // RoyalBlue
  'creature': '#bd7ebe', // LightViolet
  'organization': '#00cc00', // LightGreen
  'location': '#cf6d17', // Carrot
  'event': '#00bfa0', // Turquoise
  'concept': '#e3493b', // GoogleRed
  'method': '#b71c1c', // red
  'content': '#0f558a', // NavyBlue
  'data': '#0000ff', // Blue
  'artifact': '#4421af', // DeepPurple
  'naturalobject': '#b2e061', // YellowGreen
  'other': '#f4d371', // Yellow
  'unknown': '#b0b0b0', // Yellow
};

// Extended colors pool - Used for unknown node types
const EXTENDED_COLORS = [
  '#84a3e1', // SkyBlue
  '#5a2c6d', // DeepViolet
  '#2F4F4F', // DarkSlateGray
  '#003366', // DarkBlue
  '#9b3a31', // DarkBrown
  '#00CED1', // DarkTurquoise
  '#b300b3', // Purple
  '#0f705d', // Green
  '#ff99cc', // Pale Pink
  '#6ef7b3', // LightGreen
  '#cd071e', // ChinaRed
];

// Select color based on node type
const getNodeColorByType = (nodeType: string | undefined): string => {

  const defaultColor = '#5D6D7E';

  const normalizedType = nodeType ? nodeType.toLowerCase() : 'unknown';
  const typeColorMap = useGraphStore.getState().typeColorMap;

  // First try to find standard type
  const standardType = TYPE_SYNONYMS[normalizedType];

  // Check cache using standard type if available, otherwise use normalized type
  const cacheKey = standardType || normalizedType;
  if (typeColorMap.has(cacheKey)) {
    return typeColorMap.get(cacheKey) || defaultColor;
  }

  if (standardType) {
    const color = NODE_TYPE_COLORS[standardType];
    // Store using standard type name as key
    const newMap = new Map(typeColorMap);
    newMap.set(standardType, color);
    useGraphStore.setState({ typeColorMap: newMap });
    return color;
  }

  // For unpredefind nodeTypes, use extended colors
  // Find used extended colors
  const usedExtendedColors = new Set(
    Array.from(typeColorMap.entries())
      .filter(([, color]) => !Object.values(NODE_TYPE_COLORS).includes(color))
      .map(([, color]) => color)
  );

  // Find and use the first unused extended color
  const unusedColor = EXTENDED_COLORS.find(color => !usedExtendedColors.has(color));
  const newColor = unusedColor || defaultColor;

  // Update color mapping - use normalized type for unknown types
  const newMap = new Map(typeColorMap);
  newMap.set(normalizedType, newColor);
  useGraphStore.setState({ typeColorMap: newMap });

  return newColor;
};


const validateGraph = (graph: RawGraph) => {
  // Check if graph exists
  if (!graph) {
    console.log('Graph validation failed: graph is null');
    return false;
  }

  // Check if nodes and edges are arrays
  if (!Array.isArray(graph.nodes) || !Array.isArray(graph.edges)) {
    console.log('Graph validation failed: nodes or edges is not an array');
    return false;
  }

  // Check if nodes array is empty
  if (graph.nodes.length === 0) {
    console.log('Graph validation failed: nodes array is empty');
    return false;
  }

  // Validate each node
  for (const node of graph.nodes) {
    if (!node.id || !node.labels || !node.properties) {
      console.log('Graph validation failed: invalid node structure');
      return false;
    }
  }

  // Validate each edge
  for (const edge of graph.edges) {
    if (!edge.id || !edge.source || !edge.target) {
      console.log('Graph validation failed: invalid edge structure');
      return false;
    }
  }

  // Validate edge connections
  for (const edge of graph.edges) {
    const source = graph.getNode(edge.source);
    const target = graph.getNode(edge.target);
    if (source == undefined || target == undefined) {
      console.log('Graph validation failed: edge references non-existent node');
      return false;
    }
  }

  console.log('Graph validation passed');
  return true;
}

export type NodeType = {
  x: number
  y: number
  label: string
  size: number
  color: string
  highlighted?: boolean
}
export type EdgeType = {
  label: string
  originalWeight?: number
  size?: number
  color?: string
  hidden?: boolean
}

const fetchGraph = async (label: string, maxDepth: number, maxNodes: number) => {
  let rawData: any = null;

  // Trigger GraphLabels component to check if the label is valid
  // console.log('Setting labelsFetchAttempted to true');
  useGraphStore.getState().setLabelsFetchAttempted(true)

  // If label is empty, use default label '*'
  const queryLabel = label || '*';

  try {
    console.log(`Fetching graph label: ${queryLabel}, depth: ${maxDepth}, nodes: ${maxNodes}`);
    rawData = await queryGraphs(queryLabel, maxDepth, maxNodes);
  } catch (e) {
    useBackendState.getState().setErrorMessage(errorMessage(e), 'Query Graphs Error!');
    return null;
  }

  let rawGraph = null;

  if (rawData) {
    const nodeIdMap: Record<string, number> = {}
    const edgeIdMap: Record<string, number> = {}

    for (let i = 0; i < rawData.nodes.length; i++) {
      const node = rawData.nodes[i]
      nodeIdMap[node.id] = i

      node.x = Math.random()
      node.y = Math.random()
      node.degree = 0
      node.size = 10
    }

    for (let i = 0; i < rawData.edges.length; i++) {
      const edge = rawData.edges[i]
      edgeIdMap[edge.id] = i

      const source = nodeIdMap[edge.source]
      const target = nodeIdMap[edge.target]
      if (source !== undefined && target !== undefined) {
        const sourceNode = rawData.nodes[source]
        const targetNode = rawData.nodes[target]
        if (!sourceNode) {
          console.error(`Source node ${edge.source} is undefined`)
          continue
        }
        if (!targetNode) {
          console.error(`Target node ${edge.target} is undefined`)
          continue
        }
        sourceNode.degree += 1
        targetNode.degree += 1
      }
    }

    // generate node size
    let minDegree = Number.MAX_SAFE_INTEGER
    let maxDegree = 0

    for (const node of rawData.nodes) {
      minDegree = Math.min(minDegree, node.degree)
      maxDegree = Math.max(maxDegree, node.degree)
    }
    const range = maxDegree - minDegree
    if (range > 0) {
      const scale = Constants.maxNodeSize - Constants.minNodeSize
      for (const node of rawData.nodes) {
        node.size = Math.round(
          Constants.minNodeSize + scale * Math.pow((node.degree - minDegree) / range, 0.5)
        )
      }
    }

    rawGraph = new RawGraph()
    rawGraph.nodes = rawData.nodes
    rawGraph.edges = rawData.edges
    rawGraph.nodeIdMap = nodeIdMap
    rawGraph.edgeIdMap = edgeIdMap

    if (!validateGraph(rawGraph)) {
      rawGraph = null
      console.warn('Invalid graph data')
    }
    console.log('Graph data loaded')
  }

  // console.debug({ data: JSON.parse(JSON.stringify(rawData)) })
  return { rawGraph, is_truncated: rawData.is_truncated }
}

// Create a new graph instance with the raw graph data
const createSigmaGraph = (rawGraph: RawGraph | null) => {
  // Get edge size settings from store
  const minEdgeSize = useSettingsStore.getState().minEdgeSize
  const maxEdgeSize = useSettingsStore.getState().maxEdgeSize
  // Skip graph creation if no data or empty nodes
  if (!rawGraph || !rawGraph.nodes.length) {
    console.log('No graph data available, skipping sigma graph creation');
    return null;
  }

  // Create new graph instance
  const graph = new UndirectedGraph()

  // Add nodes from raw graph data
  for (const rawNode of rawGraph?.nodes ?? []) {
    // Ensure we have fresh random positions for nodes
    seedrandom(rawNode.id + Date.now().toString(), { global: true })
    const x = Math.random()
    const y = Math.random()

    graph.addNode(rawNode.id, {
      label: rawNode.labels.join(', '),
      color: rawNode.color,
      x: x,
      y: y,
      size: rawNode.size,
      // for node-border
      borderColor: Constants.nodeBorderColor,
      borderSize: 0.2
    })
  }

  // Add edges from raw graph data
  for (const rawEdge of rawGraph?.edges ?? []) {
    // Get weight from edge properties or default to 1
    const weight = rawEdge.properties?.weight !== undefined ? Number(rawEdge.properties.weight) : 1

    rawEdge.dynamicId = graph.addEdge(rawEdge.source, rawEdge.target, {
      label: rawEdge.properties?.keywords || undefined,
      size: weight, // Set initial size based on weight
      originalWeight: weight, // Store original weight for recalculation
      type: 'curvedNoArrow' // Explicitly set edge type to no arrow
    })
  }

  // Calculate edge size based on weight range, similar to node size calculation
  let minWeight = Number.MAX_SAFE_INTEGER
  let maxWeight = 0

  // Find min and max weight values
  graph.forEachEdge(edge => {
    const weight = graph.getEdgeAttribute(edge, 'originalWeight') || 1
    minWeight = Math.min(minWeight, weight)
    maxWeight = Math.max(maxWeight, weight)
  })

  // Scale edge sizes based on weight range
  const weightRange = maxWeight - minWeight
  if (weightRange > 0) {
    const sizeScale = maxEdgeSize - minEdgeSize
    graph.forEachEdge(edge => {
      const weight = graph.getEdgeAttribute(edge, 'originalWeight') || 1
      const scaledSize = minEdgeSize + sizeScale * Math.pow((weight - minWeight) / weightRange, 0.5)
      graph.setEdgeAttribute(edge, 'size', scaledSize)
    })
  } else {
    // If all weights are the same, use default size
    graph.forEachEdge(edge => {
      graph.setEdgeAttribute(edge, 'size', minEdgeSize)
    })
  }

  return graph
}

const useLightrangeGraph = () => {
  const { t } = useTranslation()
  const queryLabel = useSettingsStore.use.queryLabel()
  const rawGraph = useGraphStore.use.rawGraph()
  const sigmaGraph = useGraphStore.use.sigmaGraph()
  const maxQueryDepth = useSettingsStore.use.graphQueryMaxDepth()
  const maxNodes = useSettingsStore.use.graphMaxNodes()
  const isFetching = useGraphStore.use.isFetching()
  const nodeToExpand = useGraphStore.use.nodeToExpand()
  const nodeToPrune = useGraphStore.use.nodeToPrune()
  const graphDataVersion = useGraphStore.use.graphDataVersion()
  const selectedKB = useTenantState.use.selectedKB()


  // Use ref to track if data has been loaded and initial load
  const dataLoadedRef = useRef(false)
  const initialLoadRef = useRef(false)
  // Use ref to track if empty data has been handled
  const emptyDataHandledRef = useRef(false)

  const getNode = useCallback(
    (nodeId: string) => {
      return rawGraph?.getNode(nodeId) || null
    },
    [rawGraph]
  )

  const getEdge = useCallback(
    (edgeId: string, dynamicId: boolean = true) => {
      return rawGraph?.getEdge(edgeId, dynamicId) || null
    },
    [rawGraph]
  )

  // Track if a fetch is in progress to prevent multiple simultaneous fetches
  const fetchInProgressRef = useRef(false)

  // Reset graph when query label is cleared
  useEffect(() => {
    if (!queryLabel && (rawGraph !== null || sigmaGraph !== null)) {
      const state = useGraphStore.getState()
      state.reset()
      state.setGraphDataFetchAttempted(false)
      state.setLabelsFetchAttempted(false)
      dataLoadedRef.current = false
      initialLoadRef.current = false
    }
  }, [queryLabel, rawGraph, sigmaGraph])

  // Reset fetch attempt when KB changes to trigger re-fetch
  useEffect(() => {
    const state = useGraphStore.getState()
    
    // Clear current graph immediately when KB changes
    state.reset()
    
    // Reset all flags to allow new fetch
    state.setGraphDataFetchAttempted(false)
    state.setLabelsFetchAttempted(false)
    
    // Reset refs to allow new fetch
    dataLoadedRef.current = false
    initialLoadRef.current = false
    fetchInProgressRef.current = false
    emptyDataHandledRef.current = false
    
    console.log('[useLightragGraph] KB changed, reset graph state')
  }, [selectedKB?.kb_id])

  // Graph data fetching logic
  useEffect(() => {
    // Skip if fetch is already in progress
    if (fetchInProgressRef.current) {
      return
    }

    // Empty queryLabel should be only handle once(avoid infinite loop)
    if (!queryLabel && emptyDataHandledRef.current) {
      return;
    }

    // Only fetch data when graphDataFetchAttempted is false (avoids re-fetching on vite dev mode)
    // GraphDataFetchAttempted must set to false when queryLabel is changed
    if (!isFetching && !useGraphStore.getState().graphDataFetchAttempted) {
      // Set flags
      fetchInProgressRef.current = true
      useGraphStore.getState().setGraphDataFetchAttempted(true)

      const state = useGraphStore.getState()
      state.setIsFetching(true)

      // Clear selection and highlighted nodes before fetching new graph
      state.clearSelection()
      if (state.sigmaGraph) {
        state.sigmaGraph.forEachNode((node) => {
          state.sigmaGraph?.setNodeAttribute(node, 'highlighted', false)
        })
      }

      console.log('Preparing graph data...')

      // Use a local copy of the parameters
      const currentQueryLabel = queryLabel
      const currentMaxQueryDepth = maxQueryDepth
      const currentMaxNodes = maxNodes

      // Declare a variable to store data promise
      let dataPromise: Promise<{ rawGraph: RawGraph | null; is_truncated: boolean | undefined } | null>;

      // 1. If query label is not empty, use fetchGraph
      if (currentQueryLabel) {
        dataPromise = fetchGraph(currentQueryLabel, currentMaxQueryDepth, currentMaxNodes);
      } else {
        // 2. If query label is empty, set data to null
        console.log('Query label is empty, show empty graph')
        dataPromise = Promise.resolve({ rawGraph: null, is_truncated: false });
      }

      // 3. Process data
      dataPromise.then((result) => {
        const state = useGraphStore.getState()
        const data = result?.rawGraph;

        // Assign colors based on entity_type *after* fetching
        if (data && data.nodes) {
          data.nodes.forEach(node => {
            // Use entity_type instead of type
            const nodeEntityType = node.properties?.entity_type as string | undefined;
            node.color = getNodeColorByType(nodeEntityType);
          });
        }

        if (result?.is_truncated) {
          toast.info(t('graphPanel.dataIsTruncated', 'Graph data is truncated to Max Nodes'));
        }

        // Reset state
        state.reset()

        // Check if data is empty or invalid
        if (!data || !data.nodes || data.nodes.length === 0) {
          // Create a graph with a single "Graph Is Empty" node
          const emptyGraph = new UndirectedGraph();

          // Add a single node with "Graph Is Empty" label
          emptyGraph.addNode('empty-graph-node', {
            label: t('graphPanel.emptyGraph'),
            color: '#5D6D7E', // gray color
            x: 0.5,
            y: 0.5,
            size: 15,
            borderColor: Constants.nodeBorderColor,
            borderSize: 0.2
          });

          // Set graph to store
          state.setSigmaGraph(emptyGraph);
          state.setRawGraph(null);

          // Still mark graph as empty for other logic
          state.setGraphIsEmpty(true);

          // Check if the empty graph is due to 401 authentication error
          const errorMessage = useBackendState.getState().message;
          const isAuthError = errorMessage && errorMessage.includes('Authentication required');

          // Only clear queryLabel if it's not an auth error and current label is not empty
          if (!isAuthError && currentQueryLabel) {
            useSettingsStore.getState().setQueryLabel('');
          }

          // Only clear last successful query label if it's not an auth error
          if (!isAuthError) {
            state.setLastSuccessfulQueryLabel('');
          } else {
            console.log('Keep queryLabel for post-login reload');
          }

          console.log(`Graph data is empty, created graph with empty graph node. Auth error: ${isAuthError}`);
        } else {
          // Create and set new graph
          const newSigmaGraph = createSigmaGraph(data);
          data.buildDynamicMap();

          // Set new graph data
          state.setSigmaGraph(newSigmaGraph);
          state.setRawGraph(data);
          state.setGraphIsEmpty(false);

          // Update last successful query label
          state.setLastSuccessfulQueryLabel(currentQueryLabel);

          // Reset camera view
          state.setMoveToSelectedNode(true);
        }

        // Update flags
        dataLoadedRef.current = true
        initialLoadRef.current = true
        fetchInProgressRef.current = false
        state.setIsFetching(false)

        // Mark empty data as handled if data is empty and query label is empty
        if ((!data || !data.nodes || data.nodes.length === 0) && !currentQueryLabel) {
          emptyDataHandledRef.current = true;
        }
      }).catch((error) => {
        console.error('Error fetching graph data:', error)

        // Reset state on error
        const state = useGraphStore.getState()
        state.setIsFetching(false)
        dataLoadedRef.current = false;
        fetchInProgressRef.current = false
        state.setGraphDataFetchAttempted(false)
        state.setLastSuccessfulQueryLabel('') // Clear last successful query label on error
      })
    }
  }, [queryLabel, maxQueryDepth, maxNodes, isFetching, t, graphDataVersion, selectedKB?.kb_id])

  // Handle node expansion
  useEffect(() => {
    const handleNodeExpand = async (nodeId: string | null) => {
      if (!nodeId || !sigmaGraph || !rawGraph) return;

      try {
        // Get the node to expand
        const nodeToExpand = rawGraph.getNode(nodeId);
        if (!nodeToExpand) {
          console.error('Node not found:', nodeId);
          return;
        }

        // Get the label of the node to expand
        const label = nodeToExpand.labels[0];
        if (!label) {
          console.error('Node has no label:', nodeId);
          return;
        }

        // Fetch the extended subgraph with depth 2
        const extendedGraph = await queryGraphs(label, 2, 1000);

        if (!extendedGraph || !extendedGraph.nodes || !extendedGraph.edges) {
          console.error('Failed to fetch extended graph');
          return;
        }

        // Process nodes to add required properties for RawNodeType
        const processedNodes: RawNodeType[] = [];
        for (const node of extendedGraph.nodes) {
          // Generate random color values
          seedrandom(node.id, { global: true });
          const nodeEntityType = node.properties?.entity_type as string | undefined;
          const color = getNodeColorByType(nodeEntityType);

          // Create a properly typed RawNodeType
          processedNodes.push({
            id: node.id,
            labels: node.labels,
            properties: node.properties,
            size: 10, // Default size, will be calculated later
            x: Math.random(), // Random position, will be adjusted later
            y: Math.random(), // Random position, will be adjusted later
            color: color, // Random color
            degree: 0 // Initial degree, will be calculated later
          });
        }

        // Process edges to add required properties for RawEdgeType
        const processedEdges: RawEdgeType[] = [];
        for (const edge of extendedGraph.edges) {
          // Create a properly typed RawEdgeType
          processedEdges.push({
            id: edge.id,
            source: edge.source,
            target: edge.target,
            type: edge.type,
            properties: edge.properties,
            dynamicId: '' // Will be set when adding to sigma graph
          });
        }

        // Store current node positions
        const nodePositions: Record<string, {x: number, y: number}> = {};
        sigmaGraph.forEachNode((node) => {
          nodePositions[node] = {
            x: sigmaGraph.getNodeAttribute(node, 'x'),
            y: sigmaGraph.getNodeAttribute(node, 'y')
          };
        });

        // Get existing node IDs
        const existingNodeIds = new Set(sigmaGraph.nodes());

        // Identify nodes and edges to keep
        const nodesToAdd = new Set<string>();
        const edgesToAdd = new Set<string>();

        // Get degree maxDegree from existing graph for size calculations
        const minDegree = 1;
        let maxDegree = 0;

        // Initialize edge weight min and max values
        let minWeight = Number.MAX_SAFE_INTEGER;
        let maxWeight = 0;

        // Calculate node degrees and edge weights from existing graph
        sigmaGraph.forEachNode(node => {
          const degree = sigmaGraph.degree(node);
          maxDegree = Math.max(maxDegree, degree);
        });

        // Calculate edge weights from existing graph
        sigmaGraph.forEachEdge(edge => {
          const weight = sigmaGraph.getEdgeAttribute(edge, 'originalWeight') || 1;
          minWeight = Math.min(minWeight, weight);
          maxWeight = Math.max(maxWeight, weight);
        });

        // First identify connectable nodes (nodes connected to the expanded node)
        for (const node of processedNodes) {
          // Skip if node already exists
          if (existingNodeIds.has(node.id)) {
            continue;
          }

          // Check if this node is connected to the selected node
          const isConnected = processedEdges.some(
            edge => (edge.source === nodeId && edge.target === node.id) ||
                   (edge.target === nodeId && edge.source === node.id)
          );

          if (isConnected) {
            nodesToAdd.add(node.id);
          }
        }

        // Calculate node degrees and track discarded edges in one pass
        const nodeDegrees = new Map<string, number>();
        const existingNodeDegreeIncrements = new Map<string, number>(); // Track degree increments for existing nodes
        const nodesWithDiscardedEdges = new Set<string>();

        for (const edge of processedEdges) {
          const sourceExists = existingNodeIds.has(edge.source) || nodesToAdd.has(edge.source);
          const targetExists = existingNodeIds.has(edge.target) || nodesToAdd.has(edge.target);

          if (sourceExists && targetExists) {
            edgesToAdd.add(edge.id);
            // Add degrees for both new and existing nodes
            if (nodesToAdd.has(edge.source)) {
              nodeDegrees.set(edge.source, (nodeDegrees.get(edge.source) || 0) + 1);
            } else if (existingNodeIds.has(edge.source)) {
              // Track degree increments for existing nodes
              existingNodeDegreeIncrements.set(edge.source, (existingNodeDegreeIncrements.get(edge.source) || 0) + 1);
            }

            if (nodesToAdd.has(edge.target)) {
              nodeDegrees.set(edge.target, (nodeDegrees.get(edge.target) || 0) + 1);
            } else if (existingNodeIds.has(edge.target)) {
              // Track degree increments for existing nodes
              existingNodeDegreeIncrements.set(edge.target, (existingNodeDegreeIncrements.get(edge.target) || 0) + 1);
            }
          } else {
            // Track discarded edges for both new and existing nodes
            if (sigmaGraph.hasNode(edge.source)) {
              nodesWithDiscardedEdges.add(edge.source);
            } else if (nodesToAdd.has(edge.source)) {
              nodesWithDiscardedEdges.add(edge.source);
              nodeDegrees.set(edge.source, (nodeDegrees.get(edge.source) || 0) + 1); // +1 for discarded edge
            }
            if (sigmaGraph.hasNode(edge.target)) {
              nodesWithDiscardedEdges.add(edge.target);
            } else if (nodesToAdd.has(edge.target)) {
              nodesWithDiscardedEdges.add(edge.target);
              nodeDegrees.set(edge.target, (nodeDegrees.get(edge.target) || 0) + 1); // +1 for discarded edge
            }
          }
        }

        // Helper function to update node sizes
        const updateNodeSizes = (
          sigmaGraph: UndirectedGraph,
          nodesWithDiscardedEdges: Set<string>,
          minDegree: number,
          maxDegree: number
        ) => {
          // Calculate derived values inside the function
          const range = maxDegree - minDegree || 1; // Avoid division by zero
          const scale = Constants.maxNodeSize - Constants.minNodeSize;

          // Update node sizes
          for (const nodeId of nodesWithDiscardedEdges) {
            if (sigmaGraph.hasNode(nodeId)) {
              let newDegree = sigmaGraph.degree(nodeId);
              newDegree += 1; // Add +1 for discarded edges
              // Limit newDegree to maxDegree + 1 to prevent nodes from being too large
              const limitedDegree = Math.min(newDegree, maxDegree + 1);

              const newSize = Math.round(
                Constants.minNodeSize + scale * Math.pow((limitedDegree - minDegree) / range, 0.5)
              );

              sigmaGraph.setNodeAttribute(nodeId, 'size', newSize);
            }
          }
        };

        // Helper function to update edge sizes
        const updateEdgeSizes = (
          sigmaGraph: UndirectedGraph,
          minWeight: number,
          maxWeight: number
        ) => {
          // Update edge sizes
          const minEdgeSize = useSettingsStore.getState().minEdgeSize;
          const maxEdgeSize = useSettingsStore.getState().maxEdgeSize;
          const weightRange = maxWeight - minWeight || 1; // Avoid division by zero
          const sizeScale = maxEdgeSize - minEdgeSize;

          sigmaGraph.forEachEdge(edge => {
            const weight = sigmaGraph.getEdgeAttribute(edge, 'originalWeight') || 1;
            const scaledSize = minEdgeSize + sizeScale * Math.pow((weight - minWeight) / weightRange, 0.5);
            sigmaGraph.setEdgeAttribute(edge, 'size', scaledSize);
          });
        };

        // If no new connectable nodes found, show toast and return
        if (nodesToAdd.size === 0) {
          updateNodeSizes(sigmaGraph, nodesWithDiscardedEdges, minDegree, maxDegree);
          toast.info(t('graphPanel.propertiesView.node.noNewNodes'));
          return;
        }

        // Update maxDegree considering all nodes (both new and existing)
        // 1. Consider degrees of new nodes
        for (const [, degree] of nodeDegrees.entries()) {
          maxDegree = Math.max(maxDegree, degree);
        }

        // 2. Consider degree increments for existing nodes
        for (const [nodeId, increment] of existingNodeDegreeIncrements.entries()) {
          const currentDegree = sigmaGraph.degree(nodeId);
          const projectedDegree = currentDegree + increment;
          maxDegree = Math.max(maxDegree, projectedDegree);
        }

        const range = maxDegree - minDegree || 1; // Avoid division by zero
        const scale = Constants.maxNodeSize - Constants.minNodeSize;

        // SAdd nodes and edges to the graph
        // Calculate camera ratio and spread factor once before the loop
        const cameraRatio = useGraphStore.getState().sigmaInstance?.getCamera().ratio || 1;
        const spreadFactor = Math.max(
          Math.sqrt(nodeToExpand.size) * 4, // Base on node size
          Math.sqrt(nodesToAdd.size) * 3 // Scale with number of nodes
        ) / cameraRatio; // Adjust for zoom level
        seedrandom(Date.now().toString(), { global: true });
        const randomAngle = Math.random() * 2 * Math.PI

        console.log('nodeSize:', nodeToExpand.size, 'nodesToAdd:', nodesToAdd.size);
        console.log('cameraRatio:', Math.round(cameraRatio*100)/100, 'spreadFactor:', Math.round(spreadFactor*100)/100);

        // Add new nodes
        for (const nodeId of nodesToAdd) {
          const newNode = processedNodes.find(n => n.id === nodeId)!;
          const nodeDegree = nodeDegrees.get(nodeId) || 0;

          // Calculate node size
          // Limit nodeDegree to maxDegree + 1 to prevent new nodes from being too large
          const limitedDegree = Math.min(nodeDegree, maxDegree + 1);
          const nodeSize = Math.round(
            Constants.minNodeSize + scale * Math.pow((limitedDegree - minDegree) / range, 0.5)
          );

          // Calculate angle for polar coordinates
          const angle = 2 * Math.PI * (Array.from(nodesToAdd).indexOf(nodeId) / nodesToAdd.size);

          // Calculate final position
          const x = nodePositions[nodeId]?.x ||
                    (nodePositions[nodeToExpand.id].x + Math.cos(randomAngle + angle) * spreadFactor);
          const y = nodePositions[nodeId]?.y ||
                    (nodePositions[nodeToExpand.id].y + Math.sin(randomAngle + angle) * spreadFactor);

          // Add the new node to the sigma graph with calculated position
          sigmaGraph.addNode(nodeId, {
            label: newNode.labels.join(', '),
            color: newNode.color,
            x: x,
            y: y,
            size: nodeSize,
            borderColor: Constants.nodeBorderColor,
            borderSize: 0.2
          });

          // Add the node to the raw graph
          if (!rawGraph.getNode(nodeId)) {
            // Update node properties
            newNode.size = nodeSize;
            newNode.x = x;
            newNode.y = y;
            newNode.degree = nodeDegree;

            // Add to nodes array
            rawGraph.nodes.push(newNode);
            // Update nodeIdMap
            rawGraph.nodeIdMap[nodeId] = rawGraph.nodes.length - 1;
          }
        }

        // Add new edges
        for (const edgeId of edgesToAdd) {
          const newEdge = processedEdges.find(e => e.id === edgeId)!;

          // Skip if edge already exists
          if (sigmaGraph.hasEdge(newEdge.source, newEdge.target)) {
            continue;
          }

          // Get weight from edge properties or default to 1
          const weight = newEdge.properties?.weight !== undefined ? Number(newEdge.properties.weight) : 1;

          // Update min and max weight values
          minWeight = Math.min(minWeight, weight);
          maxWeight = Math.max(maxWeight, weight);

          // Add the edge to the sigma graph
          newEdge.dynamicId = sigmaGraph.addEdge(newEdge.source, newEdge.target, {
            label: newEdge.properties?.keywords || undefined,
            size: weight, // Set initial size based on weight
            originalWeight: weight, // Store original weight for recalculation
            type: 'curvedNoArrow' // Explicitly set edge type to no arrow
          });

          // Add the edge to the raw graph
          if (!rawGraph.getEdge(newEdge.id, false)) {
            // Add to edges array
            rawGraph.edges.push(newEdge);
            // Update edgeIdMap
            rawGraph.edgeIdMap[newEdge.id] = rawGraph.edges.length - 1;
            // Update dynamic edge map
            rawGraph.edgeDynamicIdMap[newEdge.dynamicId] = rawGraph.edges.length - 1;
          } else {
            console.error('Edge already exists in rawGraph:', newEdge.id);
          }
        }

        // Update the dynamic edge map and invalidate search cache
        rawGraph.buildDynamicMap();

        // Reset search engine to force rebuild
        useGraphStore.getState().resetSearchEngine();

        // Update sizes for all nodes and edges
        updateNodeSizes(sigmaGraph, nodesWithDiscardedEdges, minDegree, maxDegree);
        updateEdgeSizes(sigmaGraph, minWeight, maxWeight);

        // Final update for the expanded node
        if (sigmaGraph.hasNode(nodeId)) {
          const finalDegree = sigmaGraph.degree(nodeId);
          const limitedDegree = Math.min(finalDegree, maxDegree + 1);
          const newSize = Math.round(
            Constants.minNodeSize + scale * Math.pow((limitedDegree - minDegree) / range, 0.5)
          );
          sigmaGraph.setNodeAttribute(nodeId, 'size', newSize);
          nodeToExpand.size = newSize;
          nodeToExpand.degree = finalDegree;
        }

      } catch (error) {
        console.error('Error expanding node:', error);
      }
    };

    // If there's a node to expand, handle it
    if (nodeToExpand) {
      handleNodeExpand(nodeToExpand);
      // Reset the nodeToExpand state after handling
      window.setTimeout(() => {
        useGraphStore.getState().triggerNodeExpand(null);
      }, 0);
    }
  }, [nodeToExpand, sigmaGraph, rawGraph, t]);

  // Helper function to get all nodes that will be deleted
  const getNodesThatWillBeDeleted = useCallback((nodeId: string, graph: UndirectedGraph) => {
    const nodesToDelete = new Set<string>([nodeId]);

    // Find all nodes that would become isolated after deletion
    graph.forEachNode((node) => {
      if (node === nodeId) return; // Skip the node being deleted

      // Get all neighbors of this node
      const neighbors = graph.neighbors(node);

      // If this node has only one neighbor and that neighbor is the node being deleted,
      // this node will become isolated, so we should delete it too
      if (neighbors.length === 1 && neighbors[0] === nodeId) {
        nodesToDelete.add(node);
      }
    });

    return nodesToDelete;
  }, []);

  // Handle node pruning
  useEffect(() => {
    const handleNodePrune = (nodeId: string | null) => {
      if (!nodeId || !sigmaGraph || !rawGraph) return;

      try {
        const state = useGraphStore.getState();

        // 1. Check if node exists
        if (!sigmaGraph.hasNode(nodeId)) {
          console.error('Node not found:', nodeId);
          return;
        }

        // 2. Get nodes to delete
        const nodesToDelete = getNodesThatWillBeDeleted(nodeId, sigmaGraph);

        // 3. Check if this would delete all nodes
        if (nodesToDelete.size === sigmaGraph.nodes().length) {
          toast.error(t('graphPanel.propertiesView.node.deleteAllNodesError'));
          return;
        }

        // 4. Clear selection - this will cause PropertiesView to close immediately
        state.clearSelection();

        // 5. Delete nodes and related edges
        for (const nodeToDelete of nodesToDelete) {
          // Remove the node from the sigma graph (this will also remove connected edges)
          sigmaGraph.dropNode(nodeToDelete);

          // Remove the node from the raw graph
          const nodeIndex = rawGraph.nodeIdMap[nodeToDelete];
          if (nodeIndex !== undefined) {
            // Find all edges connected to this node
            const edgesToRemove = rawGraph.edges.filter(
              edge => edge.source === nodeToDelete || edge.target === nodeToDelete
            );

            // Remove edges from raw graph
            for (const edge of edgesToRemove) {
              const edgeIndex = rawGraph.edgeIdMap[edge.id];
              if (edgeIndex !== undefined) {
                // Remove from edges array
                rawGraph.edges.splice(edgeIndex, 1);
                // Update edgeIdMap for all edges after this one
                for (const [id, idx] of Object.entries(rawGraph.edgeIdMap)) {
                  if (idx > edgeIndex) {
                    rawGraph.edgeIdMap[id] = idx - 1;
                  }
                }
                // Remove from edgeIdMap
                delete rawGraph.edgeIdMap[edge.id];
                // Remove from edgeDynamicIdMap
                delete rawGraph.edgeDynamicIdMap[edge.dynamicId];
              }
            }

            // Remove node from nodes array
            rawGraph.nodes.splice(nodeIndex, 1);

            // Update nodeIdMap for all nodes after this one
            for (const [id, idx] of Object.entries(rawGraph.nodeIdMap)) {
              if (idx > nodeIndex) {
                rawGraph.nodeIdMap[id] = idx - 1;
              }
            }

            // Remove from nodeIdMap
            delete rawGraph.nodeIdMap[nodeToDelete];
          }
        }

        // Rebuild the dynamic edge map and invalidate search cache
        rawGraph.buildDynamicMap();

        // Reset search engine to force rebuild
        useGraphStore.getState().resetSearchEngine();

        // Show notification if we deleted more than just the selected node
        if (nodesToDelete.size > 1) {
          toast.info(t('graphPanel.propertiesView.node.nodesRemoved', { count: nodesToDelete.size }));
        }


      } catch (error) {
        console.error('Error pruning node:', error);
      }
    };

    // If there's a node to prune, handle it
    if (nodeToPrune) {
      handleNodePrune(nodeToPrune);
      // Reset the nodeToPrune state after handling
      window.setTimeout(() => {
        useGraphStore.getState().triggerNodePrune(null);
      }, 0);
    }
  }, [nodeToPrune, sigmaGraph, rawGraph, getNodesThatWillBeDeleted, t]);

  const lightrageGraph = useCallback(() => {
    // If we already have a graph instance, return it
    if (sigmaGraph) {
      return sigmaGraph as Graph<NodeType, EdgeType>
    }

    // If no graph exists yet, create a new one and store it
    console.log('Creating new Sigma graph instance')
    const graph = new UndirectedGraph()
    useGraphStore.getState().setSigmaGraph(graph)
    return graph as Graph<NodeType, EdgeType>
  }, [sigmaGraph])

  return { lightrageGraph, getNode, getEdge }
}

export default useLightrangeGraph
