/**
 * Edge Renderer Component
 *
 * Renders visible edges in the graph using configurable path styles.
 * Uses viewport virtualization - only edges with visible endpoints are rendered.
 * Supports bezier (horizontal/vertical/smart), straight, step, and smooth-step paths.
 */

import React from 'react';
import { useAtomValue, useSetAtom } from 'jotai';
import { edgeFamilyAtom } from '../core/graph-derived';
import { departingEdgesAtom, editingEdgeLabelAtom } from '../core/graph-mutations';
import { visibleEdgeKeysAtom } from '../core/virtualization-store';
import { selectedEdgeIdAtom, selectEdgeAtom } from '../core/selection-store';
import { zoomAtom } from '../core/viewport-store';
import { isFilterActiveAtom, searchResultsAtom, searchEdgeResultsAtom } from '../core/search-store';
import { useCanvasStyle } from '../providers/CanvasStyleProvider';
import { getEdgePathCalculator, type EdgePathCalculator } from '../utils/edge-path-calculators';
import type { UIEdgeState } from '../core/types';

// Selected edge highlight color
const SELECTED_EDGE_COLOR = '#3b82f6';

export interface EdgeRendererProps {
	/** Callback when an edge is clicked. When provided, replaces the default select behavior. */
	onEdgeClick?: (edgeKey: string, edgeData: UIEdgeState, event: React.MouseEvent) => void;
	/** Callback when an edge is double-clicked */
	onEdgeDoubleClick?: (edgeKey: string, edgeData: UIEdgeState, event: React.MouseEvent) => void;
	/** Callback when an edge is right-clicked */
	onEdgeRightClick?: (edgeKey: string, edgeData: UIEdgeState, event: React.MouseEvent) => void;
	/** Callback when pointer enters an edge */
	onEdgeHover?: (edgeKey: string, edgeData: UIEdgeState) => void;
	/** Callback when pointer leaves an edge */
	onEdgeLeave?: (edgeKey: string) => void;
}

/**
 * Renders visible edges in the graph (virtualized).
 * Only edges where both source and target nodes are visible are rendered.
 */
export function EdgeRenderer({ onEdgeClick, onEdgeDoubleClick, onEdgeRightClick, onEdgeHover, onEdgeLeave }: EdgeRendererProps) {
	const edgeKeys = useAtomValue(visibleEdgeKeysAtom);
	const departingEdges = useAtomValue(departingEdgesAtom);

	return (
		<svg
			style={{
				position: 'absolute',
				top: 0,
				left: 0,
				width: '100%',
				height: '100%',
				pointerEvents: 'none',
				zIndex: 0,
				overflow: 'visible',
			}}
		>
			{edgeKeys.map((key) => (
				<EdgeItem
					key={key}
					edgeKey={key}
					onEdgeClick={onEdgeClick}
					onEdgeDoubleClick={onEdgeDoubleClick}
					onEdgeRightClick={onEdgeRightClick}
					onEdgeHover={onEdgeHover}
					onEdgeLeave={onEdgeLeave}
				/>
			))}
			{Array.from(departingEdges.entries()).map(([key, edgeData]) => (
				<DepartingEdgeItem key={`departing-${key}`} edgeData={edgeData} />
			))}
		</svg>
	);
}

interface EdgeItemProps {
	edgeKey: string;
	onEdgeClick?: (edgeKey: string, edgeData: UIEdgeState, event: React.MouseEvent) => void;
	onEdgeDoubleClick?: (edgeKey: string, edgeData: UIEdgeState, event: React.MouseEvent) => void;
	onEdgeRightClick?: (edgeKey: string, edgeData: UIEdgeState, event: React.MouseEvent) => void;
	onEdgeHover?: (edgeKey: string, edgeData: UIEdgeState) => void;
	onEdgeLeave?: (edgeKey: string) => void;
}

function EdgeItem({ edgeKey, onEdgeClick, onEdgeDoubleClick, onEdgeRightClick, onEdgeHover, onEdgeLeave }: EdgeItemProps) {
	const edgeData = useAtomValue(edgeFamilyAtom(edgeKey));
	const styles = useCanvasStyle();
	const selectedEdgeId = useAtomValue(selectedEdgeIdAtom);
	const selectEdge = useSetAtom(selectEdgeAtom);
	const zoom = useAtomValue(zoomAtom);
	const setEditingEdgeLabel = useSetAtom(editingEdgeLabelAtom);

	const isFilterActive = useAtomValue(isFilterActiveAtom);
	const searchResults = useAtomValue(searchResultsAtom);
	const searchEdgeResults = useAtomValue(searchEdgeResultsAtom);
	const isSelected = selectedEdgeId === edgeKey;

	// Get the path calculator based on style configuration
	const pathCalculator: EdgePathCalculator = getEdgePathCalculator(styles.edges.pathType || 'bezier');

	const handleClick = (e: React.MouseEvent) => {
		e.stopPropagation();
		if (onEdgeClick && edgeData) {
			onEdgeClick(edgeKey, edgeData, e);
		} else {
			selectEdge(edgeKey);
		}
	};

	const handleDoubleClick = (e: React.MouseEvent) => {
		if (onEdgeDoubleClick && edgeData) {
			e.stopPropagation();
			e.preventDefault();
			onEdgeDoubleClick(edgeKey, edgeData, e);
		}
	};

	const handleContextMenu = (e: React.MouseEvent) => {
		if (onEdgeRightClick && edgeData) {
			e.preventDefault();
			e.stopPropagation();
			onEdgeRightClick(edgeKey, edgeData, e);
		}
	};

	const handlePointerEnter = () => {
		if (onEdgeHover && edgeData) {
			onEdgeHover(edgeKey, edgeData);
		}
	};

	const handlePointerLeave = () => {
		if (onEdgeLeave) {
			onEdgeLeave(edgeKey);
		}
	};

	if (!edgeData) return null;

	const { sourcePosition, targetPosition, color, weight, type, label, sourceEndpoint, targetEndpoint } = edgeData;

	// Use port endpoints if available, otherwise fall back to node center
	const x1 = sourceEndpoint?.x ?? (sourcePosition.x + edgeData.sourceNodeWidth / 2);
	const y1 = sourceEndpoint?.y ?? (sourcePosition.y + edgeData.sourceNodeHeight / 2);
	const x2 = targetEndpoint?.x ?? (targetPosition.x + edgeData.targetNodeWidth / 2);
	const y2 = targetEndpoint?.y ?? (targetPosition.y + edgeData.targetNodeHeight / 2);

	if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2) || zoom === 0) {
		return null;
	}

	// Calculate edge path using the configurable calculator
	const { path: pathD, labelX, labelY } = pathCalculator({
		x1,
		y1,
		x2,
		y2,
		sourceWidth: edgeData.sourceNodeWidth,
		sourceHeight: edgeData.sourceNodeHeight,
		targetWidth: edgeData.targetNodeWidth,
		targetHeight: edgeData.targetNodeHeight,
	});

	// Edge styling
	const edgeColor = isSelected ? SELECTED_EDGE_COLOR : color || styles.edges.defaultColor;
	const baseStrokeWidth = weight || styles.edges.defaultWeight!;
	const effectiveStrokeWidth = (isSelected ? baseStrokeWidth * 1.5 : baseStrokeWidth) / zoom;
	const effectiveFontSize = 10 / zoom;
	const hitboxStrokeWidth = Math.max(10, baseStrokeWidth * 3) / zoom;

	// Dim edges that don't match search (neither endpoint nor edge itself matches)
	const isDimmedByFilter = isFilterActive
		&& !searchResults.has(edgeData.sourceId)
		&& !searchResults.has(edgeData.targetId)
		&& !searchEdgeResults.has(edgeKey);
	const edgeOpacity = isDimmedByFilter ? 0.2 : 1;

	return (
		<g style={{ cursor: 'pointer', opacity: edgeOpacity, transition: 'opacity 150ms ease' }}>
			{/* Invisible hitbox for easier clicking */}
			<path
				d={pathD}
				stroke="transparent"
				strokeWidth={hitboxStrokeWidth}
				fill="none"
				style={{ pointerEvents: isDimmedByFilter ? 'none' : 'stroke' }}
				onClick={handleClick}
				onDoubleClick={handleDoubleClick}
				onContextMenu={handleContextMenu}
				onPointerEnter={handlePointerEnter}
				onPointerLeave={handlePointerLeave}
			/>
			{/* Visible edge line */}
			<path
				d={pathD}
				stroke={edgeColor}
				strokeWidth={effectiveStrokeWidth}
				fill="none"
				strokeDasharray={type === 'dashed' ? `calc(5px / ${zoom}) calc(5px / ${zoom})` : undefined}
				style={{ pointerEvents: 'none' }}
			/>
			{label && (
				<text
					x={labelX}
					y={labelY}
					fontSize={`${effectiveFontSize}px`}
					fill={styles.edges.labelColor}
					stroke={styles.edges.labelStrokeColor}
					strokeWidth={`${0.2 / zoom}px`}
					textAnchor="middle"
					dy={`${-5 / zoom}px`}
					style={{ pointerEvents: 'visiblePainted', cursor: 'text' }}
					onDoubleClick={(e) => {
						e.stopPropagation();
						e.preventDefault();
						setEditingEdgeLabel(edgeKey);
					}}
				>
					{label}
				</text>
			)}
		</g>
	);
}

/**
 * Renders a departing (exiting) edge with fade-out animation.
 * Uses a static snapshot — no atom subscriptions needed.
 */
function DepartingEdgeItem({ edgeData }: { edgeData: UIEdgeState }) {
	const styles = useCanvasStyle();
	const zoom = useAtomValue(zoomAtom);
	const pathCalculator: EdgePathCalculator = getEdgePathCalculator(styles.edges.pathType || 'bezier');

	const { sourcePosition, targetPosition, color, weight, type, sourceEndpoint, targetEndpoint } = edgeData;

	const x1 = sourceEndpoint?.x ?? (sourcePosition.x + edgeData.sourceNodeWidth / 2);
	const y1 = sourceEndpoint?.y ?? (sourcePosition.y + edgeData.sourceNodeHeight / 2);
	const x2 = targetEndpoint?.x ?? (targetPosition.x + edgeData.targetNodeWidth / 2);
	const y2 = targetEndpoint?.y ?? (targetPosition.y + edgeData.targetNodeHeight / 2);

	if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2) || zoom === 0) return null;

	const { path: pathD } = pathCalculator({
		x1, y1, x2, y2,
		sourceWidth: edgeData.sourceNodeWidth,
		sourceHeight: edgeData.sourceNodeHeight,
		targetWidth: edgeData.targetNodeWidth,
		targetHeight: edgeData.targetNodeHeight,
	});

	const edgeColor = color || styles.edges.defaultColor;
	const baseStrokeWidth = weight || styles.edges.defaultWeight!;
	const effectiveStrokeWidth = baseStrokeWidth / zoom;

	return (
		<g className="canvas-edge-exit">
			<path
				d={pathD}
				stroke={edgeColor}
				strokeWidth={effectiveStrokeWidth}
				fill="none"
				strokeDasharray={type === 'dashed' ? `calc(5px / ${zoom}) calc(5px / ${zoom})` : undefined}
				style={{ pointerEvents: 'none' }}
			/>
		</g>
	);
}
