import * as d3 from "d3";
import { graphStyles } from "../../graphConfig/graphConfig";
import { CONSTANTS } from "./constant";
import { D3Link, D3Node, Neo4jRelationship } from "../../graphTypes/graphTypes";

class FlexibleLinkManager {
  private static readonly LINK_CURVES = {
    STRAIGHT: 'straight',
    CURVED: 'curved',
    ARC: 'arc',
    SPRING: 'spring'
  } as const;

  static createFlexibleLinks(
    container: d3.Selection<SVGGElement, unknown, null, undefined>,
    links: D3Link[],
    onRelationshipClick: (relationship: Neo4jRelationship) => void,
    selectedRelationship: Neo4jRelationship | null,
    isAddingNode: boolean
  ) {
    // Remove existing links
    container.selectAll('.link-group').remove();

    // Create link groups for flexible styling
    const linkGroups = container.selectAll('.link-group')
      .data(links)
      .enter().append('g')
      .attr('class', 'link-group');

    // Create flexible paths instead of straight lines
    const linkPaths = linkGroups.append('path')
      .attr('class', 'link flexible-link')
      .attr('fill', 'none')
      .attr('stroke', CONSTANTS.COLORS.LINK_DEFAULT)
      .attr('stroke-width', graphStyles.link.strokeWidth)
      .attr('opacity', graphStyles.link.opacity)
      .attr('marker-end', 'url(#arrow)')
      .style('cursor', isAddingNode ? 'crosshair' : 'pointer');

    // Add invisible wider paths for better click detection
    const linkClickPaths = linkGroups.append('path')
      .attr('class', 'link-click-area')
      .attr('fill', 'none')
      .attr('stroke', 'transparent')
      .attr('stroke-width', 12) // Wider for easier clicking
      .style('cursor', isAddingNode ? 'crosshair' : 'pointer');

    // Add event handlers
    this.addLinkEventHandlers(
      linkGroups, 
      onRelationshipClick, 
      selectedRelationship, 
      isAddingNode
    );

    return { linkPaths, linkClickPaths };
  }

  static updateFlexibleLinkPositions(
    linkPaths: d3.Selection<SVGPathElement, D3Link, SVGGElement, unknown>,
    linkClickPaths: d3.Selection<SVGPathElement, D3Link, SVGGElement, unknown>,
    nodeRadius: number
  ): void {
    const pathGenerator = (d: D3Link): string => {
      const source = d.source as D3Node;
      const target = d.target as D3Node;
      
      if (!source.x || !source.y || !target.x || !target.y) return '';

      // Calculate connection points on node edges
      const { sourcePoint, targetPoint } = this.calculateConnectionPoints(
        source, target, nodeRadius
      );

      // Generate flexible path based on relationship type and distance
      return this.generateFlexiblePath(sourcePoint, targetPoint, d);
    };

    linkPaths.attr('d', pathGenerator);
    linkClickPaths.attr('d', pathGenerator);
  }

  private static calculateConnectionPoints(
    source: D3Node, 
    target: D3Node, 
    nodeRadius: number
  ): { sourcePoint: {x: number, y: number}, targetPoint: {x: number, y: number} } {
    const dx = target.x! - source.x!;
    const dy = target.y! - source.y!;
    const distance = Math.sqrt(dx * dx + dy * dy);
    
    if (distance === 0) {
      return {
        sourcePoint: { x: source.x!, y: source.y! },
        targetPoint: { x: target.x!, y: target.y! }
      };
    }

    const unitX = dx / distance;
    const unitY = dy / distance;

    return {
      sourcePoint: {
        x: source.x! + unitX * nodeRadius,
        y: source.y! + unitY * nodeRadius
      },
      targetPoint: {
        x: target.x! - unitX * nodeRadius,
        y: target.y! - unitY * nodeRadius
      }
    };
  }

  private static generateFlexiblePath(
    source: {x: number, y: number}, 
    target: {x: number, y: number}, 
    link: D3Link
  ): string {
    const dx = target.x - source.x;
    const dy = target.y - source.y;
    const distance = Math.sqrt(dx * dx + dy * dy);

    // Choose path type based on distance and relationship properties
    const pathType = this.determinePathType(link, distance);

    switch (pathType) {
      case this.LINK_CURVES.CURVED:
        return this.createCurvedPath(source, target, distance);
      
      case this.LINK_CURVES.ARC:
        return this.createArcPath(source, target, distance);
      
      case this.LINK_CURVES.SPRING:
        return this.createSpringPath(source, target, distance);
      
      default:
        return `M ${source.x} ${source.y} L ${target.x} ${target.y}`;
    }
  }

  private static determinePathType(link: D3Link, distance: number): string {
    // Increase curve sensitivity for more dynamic links
    if (distance > 150) {
      return this.LINK_CURVES.CURVED;
    }
    
    // Use arc for medium distances
    if (distance > 80) {
      return this.LINK_CURVES.ARC;
    }
    
    // Use spring effect for close nodes
    if (distance < 60) {
      return this.LINK_CURVES.SPRING;
    }
    
    return this.LINK_CURVES.STRAIGHT;
  }

  private static createCurvedPath(
    source: {x: number, y: number}, 
    target: {x: number, y: number}, 
    distance: number
  ): string {
    const midX = (source.x + target.x) / 2;
    const midY = (source.y + target.y) / 2;
    
    // Increase curvature for more dramatic effect when dragging
    const curvature = Math.min(distance * 0.4, 120);
    const angle = Math.atan2(target.y - source.y, target.x - source.x);
    const controlX = midX + Math.cos(angle + Math.PI / 2) * curvature;
    const controlY = midY + Math.sin(angle + Math.PI / 2) * curvature;

    return `M ${source.x} ${source.y} Q ${controlX} ${controlY} ${target.x} ${target.y}`;
  }

  private static createArcPath(
    source: {x: number, y: number}, 
    target: {x: number, y: number}, 
    distance: number
  ): string {
    // Make arc more pronounced for better visual feedback
    const radius = distance * 0.5;
    const sweep = distance > 100 ? 1 : 0; // Change sweep direction for variety
    return `M ${source.x} ${source.y} A ${radius} ${radius} 0 0 ${sweep} ${target.x} ${target.y}`;
  }

  private static createSpringPath(
    source: {x: number, y: number}, 
    target: {x: number, y: number}, 
    distance: number
  ): string {
    // Create more bounce for close nodes
    const midX = (source.x + target.x) / 2;
    const midY = (source.y + target.y) / 2;
    const bounceOffset = Math.max(15, distance * 0.3); // More dynamic bounce
    
    const angle = Math.atan2(target.y - source.y, target.x - source.x);
    const controlX = midX + Math.cos(angle + Math.PI / 2) * bounceOffset;
    const controlY = midY + Math.sin(angle + Math.PI / 2) * bounceOffset;

    return `M ${source.x} ${source.y} Q ${controlX} ${controlY} ${target.x} ${target.y}`;
  }

  private static addLinkEventHandlers(
    linkGroups: d3.Selection<SVGGElement, D3Link, SVGGElement, unknown>,
    onRelationshipClick: (relationship: Neo4jRelationship) => void,
    selectedRelationship: Neo4jRelationship | null,
    isAddingNode: boolean
  ): void {
    linkGroups
      .on('click', (event, d) => {
        if (!isAddingNode) {
          event.stopPropagation();
          onRelationshipClick(d);
        }
      })
      .on('mouseover', function(event, d) {
        if (!isAddingNode && selectedRelationship?.id !== d.id) {
          d3.select(this).select('.flexible-link')
            .attr('stroke', CONSTANTS.COLORS.LINK_HOVER)
            .attr('stroke-width', CONSTANTS.SIZES.STROKE_WIDTH.link);
        }
      })
      .on('mouseout', function(event, d) {
        if (!isAddingNode && selectedRelationship?.id !== d.id) {
          d3.select(this).select('.flexible-link')
            .attr('stroke', CONSTANTS.COLORS.LINK_DEFAULT)
            .attr('stroke-width', graphStyles.link.strokeWidth);
        }
      });
  }
}

export default FlexibleLinkManager;