'use client'

import { MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon } from '@heroicons/react/24/outline'
import React, { useEffect, useRef, useState, WheelEvent } from 'react'
import { useProject } from '../../context/Project/provider'
import loadImage from '../../useCases/loadImage'
import processImageArea from '../../useCases/processImageArea'
import classNames from '../../utils/classNames'
import LanguageSelect from './LanguageSelect'
import isInBounds from '../../utils/isInBounds'
import { ipc } from '../../wailsjs/wailsjs/go/models'
import onEnterHandler from '../../utils/onEnterHandler'

const zoomStep = 0.025
const maxZoomLevel = 4

const DocumentRenderer = () => {
  const {
    getSelectedDocument,
    requestAddArea,
    selectedAreaId,
    setSelectedAreaId,
    getProcessedAreasByDocumentId,
    requestUpdateProcessedWordById
  } = useProject()
  const selectedDocument = getSelectedDocument()
  const areas = selectedDocument?.areas
  const documentCanvas = useRef<HTMLCanvasElement>(null)
  const areaCanvas = useRef<HTMLCanvasElement>(null)
  const uiCanvas = useRef<HTMLCanvasElement>(null)
  const drawingCanvas = useRef<HTMLCanvasElement>(null)
  const editWordInput = useRef<HTMLInputElement>(null)

  const [zoomLevel, setZoomLevel] = useState(1)
  const [hoverOverAreaId, setHoverOverAreaId] = useState('')
  const [hoveredProcessedArea, setHoveredProcessedArea] = useState<ipc.ProcessedArea | undefined>()
  const [wordToEdit, setWordToEdit] = useState<{ word: ipc.ProcessedWord, areaId: string } | undefined>()

  let downClickX = 0
  let downClickY = 0
  let isDrawing = false

  const applyCanvasSizes = (size: { width: number, height: number }) => {
    const documentCanvasInstance = documentCanvas.current
    if (!documentCanvasInstance) return
    documentCanvasInstance.width = size.width
    documentCanvasInstance.height = size.height

    const areaCanvasInstance = areaCanvas.current
    if (!areaCanvasInstance) return
    areaCanvasInstance.width = size.width
    areaCanvasInstance.height = size.height

    const uiCanvasInstance = uiCanvas.current
    if (!uiCanvasInstance) return
    uiCanvasInstance.width = size.width
    uiCanvasInstance.height = size.height

    const drawingCanvasInstance = drawingCanvas.current
    if (!drawingCanvasInstance) return
    drawingCanvasInstance.width = size.width
    drawingCanvasInstance.height = size.height
  }

  const applyDocumentToCanvas = async (path: string) => {
    let image: HTMLImageElement
    try {
      image = await loadImage(path)
    } catch (err) {
      return
    }

    const width = image.naturalWidth * zoomLevel
    const height = image.naturalHeight * zoomLevel

    applyCanvasSizes({ width, height })

    const documentCanvasInstance = documentCanvas.current
    if (!documentCanvasInstance) return

    const context = documentCanvasInstance.getContext('2d')
    if (!context) return
    context.drawImage(image, 0, 0, width, height)

    if (areas) applyAreasToCanvas()
    applyUiCanvasUpdates()
  }

  const applyAreasToCanvas = () => {
    const areaCanvasInstance = areaCanvas.current
    if (!areaCanvasInstance) return
    const context = areaCanvasInstance.getContext('2d')!
    if (!context) return

    context.clearRect(0, 0, areaCanvasInstance.width, areaCanvasInstance.height)

    if (!areas || !areas.length) return

    areas.forEach(a => {
      context.beginPath()
      if (a.id !== selectedAreaId) {
        context.setLineDash([4])
        context.lineWidth = 2
        context.strokeStyle = '#010101'
      } else {
        context.setLineDash([])
        context.lineWidth = 3
        context.strokeStyle = '#dc8dec'
      }
      const width = (a.endX - a.startX) * zoomLevel
      const height = (a.endY - a.startY) * zoomLevel
      const x = a.startX * zoomLevel
      const y = a.startY * zoomLevel
      context.roundRect(x, y, width, height, 4)
      context.stroke()
      context.closePath()
    })
  }

  const applyUiCanvasUpdates = () => {
    const uiCanvasInstance = uiCanvas.current
    if (!uiCanvasInstance) return
    const context = uiCanvasInstance.getContext('2d')!
    if (!context) return

    context.clearRect(0, 0, uiCanvasInstance.width, uiCanvasInstance.height)

    if (!areas || !areas.length) return

    const hoverArea = areas.find(a => a.id === hoverOverAreaId)
    if (!hoverArea) return

    context.beginPath()
    context.setLineDash([])
    context.lineWidth = 6
    context.strokeStyle = '#dc8dec'
    const width = (hoverArea.endX - hoverArea.startX) * zoomLevel
    const height = (hoverArea.endY - hoverArea.startY) * zoomLevel
    const x = hoverArea.startX * zoomLevel
    const y = hoverArea.startY * zoomLevel
    context.roundRect(x, y, width, height, 4)
    context.stroke()
    context.closePath()
  }

  const getProcessedAreaById = async (areaId: string) => {
    try {
      if (!selectedDocument || !selectedDocument.id || !areaId) return
      const processedAreas = await getProcessedAreasByDocumentId(selectedDocument.id)
      const foundProcessedArea = processedAreas.find(a => a.id === areaId)
      console.log(foundProcessedArea)
      return foundProcessedArea
    } catch (err) {
      console.error(err)
      return
    }
  }

  const handleHoverOverArea = (e: React.MouseEvent) => {
    const domRect = e.currentTarget.getBoundingClientRect()
    const x = e.clientX - domRect.left
    const y = e.clientY - domRect.top
    const point = { x, y }

    const areaContainingCoords = areas?.find(a => {
      const bounds = { startX: a.startX, startY: a.startY, endX: a.endX, endY: a.endY }
      return isInBounds(point, bounds, zoomLevel)
    })

    if (areaContainingCoords?.id === hoverOverAreaId) return

    setHoverOverAreaId(areaContainingCoords?.id || '')
    getProcessedAreaById(areaContainingCoords?.id || '').then(response => {
      setHoveredProcessedArea(response)
    })
  }

  const handleMouseDrawArea = (x: number, y: number) => {
    const drawingCanvasInstance = drawingCanvas.current
    if (!drawingCanvasInstance) return
    const context = drawingCanvasInstance.getContext('2d')
    if (!context) return

    context.clearRect(0, 0, drawingCanvasInstance.width, drawingCanvasInstance.height)
    context.beginPath()
    const width = x - downClickX
    const height = y - downClickY
    context.rect(downClickX, downClickY, width, height)
    context.strokeStyle = '#000'
    context.lineWidth = 2
    context.stroke()
  }

  const handleMouseDown = (e: React.MouseEvent) => {
    console.log(e)
    if (e.nativeEvent.shiftKey) {
      const drawingCanvasInstance = drawingCanvas.current
      if (!drawingCanvasInstance) return

      downClickX = e.nativeEvent.offsetX
      downClickY = e.nativeEvent.offsetY
      isDrawing = true
    }
  }

  const handleMouseUp = async (e: React.MouseEvent) => {
    if (isDrawing) {
      const drawingCanvasInstance = drawingCanvas.current
      if (!drawingCanvasInstance) return

      const mouseX = e.nativeEvent.offsetX
      const mouseY = e.nativeEvent.offsetY

      let startX: number, endX: number
      if (downClickX < mouseX) {
        startX = Math.floor(downClickX / zoomLevel)
        endX = Math.floor(mouseX / zoomLevel)
      } else {
        startX = Math.floor(mouseX / zoomLevel)
        endX = Math.floor(downClickX / zoomLevel)
      }

      let startY: number, endY: number
      if (downClickY < mouseY) {
        startY = Math.floor(downClickY / zoomLevel)
        endY = Math.floor(mouseY / zoomLevel)
      } else {
        startY = Math.floor(mouseY / zoomLevel)
        endY = Math.floor(downClickY / zoomLevel)
      }

      if (selectedDocument?.id) {
        const addedArea = await requestAddArea(selectedDocument.id, { startX, startY, endX, endY })
        setSelectedAreaId(addedArea.id)
        processImageArea(selectedDocument.id, addedArea.id)
      }

      const context = drawingCanvasInstance.getContext('2d')
      context?.clearRect(0, 0, drawingCanvasInstance.width, drawingCanvasInstance.height)
      isDrawing = false
      downClickX = 0
      downClickY = 0
    }
  }

  const handleMouseMove = (e: React.MouseEvent) => {
    let mouseX = e.nativeEvent.offsetX
    let mouseY = e.nativeEvent.offsetY

    if (isDrawing) handleMouseDrawArea(mouseX, mouseY)
    else handleHoverOverArea(e)
  }

  const handleWheelEvent = (e: WheelEvent<HTMLDivElement>) => {
    if (!e.ctrlKey) return

    const shouldAttemptToZoomIn = (e.deltaY < 0) && zoomLevel < maxZoomLevel
    if (shouldAttemptToZoomIn) setZoomLevel(zoomLevel + zoomStep)
    else if (zoomLevel > (zoomStep * 2)) setZoomLevel(zoomLevel - zoomStep)
  }

  const handleWordCorrectionSubmit = (wordId: string, newWordValue: string) => {
    console.log(newWordValue)
    requestUpdateProcessedWordById(wordId, newWordValue)
      .then(res => {
        console.log('res', res)
        getProcessedAreaById(hoverOverAreaId || '').then(response => {
          setHoveredProcessedArea(response)
        })
      })
      .catch(console.error)
    setWordToEdit(undefined)
  }

  useEffect(() => {
    if (selectedDocument?.path) applyDocumentToCanvas(selectedDocument.path)
  })

  useEffect(() => {
    applyUiCanvasUpdates()
  }, [hoverOverAreaId])

  const renderAreaPreview = () => {
    if (!areas || !areas.length || !hoveredProcessedArea) return <></>

    const hoverArea = areas.find(a => a.id === hoverOverAreaId)
    if (!hoverArea) return <></>


    return <div>
      {
        hoveredProcessedArea.lines?.map(l => l.words).flat().map((w, i) => {
          const width = Math.floor((w.boundingBox.x1 - w.boundingBox.x0) * zoomLevel) + 2
          const height = Math.floor((w.boundingBox.y1 - w.boundingBox.y0) * zoomLevel) + 2
          return <span
            key={i}
            dir={w.direction === 'RIGHT_TO_LEFT' ? 'rtl' : 'ltr'}
            className={classNames('absolute text-center inline-block p-1 rounded-md shadow-zinc-900 shadow-2xl',
              'hover:bg-opacity-60 hover:bg-black hover:text-white',
              'bg-opacity-80 bg-slate-300 text-slate-500'
            )}
            style={{
              fontSize: `${3.4 * zoomLevel}vmin`,
              width,
              top: Math.floor(w.boundingBox.y0 * zoomLevel) + height,
              left: Math.floor(w.boundingBox.x0 * zoomLevel)
            }}
            onDoubleClick={() => setWordToEdit({ word: w, areaId: hoverArea.id })}>
            {w.fullText}
          </span>
        })
      }
    </div>
  }

  const renderEditWord = () => {
    if (!wordToEdit) return <></>

    const { word, areaId } = wordToEdit
    const width = Math.floor((word.boundingBox.x1 - word.boundingBox.x0) * zoomLevel) + 2
    const height = Math.floor(((word.boundingBox.y1 - word.boundingBox.y0) * zoomLevel) * 2) + 4
    return <div
      dir={word.direction === 'RIGHT_TO_LEFT' ? 'rtl' : 'ltr'}
      className={classNames('absolute inline-block p-1 rounded-md',
        'bg-opacity-60 bg-black text-white',
      )}
      style={{
        width,
        height,
        top: Math.floor(word.boundingBox.y0 * zoomLevel) + (height / 2),
        left: Math.floor(word.boundingBox.x0 * zoomLevel)
      }}
      onBlur={() => setWordToEdit(undefined)}
    >
      <div
        className={classNames('text-center align-middle block p-1 rounded-md shadow-zinc-900 shadow-2xl',
          'bg-opacity-60 bg-black text-white',
        )}
        style={{
          fontSize: `${3.4 * zoomLevel}vmin`,
          height: height / 2,
        }}>
        {word.fullText}
      </div>

      <input
        type='text'
        className='inline-block text-slate-900 p-0 m-0 w-full'
        autoFocus
        width={width}
        ref={editWordInput}
        placeholder={word.fullText}
        defaultValue={word.fullText}
        style={{
          fontSize: `${3.4 * zoomLevel}vmin`,
          height: height / 2,
        }}
        onFocus={(e) => e.currentTarget.select()}
        onBlur={(e) => handleWordCorrectionSubmit(word.id, e.currentTarget.value)}
        onKeyDown={(e) => onEnterHandler(e, () => handleWordCorrectionSubmit(word.id, e.currentTarget.value))}
      />
    </div>
  }


  return <div className='relative'>
    <div className='flex justify-between align-top mb-2'>
      <div className='flex align-top'>
        <h1 className="text-xl font-semibold text-gray-900 inline-block mr-2">{getSelectedDocument()?.name}</h1>
        <LanguageSelect shouldUpdateDocument defaultLanguage={selectedDocument?.defaultLanguage} />
      </div>
      <div className='flex justify-evenly items-center'>
        <MagnifyingGlassMinusIcon className='w-4 h-4' />
        <input
          id="zoomRange" type="range" min={zoomStep} max={maxZoomLevel} step={zoomStep}
          value={zoomLevel} className="w-[calc(100%-50px)] h-2 bg-indigo-200 rounded-lg appearance-none cursor-pointer p-0"
          onChange={(e) => { setZoomLevel(e.currentTarget.valueAsNumber) }}
        />
        <MagnifyingGlassPlusIcon className='w-4 h-4' />
      </div>
    </div>
    <div
      onWheelCapture={handleWheelEvent}
      className={classNames('relative mt-2 overflow-scroll',
        'w-[calc(100vw-320px)] h-[calc(100vh-174px)] border-4',
        'border-dashed border-gray-200')}>
      <canvas
        className="absolute"
        ref={documentCanvas}
      />
      <canvas
        className="absolute"
        ref={areaCanvas}
      />
      <canvas
        className="absolute"
        ref={uiCanvas}
      />
      <canvas
        className="absolute"
        ref={drawingCanvas}
        onMouseDown={handleMouseDown}
        onMouseUp={handleMouseUp}
        onMouseMove={handleMouseMove}
      />
      {renderAreaPreview()}
      {renderEditWord()}
    </div>
  </div >
}

export default DocumentRenderer
