import React, { useCallback, useRef, useState } from 'react';
import {
  BaseEdge,
  EdgeLabelRenderer,
  getSmoothStepPath,
  useReactFlow,
  type EdgeProps,
} from '@xyflow/react';
import { bindWindowDrag } from '../../utils/pointerDrag';
import type { EdgeHandleBinding } from '../../utils/strategyFlowLayout';
import {
  buildPositionsFromNodes,
  getEdgeDragZone,
  pickSourceHandleAtPoint,
  pickTargetHandleAtPoint,
  type EdgeDragZone,
} from '../../utils/strategyEdgeInteraction';
import { edgeBindingUpdateRef, edgeDisconnectRef } from './strategyEditorCallbacks';

type StrategyEdgeData = {
  binding?: EdgeHandleBinding;
};

export function StrategyFlowEdge({
  id,
  source,
  target,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition,
  targetPosition,
  selected,
  data,
}: EdgeProps) {
  const { screenToFlowPosition, getNodes } = useReactFlow();
  const binding = (data as StrategyEdgeData | undefined)?.binding;
  const hasCenter = binding?.manualCenter && binding.centerX != null && binding.centerY != null;

  const [edgePath, labelX, labelY] = getSmoothStepPath({
    sourceX,
    sourceY,
    targetX,
    targetY,
    sourcePosition,
    targetPosition,
    ...(hasCenter ? { centerX: binding!.centerX!, centerY: binding!.centerY! } : {}),
  });

  const dragRef = useRef<{
    zone: EdgeDragZone;
    startCenterX: number;
    startCenterY: number;
  } | null>(null);
  const unbindRef = useRef<(() => void) | null>(null);
  const [dragZone, setDragZone] = useState<EdgeDragZone | null>(null);

  const commitBinding = useCallback((patch: EdgeHandleBinding) => {
    edgeBindingUpdateRef.current?.(id, patch);
  }, [id]);

  const positionsFromFlow = useCallback(() => {
    return buildPositionsFromNodes(getNodes());
  }, [getNodes]);

  const onHitPointerDown = useCallback((e: React.PointerEvent<SVGPathElement>) => {
    if (e.button !== 0) return;
    if ((e.target as Element).closest('.se-flow-edge-del')) return;
    e.preventDefault();
    e.stopPropagation();

    const flow = screenToFlowPosition({ x: e.clientX, y: e.clientY });
    const zone = getEdgeDragZone(sourceX, sourceY, targetX, targetY, flow.x, flow.y);
    setDragZone(zone);

  try {
      (e.currentTarget as SVGPathElement).setPointerCapture(e.pointerId);
    } catch { /* ignore */ }

    const positions = positionsFromFlow();
    const startCenterX = binding?.centerX ?? labelX;
    const startCenterY = binding?.centerY ?? labelY;

    dragRef.current = { zone, startCenterX, startCenterY };

    if (zone === 'source') {
      commitBinding({
        sourceHandle: pickSourceHandleAtPoint(source, flow, positions),
        manualSource: true,
      });
    } else if (zone === 'target') {
      commitBinding({
        targetHandle: pickTargetHandleAtPoint(target, flow, positions),
        manualTarget: true,
      });
    } else {
      commitBinding({
        centerX: flow.x,
        centerY: flow.y,
        manualCenter: true,
      });
    }

    unbindRef.current?.();
    unbindRef.current = bindWindowDrag(
      ({ x, y }) => {
        const d = dragRef.current;
        if (!d) return;
        const pos = screenToFlowPosition({ x, y });
        const positions = positionsFromFlow();

        if (d.zone === 'source') {
          commitBinding({
            sourceHandle: pickSourceHandleAtPoint(source, pos, positions),
            manualSource: true,
          });
        } else if (d.zone === 'target') {
          commitBinding({
            targetHandle: pickTargetHandleAtPoint(target, pos, positions),
            manualTarget: true,
          });
        } else {
          commitBinding({
            centerX: pos.x,
            centerY: pos.y,
            manualCenter: true,
          });
        }
      },
      () => {
        dragRef.current = null;
        setDragZone(null);
        unbindRef.current = null;
      },
    );
  }, [
    binding?.centerX,
    binding?.centerY,
    commitBinding,
    labelX,
    labelY,
    positionsFromFlow,
    screenToFlowPosition,
    source,
    sourceX,
    sourceY,
    target,
    targetX,
    targetY,
  ]);

  const onHitPointerUp = useCallback((e: React.PointerEvent<SVGPathElement>) => {
    dragRef.current = null;
    setDragZone(null);
    unbindRef.current?.();
    unbindRef.current = null;
    try {
      (e.currentTarget as SVGPathElement).releasePointerCapture(e.pointerId);
    } catch { /* ignore */ }
  }, []);

  const hitCursor = dragZone === 'source'
    ? 'grab'
    : dragZone === 'target'
      ? 'grab'
      : dragZone === 'middle'
        ? 'move'
        : 'pointer';

  return (
    <>
      <BaseEdge
        id={id}
        path={edgePath}
        className={`se-flow-edge${selected ? ' se-flow-edge--selected' : ''}`}
        style={{ strokeWidth: selected ? 3 : 2, pointerEvents: 'none' }}
      />
      <path
        d={edgePath}
        fill="none"
        stroke="transparent"
        strokeWidth={22}
        className={`se-flow-edge-hit${dragZone ? ` se-flow-edge-hit--${dragZone}` : ''}`}
        style={{ cursor: hitCursor }}
        onPointerDown={onHitPointerDown}
        onPointerUp={onHitPointerUp}
        onPointerCancel={onHitPointerUp}
      />
      {selected && edgeDisconnectRef.current && (
        <EdgeLabelRenderer>
          <button
            type="button"
            className="se-flow-edge-del"
            title="연결 끊기 (전략 코드에서 제외)"
            style={{
              position: 'absolute',
              transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
              pointerEvents: 'all',
            }}
            onClick={(ev) => {
              ev.stopPropagation();
              edgeDisconnectRef.current?.(id);
            }}
          >
            ×
          </button>
        </EdgeLabelRenderer>
      )}
    </>
  );
}
