import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
import * as d3 from 'd3';
import { 
  Neo4jGraphData, 
  Neo4jNode, 
  Neo4jRelationship, 
  D3Node, 
  D3Link, 
  GraphConfig 
} from '../graphTypes/graphTypes';
import { 
  truncateText 
} from '../graphUtils/graphUtils';
import { 
  prepareGraphDataWithStablePositions, 
  getOptimalForceConfig 
} from '../graphUtils/enhancedGraphUtils';
import { defaultGraphConfig, graphStyles } from '../graphConfig/graphConfig';
import { CONSTANTS} from './graphCanva/constant';
import DragManager from './graphCanva/DragManager';
import NodeManager from './graphCanva/nodeManager';
import FlexibleLinkManager from './graphCanva/linkManager';

interface GraphCanvasProps {
  graphData: Neo4jGraphData;
  selectedNode: Neo4jNode | null;
  selectedRelationship: Neo4jRelationship | null;
  onNodeClick: (node: Neo4jNode | null) => void;
  onRelationshipClick: (relationship: Neo4jRelationship) => void;
  onNodeDoubleClick?: (node: Neo4jNode) => void;
  onAddNode?: (position: { x: number, y: number }) => void;
  config?: Partial<GraphConfig>;
  isAddingNode?: boolean;
  newNodePosition?: { x: number, y: number };
}

class SVGManager {
  static setupDefs(container: d3.Selection<SVGGElement, unknown, null, undefined>): void {
    const defs = container.append('defs');
    
    // Arrow markers
    this.createArrowMarkers(defs);
    
    // Glow effect
    this.createGlowEffect(defs);
  }

  private static createArrowMarkers(defs: d3.Selection<SVGDefsElement, unknown, null, undefined>): void {
    const { width, height, refX } = CONSTANTS.SIZES.ARROW_MARKER;
    
    defs.selectAll('marker')
      .data(['end', 'selected'])
      .enter().append('marker')
      .attr('id', d => d === 'selected' ? 'arrow-selected' : 'arrow')
      .attr('viewBox', '0 -5 10 10')
      .attr('refX', refX)
      .attr('refY', 0)
      .attr('markerWidth', width)
      .attr('markerHeight', height)
      .attr('orient', 'auto')
      .append('path')
      .attr('d', 'M0,-5L10,0L0,5')
      .attr('fill', d => d === 'selected' ? CONSTANTS.COLORS.SELECTION : CONSTANTS.COLORS.LINK_DEFAULT);
  }

  private static createGlowEffect(defs: d3.Selection<SVGDefsElement, unknown, null, undefined>): void {
    const filter = defs.append('filter')
      .attr('id', 'glow')
      .attr('x', '-50%')
      .attr('y', '-50%')
      .attr('width', '200%')
      .attr('height', '200%');

    filter.append('feGaussianBlur')
      .attr('stdDeviation', CONSTANTS.SIZES.GLOW_STD_DEVIATION)
      .attr('result', 'coloredBlur');

    const feMerge = filter.append('feMerge');
    feMerge.append('feMergeNode').attr('in', 'coloredBlur');
    feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
  }
}


const GraphCanvas: React.FC<GraphCanvasProps> = ({
  graphData,
  selectedNode,
  selectedRelationship,
  onNodeClick,
  onRelationshipClick,
  onNodeDoubleClick,
  onAddNode,
  config = {},
  isAddingNode = false,
  newNodePosition,
}) => {
  const svgRef = useRef<SVGSVGElement>(null);
  const simulationRef = useRef<d3.Simulation<D3Node, D3Link> | null>(null);
  const containerRef = useRef<SVGGElement>(null);
  const zoomRef = useRef<d3.ZoomBehavior<SVGSVGElement, unknown> | null>(null);
  const [addNodePosition, setAddNodePosition] = useState<{ x: number, y: number } | null>(null);
  
  const finalConfig = useMemo(() => ({ ...defaultGraphConfig, ...config }), [config]);

  // Simulation setup and update
  const setupSimulation = useCallback(() => {
    if (!svgRef.current || !graphData) return;

    // Clear previous content
    const svg = d3.select(svgRef.current);
    svg.selectAll('*').remove();

    const { width, height, nodeRadius } = finalConfig;
    svg.attr('width', width).attr('height', height);

    // Create container group for zoom/pan
    const container = svg.append('g');
    containerRef.current = container.node();

    // Setup zoom behavior
    const zoom = d3.zoom<SVGSVGElement, unknown>()
      .scaleExtent(CONSTANTS.ZOOM_SCALE_EXTENT)
      .on('zoom', (event) => {
        container.attr('transform', event.transform);
      });

    zoomRef.current = zoom;
    svg.call(zoom);

    // Setup SVG definitions (gradients, filters, markers)
    SVGManager.setupDefs(container);

    // Prepare graph data
    const { nodes, links } = prepareGraphDataWithStablePositions(
      graphData, 
      width, 
      height, 
      newNodePosition
    );

    // Create simulation with optimal configuration
    const forceConfig = getOptimalForceConfig(nodes.length, links.length);
    const simulation = d3.forceSimulation(nodes)
      .force('link', d3.forceLink(links).id((d: any) => d.id)
        .distance(forceConfig.linkDistance)
        .strength(0.5)
      )
      .force('charge', d3.forceManyBody().strength(forceConfig.chargeStrength))
      .force('center', d3.forceCenter(width / 2, height / 2).strength(forceConfig.centerStrength))
      .force('collision', d3.forceCollide().radius(forceConfig.collisionRadius))
      .alpha(CONSTANTS.ANIMATION.INITIAL_ALPHA)
      .alphaDecay(CONSTANTS.ANIMATION.ALPHA_DECAY)
      .velocityDecay(CONSTANTS.ANIMATION.VELOCITY_DECAY);

    simulationRef.current = simulation;

    // Create flexible links
    const { linkPaths, linkClickPaths } = FlexibleLinkManager.createFlexibleLinks(
      container,
      links,
      onRelationshipClick,
      selectedRelationship,
      isAddingNode
    );

    // Add link labels
    const linkLabels = container.selectAll('.link-label')
      .data(links)
      .enter().append('text')
      .attr('class', 'link-label')
      .attr('text-anchor', 'middle')
      .attr('font-size', graphStyles.linkLabel.fontSize)
      .attr('font-family', graphStyles.linkLabel.fontFamily)
      .attr('font-weight', graphStyles.linkLabel.fontWeight)
      .attr('fill', CONSTANTS.COLORS.TEXT_DEFAULT)
      .attr('pointer-events', 'none')
      .text(d => truncateText(d.type, 15));

    // Create nodes with drag capability
    const nodeElements = NodeManager.createNodes(
      container,
      svg,
      nodes,
      nodeRadius,
      simulation, // Pass simulation for drag behavior
      onNodeClick,
      onNodeDoubleClick,
      selectedNode,
      isAddingNode
    );

    // Create node labels
    const nodeLabels = NodeManager.createNodeLabels(container, nodes);

    // Create level indicators
    const levelIndicators = NodeManager.createLevelIndicators(container, nodes);

    // Update positions on simulation tick
    simulation.on('tick', () => {
      // Update flexible link positions
      FlexibleLinkManager.updateFlexibleLinkPositions(linkPaths, linkClickPaths, nodeRadius);

      // Update link labels
      linkLabels
        .attr('x', d => ((d.source as D3Node).x! + (d.target as D3Node).x!) / 2)
        .attr('y', d => ((d.source as D3Node).y! + (d.target as D3Node).y!) / 2 - 8);

      // Update node positions
      nodeElements
        .attr('cx', d => d.x!)
        .attr('cy', d => d.y!);

      nodeLabels
        .attr('x', d => d.x!)
        .attr('y', d => d.y!);

      levelIndicators
        .attr('cx', d => d.x! + nodeRadius - 3)
        .attr('cy', d => d.y! - nodeRadius + 3);
    });

    // Handle background click
    svg.on('click', (event) => {
      if (isAddingNode && onAddNode) {
        const [x, y] = d3.pointer(event, containerRef.current);
        onAddNode({ x, y });
        setAddNodePosition(null);
      } else {
        onNodeClick(null);
      }
    });

    // Cleanup function
    return () => {
      if (simulation) {
        simulation.alphaTarget(0);
        simulation.alpha(0);
        simulation.stop();
      }
    };

  }, [
    graphData, 
    finalConfig, 
    onNodeClick, 
    onRelationshipClick, 
    onNodeDoubleClick, 
    onAddNode, 
    isAddingNode, 
    newNodePosition,
    selectedRelationship
  ]);

  // Preview node management
  const updatePreviewNode = useCallback(() => {
    if (!svgRef.current || !isAddingNode || !addNodePosition) return;

    const svg = d3.select(svgRef.current);
    const container = svg.select('g');
    
    // Remove existing preview
    container.selectAll('.preview-node, .preview-label').remove();
    
    // Add preview node
    container.append('circle')
      .attr('class', 'preview-node')
      .attr('cx', addNodePosition.x)
      .attr('cy', addNodePosition.y)
      .attr('r', finalConfig.nodeRadius)
      .attr('fill', CONSTANTS.COLORS.PREVIEW_NODE)
      .attr('stroke', CONSTANTS.COLORS.NODE_STROKE)
      .attr('stroke-width', 3)
      .attr('opacity', CONSTANTS.SIZES.PREVIEW_OPACITY)
      .attr('stroke-dasharray', '5,5')
      .style('pointer-events', 'none');

    // Add preview label
    container.append('text')
      .attr('class', 'preview-label')
      .attr('x', addNodePosition.x)
      .attr('y', addNodePosition.y)
      .attr('text-anchor', 'middle')
      .attr('dy', '.35em')
      .attr('font-size', graphStyles.text.fontSize)
      .attr('font-family', graphStyles.text.fontFamily)
      .attr('font-weight', graphStyles.text.fontWeight)
      .attr('fill', CONSTANTS.COLORS.TEXT_NODE)
      .attr('pointer-events', 'none')
      .text('New Node');
  }, [addNodePosition, isAddingNode, finalConfig.nodeRadius]);

  // Update selection visual state
  const updateSelectionState = useCallback(() => {
    if (!svgRef.current) return;

    const svg = d3.select(svgRef.current);
    
    // Update node selection state
    svg.selectAll('.node')
      .attr('stroke', (d: any) => 
        selectedNode?.id === d.id ? CONSTANTS.COLORS.SELECTION : CONSTANTS.COLORS.NODE_STROKE
      )
      .attr('stroke-width', (d: any) => 
        selectedNode?.id === d.id ? CONSTANTS.SIZES.STROKE_WIDTH.selected : CONSTANTS.SIZES.STROKE_WIDTH.default
      )
      .attr('filter', (d: any) => selectedNode?.id === d.id ? 'url(#glow)' : null);

    // Update cursor for drag functionality
    DragManager.updateCursorForDragging(svg.selectAll('.node'), isAddingNode);

    // Update link selection state
    svg.selectAll('.flexible-link')
      .attr('stroke', (d: any) => 
        selectedRelationship?.id === d.id ? CONSTANTS.COLORS.SELECTION : CONSTANTS.COLORS.LINK_DEFAULT
      )
      .attr('stroke-width', (d: any) => 
        selectedRelationship?.id === d.id ? CONSTANTS.SIZES.STROKE_WIDTH.link : graphStyles.link.strokeWidth
      )
      .attr('marker-end', (d: any) => 
        selectedRelationship?.id === d.id ? 'url(#arrow-selected)' : 'url(#arrow)'
      );

    // Update link group cursor
    svg.selectAll('.link-group')
      .style('cursor', isAddingNode ? 'crosshair' : 'pointer');

    // Update SVG cursor
    svg.style('cursor', isAddingNode ? 'crosshair' : 'default');

  }, [selectedNode, selectedRelationship, isAddingNode]);

  // Effect hooks
  useEffect(() => {
    const cleanup = setupSimulation();
    return cleanup;
  }, [setupSimulation]);

  useEffect(() => {
    updatePreviewNode();
  }, [updatePreviewNode]);

  useEffect(() => {
    if (!isAddingNode && svgRef.current) {
      const svg = d3.select(svgRef.current);
      const container = svg.select('g');
      container.selectAll('.preview-node, .preview-label').remove();
      setAddNodePosition(null);
    }
  }, [isAddingNode]);

  useEffect(() => {
    updateSelectionState();
  }, [updateSelectionState]);

  // Mouse tracking for add mode
  useEffect(() => {
    if (!isAddingNode || !svgRef.current) return;

    const svg = d3.select(svgRef.current);
    
    const handleMouseMove = (event: MouseEvent) => {
      const [x, y] = d3.pointer(event, containerRef.current);
      setAddNodePosition({ x, y });
    };

    const svgElement = svg.node();
    if (svgElement) {
      svgElement.addEventListener('mousemove', handleMouseMove);
      return () => svgElement.removeEventListener('mousemove', handleMouseMove);
    }
  }, [isAddingNode]);

  return (
    <div className="w-full h-full bg-gray-50 rounded-lg overflow-hidden border border-gray-200 relative">
      <svg ref={svgRef} className="w-full h-full" />
            
      {/* Optional: Add zoom controls */}
      <div className="absolute bottom-4 right-4 flex flex-col gap-2">
        <button
          onClick={() => {
            if (zoomRef.current && svgRef.current) {
              d3.select(svgRef.current)
                .transition()
                .duration(300)
                .call(zoomRef.current.scaleBy, 1.5);
            }
          }}
          className="w-8 h-8 bg-white rounded border border-gray-300 flex items-center justify-center hover:bg-gray-50 text-gray-600"
          title="Zoom In"
        >
          +
        </button>
        <button
          onClick={() => {
            if (zoomRef.current && svgRef.current) {
              d3.select(svgRef.current)
                .transition()
                .duration(300)
                .call(zoomRef.current.scaleBy, 0.67);
            }
          }}
          className="w-8 h-8 bg-white rounded border border-gray-300 flex items-center justify-center hover:bg-gray-50 text-gray-600"
          title="Zoom Out"
        >
          −
        </button>
        <button
          onClick={() => {
            if (zoomRef.current && svgRef.current) {
              const svg = d3.select(svgRef.current);
              const { width, height } = finalConfig;
              svg.transition()
                .duration(500)
                .call(
                  zoomRef.current.transform,
                  d3.zoomIdentity.translate(0, 0).scale(1)
                );
            }
          }}
          className="w-8 h-8 bg-white rounded border border-gray-300 flex items-center justify-center hover:bg-gray-50 text-gray-600"
          title="Reset View"
        >
          ⌂
        </button>
      </div>
    </div>
  );
};

export default GraphCanvas;