import { Close } from '@mui/icons-material'
import {
  FormControl,
  InputLabel,
  ListItemIcon,
  ListItemText,
  MenuItem,
  Select
} from '@mui/material'
import styles from './style.module.scss'
import React, { useEffect, useState } from 'react'
import { ProfileMetadata, User, UserRole } from '../../types'
import { MouseState, PdfPage, DrawnField, DrawTool } from '../../types/drawing'
import { truncate } from 'lodash'
import { settleAllFullfilfedPromises, hexToNpub, npubToHex } from '../../utils'
import { getSigitFile, SigitFile } from '../../utils/file'
import { getToolboxLabelByMarkType } from '../../utils/mark'
import { FileDivider } from '../FileDivider'
import { ExtensionFileBox } from '../ExtensionFileBox'
import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf'
import { useScale } from '../../hooks/useScale'
import { AvatarIconButton } from '../UserAvatarIconButton'
import { LoadingSpinner } from '../LoadingSpinner'

const DEFAULT_START_SIZE = {
  width: 140,
  height: 40
} as const

interface Props {
  selectedFiles: File[]
  users: User[]
  metadata: { [key: string]: ProfileMetadata }
  onDrawFieldsChange: (sigitFiles: SigitFile[]) => void
  selectedTool?: DrawTool
}

export const DrawPDFFields = (props: Props) => {
  const { selectedFiles, selectedTool, onDrawFieldsChange, users } = props
  const { to, from } = useScale()

  const [sigitFiles, setSigitFiles] = useState<SigitFile[]>([])
  const [parsingPdf, setIsParsing] = useState<boolean>(false)

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

  const [activeDrawField, setActiveDrawField] = useState<number>()

  useEffect(() => {
    if (selectedFiles) {
      /**
       * Reads the binary files and converts to internal file type
       * and sets to a state (adds images if it's a PDF)
       */
      const parsePages = async () => {
        const files = await settleAllFullfilfedPromises(
          selectedFiles,
          getSigitFile
        )

        setSigitFiles(files)
      }

      setIsParsing(true)

      parsePages().finally(() => {
        setIsParsing(false)
      })
    }
  }, [selectedFiles])

  useEffect(() => {
    if (sigitFiles) onDrawFieldsChange(sigitFiles)
  }, [onDrawFieldsChange, sigitFiles])

  /**
   * Drawing events
   */
  useEffect(() => {
    window.addEventListener('pointerup', handlePointerUp)
    window.addEventListener('pointercancel', handlePointerUp)

    return () => {
      window.removeEventListener('pointerup', handlePointerUp)
      window.removeEventListener('pointercancel', handlePointerUp)
    }
  }, [])

  const refreshPdfFiles = () => {
    setSigitFiles([...sigitFiles])
  }

  /**
   * Fired only on when left (primary pointer interaction) clicking page image
   * Creates new drawnElement and pushes in the array
   * It is re rendered and visible right away
   *
   * @param event Pointer event
   * @param page PdfPage where press happened
   */
  const handlePointerDown = (event: React.PointerEvent, page: PdfPage) => {
    // Proceed only if left click
    if (event.button !== 0) return

    if (!selectedTool) {
      return
    }

    const { x, y } = getPointerCoordinates(event)

    const newField: DrawnField = {
      left: to(page.width, x),
      top: to(page.width, y),
      width: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.width,
      height: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.height,
      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 Pointer event
   */
  const handlePointerUp = () => {
    setMouseState((prev) => {
      return {
        ...prev,
        clicked: false,
        dragging: false,
        resizing: false
      }
    })
  }

  /**
   * After {@link handlePointerDown} create an drawing element, this function gets called on every pixel moved
   * which alters the newly created drawing element, resizing it while pointer moves
   * @param event Pointer event
   * @param page PdfPage where moving is happening
   */
  const handlePointerMove = (event: React.PointerEvent, page: PdfPage) => {
    if (mouseState.clicked && selectedTool && event.pointerType === 'mouse') {
      const lastElementIndex = page.drawnFields.length - 1

      const lastDrawnField = page.drawnFields[lastElementIndex]

      // Return early if we don't have lastDrawnField
      // Issue noticed in the console when dragging out of bounds
      // to the page below (without releaseing mouse click)
      if (!lastDrawnField) return

      const { x, y } = getPointerCoordinates(event)

      const width = to(page.width, x) - lastDrawnField.left
      const height = to(page.width, y) - 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
   * pointer coordinates relative to drawn element will be stored
   * so when we start moving, offset can be calculated
   * x - offsetX
   * y - offsetY
   *
   * @param event Pointer event
   * @param drawnFieldIndex Which we are moving
   */
  const handleDrawnFieldPointerDown = (
    event: React.PointerEvent,
    drawnFieldIndex: number
  ) => {
    event.stopPropagation()

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

    const drawingRectangleCoords = getPointerCoordinates(event)

    setActiveDrawField(drawnFieldIndex)
    setMouseState({
      dragging: true,
      clicked: false,
      coordsInWrapper: {
        x: drawingRectangleCoords.x,
        y: drawingRectangleCoords.y
      }
    })
  }

  /**
   * Moves the drawnElement by the pointer position (pointer can grab anywhere on the drawn element)
   * @param event Pointer event
   * @param drawnField which we are moving
   * @param pageWidth pdf value which is used to calculate scaled offset
   */
  const handleDrawnFieldPointerMove = (
    event: React.PointerEvent,
    drawnField: DrawnField,
    pageWidth: number
  ) => {
    if (mouseState.dragging) {
      const { x, y, rect } = getPointerCoordinates(
        event,
        event.currentTarget.parentElement
      )
      const coordsOffset = mouseState.coordsInWrapper

      if (coordsOffset) {
        let left = to(pageWidth, x - coordsOffset.x)
        let top = to(pageWidth, y - coordsOffset.y)

        const rightLimit = to(pageWidth, rect.width) - drawnField.width
        const bottomLimit = to(pageWidth, rect.height) - drawnField.height

        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 Pointer event
   * @param drawnFieldIndex which we are resizing
   */
  const handleResizePointerDown = (
    event: React.PointerEvent,
    drawnFieldIndex: number
  ) => {
    // Proceed only if left click
    if (event.button !== 0) return
    event.stopPropagation()

    setActiveDrawField(drawnFieldIndex)
    setMouseState({
      resizing: true
    })
  }

  /**
   * Resizes the drawn element by the mouse position
   * @param event Pointer event
   * @param drawnField which we are resizing
   * @param pageWidth pdf value which is used to calculate scaled offset
   */
  const handleResizePointerMove = (
    event: React.PointerEvent,
    drawnField: DrawnField,
    pageWidth: number
  ) => {
    if (mouseState.resizing) {
      const { x, y } = getPointerCoordinates(
        event,
        // currentTarget = span handle
        // 1st parent = drawnField
        // 2nd parent = img
        event.currentTarget.parentElement?.parentElement
      )

      const width = to(pageWidth, x) - drawnField.left
      const height = to(pageWidth, y) - drawnField.top

      drawnField.width = width
      drawnField.height = height

      refreshPdfFiles()
    }
  }

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

    const pages = sigitFiles[pdfFileIndex]?.pages
    if (pages) {
      pages[pdfPageIndex]?.drawnFields?.splice(drawnFileIndex, 1)
    }
  }

  /**
   * Used to stop pointer click propagating to the parent elements
   * so select can work properly
   * @param event Pointer event
   */
  const handleUserSelectPointerDown = (event: React.PointerEvent) => {
    event.stopPropagation()
  }

  /**
   * Gets the pointer coordinates relative to a element in the `event` param
   * @param event PointerEvent
   * @param customTarget coordinates relative to this element, if not provided
   * event.target will be used
   */
  const getPointerCoordinates = (
    event: React.PointerEvent,
    customTarget?: HTMLElement | null
  ) => {
    const target = customTarget ? customTarget : event.currentTarget
    const rect = target.getBoundingClientRect()

    // Clamp X Y within the target
    const x = Math.min(event.clientX, rect.right) - rect.left //x position within the element.
    const y = Math.min(event.clientY, rect.bottom) - rect.top //y position within the element.

    return {
      x,
      y,
      rect
    }
  }
  /**
   * Renders the pdf pages and drawing elements
   */
  const getPdfPages = (file: SigitFile, fileIndex: number) => {
    // Early return if this is not a pdf
    if (!file.isPdf) return null

    return (
      <>
        {file.pages?.map((page, pageIndex: number) => {
          return (
            <div
              key={pageIndex}
              className={`image-wrapper ${styles.pdfImageWrapper} ${selectedTool ? styles.drawing : ''}`}
            >
              <img
                onPointerMove={(event) => {
                  handlePointerMove(event, page)
                }}
                onPointerDown={(event) => {
                  handlePointerDown(event, page)
                }}
                draggable="false"
                src={page.image}
                alt={`page ${pageIndex + 1} of ${file.name}`}
              />

              {page.drawnFields.map((drawnField, drawnFieldIndex: number) => {
                return (
                  <div
                    key={drawnFieldIndex}
                    onPointerDown={(event) =>
                      handleDrawnFieldPointerDown(event, drawnFieldIndex)
                    }
                    onPointerMove={(event) => {
                      handleDrawnFieldPointerMove(event, drawnField, page.width)
                    }}
                    className={styles.drawingRectangle}
                    style={{
                      backgroundColor: drawnField.counterpart
                        ? `#${npubToHex(drawnField.counterpart)?.substring(0, 6)}4b`
                        : undefined,
                      outlineColor: drawnField.counterpart
                        ? `#${npubToHex(drawnField.counterpart)?.substring(0, 6)}`
                        : undefined,
                      left: inPx(from(page.width, drawnField.left)),
                      top: inPx(from(page.width, drawnField.top)),
                      width: inPx(from(page.width, drawnField.width)),
                      height: inPx(from(page.width, drawnField.height)),
                      pointerEvents: mouseState.clicked ? 'none' : 'all',
                      touchAction: 'none',
                      opacity:
                        mouseState.dragging &&
                        activeDrawField === drawnFieldIndex
                          ? 0.8
                          : undefined
                    }}
                  >
                    <div
                      className={`file-mark ${styles.placeholder}`}
                      style={{
                        fontFamily: FONT_TYPE,
                        fontSize: inPx(from(page.width, FONT_SIZE))
                      }}
                    >
                      {getToolboxLabelByMarkType(drawnField.type) ||
                        'placeholder'}
                    </div>
                    <span
                      onPointerDown={(event) =>
                        handleResizePointerDown(event, drawnFieldIndex)
                      }
                      onPointerMove={(event) => {
                        handleResizePointerMove(event, drawnField, page.width)
                      }}
                      className={styles.resizeHandle}
                      style={{
                        background:
                          mouseState.resizing &&
                          activeDrawField === drawnFieldIndex
                            ? 'var(--primary-main)'
                            : undefined
                      }}
                    ></span>
                    <span
                      onPointerDown={(event) => {
                        handleRemovePointerDown(
                          event,
                          fileIndex,
                          pageIndex,
                          drawnFieldIndex
                        )
                      }}
                      className={styles.removeHandle}
                    >
                      <Close fontSize="small" />
                    </span>
                    <div
                      onPointerDown={handleUserSelectPointerDown}
                      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"
                          sx={{
                            background: 'white'
                          }}
                          renderValue={(value) => renderCounterpartValue(value)}
                        >
                          {users
                            .filter((u) => u.role === UserRole.signer)
                            .map((user, index) => {
                              const npub = hexToNpub(user.pubkey)
                              let displayValue = truncate(npub, {
                                length: 16
                              })

                              const metadata = props.metadata[user.pubkey]

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

                              return (
                                <MenuItem
                                  key={index}
                                  value={hexToNpub(user.pubkey)}
                                >
                                  <ListItemIcon>
                                    <AvatarIconButton
                                      src={metadata?.picture}
                                      hexKey={user.pubkey}
                                      aria-label={`account of user ${displayValue}`}
                                      color="inherit"
                                      sx={{
                                        padding: 0,
                                        '> img': {
                                          width: '30px',
                                          height: '30px'
                                        }
                                      }}
                                    />
                                  </ListItemIcon>
                                  <ListItemText>{displayValue}</ListItemText>
                                </MenuItem>
                              )
                            })}
                        </Select>
                      </FormControl>
                    </div>
                  </div>
                )
              })}
            </div>
          )
        })}
      </>
    )
  }

  const renderCounterpartValue = (value: string) => {
    const user = users.find((u) => u.pubkey === npubToHex(value))
    if (user) {
      let displayValue = truncate(value, {
        length: 16
      })

      const metadata = props.metadata[user.pubkey]

      if (metadata) {
        displayValue = truncate(
          metadata.name || metadata.display_name || metadata.username || value,
          {
            length: 16
          }
        )
      }
      return (
        <>
          <AvatarIconButton
            src={props.metadata[user.pubkey]?.picture}
            hexKey={npubToHex(user.pubkey) || undefined}
            sx={{
              padding: 0,
              marginRight: '6px',
              '> img': {
                width: '21px',
                height: '21px'
              }
            }}
          />
          {displayValue}
        </>
      )
    }

    return value
  }

  if (parsingPdf) {
    return <LoadingSpinner variant="small" />
  }

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

  return (
    <div className="files-wrapper">
      {sigitFiles.map((file, i) => {
        return (
          <React.Fragment key={file.name}>
            <div className="file-wrapper" id={`file-${file.name}`}>
              {file.isPdf && getPdfPages(file, i)}
              {file.isImage && (
                <img
                  className="file-image"
                  src={file.objectUrl}
                  alt={file.name}
                />
              )}
              {!(file.isPdf || file.isImage) && (
                <ExtensionFileBox extension={file.extension} />
              )}
            </div>
            {i < selectedFiles.length - 1 && <FileDivider />}
          </React.Fragment>
        )
      })}
    </div>
  )
}
