import { useClient, useQuery } from '@based/react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection'
import {
  $getNodeByKey,
  CLICK_COMMAND,
  COMMAND_PRIORITY_LOW,
  DecoratorNode,
  NodeKey,
  SerializedLexicalNode,
  Spread,
} from 'lexical'
import { ReactNode, useLayoutEffect, useRef, useState } from 'react'
import { radius } from '../../../utils/radius.js'
import { colors } from '../../../utils/colors.js'
import { shadows } from '../../../utils/shadows.js'
import { Button } from '../../Button/index.js'
import { Media, useMediaPreviews } from '../../Media/index.js'
import { IconButton } from '../../IconButton/index.js'
import { $insertNodeToNearestRoot } from '@lexical/utils'
import { Menu } from '../../Menu/index.js'
import { Modal, ModalRootProps } from '../../Modal/index.js'
import { BasedGrid } from '../../Grid/index.js'

export class ImageNode extends DecoratorNode<ReactNode> {
  __id: string

  static override getType(): string {
    return 'image'
  }

  constructor(id: string, key?: NodeKey) {
    super(key)
    this.__id = id
  }

  static override clone(node: ImageNode): ImageNode {
    return new ImageNode(node.__id, node.__key)
  }

  override createDOM() {
    return document.createElement('div')
  }

  override updateDOM() {
    return false
  }

  static override importJSON(
    serializedNode: Spread<{ id: string }, SerializedLexicalNode>,
  ): ImageNode {
    return new ImageNode(serializedNode.id)
  }

  override exportJSON(): Spread<{ id: string }, SerializedLexicalNode> {
    return {
      version: 1,
      type: 'image',
      id: this.__id,
    }
  }

  override isIsolated(): false {
    return false
  }

  override isInline(): false {
    return false
  }

  canInsertTextBefore(): false {
    return false
  }

  canInsertTextAfter(): false {
    return false
  }

  override decorate() {
    return <Image id={this.__id} nodeKey={this.__key} />
  }
}

function Image({ id, nodeKey }: { id: string; nodeKey: NodeKey }) {
  const ref = useRef<HTMLDivElement>(null)
  const [editor] = useLexicalComposerContext()
  const [isSelected, setSelected, clearSelection] =
    useLexicalNodeSelection(nodeKey)
  const { data } = useQuery('db', { $id: id, id: true, src: true })
  const [imageModalOpen, setImageModalOpen] = useState(false)

  useLayoutEffect(() => {
    return editor.registerCommand(
      CLICK_COMMAND,
      (event) => {
        if (ref.current && ref.current.contains(event.target as Node)) {
          clearSelection()
          setSelected(true)
          return true
        }

        return false
      },
      COMMAND_PRIORITY_LOW,
    )
  }, [editor, clearSelection, setSelected])

  return (
    <div
      ref={ref}
      style={{
        position: 'relative',
        display: 'flex',
        minHeight: 250,
        width: '100%',
        borderRadius: radius[8],
        background: colors.neutral5,
        overflow: 'hidden',
        border: `1px solid ${colors.neutral20Adjusted}`,
        ...(isSelected && {
          border: `1px solid ${colors.neutral100}`,
          outline: `4px solid ${colors.neutral20}`,
        }),
      }}
    >
      <Media
        data={{ id, src: data?.src }}
        optimizedImageWidth={1500}
        objectFit="cover"
      />
      {isSelected && (
        <div
          style={{
            position: 'absolute',
            right: 16,
            top: 16,
            background: colors.neutralInverted100,
            borderRadius: radius[12],
            boxShadow: shadows.popoverSmall,
          }}
        >
          <div
            style={{
              display: 'flex',
              justifyContent: 'center',
              alignItems: 'center',
              gap: 4,
              padding: 4,
              background: colors.neutral10Background,
              borderRadius: radius[12],
              border: `1px solid ${colors.neutral10Adjusted}`,
            }}
          >
            <Button
              size="small"
              variant="ghost"
              leadIcon="edit"
              onClick={(e) => {
                e.stopPropagation()
                setImageModalOpen(true)
              }}
            >
              Edit
            </Button>
            {imageModalOpen && (
              <ImageNodeModal
                onSave={(id) => {
                  editor.update(() => {
                    const node: ImageNode = $getNodeByKey(nodeKey)
                    const writeable = node.getWritable()
                    writeable.__id = id
                  })
                }}
                onOpenChange={setImageModalOpen}
              />
            )}
            <div
              style={{ width: 1, background: colors.neutral10, height: 16 }}
            />
            <Button
              size="small"
              variant="ghost"
              color="red"
              leadIcon="delete"
              onClick={(e) => {
                e.stopPropagation()
                editor.update(() => {
                  const node: ImageNode = $getNodeByKey(nodeKey)
                  node.remove()
                })
              }}
            >
              Remove
            </Button>
          </div>
        </div>
      )}
    </div>
  )
}

export function ImageButton() {
  const [editor] = useLexicalComposerContext()
  const client = useClient()
  const fileInputRef = useRef<HTMLInputElement>()
  const { addPreview } = useMediaPreviews()
  const [imageModalOpen, setImageModalOpen] = useState(false)

  return (
    <>
      <Menu>
        <Menu.Trigger>
          {({ open }) => (
            <IconButton
              tooltip="Insert image"
              trailChevron="down"
              size="small"
              icon="image"
              forceHover={open}
            />
          )}
        </Menu.Trigger>
        <Menu.Items>
          <Menu.Item
            leadIcon="arrow-up"
            onClick={() => {
              fileInputRef.current.click()
            }}
          >
            Upload new file
          </Menu.Item>
          <Menu.Item
            leadIcon="image"
            onClick={() => {
              setImageModalOpen(true)
            }}
          >
            Pick from uploaded
          </Menu.Item>
        </Menu.Items>
      </Menu>
      <input
        style={{
          position: 'absolute',
          width: 1,
          height: 1,
          padding: 0,
          margin: -1,
          overflow: 'hidden',
          clip: 'rect(0, 0, 0, 0)',
          whiteSpace: 'nowrap',
          borderWidth: 0,
        }}
        ref={fileInputRef}
        type="file"
        accept="image/*"
        onChange={async (e) => {
          const file = e.target.files[0]
          if (!file) return

          const { id } = await client.call('db:set', { type: 'file' })

          if (file.type.includes('image')) {
            addPreview(id, URL.createObjectURL(file))
          }

          editor.update(() => {
            const imageNode = new ImageNode(id)
            $insertNodeToNearestRoot(imageNode)
          })

          await client.stream('db:file-upload', {
            contents: file,
            fileName: file.name,
            mimeType: file.type,
            payload: { id },
          })
          fileInputRef.current.value = ''
        }}
      />
      {imageModalOpen && (
        <ImageNodeModal
          onSave={(id) => {
            editor.update(() => {
              const imageNode = new ImageNode(id)
              $insertNodeToNearestRoot(imageNode)
            })
          }}
          onOpenChange={setImageModalOpen}
        />
      )}
    </>
  )
}

type ImageNodeModalProps = {
  onSave: (id: string) => void
} & Pick<ModalRootProps, 'onOpenChange'>

export function ImageNodeModal({ onOpenChange, onSave }: ImageNodeModalProps) {
  return (
    <Modal open onOpenChange={onOpenChange} size="large">
      <Modal.Header title="Pick from uploaded images" />
      <Modal.Body>
        <div
          style={{
            display: 'flex',
            width: '100%',
            height: 479,
            borderRadius: 12,
            overflow: 'hidden',
            border: `1px solid ${colors.neutral20Adjusted}`,
          }}
        >
          <BasedGrid
            onItemClick={(item) => {
              onSave(item.id)
              onOpenChange(false)
            }}
            query={({ offset, limit }) => ({
              files: {
                id: true,
                src: true,
                name: true,
                createdAt: true,
                $list: {
                  $limit: limit,
                  $offset: offset,
                  $sort: {
                    $order: 'desc',
                    $field: 'createdAt',
                  },
                  $find: {
                    $traverse: 'children',
                    $filter: {
                      $field: 'type',
                      $operator: '=',
                      $value: 'file',
                    },
                  },
                },
              },
            })}
            transformQueryResult={(data) => data?.files}
            totalQuery={() => ({
              total: {
                $aggregate: {
                  $function: 'count',
                  $traverse: 'children',
                  $filter: [
                    {
                      $field: 'type',
                      $operator: '=',
                      $value: 'file',
                    },
                  ],
                },
              },
            })}
            fields={{
              src: {
                key: 'src',
              },
              name: {
                key: 'name',
              },
              createdAt: {
                key: 'createdAt',
              },
            }}
          />
        </div>
      </Modal.Body>
      <Modal.Actions>
        {({ close }) => (
          <Button variant="border" keyHint="Esc" onClick={close}>
            Cancel
          </Button>
        )}
      </Modal.Actions>
    </Modal>
  )
}
