import { nanoid } from 'nanoid'
import { useEffect, useRef, useState } from 'react'
import { FaRedo, FaUndo } from 'react-icons/fa'
import { Layer, Line, Rect, Stage, Image } from 'react-konva'

import type { KonvaEventObject } from 'konva'
import type { ChangeEventHandler } from 'react'

type ImageItem = { src: string }

type Layer = {
    id:       string
    name:     string
    lines:    Line[]
    history:  Line[][]
    future:   Line[][]
    image?:   ImageItem }

type Line = {
    mode:         Mode
    points:       number[]
    stroke:       string
    strokeWidth:  number }

const Mode = {
    Pen:    Symbol (),
    Rubber: Symbol (),
    Image:  Symbol () } as const
type Mode = keyof Mode


export default () => {
  const drawingRef = useRef (false)
  const fileInputRef = useRef<HTMLInputElement> (null)
  const stageRef = useRef<Stage | null> (null)

  const [activeLayerId, setActiveLayerId] = useState<string | null> (null)
  const [colour, setColour] = useState ('#000000')
  const [images, setImages] = useState<Record<string, window.Image[]>> ({ })
  const [layers, setLayers] = useState<Layer[]> ([])
  const [layerCnt, setLayerCnt] = useState (0)
  const [mode, setMode] = useState<Mode> (Mode.Pen)
  const [pointSize, setPointSize] = useState (3)
  const [stageWidth, setStageWidth] = useState (480)
  const [stageHeight, setStageHeight] = useState (480)

  const activeLayer = layers.find (l => l.id === activeLayerId)

  const handleMouseDown = (ev: KonvaEventObject<MouseEvent>) => {
    drawingRef.current = true
    const pos = ev.target.getStage ()?.getPointerPosition ()
    if (!(pos))
      return
    const lines: Line[] = [
	...activeLayer.lines,
	{ mode,
	  points:      [pos.x, pos.y],
	  stroke:      colour,
	  strokeWidth: pointSize }]
    updateActiveLayerLines (lines)
  }

  const handleMouseMove = (ev: KonvaEventObject<MouseEvent>) => {
    if (!(drawingRef.current))
      return
    const stage = ev.target.getStage ()
    const point = stage?.getPointerPosition ()
    if (!(point))
      return

    const lastLine = activeLayer.lines[activeLayer.lines.length - 1]
    const updatedLine = { ...lastLine,
			  points: [...lastLine.points, point.x, point.y] }
    const newLines = [...activeLayer.lines.slice (0, -1), updatedLine]
    updateActiveLayerLines (newLines)
  }

  const handleMouseUp = () => {
    drawingRef.current = false
  }

  const handleUndo = () => {
    if (activeLayer.lines.length === 0)
      return
    const newLines = [...activeLayer.lines]
    const last = newLines.pop()!
    updateActiveLayerHistory ([...activeLayer.history, activeLayer.lines])
    updateActiveLayerFuture ([])
    updateActiveLayerLines (newLines)
  }

  const handleRedo = () => {
    if (activeLayer.history.length === 0)
      return
    const prev = activeLayer.history[activeLayer.history.length - 1]
    updateActiveLayerLines (prev)
    updateActiveLayerHistory (activeLayer.history.slice (0, -1))
  }

  const handleImportImage: ChangeEventHandler<HTMLInputElement> = ev => {
    const file = ev.target.files?.[0]
    if (!(file))
      return

    const reader = new FileReader ()
    reader.onload = () => {
      const src = reader.result?.toString ()
      if (!(src))
	return
      addLayer ({ src })
    }
    reader.readAsDataURL (file)

    if (fileInputRef.current)
      fileInputRef.current.value = ''
  }

  const updateActiveLayerLines = (lines: Line[]) => {
    setLayers (prev => (
	prev.map (layer => (layer.id === activeLayerId
			    ? { ...layer, lines }
			    : layer))))
  }

  const updateActiveLayerHistory = (history: Line[][]) => {
    setLayers (prev => (
	prev.map (layer => (layer.id === activeLayerId
			    ? { ...layer, history }
			    : layer))))
  }

  const updateActiveLayerFuture = (future: Line[][]) => {
    setLayers (prev => (
	prev.map (layer => (layer.id === activeLayerId
			    ? { ...layer, future }
			    : layer))))
  }

  const addLayer = (image?: ImageItem) => {
    const layer: Layer = { id:      nanoid (),
			   name:    `Layer ${ layerCnt + 1 }`,
			   lines:   [],
			   history: [],
			   future:  [],
			   image }
    setLayers(prev => [...prev, layer])
    setActiveLayerId (layer.id)
    setLayerCnt (n => n + 1)

    return layer
  }

  const moveLayer = (direction: number) => {
    setLayers(prev => {
      const idx = prev.findIndex (l => l.id === activeLayerId)
      if (idx < 0)
	return prev
      const target = idx + direction
      if (target < 0 || target >= prev.length)
	return prev
      const next = [...prev]
      const [item] = next.splice(idx, 1)
      next.splice(target, 0, item)
      return next
    })
  }

  useEffect (() => {
    try
    {
      const paint = JSON.parse (localStorage.getItem ('paint'))
      if (!(paint instanceof Array))
	throw new Error
      setLayers (paint)
      setActiveLayerId (paint[0].id)
    }
    catch
    {
      const layer: Layer = { id:      nanoid (),
			     name:    `Layer ${ layerCnt + 1 }`,
			     lines:   [],
			     history: [],
			     future:  [] }
      setLayers([layer])
      setActiveLayerId (layer.id)
      setLayerCnt (n => n + 1)
    }
  }, [])

  useEffect (() => {
    localStorage.setItem ('paint', JSON.stringify (layers))

    layers.forEach (layer => {
      if (layer.image && !(layer.id in images))
	{
	  const image = new window.Image
	  image.src = layer.image.src
	  image.onload = () => {
	    setImages (prev => ({ ...prev, [layer.id]: image }))
	  }
	}
    })
  }, [layers])

  return (
      <div>
	<div>
	  <div>
	    <button onClick={() => {
	      if (!(confirm ('作成中の絵を消してもよろしいですか?')))
		return

	      setLayers (layers.map (layer => ({
		  ...layer,
		  lines:    [],
		  hishory:  [],
		  future:   [] })))
	    }}>
	      初期化
	    </button>

	    <button onClick={() => fileInputRef.current?.click ()}>
	      画像
	    </button>
	    <input ref={fileInputRef}
		   type="file"
		   accept="image/*"
		   className="hidden"
		   onChange={handleImportImage} />

	    <button onClick={() => {
	      const stage = stageRef.current
	      if (!(stage))
		return
	      const mimeType = 'image/png'
	      const dataURL = stage.toDataURL ({ mimeType, pixelRatio: 2 })

	      const $a = document.createElement ('a')
	      $a.href = dataURL
	      $a.download = `kekec-bbs_${ (new Date).toLocaleString ('ja-JP-u-ca-japanese').replaceAll (/[\/:]/g, '-').replaceAll (/\s/g, '_') }.png`
	      document.body.appendChild ($a)
	      $a.click ()
	      $a.remove ()
	    }}>
	      保存
	    </button>
	  </div>

	  <div>
	    <label>モード：</label>
	    <label>
	      <input type="radio"
		     name="mode"
		     value="pen"
		     checked={mode === Mode.Pen}
		     onChange={() => setMode (Mode.Pen)} />
	      ペン
	    </label>
	    <label>
	      <input type="radio"
		     name="mode"
		     value="rubber"
		     checked={mode === Mode.Rubber}
		     onChange={() => setMode (Mode.Rubber)} />
	      消しゴム
	    </label>
	  </div>

	  <div>
	    <button onClick={handleUndo} disabled={activeLayer?.lines.length === 0}>
	      <FaUndo />
	    </button>
	    <button onClick={handleRedo} disabled={activeLayer?.history.length === 0}>
	      <FaRedo />
	    </button>
	  </div>

	  <div>
	    <label>色：</label>
	    <input type="color"
		   value={colour}
		   onChange={ev => setColour (ev.target.value)} />
	  </div>

	  <div>
	    <label>サイズ：</label>
	    <input type="range"
		   min={1}
		   max={57}
		   value={pointSize}
		   onChange={ev => setPointSize (+ev.target.value)} />
	    {pointSize}
	  </div>

	  <div>
	    <label>レイア：</label>
	    {layers.map (layer => (
		<label key={layer.id}>
		  <input type="radio"
			 name="layer"
			 value={layer.id}
			 checked={layer.id === activeLayerId}
			 onChange={() => setActiveLayerId (layer.id)} />
		  {layer.name}
		</label>))}
	    <button onClick={() => {
	      if (layers.length === 1)
		{
		  alert ('何を消そうというのだ．')
		  return
		}

	      if (!(confirm ('選択中のレイアを削除するがよろしいか?')))
		return

	      let idx = layers.findIndex (l => l.id === activeLayerId) - 1
	      if (idx < 0)
		idx = 0
	      const newLayers = layers.filter (l => l.id !== activeLayerId)
	      setActiveLayerId (newLayers[idx].id)
	      setLayers (newLayers)
	    }}>
	      削除
	    </button>
	    <button onClick={() => {
	      moveLayer (-1)
	    }}>
	      下へ
	    </button>
	    <button onClick={() => {
	      moveLayer (1)
	    }}>
	      上へ
	    </button>
	    <button onClick={() => addLayer ()}>
	      追加
	    </button>
	  </div>
	</div>

	<div className="w-full flex items-center justify-center mb-16">
	  <div className="border border-black dark:border-white">
	    <Stage ref={node => {
		     stageRef.current = node
		     return node
		   }}
		   width={stageWidth}
		   height={stageHeight}
		   onMouseDown={handleMouseDown}
		   onMouseMove={handleMouseMove}
		   onMouseUp={handleMouseUp}
		   onTouchStart={handleMouseDown}
		   onTouchMove={handleMouseMove}
		   onTouchEnd={handleMouseUp}>
	      <Layer>
		<Rect x={0}
		      y={0}
		      width={stageWidth}
		      height={stageHeight}
		      fill="white" />
	      </Layer>

	      {layers.map (layer => (
		  <Layer key={layer.id}>
		    {images[layer.id] && (
			<Image image={images[layer.id]}
			       x={0}
			       y={0}
			       scaleX={stageWidth / images[layer.id].width}
			       scaleY={stageHeight / images[layer.id].height} />)}

		    {layer.lines.map ((line, i) => (
			<Line key={i}
			      points={line.points}
			      stroke={line.mode === Mode.Rubber ? 'black' : line.stroke}
			      strokeWidth={line.strokeWidth}
			      tension={.5}
			      lineCap="round"
			      globalCompositeOperation={line.mode === Mode.Rubber
							? 'destination-out'
							: 'source-over'} />))}
		  </Layer>))}
	    </Stage>
	  </div>
	</div>
      </div>)
}
