import { getEnabledElement, getRenderingEngine, VolumeViewport } from '@cornerstonejs/core'
import * as cornerstoneTools from '@cornerstonejs/tools'

const { AnnotationDisplayTool, ToolGroupManager, utilities, annotation, drawing } = cornerstoneTools
const { getAnnotations, addAnnotation } = annotation.state
const { drawLine } = drawing

class FusionJumpToPointTool extends AnnotationDisplayTool {
  constructor(toolProps = {}, defaultToolProps = {
    supportedInteractionTypes: ['Mouse', 'Touch'],
    configuration: {
      targetViewportIds: [],//要联动跳转的视口
      toolGroupId: '',
      useBrightestPoint: true,//是否走 MIP 最亮点
      jumpToTargetViewports: true,
      dispatchEventName: 'fusion-mip-point-selected',//兼容外部事件监听
      getReferenceLineColor: null,
      style: {
        lineWidth: 2,
        lineLength: 20,
        centerHoleSize: 20,
      },
      referenceLinesCenterGapRadius: 20,
      minimal: {
        enabled: true,
        lineLengthInPx: 20,
      },
      mipViewportIds: [],
    },
  }) {
    super(toolProps, defaultToolProps)
    this.isHandleDragging = false
    this.suppressNextClick = false
    this.dragSourceViewportId = null
  }

  mouseClickCallback(evt) {
    if (this.suppressNextClick) {
      this.suppressNextClick = false
      return
    }
    const { element, currentPoints } = evt.detail || {}
    if (!element || !currentPoints) return

    const enabledElement = getEnabledElement(element)
    const { viewport, renderingEngine } = enabledElement || {}
    if (!viewport || !renderingEngine) return

    const sourceIsMip = this._isMipViewportId(viewport.id)
    // Non-MIP viewport click should not trigger point relocation.
    if (!sourceIsMip) return
    const interactionWorldPoint = this._getInteractionWorldPoint(viewport, currentPoints, !sourceIsMip)
    if (!interactionWorldPoint || interactionWorldPoint.length < 3) return
    const selectedPoint = sourceIsMip
      ? this._resolveSelectedPoint(viewport, interactionWorldPoint)
      : interactionWorldPoint
    if (!selectedPoint || selectedPoint.length < 3) return

    this.setPoint(selectedPoint, viewport.id, renderingEngine.id, {
      jumpToTargetViewports: sourceIsMip,
    })
  }

  mouseDownCallback(evt) {
    this._tryStartDrag(evt)
  }

  preMouseDownCallback(evt) {
    return this._tryStartDrag(evt)
  }

  mouseDragCallback(evt) {
    if (!this.isHandleDragging) return
    const { element, currentPoints } = evt.detail || {}
    if (!element) return

    const enabledElement = getEnabledElement(element)
    const { viewport, renderingEngine } = enabledElement || {}
    if (!viewport || !renderingEngine) return

    const sourceIsMip = this._isMipViewportId(viewport.id)
    const worldPoint = this._getInteractionWorldPoint(viewport, currentPoints, !sourceIsMip)
    if (!worldPoint || worldPoint.length < 3) return

    const annotation = this._getViewportCrosshairAnnotation(viewport)
    const sourceViewportId = sourceIsMip
      ? (this.dragSourceViewportId || annotation?.data?.sourceViewportId || viewport.id)
      : viewport.id
    this.setPoint(worldPoint, sourceViewportId, renderingEngine.id, {
      jumpToTargetViewports: sourceIsMip,
    })
    evt.preventDefault?.()
  }

  mouseUpCallback(evt) {
    if (!this.isHandleDragging) return
    this.isHandleDragging = false
    this.dragSourceViewportId = null
    this.suppressNextClick = true
    evt.preventDefault?.()
  }

  getHandleNearImagePoint(element, annotation, canvasCoords, proximity) {
    const enabledElement = getEnabledElement(element)
    const viewport = enabledElement?.viewport
    if (!viewport || !annotation?.data) return null
    if (annotation.data.type !== 'fusion-jump-crosshair') return null
    if (annotation.data.viewportId !== viewport.id) return null

    const worldPoint = annotation.data?.handles?.points?.[0]
    if (!worldPoint) return null

    const pointCanvas = viewport.worldToCanvas(worldPoint)
    if (!pointCanvas || pointCanvas.length < 2) return null

    // Keep center handle hit-test generous so dragging still works after MIP rotation.
    const threshold = Math.max(12, proximity * 2)
    const near = Math.hypot(canvasCoords[0] - pointCanvas[0], canvasCoords[1] - pointCanvas[1]) < threshold
    return near ? worldPoint : null
  }

  handleSelectedCallback(evt, annotation) {
    if (!annotation?.data || annotation.data.type !== 'fusion-jump-crosshair') return
    this.isHandleDragging = true
    evt.preventDefault?.()
  }

  toolSelectedCallback(evt, annotation) {
    if (!annotation?.data || annotation.data.type !== 'fusion-jump-crosshair') return
    this.isHandleDragging = true
    evt.preventDefault?.()
  }

  isPointNearTool(element, annotation, canvasCoords, proximity) {
    const enabledElement = getEnabledElement(element)
    const viewport = enabledElement?.viewport
    if (!viewport || !annotation?.data) return false
    if (annotation.data.type !== 'fusion-jump-crosshair') return false
    if (annotation.data.viewportId !== viewport.id) return false

    const worldPoint = annotation.data?.handles?.points?.[0]
    if (!worldPoint) return false
    const pointCanvas = viewport.worldToCanvas(worldPoint)
    if (!pointCanvas || pointCanvas.length < 2) return false

    const appearance = this._normalizeAppearance(annotation.data?.crosshairAppearance || {}, annotation.data?.sourceViewportId)
    const [cx, cy] = pointCanvas
    const halfHole = appearance.centerHoleSize / 2
    const len = appearance.lineLength
    const hitPad = Math.max(8, proximity)

    const segments = [
      [[cx, cy - halfHole], [cx, cy - halfHole - len]],
      [[cx + halfHole, cy], [cx + halfHole + len, cy]],
      [[cx, cy + halfHole], [cx, cy + halfHole + len]],
      [[cx - halfHole, cy], [cx - halfHole - len, cy]],
    ]

    return segments.some(([a, b]) => this._distancePointToSegment(canvasCoords, a, b) <= hitPad)
  }

  _distancePointToSegment(point, start, end) {
    const [px, py] = point
    const [x1, y1] = start
    const [x2, y2] = end
    const dx = x2 - x1
    const dy = y2 - y1
    const lenSq = dx * dx + dy * dy
    if (lenSq === 0) return Math.hypot(px - x1, py - y1)

    let t = ((px - x1) * dx + (py - y1) * dy) / lenSq
    t = Math.max(0, Math.min(1, t))
    const projX = x1 + t * dx
    const projY = y1 + t * dy
    return Math.hypot(px - projX, py - projY)
  }

  setPoint(worldPoint, sourceViewportId, renderingEngineId, options = {}) {
    if (!Array.isArray(worldPoint) || worldPoint.length < 3) return
    const renderingEngine = typeof renderingEngineId === 'string'
      ? getRenderingEngine(renderingEngineId)
      : renderingEngineId
    if (!renderingEngine) return

    this._applyPoint({
      renderingEngine,
      worldPoint,
      sourceViewportId,
      jumpToTargetViewports: options.jumpToTargetViewports !== false,
      dispatchEvent: options.dispatchEvent !== false,
    })
  }

  renderAnnotation(enabledElement, svgDrawingHelper) {
    const { viewport } = enabledElement
    if (!viewport?.element) return false

    const annotations = getAnnotations(this.getToolName(), viewport.element) || []
    const crosshairAnnotation = this._findViewportCrosshairAnnotationFromList(annotations, viewport.id)
    if (!crosshairAnnotation) return false

    const worldPoint = crosshairAnnotation.data?.handles?.points?.[0]
    if (!worldPoint) return false

    const canvasPoint = viewport.worldToCanvas(worldPoint)
    const [cx, cy] = canvasPoint || []
    const canvasWidth = viewport.canvas?.width / (window.devicePixelRatio || 1) || 0
    const canvasHeight = viewport.canvas?.height / (window.devicePixelRatio || 1) || 0
    if (!Number.isFinite(cx) || !Number.isFinite(cy) || cx < 0 || cy < 0 || cx > canvasWidth || cy > canvasHeight) {
      return false
    }

    const appearance = this._normalizeAppearance(crosshairAnnotation.data?.crosshairAppearance || {}, crosshairAnnotation.data?.sourceViewportId)
    const halfHole = appearance.centerHoleSize / 2
    const len = appearance.lineLength
    const lineOptions = {
      color: appearance.color,
      width: appearance.lineWidth,
    }
    const horizontalLineOptions = {
      color: appearance.horizontalColor || appearance.color,
      width: appearance.lineWidth,
    }
    const verticalLineOptions = {
      color: appearance.verticalColor || appearance.color,
      width: appearance.lineWidth,
    }
    const uid = crosshairAnnotation.annotationUID

    drawLine(svgDrawingHelper, uid, 'seg-top', [cx, cy - halfHole], [cx, cy - halfHole - len], verticalLineOptions, `${uid}-top`)
    drawLine(svgDrawingHelper, uid, 'seg-right', [cx + halfHole, cy], [cx + halfHole + len, cy], horizontalLineOptions, `${uid}-right`)
    drawLine(svgDrawingHelper, uid, 'seg-bottom', [cx, cy + halfHole], [cx, cy + halfHole + len], verticalLineOptions, `${uid}-bottom`)
    drawLine(svgDrawingHelper, uid, 'seg-left', [cx - halfHole, cy], [cx - halfHole - len, cy], horizontalLineOptions, `${uid}-left`)

    return true
  }

  _applyPoint({ renderingEngine, worldPoint, sourceViewportId, jumpToTargetViewports, dispatchEvent }) {
    const targetViewports = this._getTargetViewports(renderingEngine)
    const sourceViewport = sourceViewportId ? renderingEngine.getViewport(sourceViewportId) : null
    const viewportMap = new Map()
    targetViewports.forEach((vp) => viewportMap.set(vp.id, vp))
    if (sourceViewport) {
      viewportMap.set(sourceViewport.id, sourceViewport)
    }
    const targetViewportIds = Array.from(viewportMap.keys())
    const sourceAppearance = this._resolveCrosshairAppearance(sourceViewportId || targetViewportIds[0])

    targetViewportIds.forEach((viewportId) => {
      const viewport = viewportMap.get(viewportId)
      if (!viewport?.element) return

      if (
        jumpToTargetViewports &&
        this.configuration.jumpToTargetViewports &&
        sourceViewportId &&
        viewport.id !== sourceViewportId &&
        viewport instanceof VolumeViewport
      ) {
        try {
          viewport.jumpToWorld(worldPoint)
        } catch (e) {
          console.log(e)
        }
      }

      // Align with Crosshairs color semantics: resolve per target viewport id.
      const appearance = this._resolveCrosshairAppearance(viewport.id)
      const axisColors = this._resolveAxisColorsForViewport(viewport, renderingEngine, appearance.color)
      this._upsertCrosshairAnnotation(viewport, worldPoint, sourceViewportId || viewport.id, appearance)
      if (axisColors) {
        const annotation = this._getViewportCrosshairAnnotation(viewport)
        if (annotation?.data?.crosshairAppearance) {
          annotation.data.crosshairAppearance.horizontalColor = axisColors.horizontalColor
          annotation.data.crosshairAppearance.verticalColor = axisColors.verticalColor
        }
      }
      viewport.render?.()
    })

    if (utilities?.triggerAnnotationRenderForViewportIds && targetViewportIds.length) {
      utilities.triggerAnnotationRenderForViewportIds(targetViewportIds)
    }

    if (dispatchEvent) {
      this._dispatchPointEvent(worldPoint, sourceViewportId, sourceAppearance)
    }
  }

  _upsertCrosshairAnnotation(viewport, worldPoint, sourceViewportId, crosshairAppearance) {
    let crosshairAnnotation = this._getViewportCrosshairAnnotation(viewport)

    if (!crosshairAnnotation) {
      const camera = viewport.getCamera?.() || {}
      crosshairAnnotation = {
        metadata: {
          toolName: this.getToolName(),
          viewPlaneNormal: camera.viewPlaneNormal ? [...camera.viewPlaneNormal] : undefined,
          viewUp: camera.viewUp ? [...camera.viewUp] : undefined,
          FrameOfReferenceUID: viewport.getFrameOfReferenceUID?.(),
          referencedImageId: null,
        },
        data: {
          type: 'fusion-jump-crosshair',
          viewportId: viewport.id,
          sourceViewportId,
          crosshairAppearance,
          handles: {
            points: [[...worldPoint]],
          },
        },
      }
      addAnnotation(crosshairAnnotation, viewport.element)
      return
    }

    crosshairAnnotation.data.sourceViewportId = sourceViewportId
    crosshairAnnotation.data.crosshairAppearance = crosshairAppearance
    crosshairAnnotation.data.handles.points[0] = [...worldPoint]
    crosshairAnnotation.invalidated = true
  }

  _getViewportCrosshairAnnotation(viewport) {
    if (!viewport?.element) return null
    const annotations = getAnnotations(this.getToolName(), viewport.element) || []
    return this._findViewportCrosshairAnnotationFromList(annotations, viewport.id)
  }

  _findViewportCrosshairAnnotationFromList(annotations, viewportId) {
    if (!Array.isArray(annotations) || !viewportId) return null
    for (let i = 0; i < annotations.length; i++) {
      const item = annotations[i]
      const data = item && item.data
      if (!data) continue
      if (data.type === 'fusion-jump-crosshair' && data.viewportId === viewportId) {
        return item
      }
    }
    return null
  }

  _tryStartDrag(evt) {
    const { element, currentPoints } = evt.detail || {}
    const canvasPoint = currentPoints?.canvas
    if (!element || !canvasPoint) return false

    const enabledElement = getEnabledElement(element)
    const viewport = enabledElement?.viewport
    if (!viewport?.element) return false

    const annotation = this._getViewportCrosshairAnnotation(viewport)
    if (!annotation) return false

    const nearHandle = this.getHandleNearImagePoint(element, annotation, canvasPoint, 10)
    const nearLine = this.isPointNearTool(element, annotation, canvasPoint, 10)
    this.isHandleDragging = !!(nearHandle || nearLine)
    if (!this.isHandleDragging) return false

    this.dragSourceViewportId = annotation?.data?.sourceViewportId || viewport.id
    evt.preventDefault?.()
    return true
  }

  _getInteractionWorldPoint(viewport, currentPoints, preferCanvasToWorld = false) {
    const worldPoint = currentPoints?.world
    const canvasPoint = currentPoints?.canvas
    if (preferCanvasToWorld && canvasPoint && viewport?.canvasToWorld) {
      const worldFromCanvas = viewport.canvasToWorld(canvasPoint)
      if (worldFromCanvas && worldFromCanvas.length >= 3) {
        return worldFromCanvas
      }
    }
    if (worldPoint && worldPoint.length >= 3) {
      return worldPoint
    }
    if (canvasPoint && viewport?.canvasToWorld) {
      const worldFromCanvas = viewport.canvasToWorld(canvasPoint)
      if (worldFromCanvas && worldFromCanvas.length >= 3) {
        return worldFromCanvas
      }
    }
    return null
  }

  _dispatchPointEvent(worldPoint, sourceViewportId, crosshairAppearance) {
    const { dispatchEventName } = this.configuration
    if (!dispatchEventName || typeof window === 'undefined') return
    window.dispatchEvent(new CustomEvent(dispatchEventName, {
      detail: {
        world: [...worldPoint],
        sourceViewportId,
        crosshairAppearance,
      }
    }))
  }

  _resolveSelectedPoint(viewport, worldPoint) {
    if (!this.configuration.useBrightestPoint) {
      return worldPoint
    }

    const volumeId = viewport.getVolumeId?.()
    if (!volumeId) {
      return worldPoint
    }

    let maxIntensity = -Infinity
    const maxFn = (intensity, point) => {
      if (intensity > maxIntensity) {
        maxIntensity = intensity
        return point
      }
    }

    const brightestPoint = utilities.planar.getPointInLineOfSightWithCriteria(
      viewport,
      worldPoint,
      volumeId,
      maxFn
    )

    return brightestPoint && brightestPoint.length >= 3 ? brightestPoint : worldPoint
  }

  _getTargetViewports(renderingEngine) {
    const { targetViewportIds, toolGroupId } = this.configuration
    const hasTargetIds = Array.isArray(targetViewportIds) && targetViewportIds.length > 0

    return renderingEngine.getViewports().filter((vp) => {
      if (hasTargetIds && targetViewportIds.includes(vp.id)) {
        return true
      }

      if (toolGroupId) {
        const toolGroup = ToolGroupManager.getToolGroupForViewport(vp.id, renderingEngine.id)
        if (toolGroup?.id === toolGroupId) {
          return true
        }
      }

      return !hasTargetIds && !toolGroupId
    })
  }

  _resolveCrosshairAppearance(sourceViewportId) {
    const {
      getReferenceLineColor,
      style = {},
      referenceLinesCenterGapRadius,
      minimal = {},
    } = this.configuration

    const color = typeof getReferenceLineColor === 'function'
      ? getReferenceLineColor(sourceViewportId)
      : null

    const minimalLineLength = minimal?.enabled && Number.isFinite(minimal?.lineLengthInPx)
      ? minimal.lineLengthInPx
      : undefined
    const lineLength = Number.isFinite(style.lineLength)
      ? style.lineLength
      : (Number.isFinite(minimalLineLength) ? minimalLineLength : 40)
    const centerHoleSize = Number.isFinite(style.centerHoleSize)
      ? style.centerHoleSize
      : (Number.isFinite(referenceLinesCenterGapRadius) ? referenceLinesCenterGapRadius : 20)

    return this._normalizeAppearance({
      color: color || '#6fb9ff',
      lineWidth: Number.isFinite(style.lineWidth) ? style.lineWidth : 2,
      lineLength,
      centerHoleSize,
    }, sourceViewportId)
  }

  _resolveAxisColorsForViewport(viewport, renderingEngine, fallbackColor) {
    if (!viewport || !renderingEngine) return null

    const orientation = this._getViewportOrientation(viewport)
    const orientationColorMap = this._getOrientationColorMap(renderingEngine, fallbackColor)
    const defaultColor = fallbackColor || '#6fb9ff'

    if (orientation === 'axial') {
      return {
        horizontalColor: orientationColorMap.sagittal || defaultColor,
        verticalColor: orientationColorMap.coronal || defaultColor,
      }
    }
    if (orientation === 'sagittal') {
      return {
        horizontalColor: orientationColorMap.coronal || defaultColor,
        verticalColor: orientationColorMap.axial || defaultColor,
      }
    }
    if (orientation === 'coronal') {
      return {
        horizontalColor: orientationColorMap.sagittal || defaultColor,
        verticalColor: orientationColorMap.axial || defaultColor,
      }
    }

    return {
      horizontalColor: defaultColor,
      verticalColor: defaultColor,
    }
  }

  _getOrientationColorMap(renderingEngine, fallbackColor) {
    const map = {
      axial: null,
      sagittal: null,
      coronal: null,
    }
    const targetViewports = this._getTargetViewports(renderingEngine)
    targetViewports.forEach((vp) => {
      const orientation = this._getViewportOrientation(vp)
      if (!orientation || map[orientation]) return
      map[orientation] = this._getReferenceLineColor(vp.id, fallbackColor)
    })
    return map
  }

  _getViewportOrientation(viewport) {
    const normal = viewport?.getCamera?.()?.viewPlaneNormal
    if (!normal || normal.length < 3) return null
    const abs = normal.map((n) => Math.abs(n))
    const max = Math.max(abs[0], abs[1], abs[2])
    if (max === abs[2]) return 'axial'
    if (max === abs[0]) return 'sagittal'
    if (max === abs[1]) return 'coronal'
    return null
  }

  _getReferenceLineColor(viewportId, fallbackColor) {
    if (typeof this.configuration.getReferenceLineColor === 'function') {
      try {
        const safeViewportId = viewportId || ''
        const color = this.configuration.getReferenceLineColor(safeViewportId)
        if (color) return color
      } catch (e) {
        console.log(e)
      }
    }
    return fallbackColor || '#6fb9ff'
  }

  _isMipViewportId(viewportId) {
    const mipViewportIds = this.configuration?.mipViewportIds
    return Array.isArray(mipViewportIds) && mipViewportIds.includes(viewportId)
  }

  _normalizeAppearance(appearance = {}, sourceViewportId) {
    const lineWidth = Number.isFinite(appearance.lineWidth) ? appearance.lineWidth : 2
    const lineLength = Number.isFinite(appearance.lineLength) ? appearance.lineLength : 9
    const centerHoleSize = Number.isFinite(appearance.centerHoleSize) ? appearance.centerHoleSize : 8
    let color = appearance.color
    if (!color && typeof this.configuration.getReferenceLineColor === 'function') {
      try {
        color = this.configuration.getReferenceLineColor(sourceViewportId || '')
      } catch (e) {
        console.log(e)
      }
    }

    return {
      color: color || '#6fb9ff',
      lineWidth: Math.max(1, lineWidth),
      lineLength: Math.max(4, lineLength),
      centerHoleSize: Math.max(2, centerHoleSize),
      horizontalColor: appearance.horizontalColor || null,
      verticalColor: appearance.verticalColor || null,
    }
  }
}

FusionJumpToPointTool.toolName = 'FusionJumpToPointTool'

export default FusionJumpToPointTool
