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, KeyboardCode } from '../../types'
import { MouseState, PdfPage, DrawnField, DrawTool } from '../../types/drawing'
import { hexToNpub, npubToHex, getProfileUsername } from '../../utils'
import { 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 { UserAvatar } from '../UserAvatar'
import _ from 'lodash'

const MINIMUM_RECT_SIZE = {
  width: 21,
  height: 21
} as const

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

interface HideSignersForDrawnField {
  [key: number]: boolean
}

interface Props {
  users: User[]
  metadata: { [key: string]: ProfileMetadata }
  sigitFiles: SigitFile[]
  setSigitFiles: React.Dispatch<React.SetStateAction<SigitFile[]>>
  selectedTool?: DrawTool
}

export const DrawPDFFields = (props: Props) => {
  const { selectedTool, sigitFiles, setSigitFiles, users } = props

  const signers = users.filter((u) => u.role === UserRole.signer)
  const defaultSignerNpub = signers.length ? hexToNpub(signers[0].pubkey) : ''
  const [lastSigner, setLastSigner] = useState(defaultSignerNpub)
  const [hideSignersForDrawnField, setHideSignersForDrawnField] =
    useState<HideSignersForDrawnField>({})

  /**
   * Return first pubkey that is present in the signers list
   * @param pubkeys
   * @returns available pubkey or empty string
   */
  const getAvailableSigner = (...pubkeys: string[]) => {
    const availableSigner: string | undefined = pubkeys.find((pubkey) =>
      signers.some((s) => s.pubkey === npubToHex(pubkey))
    )
    return availableSigner || ''
  }

  const { to, from } = useScale()

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

  const [activeDrawnField, setActiveDrawnField] = useState<{
    fileIndex: number
    pageIndex: number
    drawnFieldIndex: number
  }>()
  const isActiveDrawnField = (
    fileIndex: number,
    pageIndex: number,
    drawnFieldIndex: number
  ) =>
    activeDrawnField?.fileIndex === fileIndex &&
    activeDrawnField?.pageIndex === pageIndex &&
    activeDrawnField?.drawnFieldIndex === drawnFieldIndex

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

    return () => {
      window.removeEventListener('pointerup', handlePointerUp)
      window.removeEventListener('pointercancel', handlePointerUp)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  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,
    fileIndex: number,
    pageIndex: number
  ) => {
    // 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: getAvailableSigner(lastSigner, defaultSignerNpub),
      type: selectedTool.identifier
    }

    page.drawnFields.push(newField)

    setActiveDrawnField({
      fileIndex,
      pageIndex,
      drawnFieldIndex: page.drawnFields.length - 1
    })
    setMouseState((prev) => {
      return {
        ...prev,
        clicked: true
      }
    })
  }

  /**
   * Drawing is finished, resets all the variables used to draw
   * @param event Pointer event
   */
  const handlePointerUp = () => {
    sigitFiles.forEach((s) => {
      s.pages?.forEach((p) => {
        // Remove drawn fields below the MINIMUM_RECT_SIZE threshhold
        p.drawnFields = p.drawnFields.filter(
          (f) =>
            !(
              f.width < MINIMUM_RECT_SIZE.width ||
              f.height < MINIMUM_RECT_SIZE.height
            )
        )
      })
    })
    setMouseState((prev) => {
      return {
        ...prev,
        clicked: false,
        dragging: false,
        resizing: false
      }
    })
    refreshPdfFiles()
  }

  /**
   * 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,
    fileIndex: number,
    pageIndex: number,
    drawnFieldIndex: number
  ) => {
    event.stopPropagation()

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

    const drawingRectangleCoords = getPointerCoordinates(event)

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

    // make signers dropdown visible
    setHideSignersForDrawnField((prev) => ({
      ...prev,
      [drawnFieldIndex]: false
    }))
  }

  /**
   * 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,
    fileIndex: number,
    pageIndex: number,
    drawnFieldIndex: number
  ) => {
    // Proceed only if left click
    if (event.button !== 0) return
    event.stopPropagation()

    setActiveDrawnField({ fileIndex, pageIndex, 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()
  }

  /**
   * Handles Escape button-down event and hides all signers dropdowns
   * @param event SyntheticEvent event
   */
  const handleEscapeButtonDown = (event: React.SyntheticEvent) => {
    // get native event
    const { nativeEvent } = event

    //check if event is a keyboard event
    if (nativeEvent instanceof KeyboardEvent) {
      // check if event code is Escape
      if (nativeEvent.code === KeyboardCode.Escape) {
        // hide all signers dropdowns
        const newHideSignersForDrawnField: HideSignersForDrawnField = {}

        Object.keys(hideSignersForDrawnField).forEach((key) => {
          // Object.keys always returns an array of strings,
          // that is why unknown type is used below
          newHideSignersForDrawnField[key as unknown as number] = true
        })

        setHideSignersForDrawnField(newHideSignersForDrawnField)
      }
    }
  }

  /**
   * 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 : ''}`}
              tabIndex={-1}
              onKeyDown={(event) => handleEscapeButtonDown(event)}
            >
              <img
                onPointerMove={(event) => {
                  handlePointerMove(event, page)
                }}
                onPointerDown={(event) => {
                  handlePointerDown(event, page, fileIndex, pageIndex)
                }}
                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,
                        fileIndex,
                        pageIndex,
                        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 &&
                        isActiveDrawnField(
                          fileIndex,
                          pageIndex,
                          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,
                          fileIndex,
                          pageIndex,
                          drawnFieldIndex
                        )
                      }
                      onPointerMove={(event) => {
                        handleResizePointerMove(event, drawnField, page.width)
                      }}
                      className={styles.resizeHandle}
                      style={{
                        background:
                          mouseState.resizing &&
                          isActiveDrawnField(
                            fileIndex,
                            pageIndex,
                            drawnFieldIndex
                          )
                            ? 'var(--primary-main)'
                            : undefined
                      }}
                    ></span>
                    <span
                      onPointerDown={(event) => {
                        handleRemovePointerDown(
                          event,
                          fileIndex,
                          pageIndex,
                          drawnFieldIndex
                        )
                      }}
                      className={styles.removeHandle}
                    >
                      <Close fontSize="small" />
                    </span>
                    {!isActiveDrawnField(
                      fileIndex,
                      pageIndex,
                      drawnFieldIndex
                    ) &&
                      !!drawnField.counterpart && (
                        <div className={styles.counterpartAvatar}>
                          <UserAvatar
                            pubkey={npubToHex(drawnField.counterpart)!}
                          />
                        </div>
                      )}
                    {isActiveDrawnField(
                      fileIndex,
                      pageIndex,
                      drawnFieldIndex
                    ) &&
                      (!hideSignersForDrawnField ||
                        !hideSignersForDrawnField[drawnFieldIndex]) && (
                        <div
                          onPointerDown={handleUserSelectPointerDown}
                          className={styles.userSelect}
                        >
                          <FormControl fullWidth size="small">
                            <InputLabel id="counterparts">
                              Counterpart
                            </InputLabel>
                            <Select
                              value={getAvailableSigner(drawnField.counterpart)}
                              onChange={(event) => {
                                drawnField.counterpart = event.target.value
                                setLastSigner(event.target.value)
                                refreshPdfFiles()
                              }}
                              labelId="counterparts"
                              label="Counterparts"
                              sx={{
                                background: 'white'
                              }}
                              renderValue={(value) =>
                                renderCounterpartValue(value)
                              }
                            >
                              {signers.map((signer, index) => {
                                const npub = hexToNpub(signer.pubkey)
                                const metadata = props.metadata[signer.pubkey]
                                const displayValue = getProfileUsername(
                                  npub,
                                  metadata
                                )
                                // make current signers dropdown visible
                                if (
                                  hideSignersForDrawnField[drawnFieldIndex] ===
                                    undefined ||
                                  hideSignersForDrawnField[drawnFieldIndex] ===
                                    true
                                ) {
                                  setHideSignersForDrawnField((prev) => ({
                                    ...prev,
                                    [drawnFieldIndex]: false
                                  }))
                                }

                                return (
                                  <MenuItem key={index} value={npub}>
                                    <ListItemIcon>
                                      <AvatarIconButton
                                        src={metadata?.picture}
                                        hexKey={signer.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 = (npub: string) => {
    let displayValue = _.truncate(npub, { length: 16 })

    const signer = signers.find((u) => u.pubkey === npubToHex(npub))
    if (signer) {
      const metadata = props.metadata[signer.pubkey]
      displayValue = getProfileUsername(npub, metadata)

      return (
        <div className={styles.counterpartSelectValue}>
          <AvatarIconButton
            src={props.metadata[signer.pubkey]?.picture}
            hexKey={signer.pubkey || undefined}
            sx={{
              padding: 0,
              marginRight: '6px',
              '> img': {
                width: '21px',
                height: '21px'
              }
            }}
          />
          {displayValue}
        </div>
      )
    }

    return displayValue
  }

  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 < sigitFiles.length - 1 && <FileDivider />}
          </React.Fragment>
        )
      })}
    </div>
  )
}
