import {
  AccessTime,
  CalendarMonth,
  ExpandMore,
  Gesture,
  PictureAsPdf,
  Badge,
  Work,
  Close
} from '@mui/icons-material'
import {
  Box,
  Typography,
  Accordion,
  AccordionDetails,
  AccordionSummary,
  CircularProgress,
  FormControl,
  InputLabel,
  MenuItem,
  Select
} from '@mui/material'
import styles from './style.module.scss'
import { useEffect, useState } from 'react'

import * as PDFJS from 'pdfjs-dist'
import { ProfileMetadata, User } from '../../types'
import {
  PdfFile,
  DrawTool,
  MouseState,
  PdfPage,
  DrawnField,
  MarkType
} from '../../types/drawing'
import { truncate } from 'lodash'
import { hexToNpub } from '../../utils'
import { toPdfFiles } from '../../utils/pdf.ts'
PDFJS.GlobalWorkerOptions.workerSrc = new URL(
  'pdfjs-dist/build/pdf.worker.min.mjs',
  import.meta.url
).toString()

interface Props {
  selectedFiles: File[]
  users: User[]
  metadata: { [key: string]: ProfileMetadata }
  onDrawFieldsChange: (pdfFiles: PdfFile[]) => void
}

export const DrawPDFFields = (props: Props) => {
  const { selectedFiles } = props

  const [pdfFiles, setPdfFiles] = useState<PdfFile[]>([])
  const [parsingPdf, setParsingPdf] = useState<boolean>(false)
  const [showDrawToolBox, setShowDrawToolBox] = useState<boolean>(false)

  const [selectedTool, setSelectedTool] = useState<DrawTool | null>()
  const [toolbox] = useState<DrawTool[]>([
    {
      identifier: MarkType.SIGNATURE,
      icon: <Gesture />,
      label: 'Signature',
      active: false
    },
    {
      identifier: MarkType.FULLNAME,
      icon: <Badge />,
      label: 'Full Name',
      active: true
    },
    {
      identifier: MarkType.JOBTITLE,
      icon: <Work />,
      label: 'Job Title',
      active: false
    },
    {
      identifier: MarkType.DATE,
      icon: <CalendarMonth />,
      label: 'Date',
      active: false
    },
    {
      identifier: MarkType.DATETIME,
      icon: <AccessTime />,
      label: 'Datetime',
      active: false
    }
  ])

  const [mouseState, setMouseState] = useState<MouseState>({
    clicked: false
  })

  useEffect(() => {
    if (selectedFiles) {
      setParsingPdf(true)

      parsePdfPages().finally(() => {
        setParsingPdf(false)
      })
    }
  }, [selectedFiles])

  useEffect(() => {
    if (pdfFiles) props.onDrawFieldsChange(pdfFiles)
  }, [pdfFiles])

  /**
   * Drawing events
   */
  useEffect(() => {
    // window.addEventListener('mousedown', onMouseDown);
    window.addEventListener('mouseup', onMouseUp)

    return () => {
      // window.removeEventListener('mousedown', onMouseDown);
      window.removeEventListener('mouseup', onMouseUp)
    }
  }, [])

  const refreshPdfFiles = () => {
    setPdfFiles([...pdfFiles])
  }

  /**
   * Fired only when left click and mouse over pdf page
   * Creates new drawnElement and pushes in the array
   * It is re rendered and visible right away
   *
   * @param event Mouse event
   * @param page PdfPage where press happened
   */
  const onMouseDown = (event: any, page: PdfPage) => {
    // Proceed only if left click
    if (event.button !== 0) return

    // Only allow drawing if mouse is not over other drawn element
    const isOverPdfImageWrapper = event.target.tagName === 'IMG'

    if (!selectedTool || !isOverPdfImageWrapper) {
      return
    }

    const { mouseX, mouseY } = getMouseCoordinates(event)

    const newField: DrawnField = {
      left: mouseX,
      top: mouseY,
      width: 0,
      height: 0,
      counterpart: '',
      type: selectedTool.identifier
    }

    page.drawnFields.push(newField)

    setMouseState((prev) => {
      return {
        ...prev,
        clicked: true
      }
    })
  }

  /**
   * Drawing is finished, resets all the variables used to draw
   * @param event Mouse event
   */
  const onMouseUp = () => {
    setMouseState((prev) => {
      return {
        ...prev,
        clicked: false,
        dragging: false,
        resizing: false
      }
    })
  }

  /**
   * After {@link onMouseDown} create an drawing element, this function gets called on every pixel moved
   * which alters the newly created drawing element, resizing it while mouse move
   * @param event Mouse event
   * @param page PdfPage where moving is happening
   */
  const onMouseMove = (event: any, page: PdfPage) => {
    if (mouseState.clicked && selectedTool) {
      const lastElementIndex = page.drawnFields.length - 1
      const lastDrawnField = page.drawnFields[lastElementIndex]

      const { mouseX, mouseY } = getMouseCoordinates(event)

      const width = mouseX - lastDrawnField.left
      const height = mouseY - lastDrawnField.top

      lastDrawnField.width = width
      lastDrawnField.height = height

      const currentDrawnFields = page.drawnFields

      currentDrawnFields[lastElementIndex] = lastDrawnField

      refreshPdfFiles()
    }
  }

  /**
   * Fired when event happens on the drawn element which will be moved
   * mouse coordinates relative to drawn element will be stored
   * so when we start moving, offset can be calculated
   * mouseX - offsetX
   * mouseY - offsetY
   *
   * @param event Mouse event
   * @param drawnField Which we are moving
   */
  const onDrawnFieldMouseDown = (event: any) => {
    event.stopPropagation()

    // Proceed only if left click
    if (event.button !== 0) return

    const drawingRectangleCoords = getMouseCoordinates(event)

    setMouseState({
      dragging: true,
      clicked: false,
      coordsInWrapper: {
        mouseX: drawingRectangleCoords.mouseX,
        mouseY: drawingRectangleCoords.mouseY
      }
    })
  }

  /**
   * Moves the drawnElement by the mouse position (mouse can grab anywhere on the drawn element)
   * @param event Mouse event
   * @param drawnField which we are moving
   */
  const onDranwFieldMouseMove = (event: any, drawnField: DrawnField) => {
    if (mouseState.dragging) {
      const { mouseX, mouseY, rect } = getMouseCoordinates(
        event,
        event.target.parentNode
      )
      const coordsOffset = mouseState.coordsInWrapper

      if (coordsOffset) {
        let left = mouseX - coordsOffset.mouseX
        let top = mouseY - coordsOffset.mouseY

        const rightLimit = rect.width - drawnField.width - 3
        const bottomLimit = rect.height - drawnField.height - 3

        if (left < 0) left = 0
        if (top < 0) top = 0
        if (left > rightLimit) left = rightLimit
        if (top > bottomLimit) top = bottomLimit

        drawnField.left = left
        drawnField.top = top

        refreshPdfFiles()
      }
    }
  }

  /**
   * Fired when clicked on the resize handle, sets the state for a resize action
   * @param event Mouse event
   * @param drawnField which we are resizing
   */
  const onResizeHandleMouseDown = (event: any) => {
    // Proceed only if left click
    if (event.button !== 0) return

    event.stopPropagation()

    setMouseState({
      resizing: true
    })
  }

  /**
   * Resizes the drawn element by the mouse position
   * @param event Mouse event
   * @param drawnField which we are resizing
   */
  const onResizeHandleMouseMove = (event: any, drawnField: DrawnField) => {
    if (mouseState.resizing) {
      const { mouseX, mouseY } = getMouseCoordinates(
        event,
        event.target.parentNode.parentNode
      )

      const width = mouseX - drawnField.left
      const height = mouseY - drawnField.top

      drawnField.width = width
      drawnField.height = height

      refreshPdfFiles()
    }
  }

  /**
   * Removes the drawn element using the indexes in the params
   * @param event Mouse event
   * @param pdfFileIndex pdf file index
   * @param pdfPageIndex pdf page index
   * @param drawnFileIndex drawn file index
   */
  const onRemoveHandleMouseDown = (
    event: any,
    pdfFileIndex: number,
    pdfPageIndex: number,
    drawnFileIndex: number
  ) => {
    event.stopPropagation()

    pdfFiles[pdfFileIndex].pages[pdfPageIndex].drawnFields.splice(
      drawnFileIndex,
      1
    )
  }

  /**
   * Used to stop mouse click propagating to the parent elements
   * so select can work properly
   * @param event Mouse event
   */
  const onUserSelectHandleMouseDown = (event: any) => {
    event.stopPropagation()
  }

  /**
   * Gets the mouse coordinates relative to a element in the `event` param
   * @param event MouseEvent
   * @param customTarget mouse coordinates relative to this element, if not provided
   * event.target will be used
   */
  const getMouseCoordinates = (event: any, customTarget?: any) => {
    const target = customTarget ? customTarget : event.target
    const rect = target.getBoundingClientRect()
    const mouseX = event.clientX - rect.left //x position within the element.
    const mouseY = event.clientY - rect.top //y position within the element.

    return {
      mouseX,
      mouseY,
      rect
    }
  }

  /**
   * Reads the pdf binary files and converts it's pages to images
   * creates the pdfFiles object and sets to a state
   */
  const parsePdfPages = async () => {
    const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles)

    setPdfFiles(pdfFiles)
  }

  /**
   *
   * @returns if expanded pdf accordion is present
   */
  const hasExpandedPdf = () => {
    return !!pdfFiles.filter((pdfFile) => !!pdfFile.expanded).length
  }

  const handleAccordionExpandChange = (expanded: boolean, pdfFile: PdfFile) => {
    pdfFile.expanded = expanded

    refreshPdfFiles()
    setShowDrawToolBox(hasExpandedPdf())
  }

  /**
   * Changes the drawing tool
   * @param drawTool to draw with
   */
  const handleToolSelect = (drawTool: DrawTool) => {
    // If clicked on the same tool, unselect
    if (drawTool.identifier === selectedTool?.identifier) {
      setSelectedTool(null)
      return
    }

    setSelectedTool(drawTool)
  }

  /**
   * Renders the pdf pages and drawing elements
   */
  const getPdfPages = (pdfFile: PdfFile, pdfFileIndex: number) => {
    return (
      <Box
        sx={{
          width: '100%'
        }}
      >
        {pdfFile.pages.map((page, pdfPageIndex: number) => {
          return (
            <div
              key={pdfPageIndex}
              style={{
                border: '1px solid #c4c4c4',
                marginBottom: '10px'
              }}
              className={`${styles.pdfImageWrapper} ${selectedTool ? styles.drawing : ''}`}
              onMouseMove={(event) => {
                onMouseMove(event, page)
              }}
              onMouseDown={(event) => {
                onMouseDown(event, page)
              }}
            >
              <img
                draggable="false"
                style={{ width: '100%' }}
                src={page.image}
              />

              {page.drawnFields.map((drawnField, drawnFieldIndex: number) => {
                return (
                  <div
                    key={drawnFieldIndex}
                    onMouseDown={onDrawnFieldMouseDown}
                    onMouseMove={(event) => {
                      onDranwFieldMouseMove(event, drawnField)
                    }}
                    className={styles.drawingRectangle}
                    style={{
                      left: `${drawnField.left}px`,
                      top: `${drawnField.top}px`,
                      width: `${drawnField.width}px`,
                      height: `${drawnField.height}px`,
                      pointerEvents: mouseState.clicked ? 'none' : 'all'
                    }}
                  >
                    <span
                      onMouseDown={onResizeHandleMouseDown}
                      onMouseMove={(event) => {
                        onResizeHandleMouseMove(event, drawnField)
                      }}
                      className={styles.resizeHandle}
                    ></span>
                    <span
                      onMouseDown={(event) => {
                        onRemoveHandleMouseDown(
                          event,
                          pdfFileIndex,
                          pdfPageIndex,
                          drawnFieldIndex
                        )
                      }}
                      className={styles.removeHandle}
                    >
                      <Close fontSize="small" />
                    </span>
                    <div
                      onMouseDown={onUserSelectHandleMouseDown}
                      className={styles.userSelect}
                    >
                      <FormControl fullWidth size="small">
                        <InputLabel id="counterparts">Counterpart</InputLabel>
                        <Select
                          value={drawnField.counterpart}
                          onChange={(event) => {
                            drawnField.counterpart = event.target.value
                            refreshPdfFiles()
                          }}
                          labelId="counterparts"
                          label="Counterparts"
                        >
                          {props.users.map((user, index) => {
                            let displayValue = truncate(
                              hexToNpub(user.pubkey),
                              {
                                length: 16
                              }
                            )

                            const metadata = props.metadata[user.pubkey]

                            if (metadata) {
                              displayValue = truncate(
                                metadata.name ||
                                  metadata.display_name ||
                                  metadata.username,
                                {
                                  length: 16
                                }
                              )
                            }

                            return (
                              <MenuItem
                                key={index}
                                value={hexToNpub(user.pubkey)}
                              >
                                {displayValue}
                              </MenuItem>
                            )
                          })}
                        </Select>
                      </FormControl>
                    </div>
                  </div>
                )
              })}
            </div>
          )
        })}
      </Box>
    )
  }

  if (parsingPdf) {
    return (
      <Box sx={{ width: '100%', textAlign: 'center' }}>
        <CircularProgress />
      </Box>
    )
  }

  if (!pdfFiles.length) {
    return ''
  }

  return (
    <Box>
      <Box sx={{ mt: 1 }}>
        <Typography sx={{ mb: 1 }}>Draw fields on the PDFs:</Typography>

        {pdfFiles.map((pdfFile, pdfFileIndex: number) => {
          return (
            <Accordion
              key={pdfFileIndex}
              expanded={pdfFile.expanded}
              onChange={(_event, expanded) => {
                handleAccordionExpandChange(expanded, pdfFile)
              }}
            >
              <AccordionSummary
                expandIcon={<ExpandMore />}
                aria-controls={`panel${pdfFileIndex}-content`}
                id={`panel${pdfFileIndex}header`}
              >
                <PictureAsPdf sx={{ mr: 1 }} />
                {pdfFile.file.name}
              </AccordionSummary>
              <AccordionDetails>
                {getPdfPages(pdfFile, pdfFileIndex)}
              </AccordionDetails>
            </Accordion>
          )
        })}
      </Box>

      {showDrawToolBox && (
        <Box className={styles.drawToolBoxContainer}>
          <Box className={styles.drawToolBox}>
            {toolbox
              .filter((drawTool) => drawTool.active)
              .map((drawTool: DrawTool, index: number) => {
                return (
                  <Box
                    key={index}
                    onClick={() => {
                      handleToolSelect(drawTool)
                    }}
                    className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`}
                  >
                    {drawTool.icon}
                    {drawTool.label}
                  </Box>
                )
              })}
          </Box>
        </Box>
      )}
    </Box>
  )
}
