import {
  ArticleAttributes,
  useUpdateItemReadStatus,
} from '../../../lib/networking/library_items/useLibraryItems'
import { Box } from '../../elements/LayoutPrimitives'
import { v4 as uuidv4 } from 'uuid'
import { nanoid } from 'nanoid'
import { useState, useEffect, useRef } from 'react'
import { isDarkTheme } from '../../../lib/themeUpdater'
import PSPDFKit from 'pspdfkit'
import { Instance, HighlightAnnotation, List, Annotation, Rect } from 'pspdfkit'
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { pspdfKitKey } from '../../../lib/appConfig'
import { HighlightNoteModal } from './HighlightNoteModal'
import { showErrorToast } from '../../../lib/toastHelpers'
import { DEFAULT_HEADER_HEIGHT } from '../homeFeed/HeaderSpacer'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import 'react-sliding-pane/dist/react-sliding-pane.css'
import { NotebookContent } from './Notebook'
import { NotebookHeader } from './NotebookHeader'
import useWindowDimensions from '../../../lib/hooks/useGetWindowDimensions'
import { ResizableSidebar } from './ResizableSidebar'
import { DEFAULT_HOME_PATH } from '../../../lib/navigations'
import {
  useCreateHighlight,
  useDeleteHighlight,
  useMergeHighlight,
  useUpdateHighlight,
} from '../../../lib/networking/highlights/useItemHighlights'
import { useRouter } from 'next/router'

export type PdfArticleContainerProps = {
  viewer: UserBasicData
  article: ArticleAttributes
  showHighlightsModal: boolean
  setShowHighlightsModal: React.Dispatch<React.SetStateAction<boolean>>
}

export default function PdfArticleContainer(
  props: PdfArticleContainerProps
): JSX.Element {
  const router = useRouter()
  const containerRef = useRef<HTMLDivElement | null>(null)
  const [notebookKey, setNotebookKey] = useState<string>(uuidv4())
  const [noteTarget, setNoteTarget] = useState<Highlight | undefined>(undefined)
  const [noteTargetPageIndex, setNoteTargetPageIndex] =
    useState<number | undefined>(undefined)
  const highlightsRef = useRef<Highlight[]>([])
  const createHighlight = useCreateHighlight()
  const deleteHighlight = useDeleteHighlight()
  const mergeHighlight = useMergeHighlight()
  const updateHighlight = useUpdateHighlight()
  const updateItemReadStatus = useUpdateItemReadStatus()

  const annotationOmnivoreId = (annotation: Annotation): string | undefined => {
    if (
      annotation &&
      annotation.customData &&
      annotation.customData.omnivoreHighlight &&
      (annotation.customData.omnivoreHighlight as Highlight).id
    ) {
      return (annotation.customData.omnivoreHighlight as Highlight).id
    }
    return undefined
  }

  useEffect(() => {
    let instance: Instance
    const container = containerRef.current
    ;(async function () {
      const ALLOWED_TOOLBAR_ITEM_TYPES = [
        'pager',
        'zoom-out',
        'zoom-in',
        'zoom-mode',
        'spacer',
        'search',
        'export-pdf',
        'sidebar-bookmarks',
        'sidebar-thumbnails',
        'sidebar-document-outline',
      ]

      console.log('PSPDFKit.defaultToolbarItems', PSPDFKit.defaultToolbarItems)
      const toolbarItems = PSPDFKit.defaultToolbarItems.filter(
        (i) => ALLOWED_TOOLBAR_ITEM_TYPES.indexOf(i.type) !== -1
      )

      const positionPercentForAnnotation = (annotation: Annotation) => {
        let totalSize = 0
        let sizeBefore = 0
        for (let idx = 0; idx < annotation.pageIndex; idx++) {
          sizeBefore += instance.pageInfoForIndex(idx)?.height ?? 0
        }
        for (let idx = 0; idx < instance.totalPageCount; idx++) {
          totalSize += instance.pageInfoForIndex(idx)?.height ?? 0
        }
        return (sizeBefore + annotation.boundingBox.top) / totalSize
      }

      const annotationTooltipCallback = (annotation: Annotation) => {
        const highlightAnnotation = annotation as HighlightAnnotation
        const copy = {
          type: 'custom' as const,
          title: 'Copy',
          id: 'tooltip-copy-annotation',
          className: 'TooltipItem-Copy',
          onPress: async () => {
            const highlightText = await instance.getMarkupAnnotationText(
              highlightAnnotation
            )
            navigator.clipboard.writeText(highlightText)
            instance.setSelectedAnnotation(null)
          },
        }
        const remove = {
          type: 'custom' as const,
          title: 'Remove',
          id: 'tooltip-remove-annotation',
          className: 'TooltipItem-Remove',
          onPress: () => {
            const annotationId = annotationOmnivoreId(annotation)

            instance
              .delete(annotation)
              .then(() => {
                if (annotationId) {
                  return deleteHighlight.mutateAsync({
                    itemId: props.article.id,
                    slug: props.article.slug,
                    highlightId: annotationId,
                  })
                }
              })
              .then(() => {
                const highlightIdx = highlightsRef.current.findIndex(
                  (value) => {
                    return value.id == annotationId
                  }
                )
                if (highlightIdx > -1) {
                  highlightsRef.current.splice(highlightIdx, 1)
                }
              })
              .catch((err) => {
                showErrorToast('Error deleting highlight: ' + err)
              })
          },
        }
        const note = {
          type: 'custom' as const,
          title: 'Note',
          id: 'tooltip-note-annotation',
          className: 'TooltipItem-Note',
          onPress: async () => {
            if (
              annotation.customData &&
              annotation.customData.omnivoreHighlight &&
              (annotation.customData.omnivoreHighlight as Highlight).shortId
            ) {
              const data = annotation.customData.omnivoreHighlight as Highlight
              const savedHighlight = highlightsRef.current.find(
                (other: Highlight) => {
                  return other.id === data.id
                }
              )
              data.annotation = savedHighlight?.annotation ?? data.annotation
              setNoteTargetPageIndex(annotation.pageIndex)
              setNoteTarget(data)
            }
            instance.setSelectedAnnotation(null)
          },
        }
        // const share = {
        //   type: 'custom' as const,
        //   title: 'Share',
        //   id: 'tooltip-share-annotation',
        //   className: 'TooltipItem-Share',
        //   onPress: () => {
        //     if (
        //       annotation.customData &&
        //       annotation.customData.omnivoreHighlight &&
        //       (annotation.customData.omnivoreHighlight as Highlight).shortId
        //     ) {
        //       const data = annotation.customData.omnivoreHighlight as Highlight
        //       handleOpenShare(data)
        //     }
        //     instance.setSelectedAnnotation(null)
        //   },
        // }
        return [copy, note, remove]
      }

      const annotationPresets = PSPDFKit.defaultAnnotationPresets
      annotationPresets.highlight = {
        opacity: 0.45,
        color: new PSPDFKit.Color({ r: 255, g: 210, b: 52 }),
        blendMode: PSPDFKit.BlendMode.multiply,
      }

      const initialPage = () => {
        const highlightHref = window.location.hash
          ? window.location.hash.split('#')[1]
          : null
        if (highlightHref) {
          // find the page index if possible
          const highlight = props.article.highlights?.find(
            (h) => h.id === highlightHref
          )
          if (highlight) {
            return highlight.highlightPositionAnchorIndex
          }
        }
        return props.article.readingProgressAnchorIndex
      }

      const config = {
        container: container || '.pdf-container',
        toolbarItems,
        annotationPresets,
        document: props.article.url,
        theme: isDarkTheme() ? PSPDFKit.Theme.DARK : PSPDFKit.Theme.LIGHT,
        baseUrl: `${window.location.protocol}//${window.location.host}/`,
        licenseKey: pspdfKitKey,
        styleSheets: ['/static/pspdfkit-lib.css'],
        annotationTooltipCallback: annotationTooltipCallback,
        initialViewState: new PSPDFKit.ViewState({
          zoom: PSPDFKit.ZoomMode.FIT_TO_WIDTH,
          currentPageIndex: initialPage() || 0,
        }),
      }

      instance = await PSPDFKit.load(config)
      console.log('created PDF instance', instance)

      instance.addEventListener('annotations.willChange', async (event) => {
        const annotation = event.annotations.get(0)
        if (
          !annotation ||
          event.reason !== PSPDFKit.AnnotationsWillChangeReason.DELETE_END
        ) {
          return
        }
        const annotationId = annotationOmnivoreId(annotation)
        if (annotationId) {
          await deleteHighlight.mutateAsync({
            itemId: props.article.id,
            slug: props.article.slug,
            highlightId: annotationId,
          })
        }
      })

      // Store the highlights in the highlightsRef and apply them to the PDF
      highlightsRef.current = props.article.highlights ?? []
      for (const highlight of (props.article.highlights ?? []).filter(
        (h) => h.type == 'HIGHLIGHT'
      )) {
        const patch = JSON.parse(highlight.patch)
        if (highlight.annotation && patch.customData.omnivoreHighight) {
          patch.customData.omnivoreHighight.annotation = highlight.annotation
        }

        const annotation = PSPDFKit.Annotations.fromSerializableObject(
          patch
        ) as Annotation

        try {
          await instance.create(annotation)
        } catch (e) {
          console.log('error adding highlight')
          console.log(e)
        }
      }

      const findOverlappingHighlights = async (
        instance: Instance,
        highlightAnnotation: HighlightAnnotation
      ): Promise<List<Annotation>> => {
        const existing = await instance.getAnnotations(
          highlightAnnotation.pageIndex
        )

        const highlights = existing.filter((annotation) => {
          return (
            annotation instanceof PSPDFKit.Annotations.HighlightAnnotation &&
            annotation.customData &&
            annotation.customData.omnivoreHighlight
          )
        })

        const overlapping = highlights.filter((annotation) => {
          const isRes = annotation.rects.some((rect: Rect) => {
            return highlightAnnotation.rects.some((highlightRect) => {
              return rect.isRectOverlapping(highlightRect)
            })
          })
          return isRes
        })

        return overlapping
      }

      instance.addEventListener(
        'annotations.create',
        async (createdAnnotations) => {
          const highlightAnnotation = createdAnnotations.get(0)

          if (
            !(
              highlightAnnotation instanceof
              PSPDFKit.Annotations.HighlightAnnotation
            )
          ) {
            return
          }

          // If the annotation already has the omnivore highlight
          // custom data its already been created, so we can
          // ignore this event.
          if (
            highlightAnnotation.customData &&
            highlightAnnotation.customData.omnivoreHighlight
          ) {
            // This highlight has already been created, so we skip adding it
            return
          }

          const overlapping = await findOverlappingHighlights(
            instance,
            highlightAnnotation
          )

          const id = uuidv4()
          const shortId = nanoid(8)
          const quote = (
            await instance.getMarkupAnnotationText(highlightAnnotation)
          )
            .replace(/(\r\n|\n|\r)/gm, ' ')
            .trim()

          const surroundingText = { prefix: '', suffix: '' }
          const annotation = highlightAnnotation.set('customData', {
            omnivoreHighlight: {
              id,
              quote,
              shortId,
              prefix: surroundingText.prefix,
              suffix: surroundingText.suffix,
              articleId: props.article.id,
            },
          })

          await instance.update(annotation)
          const serialized =
            PSPDFKit.Annotations.toSerializableObject(annotation)

          if (overlapping.size === 0) {
            const positionPercent = positionPercentForAnnotation(annotation)

            const result = await createHighlight.mutateAsync({
              itemId: props.article.id,
              slug: props.article.slug,
              input: {
                id: id,
                shortId: shortId,
                quote: quote,
                articleId: props.article.id,
                prefix: surroundingText.prefix,
                suffix: surroundingText.suffix,
                patch: JSON.stringify(serialized),
                highlightPositionPercent: positionPercent * 100,
                highlightPositionAnchorIndex: annotation.pageIndex,
              },
            })
            if (result) {
              highlightsRef.current.push(result)
            }
          } else {
            // Create a new single highlight in the PDF
            const rects = highlightAnnotation.rects.concat(
              overlapping.flatMap((ha) => ha.rects as List<Rect>)
            )
            const annotation = new PSPDFKit.Annotations.HighlightAnnotation({
              pageIndex: highlightAnnotation.pageIndex,
              rects: rects,
              opacity: 0.45,
              color: new PSPDFKit.Color({ r: 255, g: 210, b: 52 }),
              boundingBox: PSPDFKit.Geometry.Rect.union(rects),
              customData: {
                omnivoreHighlight: {
                  id,
                  quote,
                  shortId,
                  prefix: surroundingText.prefix,
                  suffix: surroundingText.suffix,
                  articleId: props.article.id,
                },
              },
            })

            await instance.create(annotation)
            await instance.delete(overlapping)
            await instance.delete(highlightAnnotation)

            const mergedIds = overlapping.map(
              (ha) => (ha.customData?.omnivoreHighlight as Highlight).id
            )
            const positionPercent = positionPercentForAnnotation(annotation)
            const result = await mergeHighlight.mutateAsync({
              itemId: props.article.id,
              slug: props.article.slug,
              input: {
                quote,
                id,
                shortId,
                patch: JSON.stringify(serialized),
                prefix: surroundingText.prefix,
                suffix: surroundingText.suffix,
                articleId: props.article.id,
                overlapHighlightIdList: mergedIds.toArray(),
                highlightPositionPercent: positionPercent * 100,
                highlightPositionAnchorIndex: annotation.pageIndex,
              },
            })
            if (result && result.highlight) {
              highlightsRef.current.push(result.highlight)
            }
          }
        }
      )

      instance.addEventListener(
        'viewState.currentPageIndex.change',
        async (pageIndex) => {
          const percent = Math.min(
            100,
            Math.max(0, ((pageIndex + 1) / instance.totalPageCount) * 100)
          )
          await updateItemReadStatus.mutateAsync({
            itemId: props.article.id,
            slug: props.article.slug,
            input: {
              id: props.article.id,
              force: true,
              readingProgressPercent: percent,
              readingProgressAnchorIndex: pageIndex,
            },
          })
        }
      )

      type PossibleInputEventTarget = KeyboardEvent & {
        nodeName: string
      }

      function isPossibleInputEventTarget(
        target: any
      ): target is PossibleInputEventTarget {
        return (
          'nodeName' in target &&
          typeof target.nodeName == 'string' &&
          target.nodeName
        )
      }

      function keyDownHandler(event: KeyboardEvent) {
        const inputs = ['input', 'select', 'button', 'textarea']

        if (event.target && isPossibleInputEventTarget(event.target)) {
          const nodeName = event.target.nodeName.toLowerCase()
          if (inputs.indexOf(nodeName) != -1) {
            return
          }
        }

        const key = event.key.toLowerCase()
        switch (key) {
          case 'o':
            document.dispatchEvent(new Event('openOriginalArticle'))
            break
          case 'u':
            const navReturn = window.localStorage.getItem('nav-return')
            if (navReturn) {
              router.push(navReturn)
              return
            }
            const query = window.sessionStorage.getItem('q')
            if (query) {
              router.push(`${DEFAULT_HOME_PATH}?${query}`)
            } else {
              router.push(DEFAULT_HOME_PATH)
            }
            break
          case 'e':
            document.dispatchEvent(new Event('archive'))
            break
          case '#':
            document.dispatchEvent(new Event('delete'))
            break
          case 'h':
            const root = (event.target as HTMLElement).querySelector(
              '.PSPDFKit-Root'
            )
            const highlight = root?.querySelector(
              '.PSPDFKit-Text-Markup-Inline-Toolbar-Highlight'
            )
            if (highlight && highlight?.nodeName == 'BUTTON') {
              const button = highlight as HTMLButtonElement
              button.click()
            }
            break
          // case 'n':
          // TODO: need to set a post creation event here, then
          // go through the regular highlight creation
          //   document.dispatchEvent(new Event('annotate'))
          //   break
          case 't':
            props.setShowHighlightsModal(true)
            break
          case 'i':
            document.dispatchEvent(new Event('showEditModal'))
            break
        }
      }

      const isIE11 = navigator.userAgent.indexOf('Trident/') > -1
      instance.contentDocument.addEventListener(
        'keydown',
        keyDownHandler,
        isIE11
          ? {
              capture: true,
            }
          : true
      )
    })()

    document.addEventListener('deleteHighlightbyId', async (event) => {
      const annotationId = (event as CustomEvent).detail as string
      for (let pageIdx = 0; pageIdx < instance.totalPageCount; pageIdx++) {
        const annotations = await instance.getAnnotations(pageIdx)
        for (let annIdx = 0; annIdx < annotations.size; annIdx++) {
          const annotation = annotations.get(annIdx)
          if (!annotation) {
            continue
          }
          const storedId = annotationOmnivoreId(annotation)
          if (storedId == annotationId) {
            await instance.delete(annotation)
            await deleteHighlight.mutateAsync({
              itemId: props.article.id,
              slug: props.article.slug,
              highlightId: annotationId,
            })

            const highlightIdx = highlightsRef.current.findIndex((value) => {
              return value.id == annotationId
            })
            if (highlightIdx > -1) {
              highlightsRef.current.splice(highlightIdx, 1)
            }
            // This is needed to force the notebook to reload the highlights
            setNotebookKey(uuidv4())
          }
        }
      }
    })

    document.addEventListener('scrollToHighlightId', async (event) => {
      const annotationId = (event as CustomEvent).detail as string
      for (let pageIdx = 0; pageIdx < instance.totalPageCount; pageIdx++) {
        const annotations = await instance.getAnnotations(pageIdx)
        for (let annIdx = 0; annIdx < annotations.size; annIdx++) {
          const annotation = annotations.get(annIdx)
          if (!annotation) {
            continue
          }
          const storedId = annotationOmnivoreId(annotation)
          if (storedId == annotationId) {
            instance.jumpToRect(pageIdx, annotation.boundingBox)
          }
        }
      }
    })

    document.addEventListener('pdfReaderUpdateSettings', () => {
      const show = localStorage.getItem('reader-show-pdf-tool-bar')
      const showToolbarbar = show ? JSON.parse(show) == true : false

      instance.setViewState((viewState) =>
        viewState.set('showToolbar', showToolbarbar)
      )
    })

    return () => {
      PSPDFKit && container && PSPDFKit.unload(container)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])
  // We are intentially not setting exhaustive deps here, we only want to reload
  // the PSPDFKit instance if the theme, article URL, or page URL changes. Everything else
  // should be handled by the PSPDFKit instance callbacks.

  const windowDimensions = useWindowDimensions()

  return (
    <Box
      id="article-wrapper"
      css={{
        width: '100%',
        height: `calc(100vh - ${DEFAULT_HEADER_HEIGHT})`,
      }}
    >
      <div ref={containerRef} style={{ width: '100%', height: '100%' }} />
      {noteTarget && (
        <HighlightNoteModal
          highlight={noteTarget}
          libraryItemId={props.article.id}
          libraryItemSlug={props.article.slug}
          onUpdate={(highlight: Highlight) => {
            const savedHighlight = highlightsRef.current.find(
              (other: Highlight) => {
                return other.id == highlight.id
              }
            )

            if (savedHighlight) {
              savedHighlight.annotation = highlight.annotation
            }
          }}
          onOpenChange={() => {
            setNoteTarget(undefined)
          }}
        />
      )}
      <ResizableSidebar
        isShow={props.showHighlightsModal}
        onClose={() => {
          props.setShowHighlightsModal(false)
        }}
      >
        <NotebookHeader
          viewer={props.viewer}
          item={props.article}
          setShowNotebook={props.setShowHighlightsModal}
        />
        <NotebookContent
          viewer={props.viewer}
          item={props.article}
          viewInReader={(highlightId) => {
            const event = new CustomEvent('scrollToHighlightId', {
              detail: highlightId,
            })
            document.dispatchEvent(event)
          }}
        />
      </ResizableSidebar>
    </Box>
  )
}
