import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import {
  $getRoot,
  $getSelection,
  $isElementNode,
  $isRangeSelection,
  $isRootOrShadowRoot,
  CAN_REDO_COMMAND,
  CAN_UNDO_COMMAND,
  COMMAND_PRIORITY_LOW,
  COMMAND_PRIORITY_NORMAL,
  FORMAT_ELEMENT_COMMAND,
  FORMAT_TEXT_COMMAND,
  KEY_MODIFIER_COMMAND,
  ParagraphNode,
  REDO_COMMAND,
  UNDO_COMMAND,
} from 'lexical'
import { HeadingNode, QuoteNode } from '@lexical/rich-text'
import { $setBlocksType } from '@lexical/selection'
import { useEffect, useLayoutEffect, useState } from 'react'
import {
  $findMatchingParent,
  $insertNodeToNearestRoot,
  mergeRegister,
} from '@lexical/utils'
import { ImageButton } from './nodes/ImageNode.js'
import { EmbedModal, EmbedNode } from './nodes/EmbedNode.js'
import {
  INSERT_ORDERED_LIST_COMMAND,
  INSERT_UNORDERED_LIST_COMMAND,
  ListNode,
  REMOVE_LIST_COMMAND,
} from '@lexical/list'
import { $getNearestNodeOfType } from '@lexical/utils'
import { $isLinkNode, LinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
import { IconButton } from '../IconButton/index.js'
import { Menu } from '../Menu/index.js'
import { MiniSheet } from '../MiniSheet/index.js'
import { colors } from '../../utils/colors.js'
import { Button } from '../Button/index.js'
import { useIsMac } from '../../hooks/useIsMac.js'

function Separator() {
  return (
    <div
      style={{
        height: 16,
        width: 1,
        background: colors.neutral10,
        margin: '0 6px',
      }}
    />
  )
}

type ToolbarProps = {
  onOpenChange: (open: boolean) => void
}

export function Toolbar({ onOpenChange }: ToolbarProps) {
  const [editor] = useLexicalComposerContext()
  const [canRedo, setCanRedo] = useState(false)
  const [canUndo, setCanUndo] = useState(false)
  const [nodeType, setNodeType] = useState('')
  const [isBold, setIsBold] = useState(false)
  const [isItalic, setIsItalic] = useState(false)
  const [isUnderline, setIsUnderline] = useState(false)
  const [isStrikethrough, setIsStrikethrough] = useState(false)
  const [isLink, setIsLink] = useState(false)
  const [align, setAlign] = useState('left')
  const [embedModalOpen, setEmbedModalOpen] = useState(false)
  const [stats, setStats] = useState({ wordCount: 0, characterCount: 0 })
  const [visibleMetric, setVisibleMetric] = useState('words')
  const isMac = useIsMac()

  useLayoutEffect(() => {
    return mergeRegister(
      editor.registerUpdateListener(({ editorState }) => {
        editorState.read(() => {
          const selection = $getSelection()
          if (!$isRangeSelection(selection)) return

          const anchorNode = selection.anchor.getNode()
          const parent = anchorNode.getParent()

          setIsBold(selection.hasFormat('bold'))
          setIsItalic(selection.hasFormat('italic'))
          setIsUnderline(selection.hasFormat('underline'))
          setIsStrikethrough(selection.hasFormat('strikethrough'))

          let matchingParent
          if ($isLinkNode(parent)) {
            matchingParent = $findMatchingParent(
              anchorNode,
              (parentNode) =>
                $isElementNode(parentNode) && !parentNode.isInline(),
            )
          }

          setAlign(
            ($isElementNode(matchingParent)
              ? matchingParent.getFormatType()
              : $isElementNode(anchorNode)
                ? anchorNode.getFormatType()
                : anchorNode.getParent()?.getFormatType()) || 'left',
          )

          const parentList = $getNearestNodeOfType<ListNode>(
            anchorNode,
            ListNode,
          )
          if (parentList) {
            setNodeType(parentList.getListType())
            return
          }

          setIsLink($isLinkNode(anchorNode) || (parent && $isLinkNode(parent)))

          const anchorClosestRootNode =
            anchorNode.getKey() === 'root'
              ? anchorNode
              : $findMatchingParent(anchorNode, (e) =>
                  $isRootOrShadowRoot(e.getParent()),
                )
          const nodeType = anchorClosestRootNode?.getType() ?? ''
          if (nodeType === 'heading') {
            setNodeType(
              `heading-${(anchorClosestRootNode as HeadingNode).getTag()}`,
            )
            return
          }
          setNodeType(nodeType)
        })
      }),
      editor.registerCommand(
        CAN_UNDO_COMMAND,
        (payload) => {
          setCanUndo(payload)
          return false
        },
        COMMAND_PRIORITY_LOW,
      ),
      editor.registerCommand(
        CAN_REDO_COMMAND,
        (payload) => {
          setCanRedo(payload)
          return false
        },
        COMMAND_PRIORITY_LOW,
      ),
      editor.registerUpdateListener(({ editorState }) => {
        editorState.read(() => {
          const textContent = $getRoot().getTextContent()
          const segmenter = new Intl.Segmenter('en', { granularity: 'word' })
          const words = Array.from(segmenter.segment(textContent)).filter(
            (e) => e.isWordLike,
          )

          setStats({
            wordCount: words.length,
            characterCount: words.reduce((acc, curr) => {
              acc += curr.segment.length
              return acc
            }, 0),
          })
        })
      }),
    )
  }, [editor])

  useLayoutEffect(() => {
    return mergeRegister(
      editor.registerCommand(
        KEY_MODIFIER_COMMAND,
        (event) => {
          const { code, metaKey, ctrlKey } = event
          if (code === 'KeyK' && isMac ? metaKey : ctrlKey) {
            editor.dispatchCommand(TOGGLE_LINK_COMMAND, isLink ? null : '')
          }
          return false
        },
        COMMAND_PRIORITY_NORMAL,
      ),
    )
  }, [editor, isMac, isLink])

  return (
    <div
      style={{
        display: 'flex',
        alignItems: 'center',
        gap: 2,
        padding: '12px 20px',
        borderBottom: `1px solid ${colors.neutral20Adjusted}`,
        overflowX: 'auto',
        scrollbarWidth: 'none',
      }}
    >
      <Button
        size="small"
        leadIcon="arrow-left"
        variant="ghost"
        onClick={() => {
          onOpenChange(false)
        }}
      >
        Back
      </Button>
      <Separator />
      <Menu>
        <Menu.Trigger>
          {({ open }) => (
            <IconButton
              tooltip="Select text style"
              size="small"
              icon="text-styling"
              trailChevron="down"
              forceHover={open}
            />
          )}
        </Menu.Trigger>
        <Menu.Items>
          <Menu.ToggleItem
            visualOnlyKeyHint="Cmd+Alt+0"
            value={nodeType === 'paragraph'}
            onChange={() => {
              editor.update(() => {
                const selection = $getSelection()

                $setBlocksType(selection, () => new ParagraphNode())
              })
            }}
          >
            Paragraph
          </Menu.ToggleItem>
          <Menu.ToggleItem
            value={nodeType === 'heading-h1'}
            visualOnlyKeyHint="Cmd+Alt+1"
            onChange={() => {
              editor.update(() => {
                const selection = $getSelection()

                $setBlocksType(selection, () => new HeadingNode('h1'))
              })
            }}
          >
            Heading 1
          </Menu.ToggleItem>
          <Menu.ToggleItem
            visualOnlyKeyHint="Cmd+Alt+2"
            value={nodeType === 'heading-h2'}
            onChange={() => {
              editor.update(() => {
                const selection = $getSelection()

                $setBlocksType(selection, () => new HeadingNode('h2'))
              })
            }}
          >
            Heading 2
          </Menu.ToggleItem>
          <Menu.ToggleItem
            visualOnlyKeyHint="Cmd+Alt+3"
            value={nodeType === 'heading-h3'}
            onChange={() => {
              editor.update(() => {
                const selection = $getSelection()

                $setBlocksType(selection, () => new HeadingNode('h3'))
              })
            }}
          >
            Heading 3
          </Menu.ToggleItem>
        </Menu.Items>
      </Menu>
      <Separator />

      {!nodeType.startsWith('heading') && (
        <IconButton
          toggled={isBold}
          tooltip="Bold"
          size="small"
          icon="bold"
          keyHint="Cmd+B"
          keyHintVisualOnly
          onClick={() => {
            editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold')
          }}
        />
      )}
      <IconButton
        tooltip="Italic"
        size="small"
        icon="italic"
        keyHint="Cmd+I"
        keyHintVisualOnly
        toggled={isItalic}
        onClick={() => {
          editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic')
        }}
      />
      <IconButton
        tooltip="Underlined"
        size="small"
        icon="underline"
        keyHint="Cmd+U"
        keyHintVisualOnly
        toggled={isUnderline}
        onClick={() => {
          editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline')
        }}
      />
      <IconButton
        tooltip="Strikethrough"
        size="small"
        keyHint="Cmd+Shift+S"
        keyHintVisualOnly
        toggled={isStrikethrough}
        icon="strikethrough"
        onClick={() => {
          editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough')
        }}
      />
      <Separator />
      <MiniSheet>
        <MiniSheet.Trigger>
          {({ open }) => (
            <IconButton
              tooltip="Align text"
              size="small"
              icon={align ? (`text-align-${align}` as any) : 'text-align-left'}
              trailChevron="down"
              forceHover={open}
            />
          )}
        </MiniSheet.Trigger>
        <MiniSheet.Items>
          <MiniSheet.Item
            size="small"
            icon="text-align-left"
            tooltip="Align left"
            keyHint="Cmd+Shift+L"
            keyHintVisualOnly
            onClick={() => {
              editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'left')
            }}
            toggled={align === 'left'}
          />
          <MiniSheet.Item
            size="small"
            icon="text-align-center"
            tooltip="Align center"
            keyHint="Cmd+Shift+E"
            keyHintVisualOnly
            onClick={() => {
              editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'center')
            }}
            toggled={align === 'center'}
          />
          <MiniSheet.Item
            size="small"
            icon="text-align-right"
            tooltip="Align right"
            keyHint="Cmd+Shift+R"
            keyHintVisualOnly
            onClick={() => {
              editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'right')
            }}
            toggled={align === 'right'}
          />
        </MiniSheet.Items>
      </MiniSheet>
      <Separator />
      <IconButton
        tooltip={'Insert link'}
        keyHint="Cmd+K"
        keyHintVisualOnly
        size="small"
        icon={'link'}
        toggled={isLink}
        onClick={() => {
          editor.update(() => {
            editor.dispatchCommand(TOGGLE_LINK_COMMAND, isLink ? null : '')
          })
        }}
      />
      <ImageButton />
      <Separator />
      <MiniSheet>
        <MiniSheet.Trigger>
          {({ open }) => (
            <IconButton
              tooltip="Create list"
              size="small"
              icon={nodeType === 'bullet' ? 'bulleted-list' : 'numbered-list'}
              trailChevron="down"
              toggled={['bullet', 'number'].includes(nodeType)}
              forceHover={open}
            />
          )}
        </MiniSheet.Trigger>
        <MiniSheet.Items>
          <MiniSheet.Item
            size="small"
            icon="bulleted-list"
            toggled={nodeType === 'bullet'}
            keyHint="Cmd+Alt+4"
            tooltip="Bulleted list"
            keyHintVisualOnly
            onClick={() => {
              editor.update(() => {
                editor.dispatchCommand(
                  nodeType === 'bullet'
                    ? REMOVE_LIST_COMMAND
                    : INSERT_UNORDERED_LIST_COMMAND,
                  undefined,
                )
              })
            }}
          />
          <MiniSheet.Item
            size="small"
            icon="numbered-list"
            keyHint="Cmd+Alt+5"
            keyHintVisualOnly
            tooltip="Number list"
            toggled={nodeType === 'number'}
            onClick={() => {
              editor.update(() => {
                editor.dispatchCommand(
                  nodeType === 'number'
                    ? REMOVE_LIST_COMMAND
                    : INSERT_ORDERED_LIST_COMMAND,
                  undefined,
                )
              })
            }}
          />
        </MiniSheet.Items>
      </MiniSheet>
      <Separator />
      <IconButton
        tooltip={nodeType === 'quote' ? 'Remove Quote' : 'Quote'}
        size="small"
        icon="quote"
        keyHint="Cmd+Alt+Q"
        keyHintVisualOnly
        toggled={nodeType === 'quote'}
        onClick={() => {
          editor.update(() => {
            const selection = $getSelection()

            $setBlocksType(selection, () =>
              nodeType === 'quote' ? new ParagraphNode() : new QuoteNode(),
            )
          })
        }}
      />
      <IconButton
        tooltip="Insert embed"
        size="small"
        icon="embed"
        onClick={() => {
          setEmbedModalOpen(true)
        }}
      />
      {embedModalOpen && (
        <EmbedModal
          onOpenChange={setEmbedModalOpen}
          onSave={(value) => {
            editor.update(() => {
              const embedNode = new EmbedNode(value)
              $insertNodeToNearestRoot(embedNode)
            })
          }}
        />
      )}
      <Separator />
      <IconButton
        tooltip="Undo"
        keyHint="Cmd+Z"
        keyHintVisualOnly
        size="small"
        icon="arrow-left-curved"
        disabled={!canUndo}
        onClick={() => {
          editor.dispatchCommand(UNDO_COMMAND, undefined)
        }}
      />
      <IconButton
        tooltip="Redo"
        keyHint="Cmd+Shift+Z"
        keyHintVisualOnly
        size="small"
        icon="arrow-right-curved"
        disabled={!canRedo}
        onClick={() => {
          editor.dispatchCommand(REDO_COMMAND, undefined)
        }}
      />
      <div
        style={{
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          marginLeft: 'auto',
        }}
      >
        <Menu>
          <Menu.Trigger>
            {({ open }) => (
              <Button
                variant="ghost"
                size="small"
                trailIcon="tiny-chevron-down"
                forceHover={open}
              >
                {visibleMetric === 'words' && `${stats.wordCount} words`}
                {visibleMetric === 'characters' &&
                  `${stats.characterCount} characters`}
              </Button>
            )}
          </Menu.Trigger>
          <Menu.Items>
            <Menu.Header>Show metric...</Menu.Header>
            <Menu.ToggleItem
              value={visibleMetric === 'words'}
              onChange={() => {
                setVisibleMetric('words')
              }}
            >
              {stats.wordCount} words
            </Menu.ToggleItem>
            <Menu.ToggleItem
              value={visibleMetric === 'characters'}
              onChange={() => {
                setVisibleMetric('characters')
              }}
            >
              {stats.characterCount} characters
            </Menu.ToggleItem>
          </Menu.Items>
        </Menu>
      </div>
    </div>
  )
}
