<script setup>
import { computed, ref, nextTick, watch } from 'vue'
import { BaseEdge, EdgeLabelRenderer, getBezierPath, getSmoothStepPath, MarkerType } from '@vue-flow/core'
import { useVueFlow } from '@vue-flow/core'
import RichTooltip from './RichTooltip.vue'
import { getEdgeHelp } from '../utils/helpContent.js'
import { configStore } from '../utils/configStore.js'
import { useI18n } from 'vue-i18n'

const { findNode } = useVueFlow()
const { t } = useI18n()

const props = defineProps({
  id: {
    type: String,
    required: true,
  },
  hoveredNodeId: {
    type: String,
    default: null,
  },
  source: {
    type: String,
    required: true,
  },
  target: {
    type: String,
    required: true,
  },
  sourceX: {
    type: Number,
    required: true,
  },
  sourceY: {
    type: Number,
    required: true,
  },
  targetX: {
    type: Number,
    required: true,
  },
  targetY: {
    type: Number,
    required: true,
  },
  sourcePosition: {
    type: String,
    required: true,
  },
  targetPosition: {
    type: String,
    required: true,
  },
  data: {
    type: Object,
    default: () => ({})
  },
  markerEnd: {
    type: [String, Object],
    default: MarkerType.ArrowClosed,
  },
  animated: {
    type: Boolean,
    default: false,
  },
  style: {
    type: Object,
    default: () => ({}),
  },
})

// Unified hover logic
const hoverState = computed(() => {
  const hovered = props.hoveredNodeId
  return {
    isEntry: hovered && hovered === props.target,
    isExit: hovered && hovered === props.source,
    isActive: !!(hovered && (hovered === props.target || hovered === props.source))
  }
})

const edgeMarkerEnd = computed(() => {
  const base = (typeof props.markerEnd === 'object' && props.markerEnd !== null)
    ? { type: MarkerType.Arrow, width: 18, height: 18, color: '#f2f2f2', strokeWidth: 2, ...props.markerEnd }
    : props.markerEnd

  const { isEntry, isExit } = hoverState.value

  if (isEntry) {
    // warm orange marker for incoming (entry) edges
    return { ...(typeof base === 'object' ? base : {}), color: '#ff8a00' }
  }
  if (isExit) {
    // cyan-turquoise marker for outgoing (exit) edges
    return { ...(typeof base === 'object' ? base : {}), color: '#00b8d8' }
  }

  return base
})

const edgeStyle = computed(() => {
  const baseStyle = {
    stroke: '#f2f2f2',
    strokeWidth: 1.2,
    ...props.style,
  }

  const { isEntry, isExit } = hoverState.value

  if (isEntry) {
    return {
      ...baseStyle,
      // warm yellow -> orange
      stroke: 'url(#incomingEdgeGradient)',
      strokeWidth: 1.4,
      transition: 'stroke 120ms ease, stroke-width 120ms ease',
    }
  }

  if (isExit) {
    return {
      ...baseStyle,
      // cyan -> turquoise
      stroke: 'url(#outgoingEdgeGradient)',
      strokeWidth: 1.4,
      transition: 'stroke 120ms ease, stroke-width 120ms ease',
    }
  }

  if (props.data?.trigger === false) {
    return {
      ...baseStyle,
      stroke: '#868686',
      strokeDasharray: '5, 5',
      animation: 'none',
    }
  }

  return baseStyle
})

// Ref for the animated overlay edge and animation handles
const animEdgeRef = ref(null)
let edgeAnimations = []
const labelAnimationDuration = ref(0) // Duration in ms for one dash cycle

const animEdgeStyle = computed(() => {
  const { isEntry, isExit } = hoverState.value

  if (isEntry) {
    return {
      stroke: 'url(#incomingEdgeGradient)',
      strokeWidth: 2.3,
      pointerEvents: 'none',
      filter: 'url(#incomingEdgeGlow)',
    }
  }

  if (isExit) {
    return {
      stroke: 'url(#outgoingEdgeGradient)',
      strokeWidth: 2.3,
      pointerEvents: 'none',
      filter: 'url(#outgoingEdgeGlow)',
    }
  }

  return { display: 'none' }
})

const isHovered = computed(() => hoverState.value.isActive)

watch(isHovered, async (val) => {
  if (val) {
    await nextTick()
    runEdgeAnimation()
  } else {
    cancelEdgeAnimation()
  }
})

function runEdgeAnimation() {
  const pathEl = animEdgeRef.value?.pathEl
  if (!pathEl) return

  const totalLength = pathEl.getTotalLength()
  const dash = 12
  pathEl.style.strokeDasharray = `${dash} ${Math.round(dash * 2.5)}`
  pathEl.style.strokeDashoffset = '0'

  const duration = Math.min(Math.max(totalLength * 6, 8000), 8000)

  // Dash offset animation (infinite loop)
  const dashAnim = pathEl.animate(
    [{ strokeDashoffset: '0' }, { strokeDashoffset: `-${totalLength}` }],
    { duration: duration * totalLength / 150, iterations: Infinity, easing: 'linear' }
  )

  // Pulse opacity to enhance glow effect
  const glowAnim = pathEl.animate(
    [{ strokeOpacity: 2 }, { strokeOpacity: 1 }, { strokeOpacity: 2 }],
    { duration: Math.max(600, Math.round(duration / 2)), iterations: Infinity, easing: 'ease-in-out' }
  )

  edgeAnimations = [dashAnim, glowAnim]

  // Calculate label animation duration based on dash cycle speed
  // Dash cycle = dash + gap. Here gap is approx 2.5 * dash.
  // We approximate wavelength as dash + gap ≈ 3.5 * dash
  // Since dash ≈ L/10, Wavelength ≈ 0.35 * L
  // Velocity = L / duration
  // Duration of one wavelength passing = Wavelength / Velocity = (0.35 L) / (L / duration) = 0.35 * duration
  labelAnimationDuration.value = duration * 0.35
}

function cancelEdgeAnimation() {
  edgeAnimations.forEach((a) => a.cancel && a.cancel())
  edgeAnimations = []
  if (animEdgeRef.value?.pathEl) {
    animEdgeRef.value.pathEl.style.strokeDashoffset = '0'
    animEdgeRef.value.pathEl.style.strokeOpacity = '1'
    animEdgeRef.value.pathEl.style.strokeDasharray = ''
  }
  labelAnimationDuration.value = 0
}

function getArcMidpoint({
  startX,
  startY,
  endX,
  endY,
  radiusX,
  radiusY,
  xAxisRotation = 0,
  largeArcFlag = 0,
  sweepFlag = 0,
}) {
  let rx = Math.abs(radiusX)
  let ry = Math.abs(radiusY)

  if (!rx || !ry) {
    return null
  }

  const phi = (xAxisRotation * Math.PI) / 180
  const cosPhi = Math.cos(phi)
  const sinPhi = Math.sin(phi)

  const dx = (startX - endX) / 2
  const dy = (startY - endY) / 2

  const x1p = cosPhi * dx + sinPhi * dy
  const y1p = -sinPhi * dx + cosPhi * dy

  let lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry)
  if (lambda > 1) {
    const scale = Math.sqrt(lambda)
    rx *= scale
    ry *= scale
  }

  const rxSq = rx * rx
  const rySq = ry * ry
  const x1pSq = x1p * x1p
  const y1pSq = y1p * y1p
  const denom = rxSq * y1pSq + rySq * x1pSq

  if (denom === 0) {
    return null
  }

  let factor = (rxSq * rySq - denom) / denom
  factor = Math.max(0, factor)

  const coef = (largeArcFlag === sweepFlag ? -1 : 1) * Math.sqrt(factor)

  const cxp = coef * ((rx * y1p) / ry)
  const cyp = coef * (-(ry * x1p) / rx)

  const cx = cosPhi * cxp - sinPhi * cyp + (startX + endX) / 2
  const cy = sinPhi * cxp + cosPhi * cyp + (startY + endY) / 2

  const angleBetween = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy)

  const v1x = (x1p - cxp) / rx
  const v1y = (y1p - cyp) / ry
  const v2x = (-x1p - cxp) / rx
  const v2y = (-y1p - cyp) / ry

  let startAngle = angleBetween(1, 0, v1x, v1y)
  let sweep = angleBetween(v1x, v1y, v2x, v2y)

  if (!sweepFlag && sweep > 0) {
    sweep -= 2 * Math.PI
  } else if (sweepFlag && sweep < 0) {
    sweep += 2 * Math.PI
  }

  const midAngle = startAngle + sweep / 2

  const midX = cosPhi * rx * Math.cos(midAngle) - sinPhi * ry * Math.sin(midAngle) + cx
  const midY = sinPhi * rx * Math.cos(midAngle) + cosPhi * ry * Math.sin(midAngle) + cy

  if (!Number.isFinite(midX) || !Number.isFinite(midY)) {
    return null
  }

  return { x: midX, y: midY }
}

// Get the path for the edge
const path = computed(() => {
  // First obtain source and target node dimensions
  const sourceNode = findNode(props.source)
  const targetNode = findNode(props.target)
  const sourceHeight = sourceNode?.dimensions.height
  const sourceWidth = sourceNode?.dimensions.width
  const targetHeight = targetNode?.dimensions.height
  const targetWidth = targetNode?.dimensions.width

  // Check if this is a self-loop edge (source === target)
  const isSelfLoop = props.source === props.target
  
  // Check if target node is to the left of source node
  const isLeftwardEdge = props.targetX < props.sourceX

  // Check if path can point from top/bottom of source node to target handle
  const isSourceNodeToTargetHandle = props.targetX >= props.sourceX - sourceWidth / 2
                    && props.targetX < props.sourceX

  // Check if path can point from source handle to top/bottom of target node
  const isSourceHandleToTargetNode = (props.targetX >= props.sourceX
                    && props.targetX < props.sourceX + sourceWidth / 4) &&
                    (Math.abs(props.targetY - props.sourceY) > (targetHeight + sourceHeight) / 2)


  // Check if path can point from top/bottom of source node to top/bottom of target node
  const isSourceNodeToTargetNode = props.targetX >= props.sourceX - sourceWidth
                    && props.targetX < props.sourceX - sourceWidth / 2
  
  if (isSelfLoop) {
    const startX = props.sourceX - sourceWidth / 6
    const startY = props.sourceY - sourceHeight / 2

    const endX = props.targetX + targetWidth / 6
    const endY = props.targetY - targetHeight / 2

    // For self-loop edges, create a circular path using SVG arc
    const radiusX = Math.abs(startX - endX) * 0.2
    const radiusY = 20
    
    // Calculate the arc path
    const arcPath = `M ${startX - 5} ${startY} A ${radiusX} ${radiusY} 0 1 0 ${endX + 5} ${endY}`
    
    // Calculate label position (center of the arc)
    const labelX = (startX + endX) / 2
    const labelY = Math.min(startY, endY) - radiusY - 20
    
    return [arcPath, labelX, labelY]
  }

  else if (isSourceNodeToTargetNode) {
    let adjustedSourceX = props.sourceX - sourceWidth / 2 - 1
    let adjustedSourceY = props.sourceY
    let adjustedTargetX = props.targetX + targetWidth / 2 + 1
    let adjustedTargetY = props.targetY

    if (props.targetY > props.sourceY) {
      adjustedSourceY = props.sourceY + sourceHeight / 2
      adjustedTargetY = props.targetY - targetHeight / 2 - 1
    } else {
      adjustedSourceY = props.sourceY - sourceHeight / 2
      adjustedTargetY = props.targetY + targetHeight / 2 + 1
    }

    return getBezierPath({
      sourceX: adjustedSourceX,
      sourceY: adjustedSourceY,
      targetX: adjustedTargetX,
      targetY: adjustedTargetY,
      sourcePosition: props.sourcePosition,
      targetPosition: props.targetPosition,
    })
  }

  else if (isSourceNodeToTargetHandle) {
      let adjustedSourceX = props.sourceX - sourceWidth / 2
      let adjustedSourceY = props.sourceY

      const isTargetBelowSource = props.targetY > props.sourceY
      if (isTargetBelowSource) {
        adjustedSourceY = props.sourceY + sourceHeight / 2
      } else {
        adjustedSourceY = props.sourceY - sourceHeight / 2
      }

      return getBezierPath({
        sourceX: adjustedSourceX,
        sourceY: adjustedSourceY,
        targetX: props.targetX,
        targetY: props.targetY,
        sourcePosition: props.sourcePosition,
        targetPosition: props.targetPosition,
      })
  }

  else if (isSourceHandleToTargetNode) {
      let adjustedTargetX = props.targetX + targetWidth / 2
      let adjustedTargetY = props.targetY

      const isTargetBelowSource = props.targetY > props.sourceY
      if (isTargetBelowSource) {
        adjustedTargetY = props.targetY - targetHeight / 2
      } else {
        adjustedTargetY = props.targetY + targetHeight / 2
      }

      return getBezierPath({
        sourceX: props.sourceX,
        sourceY: props.sourceY,
        targetX: adjustedTargetX,
        targetY: adjustedTargetY,
        sourcePosition: props.sourcePosition,
        targetPosition: props.targetPosition,
      })
  }
  
  else if (isLeftwardEdge) {
    // Determine clockwise (1) or counterclockwise (0) based on vertical position
    const isClockwise = props.targetY > props.sourceY
    
    // Adjust coordinates based on clockwise direction
    let adjustedSourceX = props.sourceX
    let adjustedSourceY = props.sourceY
    let adjustedTargetX = props.targetX
    let adjustedTargetY = props.targetY
    
    if (isClockwise) {
      adjustedSourceX = props.sourceX - sourceWidth / 2.05
      adjustedSourceY = props.sourceY + sourceHeight / 2
      adjustedTargetX = props.targetX + targetWidth / 2.05
      adjustedTargetY = props.targetY + targetHeight / 2
    } else {
      adjustedSourceX = props.sourceX - sourceWidth / 2.05
      adjustedSourceY = props.sourceY - sourceHeight / 2
      adjustedTargetX = props.targetX + targetWidth / 2.05 
      adjustedTargetY = props.targetY - targetHeight / 2
    }
    
    const radiusMultiplier = (isLeftwardEdge && !isClockwise) ? 1.1 : 1.1
    const radiusX = Math.abs(adjustedSourceX - adjustedTargetX) * 0.55 * radiusMultiplier
    const radiusY = Math.abs(adjustedSourceY - adjustedTargetY) * 0.70 * radiusMultiplier + 110
    
    const sweepFlag = isClockwise ? 1 : 0
    
    // Calculate the arc path
    const arcPath = `M ${adjustedSourceX} ${adjustedSourceY} A ${radiusX} ${radiusY} 1 0 ${sweepFlag} ${adjustedTargetX} ${adjustedTargetY}`
    
    const fallbackLabelX = (adjustedSourceX + adjustedTargetX) / 2
    const fallbackLabelY = (adjustedSourceY + adjustedTargetY) / 2

    const arcMidpoint = getArcMidpoint({
      startX: adjustedSourceX,
      startY: adjustedSourceY,
      endX: adjustedTargetX,
      endY: adjustedTargetY,
      radiusX,
      radiusY,
      xAxisRotation: 1,
      largeArcFlag: 0,
      sweepFlag,
    })

    const labelX = arcMidpoint?.x ?? fallbackLabelX
    const labelY = arcMidpoint?.y ?? fallbackLabelY
    
    return [arcPath, labelX, labelY]
  }
  
  // Default to bezier path for non-loopback and non-leftward edges
  return getBezierPath({
    sourceX: props.sourceX,
    sourceY: props.sourceY,
    targetX: props.targetX,
    targetY: props.targetY,
    sourcePosition: props.sourcePosition,
    targetPosition: props.targetPosition,
  })
})

// Extract path components from computed result
const edgePath = computed(() => path.value[0])
const labelX = computed(() => path.value[1])
const labelY = computed(() => path.value[2])

// Get edge label text depending on function/keyword
const edgeLabel = computed(() => {
  const condition = props.data?.condition
  if (condition === undefined || condition === null) {
    return ''
  }

  if (typeof condition === 'object' && condition !== null && condition.type) {
    if (condition.type === 'function') {
      // Don't show label for default condition
      const name = condition.config?.name || ''
      if (name === 'true') {
        return ''
      }
      return name
    } else if (condition.type === 'keyword') {
      // Format keyword condition: "Includes:", "Excludes:", "Regex:"
      const parts = []
      const config = condition.config || {}
      
      if (config.any && Array.isArray(config.any) && config.any.length > 0) {
        parts.push(t('components.workflow_edge.includes', { values: config.any.join(', ') }))
      }
      if (config.none && Array.isArray(config.none) && config.none.length > 0) {
        parts.push(t('components.workflow_edge.excludes', { values: config.none.join(', ') }))
      }
      if (config.regex && Array.isArray(config.regex) && config.regex.length > 0) {
        parts.push(t('components.workflow_edge.regex', { values: config.regex.join(', ') }))
      }
      
      return parts.join('\n')
    }
  }
  
  return ''
})

const edgeLabelKey = computed(() => `${props.id}-${edgeLabel.value}`)

const labelStyle = computed(() => {
  // Base style
  const s = {
    position: 'absolute',
    transform: `translate(-50%, -50%) translate(${labelX.value}px, ${labelY.value}px)`,
    pointerEvents: 'none',
    backgroundColor: 'rgba(10, 10, 10, 0.9)',
    color: 'rgba(240, 240, 240, 1)',
    padding: '4px 6px',
    borderRadius: '3px',
    border: '1px solid #f2f2f2',
    fontSize: '12px',
    whiteSpace: 'pre-line',
    textAlign: 'center',
    lineHeight: '1.4',
    transition: 'border-color 200ms ease, box-shadow 200ms ease, color 200ms ease',
  }

  const { isEntry, isExit } = hoverState.value

  if (isEntry) {
    s.borderColor = '#ff8a00'
    s.color = '#ffdec2'
  } else if (isExit) {
    s.borderColor = '#00b8d8'
    s.color = '#c2f8ff'
  }

  // Inject CSS var for animation duration
  if (labelAnimationDuration.value > 0) {
    s['--label-anim-duration'] = `${labelAnimationDuration.value}ms`
  }

  return s
})

const edgeHelpContent = computed(() => getEdgeHelp(props.data))

// Compute tooltip position (geometric midpoint of edge path)
const tooltipX = computed(() => {
  const sourceNode = findNode(props.source)
  const targetNode = findNode(props.target)
  const isSelfLoop = props.source === props.target
  const isLeftwardEdge = props.targetX < props.sourceX
  
  // For self-loops and leftward edges with arc paths, use arc midpoint calculation
  if (isSelfLoop || isLeftwardEdge) {
    // Return labelX for arcs (already computed as arc midpoint)
    return labelX.value
  }
  
  // For bezier paths, compute midpoint at t=0.5
  return (props.sourceX + props.targetX) / 2
})

const tooltipY = computed(() => {
  const sourceNode = findNode(props.source)
  const targetNode = findNode(props.target)
  const isSelfLoop = props.source === props.target
  const isLeftwardEdge = props.targetX < props.sourceX
  
  // For self-loops and leftward edges with arc paths, use arc midpoint calculation
  if (isSelfLoop || isLeftwardEdge) {
    // Return labelY for arcs (already computed as arc midpoint)
    return labelY.value
  }
  
  // For bezier paths, compute midpoint at t=0.5
  return (props.sourceY + props.targetY) / 2
})

const shouldShowTooltip = computed(() => configStore.ENABLE_HELP_TOOLTIPS)

</script>

<template>
  <!-- Invisible SVG defs to provide gradients for edge strokes -->
  <svg style="position: absolute; width: 0; height: 0; overflow: hidden;" aria-hidden="true" focusable="false">
    <defs>
      <linearGradient id="incomingEdgeGradient" x1="0%" y1="0%" x2="100%" y2="0%">
        <stop offset="0%" stop-color="#FFD97A" />
        <stop offset="60%" stop-color="#FFB84D" />
        <stop offset="100%" stop-color="#FF6A00" />
      </linearGradient>
      <linearGradient id="outgoingEdgeGradient" x1="0%" y1="0%" x2="100%" y2="0%">
        <stop offset="0%" stop-color="#00F5D4" />
        <stop offset="60%" stop-color="#00C7E6" />
        <stop offset="100%" stop-color="#00A0FF" />
      </linearGradient>
      <filter id="incomingEdgeGlow" x="-50%" y="-50%" width="200%" height="200%">
        <feGaussianBlur in="SourceAlpha" stdDeviation="6" result="blur"/>
        <feFlood flood-color="#FF8A00" flood-opacity="0.65" result="color"/>
        <feComposite in="color" in2="blur" operator="in" result="glow"/>
        <feMerge>
          <feMergeNode in="glow"/>
          <feMergeNode in="SourceGraphic"/>
        </feMerge>
      </filter>
      <filter id="outgoingEdgeGlow" x="-50%" y="-50%" width="200%" height="200%">
        <feGaussianBlur in="SourceAlpha" stdDeviation="6" result="blur"/>
        <feFlood flood-color="#00B8D8" flood-opacity="0.65" result="color"/>
        <feComposite in="color" in2="blur" operator="in" result="glow"/>
        <feMerge>
          <feMergeNode in="glow"/>
          <feMergeNode in="SourceGraphic"/>
        </feMerge>
      </filter>
    </defs>
  </svg>
  <BaseEdge
    :id="id"
    :path="edgePath"
    :marker-end="edgeMarkerEnd"
    :style="edgeStyle"
    :animated=false
  />
  <!-- Animated overlay edge (on top of base) -->
  <BaseEdge
    ref="animEdgeRef"
    :id="`${id}-anim`"
    :path="edgePath"
    :style="animEdgeStyle"
    :animated="false"
    class="nodrag nopan"
  />
  <!-- Tooltip-enabled hover area at edge midpoint -->
  <EdgeLabelRenderer v-if="shouldShowTooltip">
    <RichTooltip :content="edgeHelpContent" placement="top">
      <div
        :style="{
          position: 'absolute',
          transform: `translate(-50%, -50%) translate(${tooltipX}px, ${tooltipY}px)`,
          pointerEvents: 'all',
          width: '20px',
          height: '20px',
          borderRadius: '50%',
          cursor: 'pointer'
        }"
        class="edge-tooltip-trigger"
      />
    </RichTooltip>
  </EdgeLabelRenderer>
  <EdgeLabelRenderer v-if="edgeLabel">
    <div
      :key="edgeLabelKey"
      :style="labelStyle"
      :class="{ 'animated-label': labelAnimationDuration > 0, 'nodrag': true, 'nopan': true }"
    >
      {{ edgeLabel }}
    </div>
  </EdgeLabelRenderer>
</template>

<style scoped>
@keyframes edge-dash {
  to { stroke-dashoffset: -20; }
}

@keyframes edge-glow {
  0%, 100% { stroke-opacity: 1; }
  50% { stroke-opacity: 0.65; }
}

@keyframes label-pulse {
  0%, 100% {
    box-shadow: 0 0 1px inset rgba(255, 255, 255, 0);
    background-color: rgba(10, 10, 10, 0.95);
  }
  50% {
    box-shadow: 0 0 8px inset currentColor;
    background-color: rgba(10, 10, 10, 0.95);
  }
}

.animated-label {
  animation: label-pulse var(--label-anim-duration) infinite linear;
}

.edge-tooltip-trigger {
  background: transparent;
  transition: background-color 0.2s;
}

.edge-tooltip-trigger:hover {
  background-color: rgba(255, 255, 255, 0.1);
}
</style>
