import { Box, HStack, VStack, SpanBox } from '../../elements/LayoutPrimitives'
import { StyledText } from '../../elements/StyledText'
import { theme } from '../../tokens/stitches.config'
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import { BookOpen, PencilLine, X } from 'phosphor-react'
import { SetLabelsModal } from './SetLabelsModal'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { setLabelsForHighlight } from '../../../lib/networking/mutations/setLabelsForHighlight'
import { updateHighlightMutation } from '../../../lib/networking/mutations/updateHighlightMutation'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { diff_match_patch } from 'diff-match-patch'
import { highlightsAsMarkdown } from '../homeFeed/HighlightItem'
import 'react-markdown-editor-lite/lib/index.css'
import { createHighlightMutation } from '../../../lib/networking/mutations/createHighlightMutation'
import { v4 as uuidv4 } from 'uuid'
import { nanoid } from 'nanoid'
import { deleteHighlightMutation } from '../../../lib/networking/mutations/deleteHighlightMutation'
import { HighlightNoteBox, MarkdownNote } from '../../patterns/HighlightNotes'
import { HighlightViewItem } from './HighlightViewItem'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { TrashIcon } from '../../elements/images/TrashIcon'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import {
  LibraryItem,
  ReadableItem,
} from '../../../lib/networking/queries/useGetLibraryItemsQuery'

type NotebookProps = {
  viewer: UserBasicData

  item: ReadableItem
  highlights: Highlight[]

  sizeMode: 'normal' | 'maximized'

  viewInReader: (highlightId: string) => void

  onAnnotationsChanged?: (
    highlights: Highlight[],
    deletedAnnotations: Highlight[]
  ) => void
}

export const getHighlightLocation = (patch: string): number | undefined => {
  const dmp = new diff_match_patch()
  const patches = dmp.patch_fromText(patch)
  return patches[0].start1 || undefined
}

type AnnotationInfo = {
  loaded: boolean

  note: Highlight | undefined
  noteId: string

  allAnnotations: Highlight[]
  deletedAnnotations: Highlight[]
}

export function Notebook(props: NotebookProps): JSX.Element {
  const [showConfirmDeleteHighlightId, setShowConfirmDeleteHighlightId] =
    useState<undefined | string>(undefined)
  const [labelsTarget, setLabelsTarget] =
    useState<Highlight | undefined>(undefined)
  const [showConfirmDeleteNote, setShowConfirmDeleteNote] = useState(false)
  const [notesEditMode, setNotesEditMode] =
    useState<'edit' | 'preview'>('preview')
  const [, updateState] = useState({})

  const annotationsReducer = (
    state: AnnotationInfo,
    action: {
      type: string
      allHighlights?: Highlight[]
      note?: Highlight | undefined

      updateHighlight?: Highlight | undefined
      deleteHighlightId?: string | undefined
    }
  ) => {
    switch (action.type) {
      case 'RESET': {
        const note = action.allHighlights?.find((h) => h.type == 'NOTE')
        return {
          ...state,
          loaded: true,
          note: note,
          noteId: note?.id ?? state.noteId,
          allAnnotations: [...(action.allHighlights ?? [])],
        }
      }
      case 'CREATE_NOTE': {
        if (!action.note) {
          throw new Error('No note on CREATE_NOTE action')
        }
        return {
          ...state,
          note: action.note,
          noteId: action.note.id,
          allAnnotations: [...state.allAnnotations, action.note],
        }
      }
      case 'DELETE_NOTE': {
        // If there is no note to delete, just make sure we have cleared out the note
        const noteId = action.note?.id
        if (!action.note?.id) {
          return {
            ...state,
            node: undefined,
            noteId: uuidv4(),
          }
        }
        const idx = state.allAnnotations.findIndex((h) => h.id === noteId)
        return {
          ...state,
          note: undefined,
          noteId: uuidv4(),
          allAnnotations: state.allAnnotations.splice(idx, 1),
        }
      }
      case 'DELETE_HIGHLIGHT': {
        const highlightId = action.deleteHighlightId
        if (!highlightId) {
          throw new Error('No highlightId for delete action.')
        }
        const idx = state.allAnnotations.findIndex((h) => h.id === highlightId)
        if (idx < 0) {
          return { ...state }
        }
        const deleted = state.deletedAnnotations
        deleted.push(state.allAnnotations[idx])

        return {
          ...state,
          deletedAnnotations: deleted,
          allAnnotations: state.allAnnotations.splice(idx, 1),
        }
      }
      case 'UPDATE_HIGHLIGHT': {
        const highlight = action.updateHighlight
        if (!highlight) {
          throw new Error('No highlightId for delete action.')
        }
        const idx = state.allAnnotations.findIndex((h) => h.id === highlight.id)
        if (idx !== -1) {
          state.allAnnotations[idx] = highlight
        }
        return {
          ...state,
        }
      }
      default:
        return state
    }
  }

  const [annotations, dispatchAnnotations] = useReducer(annotationsReducer, {
    loaded: false,
    note: undefined,
    noteId: uuidv4(),
    allAnnotations: [],
    deletedAnnotations: [],
  })

  useEffect(() => {
    dispatchAnnotations({
      type: 'RESET',
      allHighlights: props.highlights,
    })
  }, [props.highlights])

  useEffect(() => {
    if (props.onAnnotationsChanged) {
      props.onAnnotationsChanged(
        annotations.allAnnotations,
        annotations.deletedAnnotations
      )
    }
  }, [annotations])

  const deleteDocumentNote = useCallback(() => {
    const note = annotations.note
    if (!note) {
      showErrorToast('No note found')
      return
    }
    ;(async () => {
      try {
        const result = await deleteHighlightMutation(note.id)
        if (!result) {
          throw new Error()
        }
        showSuccessToast('Note deleted')
        dispatchAnnotations({
          note,
          type: 'DELETE_NOTE',
        })
      } catch (err) {
        console.log('error deleting note', err)
        showErrorToast('Error deleting note')
      }
    })()
  }, [annotations])

  const sortedHighlights = useMemo(() => {
    const sorted = (a: number, b: number) => {
      if (a < b) {
        return -1
      }
      if (a > b) {
        return 1
      }
      return 0
    }

    return annotations.allAnnotations
      .filter((h) => h.type === 'HIGHLIGHT')
      .sort((a: Highlight, b: Highlight) => {
        if (a.highlightPositionPercent && b.highlightPositionPercent) {
          return sorted(a.highlightPositionPercent, b.highlightPositionPercent)
        }
        // We do this in a try/catch because it might be an invalid diff
        // With PDF it will definitely be an invalid diff.
        try {
          const aPos = getHighlightLocation(a.patch)
          const bPos = getHighlightLocation(b.patch)
          if (aPos && bPos) {
            return sorted(aPos, bPos)
          }
        } catch {}
        return a.createdAt.localeCompare(b.createdAt)
      })
  }, [annotations])

  const handleSaveNoteText = useCallback(
    (text, cb: (success: boolean) => void) => {
      if (!annotations.loaded) {
        // We haven't loaded the user's annotations yet, so we can't
        // find or create their highlight note.
        return
      }

      if (!annotations.note) {
        const noteId = annotations.noteId
        ;(async () => {
          const success = await createHighlightMutation({
            id: noteId,
            shortId: nanoid(8),
            type: 'NOTE',
            articleId: props.item.id,
            annotation: text,
          })
          console.log('success creating annotation note: ', success)
          if (success) {
            dispatchAnnotations({
              type: 'CREATE_NOTE',
              note: success,
            })
          }
          cb(!!success)
        })()
        return
      }

      if (annotations.note) {
        const note = annotations.note
        ;(async () => {
          const success = await updateHighlightMutation({
            highlightId: note.id,
            annotation: text,
          })
          console.log('success updating annotation note: ', success)
          if (success) {
            note.annotation = text
            dispatchAnnotations({
              type: 'UPDATE_NOTE',
              note: note,
            })
          }
          cb(!!success)
        })()
        return
      }
    },
    [annotations, props.item]
  )
  return (
    <VStack
      distribution="start"
      css={{ height: '100%', width: '100%', p: '20px' }}
    >
      <TitledSection
        title="ARTICLE NOTES"
        editMode={notesEditMode == 'edit'}
        setEditMode={(edit) => setNotesEditMode(edit ? 'edit' : 'preview')}
      />
      <HighlightNoteBox
        mode={notesEditMode}
        sizeMode={props.sizeMode}
        setEditMode={setNotesEditMode}
        text={annotations.note?.annotation}
        placeHolder="Add notes to this document..."
        saveText={handleSaveNoteText}
      />
      <SpanBox css={{ mt: '10px', mb: '25px' }} />
      <Box css={{ width: '100%' }}>
        <TitledSection title="HIGHLIGHTS" />

        {sortedHighlights.map((highlight) => (
          <HighlightViewItem
            key={highlight.id}
            item={props.item}
            viewer={props.viewer}
            highlight={highlight}
            viewInReader={props.viewInReader}
            setSetLabelsTarget={setLabelsTarget}
            setShowConfirmDeleteHighlightId={setShowConfirmDeleteHighlightId}
            updateHighlight={() => {
              dispatchAnnotations({
                type: 'UPDATE_HIGHLIGHT',
                updateHighlight: highlight,
              })
            }}
          />
        ))}
        {sortedHighlights.length === 0 && (
          <Box
            css={{
              mt: '15px',
              width: '100%',
              fontSize: '9px',
              color: '$thTextSubtle',
              alignItems: 'center',
              justifyContent: 'center',
              mb: '100px',
            }}
          >
            You have not added any highlights to this document.
          </Box>
        )}
        <Box
          css={{
            '@mdDown': {
              height: '320px',
              width: '100%',
              background: 'transparent',
            },
          }}
        />
      </Box>

      {showConfirmDeleteHighlightId && (
        <ConfirmationModal
          message={'Are you sure you want to delete this highlight?'}
          onAccept={() => {
            ;(async () => {
              const success = await deleteHighlightMutation(
                showConfirmDeleteHighlightId
              )
              console.log(' ConfirmationModal::DeleteHighlight', success)
              if (success) {
                dispatchAnnotations({
                  type: 'DELETE_HIGHLIGHT',
                  deleteHighlightId: showConfirmDeleteHighlightId,
                })
                showSuccessToast('Highlight deleted.')
              } else {
                showErrorToast('Error deleting highlight')
              }
            })()
            setShowConfirmDeleteHighlightId(undefined)
          }}
          onOpenChange={() => setShowConfirmDeleteHighlightId(undefined)}
          icon={
            <TrashIcon
              size={40}
              strokeColor={theme.colors.grayTextContrast.toString()}
            />
          }
        />
      )}
      {labelsTarget && (
        <SetLabelsModal
          provider={labelsTarget}
          onOpenChange={function (open: boolean): void {
            setLabelsTarget(undefined)
          }}
          onLabelsUpdated={function (labels: Label[]): void {
            updateState({})
          }}
          save={function (labels: Label[]): Promise<Label[] | undefined> {
            const result = setLabelsForHighlight(
              labelsTarget.id,
              labels.map((label) => label.id)
            )
            return result
          }}
        />
      )}
      {showConfirmDeleteNote && (
        <ConfirmationModal
          message="Are you sure you want to delete the note from this document?"
          acceptButtonLabel="Delete"
          onAccept={() => {
            deleteDocumentNote()
            setShowConfirmDeleteNote(false)
          }}
          onOpenChange={() => setShowConfirmDeleteNote(false)}
        />
      )}
    </VStack>
  )
}

type TitledSectionProps = {
  title: string
  editMode?: boolean
  setEditMode?: (set: boolean) => void
}

function TitledSection(props: TitledSectionProps): JSX.Element {
  return (
    <>
      <HStack
        css={{ width: '100%', borderBottom: '1px solid $thBorderColor' }}
        alignment="start"
        distribution="start"
      >
        <StyledText
          css={{
            fontFamily: '$display',
            fontStyle: 'normal',
            fontWeight: '700',
            fontSize: '12px',
            lineHeight: '20px',
            color: '#898989',
            marginBottom: '1px',
          }}
        >
          {props.title}
        </StyledText>
        {props.setEditMode && (
          <SpanBox
            css={{
              marginLeft: 'auto',
              justifyContent: 'end',
              lineHeight: '1',
              alignSelf: 'end',
              padding: '2px',
              cursor: 'pointer',
              borderRadius: '1000px',
              '&:hover': {
                background: '#EBEBEB',
              },
            }}
            onClick={(event) => {
              if (props.setEditMode) {
                props.setEditMode(!props.editMode)
              }
              event.preventDefault()
            }}
          >
            {props.editMode ? (
              <BookOpen size={15} color="#898989" />
            ) : (
              <PencilLine size={15} color="#898989" />
            )}
          </SpanBox>
        )}
      </HStack>
    </>
  )
}
