import { indentMore, indentLess } from "@codemirror/commands"
import { EditorView, keymap, type KeyBinding } from "@codemirror/view"
import { Todo } from "./todo"
import { type RefObject } from "hono/jsx"
import { parseTodoList, todoListToString } from "./todoList"
import { todoTimer } from "./todoTimer"
import { triggerTodoCompletionEffect } from "./todoCompletion"
import { selectNextOccurrence } from "@codemirror/search"

export type Command = KeyBinding & { label?: string; key: string }

export const buildKeyBindings = (
  filterElRef: RefObject<HTMLInputElement>,
  setShowShortcuts?: (show: boolean) => void
): Command[] => [
  {
    label: "Insert empty",
    key: "alt-l",
    preventDefault: true,
    run: (view: EditorView) => {
      // if the line is empty, insert a new todo item
      const { state } = view
      const { head } = state.selection.main
      const line = state.doc.lineAt(head)

      if (line.text.trim() === "") {
        const newTodo = "[ ] "
        view.dispatch({
          changes: { from: line.from, to: line.to, insert: newTodo },
          selection: { anchor: line.to + newTodo.length },
          userEvent: "input",
        })
      } else {
        // otherwise insert a new empty line
        view.dispatch({
          changes: { from: head, to: head, insert: "\n" },
          selection: { anchor: head + 1 },
          userEvent: "input",
        })
      }

      return true
    },
  },
  {
    label: "Show keyboard shortcuts",
    key: "cmd-k",
    preventDefault: true,
    run: (_view: EditorView) => {
      setShowShortcuts?.(true)
      return true
    },
  },
  {
    label: "Toggle done state of the todo item",
    key: "alt-j",
    preventDefault: true,
    run: (view: EditorView) => toggleDone(view),
  },
  {
    label: "Toggle the todo timer",
    key: "alt-k",
    preventDefault: true,
    run: (view: EditorView) => {
      const timerPlugin = view.plugin(todoTimer)
      if (timerPlugin?.currentWidget) {
        timerPlugin.currentWidget.toggleTimer()
        return true
      }
      return false
    },
  },
  {
    label: "Move all done todos to # Done",
    key: "alt-,",
    preventDefault: true,
    run: (view: EditorView) => {
      moveToDone(view)
      return true
    },
  },
  {
    key: "Escape",
    run: (view: EditorView) => {
      const timerPlugin = view.plugin(todoTimer)
      if (timerPlugin?.currentWidget) {
        timerPlugin.currentWidget.stopTimer()
      }
      return false
    },
  },
  {
    label: "Focus the filter input",
    key: "alt-l",
    preventDefault: true,
    run: (_view: EditorView) => {
      filterElRef.current?.focus()
      return true
    },
  },
  {
    key: "Tab",
    preventDefault: true,
    run: indentMore,
  },
  {
    key: "Shift-Tab",
    preventDefault: true,
    run: indentLess,
  },
]

export const todoKeymap = (
  filterElRef: RefObject<HTMLInputElement>,
  setShowShortcuts?: (show: boolean) => void
) => {
  return keymap.of(buildKeyBindings(filterElRef, setShowShortcuts))
}

const moveToDone = (view: EditorView) => {
  const todoList = parseTodoList(view.state.doc.toString())
  let doneHeader = todoList.find((header) => header.title === "# Done")

  if (!doneHeader) {
    doneHeader = { title: "\n# Done", todos: [] }
    todoList.push(doneHeader)
  }

  // collect and remove all done todos
  const doneTodos: Todo[] = []
  todoList.forEach((header) => {
    header.todos = header.todos.filter((todo) => {
      if (typeof todo === "string") return true
      doneTodos.push(...collectDoneTodos(todo))
      return !todo.done
    })
  })

  doneHeader.todos.push(...doneTodos)

  const updatedList = todoListToString(todoList).trimEnd()
  view.dispatch({
    changes: { from: 0, to: view.state.doc.length, insert: updatedList },
    selection: { anchor: 0 },
    userEvent: "input",
  })
}

const collectDoneTodos = (todo: Todo | string): Todo[] => {
  if (typeof todo === "string") return []

  const doneTodos: Todo[] = []
  if (todo.done) {
    todo.indent = ""
    doneTodos.push(todo)
  } else {
    todo.children = todo.children.filter((child) => {
      if (typeof child === "string") return true
      doneTodos.push(...collectDoneTodos(child))
      return !child.done
    })
  }

  return doneTodos
}

const toggleDone = (view: EditorView) => {
  const { state } = view
  const { head } = state.selection.main
  const line = state.doc.lineAt(head)

  const todo = Todo.parse(line.text)
  if (!todo) return false

  todo.done = !todo.done

  const timerPlugin = view.plugin(todoTimer)
  if (timerPlugin?.currentWidget) {
    timerPlugin.currentWidget.stopTimer()
  }

  const from = line.from
  const to = line.to
  const offset = head - from
  view.dispatch({
    changes: { from, to, insert: todo.toString() },
    selection: { anchor: from + offset },
    userEvent: "input",
  })

  // Add animation and sound effect when completing (not uncompleting)
  if (todo.done) {
    triggerTodoCompletionEffect(view, line.from)
  }

  return true
}
