import { CSSProperties, MouseEvent, useEffect, useRef, useState } from 'react'

// rename type to avoid naming conflict
import { AnnotationSettings, Point } from '../../../types'
import Node from '../atoms/Node'
import AnnotationMode from '../../../models/AnnotationMode'
import Edge from '../atoms/Edge'
import mouse from '../../../utils/mouse'

type LineProps = {
  annotationSettings: AnnotationSettings
  coordinates: Point[]
  isSelected: boolean
  annotationMode: AnnotationMode
  pageToStageOffset: Point
  svgScale: number
  svgTranslation: Point
  style: CSSProperties
  onAddNode: (coordinates: Point[]) => void
  onDeleteNode: (coordinates: Point[]) => void
  onFinishAnnoCreate: () => void
  onIsDraggingStateChanged: (newDraggingState: boolean) => void
  onMoving: (coordinates: Point[]) => void // during moving - update coordinates in parent
  onMoved: () => void // moving finished - send annotation changed event
}

const Line = ({
  annotationSettings,
  coordinates,
  isSelected,
  annotationMode,
  pageToStageOffset,
  svgScale,
  svgTranslation,
  style,
  onAddNode,
  onDeleteNode,
  onFinishAnnoCreate,
  onMoving,
  onMoved,
  onIsDraggingStateChanged,
}: LineProps) => {
  const [isAnnoDragging, setIsAnnoDragging] = useState<boolean>(false)

  // onMove and onMouseUp events are fired in the same frame
  // use a ref to access the updated value without waiting until the next frame
  const [didAnnoActuallyMove, setDidAnnoActuallyMove] = useState<boolean>(false)
  const didAnnoActuallyMoveRef = useRef<boolean>(didAnnoActuallyMove)

  useEffect(() => {
    didAnnoActuallyMoveRef.current = didAnnoActuallyMove
  }, [didAnnoActuallyMove])

  const onMouseDown = (e: MouseEvent) => {
    if (annotationSettings.canEdit === false) return

    if (isSelected && annotationMode !== AnnotationMode.CREATE && e.button === 0)
      setIsAnnoDragging(true)

    if (e.button === 2 && annotationMode == AnnotationMode.CREATE) {
      const antiScaledMousePositionInStageCoordinates =
        mouse.getAntiScaledMouseStagePosition(
          e,
          pageToStageOffset,
          svgScale,
          svgTranslation,
        )

      const newCoordinates = [...coordinates]
      newCoordinates.push(antiScaledMousePositionInStageCoordinates)

      onAddNode(newCoordinates)
    }
  }

  const onMouseMove = (e: MouseEvent) => {
    if (isAnnoDragging) {
      // apply mouse move to all coordinates
      const movedCoordinates: Point[] = coordinates.map((coordinate: Point) => {
        // counter the canvas scaling (it will be automatically applied when rendering the annotation coordinates)
        const newX = (coordinate.x += e.movementX / svgScale)
        const newY = (coordinate.y += e.movementY / svgScale)
        return {
          x: newX,
          y: newY,
        }
      })

      // only escalate event when mouse actually moved
      if (e.movementX !== 0 || e.movementY !== 0) {
        setDidAnnoActuallyMove(true)
        onMoving(movedCoordinates)
      }
    }

    if (annotationMode === AnnotationMode.CREATE) {
      const mousePointInStage = mouse.getAntiScaledMouseStagePosition(
        e,
        pageToStageOffset,
        svgScale,
        svgTranslation,
      )

      let newCoords: Point[] = [...coordinates]

      // last coordinate = mouse position - update it
      if (coordinates.length > 1) newCoords = coordinates.slice(0, -1)

      newCoords.push(mousePointInStage)

      onMoving(newCoords)
    }
  }

  useEffect(() => {
    onIsDraggingStateChanged(isAnnoDragging)
    if (!isAnnoDragging) return

    const handleMouseUp = () => {
      setIsAnnoDragging(false)

      if (didAnnoActuallyMoveRef.current) onMoved()
      setDidAnnoActuallyMove(false)
    }

    globalThis.addEventListener('mouseup', handleMouseUp)

    return () => {
      globalThis.removeEventListener('mouseup', handleMouseUp)
    }
  }, [isAnnoDragging])

  const renderInfiniteSelectionArea = () => {
    return (
      <circle
        cx={coordinates[0].x}
        cy={coordinates[0].y}
        r={'100%'}
        style={{ opacity: 0 }}
        onMouseDown={onMouseDown}
        onMouseMove={onMouseMove}
        onContextMenu={(e) => e.preventDefault()}
      />
    )
  }

  const renderNodes = () => {
    const svgNodes = coordinates.map((coordinate: Point, index: number) => (
      <Node
        key={`node_${index}`}
        index={index}
        annotationSettings={annotationSettings}
        coordinates={coordinate}
        pageToStageOffset={pageToStageOffset}
        svgScale={svgScale}
        svgTranslation={svgTranslation}
        style={style}
        onDeleteNode={() => {
          const newCoordinates = [...coordinates]
          newCoordinates.splice(index, 1)
          onDeleteNode(newCoordinates)
        }}
        onMoving={(index, newPoint) => {
          const newCoordinates = [...coordinates]
          newCoordinates[index] = newPoint
          onMoving(newCoordinates)
        }}
        onMoved={() => onMoved()}
        onIsDraggingStateChanged={onIsDraggingStateChanged}
      />
    ))

    return svgNodes
  }

  const renderEdges = () => {
    const svgEdges = coordinates.map((coordinate: Point, index: number) => {
      // last coordinate has no end - dont draw it
      if (index + 1 >= coordinates.length) return

      return (
        <Edge
          key={`edge_${index}`}
          startCoordinate={coordinate}
          endCoordinate={coordinates[index + 1]}
          pageToStageOffset={pageToStageOffset}
          svgScale={svgScale}
          svgTranslation={svgTranslation}
          style={style}
          onAddNode={(coordinate: Point) => {
            const newCoordinates = [...coordinates]

            // add element at index while keeping the others
            newCoordinates.splice(index + 1, 0, coordinate)

            onAddNode(newCoordinates)
          }}
          onDoubleClick={() =>
            annotationMode === AnnotationMode.CREATE && onFinishAnnoCreate()
          }
          onMouseDown={onMouseDown}
          onMouseMove={onMouseMove}
        />
      )
    })

    return svgEdges
  }

  const canRenderNodes: boolean = isSelected && annotationMode !== AnnotationMode.CREATE

  // nodes need to be drawn after the edges to make them fully clickable
  return (
    <g>
      {(isAnnoDragging || annotationMode === AnnotationMode.CREATE) &&
        renderInfiniteSelectionArea()}
      {renderEdges()}
      {canRenderNodes && renderNodes()}
    </g>
  )
}

export default Line
