import { useEffect, useRef, useState } from 'react'
import { fabric } from 'fabric'
import { actionHandler, anchorWrapper, polygonPositionHandler } from '@/util/canvas-util'

import { useRecoilState } from 'recoil'
import { canvasSizeState, fontSizeState } from '@/store/canvasAtom'
import { QLine } from '@/components/fabric/QLine'
import QRect from '@/components/fabric/QRect'
import { QPolygon } from '@/components/fabric/QPolygon'
import { defineQLine } from '@/util/qline-utils'
import { defineQPloygon } from '@/util/qpolygon-utils'

export function useCanvas(id) {
  const [canvas, setCanvas] = useState()
  const [isLocked, setIsLocked] = useState(false)
  const [history, setHistory] = useState([])
  const [canvasSize] = useRecoilState(canvasSizeState)
  const [fontSize] = useRecoilState(fontSizeState)
  const points = useRef([])

  /**
   * 처음 셋팅
   */
  useEffect(() => {
    const c = new fabric.Canvas(id, {
      height: canvasSize.vertical,
      width: canvasSize.horizontal,
      backgroundColor: 'white',
      selection: false,
    })

    setCanvas(c)
    return () => {
      c.dispose()
    }
  }, [])

  useEffect(() => {
    // canvas 사이즈가 변경되면 다시
    removeEventOnCanvas()
    addEventOnCanvas()
  }, [canvasSize])

  useEffect(() => {
    canvas
      ?.getObjects()
      ?.filter((obj) => obj.type === 'textbox' || obj.type === 'text' || obj.type === 'i-text')
      .forEach((obj) => {
        obj.set({ fontSize: fontSize })
      })

    canvas
      ?.getObjects()
      ?.filter((obj) => obj.type === 'QLine' || obj.type === 'QPolygon' || obj.type === 'QRect')
      .forEach((obj) => {
        obj.setFontSize(fontSize)
      })
    canvas?.getObjects().length > 0 && canvas?.renderAll()
  }, [fontSize])

  /**
   * 캔버스 초기화
   */
  useEffect(() => {
    if (canvas) {
      initialize()
      canvas?.on('object:added', onChange)
      canvas?.on('object:modified', onChange)
      canvas?.on('object:removed', onChange)
      canvas?.on('mouse:move', drawMouseLines)
      canvas?.on('mouse:out', removeMouseLines)
    }
  }, [canvas])
  const addEventOnCanvas = () => {
    canvas?.on('object:added', onChange)
    canvas?.on('object:modified', onChange)
    canvas?.on('object:removed', onChange)
    canvas?.on('object:added', () => {
      document.addEventListener('keydown', handleKeyDown)
    })

    canvas?.on('mouse:move', drawMouseLines)
    canvas?.on('mouse:down', handleMouseDown)
    canvas?.on('mouse:out', removeMouseLines)
  }

  const removeEventOnCanvas = () => {
    canvas?.off('object:added')
    canvas?.off('object:modified')
    canvas?.off('object:removed')
    canvas?.off('object:added')
    canvas?.off('mouse:move', drawMouseLines)
    canvas?.off('mouse:down', handleMouseDown)
  }

  /**
   * 마우스 포인터의 가이드라인을 제거합니다.
   */
  const removeMouseLines = () => {
    if (canvas?._objects.length > 0) {
      const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine')
      mouseLines.forEach((item) => canvas?.remove(item))
    }
    canvas?.renderAll()
  }

  const initialize = () => {
    canvas.getObjects().length > 0 && canvas?.clear()
    // settings for all canvas in the app
    fabric.Object.prototype.transparentCorners = false
    fabric.Object.prototype.cornerColor = '#2BEBC8'
    fabric.Object.prototype.cornerStyle = 'rect'
    fabric.Object.prototype.cornerStrokeColor = '#2BEBC8'
    fabric.Object.prototype.cornerSize = 6
    fabric.QLine = QLine
    fabric.QPolygon = QPolygon
    QPolygon.prototype.canvas = canvas
    QLine.prototype.canvas = canvas
    QRect.prototype.canvas = canvas
    defineQLine()
    defineQPloygon()
  }

  /**
   * 캔버스에 도형을 추가한다. 도형은 사용하는 페이지에서 만들어서 파라미터로 넘겨주어야 한다.
   */
  const addShape = (shape) => {
    canvas?.add(shape)
    canvas?.setActiveObject(shape)
    canvas?.requestRenderAll()
  }

  const onChange = (e) => {
    const target = e.target
    if (target) {
      // settleDown(target)
    }

    if (!isLocked) {
      setHistory([])
    }
    setIsLocked(false)
  }

  const drawMouseLines = (e) => {
    // 현재 마우스 포인터의 위치를 가져옵니다.
    const pointer = canvas?.getPointer(e.e)

    // 기존에 그려진 가이드라인을 제거합니다.
    removeMouseLines()

    if (canvas?.getActiveObject()) {
      return
    }

    // 가로선을 그립니다.
    const horizontalLine = new fabric.Line([0, pointer.y, canvasSize.horizontal, pointer.y], {
      stroke: 'black',
      strokeWidth: 1,
      selectable: false,
      name: 'mouseLine',
    })

    // 세로선을 그립니다.
    const verticalLine = new fabric.Line([pointer.x, 0, pointer.x, canvasSize.vertical], {
      stroke: 'black',
      strokeWidth: 1,
      selectable: false,
      name: 'mouseLine',
    })

    // 선들을 캔버스에 추가합니다.
    canvas?.add(horizontalLine, verticalLine)

    // 캔버스를 다시 그립니다.
    canvas?.renderAll()
  }

  const handleMouseDown = (e) => {
    // 현재 마우스 포인터의 위치를 가져옵니다.
    if (canvas?.getActiveObject()) {
      points.current = []
      return
    }
    const pointer = canvas?.getPointer(e.e)

    // 클릭한 위치를 배열에 추가합니다.
    points.current.push(pointer)

    // 두 점을 모두 찍었을 때 사각형을 그립니다.
    if (points.current.length === 2) {
      const rect = new fabric.Rect({
        left: points.current[0].x,
        top: points.current[0].y,
        width: points.current[1].x - points.current[0].x,
        height: points.current[1].y - points.current[0].y,
        fill: 'transparent',
        stroke: 'black',
        strokeWidth: 1,
      })

      // 사각형을 캔버스에 추가합니다.
      canvas?.add(rect)

      // 배열을 초기화합니다.
      points.current = []
    }
  }

  /**
   * 눈금 모양에 맞게 움직이도록 한다.
   */
  const settleDown = (shape) => {
    const left = Math.round(shape?.left / 10) * 10
    const top = Math.round(shape?.top / 10) * 10

    shape?.set({ left: left, top: top })
  }

  /**
   * redo, undo가 필요한 곳에서 사용한다.
   */
  const handleUndo = () => {
    if (canvas) {
      if (canvas?._objects.length > 0) {
        const poppedObject = canvas?._objects.pop()

        setHistory((prev) => {
          if (prev === undefined) {
            return poppedObject ? [poppedObject] : []
          }
          return poppedObject ? [...prev, poppedObject] : prev
        })
        canvas?.renderAll()
      }
    }
  }

  const handleRedo = () => {
    if (canvas && history) {
      if (history.length > 0) {
        setIsLocked(true)
        canvas?.add(history[history.length - 1])
        const newHistory = history.slice(0, -1)
        setHistory(newHistory)
      }
    }
  }

  /**
   * 선택한 도형을 복사한다.
   */
  const handleCopy = () => {
    const activeObjects = canvas?.getActiveObjects()

    if (activeObjects?.length === 0) {
      return
    }

    activeObjects?.forEach((obj) => {
      obj.clone((cloned) => {
        cloned.set({ left: obj.left + 10, top: obj.top + 10 })
        addShape(cloned)
      })
    })
  }

  /**
   * 선택한 도형을 삭제한다.
   */
  const handleDelete = () => {
    const targets = canvas?.getActiveObjects()
    if (targets?.length === 0) {
      alert('삭제할 대상을 선택해주세요.')
      return
    }

    if (!confirm('정말로 삭제하시겠습니까?')) {
      return
    }

    targets?.forEach((target) => {
      canvas?.remove(target)
    })
  }

  /**
   * 페이지 내 캔버스 저장
   * todo : 현재는 localStorage에 그냥 저장하지만 나중에 변경해야함
   */
  const handleSave = () => {
    const objects = canvas?.getObjects()

    if (objects?.length === 0) {
      alert('저장할 대상이 없습니다.')
      return
    }
    const jsonStr = JSON.stringify(canvas)
    localStorage.setItem('canvas', jsonStr)
  }

  /**
   * 페이지 내 캔버스에 저장한 내용 불러오기
   * todo : 현재는 localStorage에 그냥 저장하지만 나중에 변경해야함
   */

  const handlePaste = () => {
    const jsonStr = localStorage.getItem('canvas')
    if (!jsonStr) {
      alert('붙여넣기 할 대상이 없습니다.')
      return
    }

    canvas?.loadFromJSON(JSON.parse(jsonStr), () => {
      localStorage.removeItem('canvas')
      console.log('paste done')
    })
  }

  const moveDown = () => {
    const targetObj = canvas?.getActiveObject()
    if (!targetObj) {
      return
    }

    let top = targetObj.top + 10

    if (top > CANVAS.HEIGHT) {
      top = CANVAS.HEIGHT
    }

    targetObj.set({ top: top })
    canvas?.renderAll()
  }

  const moveUp = () => {
    const targetObj = canvas?.getActiveObject()
    if (!targetObj) {
      return
    }

    let top = targetObj.top - 10

    if (top < 0) {
      top = 0
    }

    targetObj.set({ top: top })
    canvas?.renderAll()
  }

  const moveLeft = () => {
    const targetObj = canvas?.getActiveObject()
    if (!targetObj) {
      return
    }

    let left = targetObj.left - 10

    if (left < 0) {
      left = 0
    }

    targetObj.set({ left: left })
    canvas?.renderAll()
  }

  const moveRight = () => {
    const targetObj = canvas?.getActiveObject()
    if (!targetObj) {
      return
    }

    let left = targetObj.left + 10

    if (left > CANVAS.WIDTH) {
      left = CANVAS.WIDTH
    }

    targetObj.set({ left: left })
    canvas?.renderAll()
  }

  /**
   * 각종 키보드 이벤트
   * https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
   */
  const handleKeyDown = (e) => {
    const key = e.key

    switch (key) {
      case 'Delete':
      case 'Backspace':
        handleDelete()
        break
      case 'Down': // IE/Edge에서 사용되는 값
      case 'ArrowDown':
        // "아래 화살표" 키가 눌렸을 때의 동작입니다.
        moveDown()
        break
      case 'Up': // IE/Edge에서 사용되는 값
      case 'ArrowUp':
        // "위 화살표" 키가 눌렸을 때의 동작입니다.
        moveUp()
        break
      case 'Left': // IE/Edge에서 사용되는 값
      case 'ArrowLeft':
        // "왼쪽 화살표" 키가 눌렸을 때의 동작입니다.
        moveLeft()
        break
      case 'Right': // IE/Edge에서 사용되는 값
      case 'ArrowRight':
        // "오른쪽 화살표" 키가 눌렸을 때의 동작입니다.
        moveRight()
        break
      case 'Enter':
        // "enter" 또는 "return" 키가 눌렸을 때의 동작입니다.
        break
      case 'Esc': // IE/Edge에서 사용되는 값
      case 'Escape':
        break
      default:
        return // 키 이벤트를 처리하지 않는다면 종료합니다.
    }
    e.preventDefault()
  }

  const handleRotate = (degree = 45) => {
    const target = canvas?.getActiveObject()

    if (!target) {
      return
    }

    const currentAngle = target.angle

    target.set({ angle: currentAngle + degree })
    canvas?.renderAll()
  }

  /**
   * Polygon 타입만 가능
   * 생성한 polygon을 넘기면 해당 polygon은 꼭지점으로 컨트롤 가능한 polygon이 됨
   */
  const attachCustomControlOnPolygon = (poly) => {
    const lastControl = poly.points?.length - 1
    poly.cornerStyle = 'rect'
    poly.cornerColor = 'rgba(0,0,255,0.5)'
    poly.objectCaching = false
    poly.controls = poly.points.reduce(function (acc, point, index) {
      acc['p' + index] = new fabric.Control({
        positionHandler: polygonPositionHandler,
        actionHandler: anchorWrapper(index > 0 ? index - 1 : lastControl, actionHandler),
        actionName: 'modifyPolygon',
        pointIndex: index,
      })
      return acc
    }, {})

    poly.hasBorders = !poly.edit
    canvas?.requestRenderAll()
  }

  /**
   * 이미지로 저장하는 함수
   * @param {string} title - 저장할 이미지 이름
   */
  const saveImage = (title = 'canvas') => {
    const dataURL = canvas?.toDataURL('png')

    // 이미지 다운로드 링크 생성
    const link = document.createElement('a')
    link.download = `${title}.png`
    link.href = dataURL

    // 링크 클릭하여 이미지 다운로드
    link.click()
  }

  const handleFlip = () => {
    const target = canvas?.getActiveObject()

    if (!target) {
      return
    }

    // 현재 scaleX 및 scaleY 값을 가져옵니다.
    const scaleX = target.scaleX
    // const scaleY = target.scaleY;

    // 도형을 반전시킵니다.
    target.set({
      scaleX: scaleX * -1,
      // scaleY: scaleY * -1
    })

    // 캔버스를 다시 그립니다.
    canvas?.renderAll()
  }

  function fillCanvasWithDots(canvas, gap) {
    const width = canvas.getWidth()
    const height = canvas.getHeight()

    for (let x = 0; x < width; x += gap) {
      for (let y = 0; y < height; y += gap) {
        const circle = new fabric.Circle({
          radius: 1,
          fill: 'black',
          left: x,
          top: y,
          selectable: false,
        })
        canvas.add(circle)
      }
    }

    canvas?.renderAll()
  }

  const setCanvasBackgroundWithDots = (canvas, gap) => {
    // Create a new canvas and fill it with dots
    const tempCanvas = new fabric.StaticCanvas()
    tempCanvas.setDimensions({
      width: canvas.getWidth(),
      height: canvas.getHeight(),
    })
    fillCanvasWithDots(tempCanvas, gap)

    // Convert the dotted canvas to an image
    const dataUrl = tempCanvas.toDataURL({ format: 'png' })

    // Set the image as the background of the original canvas
    fabric.Image.fromURL(dataUrl, function (img) {
      canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {
        scaleX: canvas.width / img.width,
        scaleY: canvas.height / img.height,
      })
    })
  }

  const addCanvas = () => {
    // const canvasState = canvas

    const objs = canvas?.toJSON([
      'selectable',
      'name',
      'parentId',
      'id',
      'length',
      'idx',
      'direction',
      'lines',
      'points',
      'lockMovementX',
      'lockMovementY',
      'lockRotation',
      'lockScalingX',
      'lockScalingY',
      'opacity',
      'cells',
    ])

    const str = JSON.stringify(objs)

    canvas?.clear()

    setTimeout(() => {
      // 역직렬화하여 캔버스에 객체를 다시 추가합니다.
      canvas?.loadFromJSON(JSON.parse(str), function () {
        // 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다.
        console.log(canvas?.getObjects().filter((obj) => obj.name === 'roof'))
        canvas?.renderAll() // 캔버스를 다시 그립니다.
      })
    }, 1000)
  }

  return {
    canvas,
    addShape,
    handleUndo,
    handleRedo,
    handleCopy,
    handleDelete,
    handleSave,
    handlePaste,
    handleRotate,
    attachCustomControlOnPolygon,
    saveImage,
    handleFlip,
    setCanvasBackgroundWithDots,
    addCanvas,
  }
}
