import { useCallback, useEffect, useRef, useState, type KeyboardEvent, type RefObject } from "hono/jsx"
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"
import { EditorState } from "@codemirror/state"
import { EditorView, lineNumbers, keymap } from "@codemirror/view"
import { foldGutter, foldKeymap } from "@codemirror/language"
import { refreshFilterEffect, todoDecorations } from "./todoDecorations"
import { autoTodoOnNewline } from "./autoTodoOnNewline"
import { buildKeyBindings, todoKeymap } from "./todoKeymap"
import { todoClickHandler } from "./todoClickHandler"
import { dateAutocompletion } from "./dateAutocompletion"
import { todoTimer } from "./todoTimer"
import { DateTime } from "luxon"
import { searchKeymap } from "@codemirror/search"

import "./todo.css"

type TodoEditorProps = {
  defaultValue?: string
  onChange: (todos: string) => void
}

const createEditorExtensions = (
  filterRef: RefObject<string>,
  filterElRef: RefObject<HTMLInputElement>,
  setShowShortcuts: (show: boolean) => void,
  onChange: (todos: string) => void
) => {
  const changeListener = EditorView.updateListener.of((update) => {
    if (update.docChanged) {
      onChange(update.state.doc.toString())
    }
  })

  return [
    foldGutter(),
    lineNumbers(),
    history(),
    todoDecorations(filterRef),
    changeListener,
    autoTodoOnNewline,
    todoKeymap(filterElRef, setShowShortcuts),
    todoClickHandler,
    dateAutocompletion,
    todoTimer,
    keymap.of(historyKeymap),
    keymap.of(defaultKeymap),
    keymap.of(foldKeymap),
    keymap.of(searchKeymap),
  ]
}

export const TodoEditor = ({ defaultValue, onChange }: TodoEditorProps) => {
  const editorContainer = useRef<HTMLDivElement>(null)
  const editorRef = useRef<EditorView>(null)
  const [filter, setFilter] = useState<string>("")
  const filterRef = useRef(filter)
  const filterElRef = useRef<HTMLInputElement>(null)
  const [showShortcuts, setShowShortcuts] = useState<boolean>(false)

  useEffect(() => {
    filterRef.current = filter
    editorRef.current?.dispatch({ effects: refreshFilterEffect.of() }) // trigger update to decorations when filter changes
  }, [filter])

  useEffect(() => {
    if (editorRef.current) editorRef.current.destroy()
    if (!editorContainer.current) return

    const state = EditorState.create({
      doc: defaultValue || getDefaultDocument(),
      extensions: createEditorExtensions(filterRef, filterElRef, setShowShortcuts, onChange),
    })

    const view = new EditorView({ state, parent: editorContainer.current })
    view.focus()
    editorRef.current = view

    return () => view.destroy()
  }, [onChange, setShowShortcuts])

  // Handle escape key globally to close shortcuts modal
  useEffect(() => {
    if (!showShortcuts) return

    const handleEscape = (e: globalThis.KeyboardEvent) => {
      if (e.key === "Escape") {
        setShowShortcuts(false)
        e.preventDefault()
      }
    }

    document.addEventListener("keydown", handleEscape)
    return () => document.removeEventListener("keydown", handleEscape)
  }, [showShortcuts])

  const handleFilterInput = useCallback((e: KeyboardEvent) => {
    const target = e.target as HTMLInputElement

    if (e.key === "Enter" || e.key === "Escape") {
      editorRef.current?.focus()
      e.preventDefault()
    } else {
      setFilter(target.value)
    }
  }, [])

  return (
    <div class="grow flex flex-col">
      {showShortcuts && <KeyboardShortcuts onClose={() => setShowShortcuts(false)} />}
      <input
        type="text"
        placeholder={FILTER_PLACEHOLDER}
        value={filter}
        ref={filterElRef}
        onKeyUp={handleFilterInput}
        onFocus={(e) => (e.target as HTMLInputElement).select()}
        class="p-2 border-b"
      />
      <div ref={editorContainer} class="grow" />
    </div>
  )
}

const KeyboardShortcuts = ({ onClose }: { onClose: () => void }) => {
  const shortcuts = buildKeyBindings(undefined as any).filter((k) => k.label)

  return (
    <div class="fixed inset-0 bg-[#00000088] z-50" onClick={onClose}>
      <div class="fixed inset-0 flex items-center justify-center">
        <div class="bg-white rounded-sm p-3 shadow-xl" onClick={(e) => e.stopPropagation()}>
          <ul class="grid grid-cols-1 gap-2 text-xs">
            {shortcuts.map((binding) => (
              <li class="grid grid-cols-2 items-center gap-4" key={binding.key}>
                <span>{binding.label}</span>
                <div class="flex gap-1 justify-end">
                  {binding.key.split("-").map((part, index) => (
                    <kbd class="bg-gray-200 px-2 py-1 rounded" key={index}>
                      {part}
                    </kbd>
                  ))}
                </div>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
  )
}

const FILTER_PLACEHOLDER = "Filter by tag"

const getDefaultDocument = () => {
  const today = DateTime.local().toFormat("MM/dd/yyyy")
  const future = DateTime.local().plus({ days: 3 }).toFormat("MM/dd/yyyy")

  return `
# Today

[ ] Sample task with a due date @${today} 
[ ] You can use a #tag to filter todos
  [ ] A sub task! Create nested todos by indenting with <tab>
[x] Complete a todo by pressing <opt+k>
[ ] You can also set a time estimate for todos (press the play button to start) 10m

# This week
[ ] Another todo with a due date @${future}

# Later
[ ] I use later as a junk drawer for todos I don't want to forget

`.trim()
}
