import { Close } from '@mui/icons-material'
import {
  FormControl,
  InputLabel,
  ListItemIcon,
  ListItemText,
  MenuItem,
  Select
} from '@mui/material'
import styles from './style.module.scss'
import React, { useCallback, useEffect, useState } from 'react'
import { User, UserRole, KeyboardCode } from '../../types'
import { MouseState, DrawnField, DrawTool } from '../../types/drawing'
import { hexToNpub, npubToHex, getProfileUsername } from '../../utils'
import { SigitFile } from '../../utils/file'
import { getToolboxLabelByMarkType } from '../../utils/mark'
import { FONT_SIZE, FONT_TYPE, inPx } from '../../utils/pdf'
import { useScale } from '../../hooks/useScale'
import { AvatarIconButton } from '../UserAvatarIconButton'
import { UserAvatar } from '../UserAvatar'
import { Updater } from 'use-immer'
import { FileItem } from './internal/FileItem'
import { FileDivider } from '../FileDivider'
import { Counterpart } from './internal/Counterpart'

const MINIMUM_RECT_SIZE = {
  width: 10,
  height: 10
} as const
import { NDKUserProfile } from '@nostr-dev-kit/ndk'

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

interface HideSignersForDrawnField {
  [key: number]: boolean
}

type PageIndexer = [file: number, page: number]
type FieldIndexer = [...PageIndexer, field: number]

interface DrawPdfFieldsProps {
  users: User[]
  userProfiles: { [key: string]: NDKUserProfile }
  sigitFiles: SigitFile[]
  updateSigitFiles: Updater<SigitFile[]>
  selectedTool?: DrawTool
}

export const DrawPDFFields = ({
  selectedTool,
  userProfiles,
  sigitFiles,
  updateSigitFiles,
  users
}: DrawPdfFieldsProps) => {
  const { to, from } = useScale()

  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 = useCallback(
    (...pubkeys: string[]) => {
      const availableSigner: string | undefined = pubkeys.find((pubkey) =>
        signers.some((s) => s.pubkey === npubToHex(pubkey))
      )
      return availableSigner || ''
    },
    [signers]
  )

  const [mouseState, setMouseState] = useState<MouseState>({})
  const [indexer, setIndexer] = useState<PageIndexer | FieldIndexer>()
  const [field, setField] = useState<
    DrawnField & {
      x: number
      y: number
    }
  >()
  const [lastIndexer, setLastIndexer] = useState<FieldIndexer>()
  const isActiveDrawnField = (
    fileIndex: number,
    pageIndex: number,
    drawnFieldIndex: number
  ) =>
    lastIndexer &&
    lastIndexer[0] === fileIndex &&
    lastIndexer[1] === pageIndex &&
    lastIndexer[2] === drawnFieldIndex

  /**
   * 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.max(0, Math.min(event.clientX, rect.right) - rect.left) //x position within the element.
    const y = Math.max(0, Math.min(event.clientY, rect.bottom) - rect.top) //y position within the element.

    return {
      x,
      y,
      rect
    }
  }

  /**
   * Fired only on when left (primary pointer interaction) clicking page image
   * Creates new drawnElement
   *
   * @param event Pointer event
   * @param pageIndexer File and page index
   * @param pageWidth pdf value used to scale pointer coordinates
   */
  const handlePointerDown = useCallback(
    (
      event: React.PointerEvent,
      pageIndexer: PageIndexer,
      pageWidth: number
    ) => {
      // Proceed only if left click
      if (event.button !== 0) return
      if (!selectedTool) return
      event.currentTarget.setPointerCapture(event.pointerId)
      const counterpart = getAvailableSigner(lastSigner, defaultSignerNpub)
      const { x, y } = getPointerCoordinates(event)

      setIndexer(pageIndexer)
      setField({
        x: to(pageWidth, x),
        y: to(pageWidth, y),
        left: to(pageWidth, x),
        top: to(pageWidth, y),
        width: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.width,
        height: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.height,
        type: selectedTool.identifier,
        counterpart
      })
      setMouseState({
        clicked: true
      })
    },
    [defaultSignerNpub, getAvailableSigner, lastSigner, selectedTool, to]
  )

  /**
   * After {@link handlePointerDown} creates an drawing element, this function
   * alters the newly created drawing element, resizing it while pointer moves
   * @param event Pointer event
   * @param pageWidth pdf value used to scale pointer coordinates
   */
  const handlePointerMove = useCallback(
    (event: React.PointerEvent, pageWidth: number) => {
      if (mouseState.clicked && selectedTool && event.pointerType === 'mouse') {
        const { x, y } = getPointerCoordinates(event)
        const pageX = to(pageWidth, x)
        const pageY = to(pageWidth, y)

        // Calculate left, top, width, and height based on direction
        setField((prev) => {
          const left = pageX < prev!.x ? pageX : prev!.x
          const top = pageY < prev!.y ? pageY : prev!.y

          const width = Math.abs(pageX - prev!.x)
          const height = Math.abs(pageY - prev!.y)
          return { ...prev!, left, top, width, height }
        })
      }
    },
    [mouseState.clicked, selectedTool, to]
  )

  /**
   * 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 fieldIndexer Which field we are moving
   */
  const handleDrawnFieldPointerDown = (
    event: React.PointerEvent,
    fieldIndexer: FieldIndexer
  ) => {
    event.stopPropagation()

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

    event.currentTarget.setPointerCapture(event.pointerId)
    const drawingRectangleCoords = getPointerCoordinates(event)
    const [fileIndex, pageIndex, drawnFieldIndex] = fieldIndexer
    const page = sigitFiles[fileIndex].pages![pageIndex]
    const drawnField = page.drawnFields[drawnFieldIndex]

    setIndexer(fieldIndexer)
    setField({
      ...drawnField,
      x: to(page.width, drawingRectangleCoords.x),
      y: to(page.width, drawingRectangleCoords.y)
    })
    setLastIndexer(fieldIndexer)
    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 pageWidth pdf value used to scale pointer coordinates
   */
  const handleDrawnFieldPointerMove = (
    event: React.PointerEvent,
    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)

        setField((prev) => {
          const rightLimit = to(pageWidth, rect.width) - prev!.width
          const bottomLimit = to(pageWidth, rect.height) - prev!.height

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

          return {
            ...prev!,
            left,
            top
          }
        })
      }
    }
  }

  /**
   * Fired when clicked on the resize handle, sets the state for a resize action
   * @param event Pointer event
   * @param fieldIndexer which field we are resizing
   */
  const handleResizePointerDown = useCallback(
    (event: React.PointerEvent, fieldIndexer: FieldIndexer) => {
      // Proceed only if left click
      if (event.button !== 0) return
      event.stopPropagation()

      event.currentTarget.setPointerCapture(event.pointerId)
      const [fileIndex, pageIndex, drawnFieldIndex] = fieldIndexer
      const page = sigitFiles[fileIndex].pages![pageIndex]
      const drawnField = page.drawnFields[drawnFieldIndex]
      setIndexer(fieldIndexer)
      setField({
        ...drawnField,
        x: drawnField.left,
        y: drawnField.top
      })
      setLastIndexer(fieldIndexer)
      setMouseState({
        resizing: true
      })
    },
    [sigitFiles]
  )

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

        const pageX = to(pageWidth, x)
        const pageY = to(pageWidth, y)

        setField((prev) => {
          const left = pageX < prev!.x ? pageX : prev!.x
          const top = pageY < prev!.y ? pageY : prev!.y

          const width = Math.abs(pageX - prev!.x)
          const height = Math.abs(pageY - prev!.y)
          return { ...prev!, left, top, width, height }
        })
      }
    },
    [mouseState.resizing, to]
  )

  const handlePointerUpReleaseCapture = useCallback(
    (event: React.PointerEvent) => {
      event.currentTarget.releasePointerCapture(event.pointerId)
    },
    []
  )

  /**
   * Removes the drawn element using the indexes in the params
   * @param event Pointer event
   * @param fieldIIndexer [file index, page index, field index]
   */
  const handleRemovePointerDown = (
    event: React.PointerEvent,
    [fileIndex, pageIndex, fieldIndex]: FieldIndexer
  ) => {
    event.stopPropagation()

    updateSigitFiles((draft) => {
      draft[fileIndex]?.pages![pageIndex]?.drawnFields?.splice(fieldIndex, 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)
      }
    }
  }

  /**
   * Drawing is finished, resets all the variables used to draw
   */
  const handlePointerUp = useCallback(() => {
    // Proceed if we have selected something
    if (indexer) {
      // Check if we have the "preview" field state
      if (field) {
        // Cancel update if preview field is below the MINIMUM_RECT_SIZE threshhold
        if (
          field.width < MINIMUM_RECT_SIZE.width ||
          field.height < MINIMUM_RECT_SIZE.height
        ) {
          setIndexer(undefined)
          setMouseState({})
          return
        }

        const [fileIndex, pageIndex, fieldIndex] = indexer

        // Add new drawn field to the files
        if (mouseState.clicked) {
          updateSigitFiles((draft) => {
            draft[fileIndex].pages![pageIndex].drawnFields.push(field)
          })
        }

        // Move
        if (mouseState.dragging) {
          updateSigitFiles((draft) => {
            draft[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
          })
        }

        // Resize
        if (mouseState.resizing) {
          updateSigitFiles((draft) => {
            draft[fileIndex].pages![pageIndex].drawnFields[fieldIndex!] = field
          })
        }

        // Clear indexer after applying the update
        setIndexer(undefined)
      }
    }
    setMouseState({})
  }, [
    field,
    indexer,
    mouseState.clicked,
    mouseState.dragging,
    mouseState.resizing,
    updateSigitFiles
  ])

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

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

  /**
   * 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) => {
          let isPageIndexerActive = false
          if (indexer) {
            const [fi, pi, di] = indexer
            isPageIndexerActive =
              fi === fileIndex && pi === pageIndex && typeof di === 'undefined'
          }

          return (
            <div
              key={pageIndex}
              className={`image-wrapper ${styles.pdfImageWrapper} ${selectedTool ? styles.drawing : ''}`}
              tabIndex={-1}
              onKeyDown={(event) => handleEscapeButtonDown(event)}
            >
              <img
                onPointerDown={(event) => {
                  handlePointerDown(event, [fileIndex, pageIndex], page.width)
                }}
                onPointerMove={(event) => {
                  handlePointerMove(event, page.width)
                }}
                onPointerUp={handlePointerUpReleaseCapture}
                draggable="false"
                src={page.image}
                alt={`page ${pageIndex + 1} of ${file.name}`}
              />
              {isPageIndexerActive && field && (
                <div
                  className={styles.drawingRectanglePreview}
                  style={{
                    backgroundColor: field.counterpart
                      ? `#${npubToHex(field.counterpart)?.substring(0, 6)}4b`
                      : undefined,
                    outlineColor: field.counterpart
                      ? `#${npubToHex(field.counterpart)?.substring(0, 6)}`
                      : undefined,
                    left: inPx(from(page.width, field.left)),
                    top: inPx(from(page.width, field.top)),
                    width: inPx(from(page.width, field.width)),
                    height: inPx(from(page.width, field.height))
                  }}
                >
                  <div
                    className={`file-mark ${styles.placeholder}`}
                    style={{
                      fontFamily: FONT_TYPE,
                      fontSize: inPx(from(page.width, FONT_SIZE))
                    }}
                  >
                    {getToolboxLabelByMarkType(field.type) || 'placeholder'}
                  </div>
                </div>
              )}
              {page.drawnFields.map((drawnField, drawnFieldIndex: number) => {
                let isFieldIndexerActive = false
                if (indexer) {
                  const [fi, pi, di] = indexer
                  isFieldIndexerActive =
                    fi === fileIndex &&
                    pi === pageIndex &&
                    di === drawnFieldIndex
                }

                return (
                  <div
                    key={drawnFieldIndex}
                    onPointerDown={(event) =>
                      handleDrawnFieldPointerDown(event, [
                        fileIndex,
                        pageIndex,
                        drawnFieldIndex
                      ])
                    }
                    onPointerMove={(event) => {
                      handleDrawnFieldPointerMove(event, page.width)
                    }}
                    onPointerUp={handlePointerUpReleaseCapture}
                    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,
                      ...(isFieldIndexerActive && field
                        ? {
                            left: inPx(from(page.width, field.left)),
                            top: inPx(from(page.width, field.top)),
                            width: inPx(from(page.width, field.width)),
                            height: inPx(from(page.width, field.height))
                          }
                        : {
                            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',
                      zIndex: isActiveDrawnField(
                        fileIndex,
                        pageIndex,
                        drawnFieldIndex
                      )
                        ? 60
                        : undefined,
                      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, page.width)
                      }}
                      onPointerUp={handlePointerUpReleaseCapture}
                      className={styles.resizeHandle}
                      style={{
                        ...(mouseState.resizing &&
                          isActiveDrawnField(
                            fileIndex,
                            pageIndex,
                            drawnFieldIndex
                          ) && {
                            cursor: 'grabbing',
                            opacity: 0.1
                          })
                      }}
                    ></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)
                              }}
                              labelId="counterparts"
                              label="Counterparts"
                              sx={{
                                background: 'white'
                              }}
                              renderValue={(value) => (
                                <Counterpart
                                  npub={value}
                                  userProfiles={userProfiles}
                                  signers={signers}
                                />
                              )}
                            >
                              {signers.map((signer, index) => {
                                const npub = hexToNpub(signer.pubkey)
                                const profile = userProfiles[signer.pubkey]
                                const displayValue = getProfileUsername(
                                  npub,
                                  profile
                                )
                                // 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={profile?.image}
                                        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>
          )
        })}
      </>
    )
  }

  return (
    <div className="files-wrapper">
      {sigitFiles.length > 0 &&
        sigitFiles
          .map<React.ReactNode>((file, i) =>
            file.isPdf ? (
              <React.Fragment key={file.name}>
                {getPdfPages(file, i)}
              </React.Fragment>
            ) : (
              <FileItem key={file.name} file={file} />
            )
          )
          .reduce((prev, curr, i) => [
            prev,
            <FileDivider key={`separator-${i}`} />,
            curr
          ])}
    </div>
  )
}
