import { Todo } from "./todo"
import { RangeSetBuilder, StateEffect } from "@codemirror/state"
import { EditorView, Decoration, ViewPlugin, ViewUpdate, WidgetType } from "@codemirror/view"
import { type RefObject } from "hono/jsx"

// Effect to trigger a decoration refresh on filter change
export const refreshFilterEffect = StateEffect.define<void>()

// Effect to add temporary completion animation
export const completionAnimationEffect = StateEffect.define<{ pos: number; duration?: number }>()

export const todoDecorations = (filterRef: RefObject<string>) => {
  return ViewPlugin.fromClass(
    class {
      decorations: any
      completingLines: Set<number> = new Set()

      constructor(view: EditorView) {
        this.decorations = this.buildDecorations(view)
      }

      update(update: ViewUpdate) {
        // Handle completion animation effects
        for (const transaction of update.transactions) {
          for (const effect of transaction.effects) {
            if (effect.is(completionAnimationEffect)) {
              const { pos, duration = 1000 } = effect.value
              const line = update.view.state.doc.lineAt(pos)
              this.completingLines.add(line.number)

              // Remove the animation after the duration
              setTimeout(() => {
                this.completingLines.delete(line.number)
                update.view.dispatch({ effects: refreshFilterEffect.of() })
              }, duration)
            }
          }
        }

        if (
          update.docChanged ||
          update.viewportChanged ||
          update.transactions.some((tr) =>
            tr.effects.some((e) => e.is(refreshFilterEffect) || e.is(completionAnimationEffect))
          )
        ) {
          this.decorations = this.buildDecorations(update.view)
        }
      }

      buildDecorations(view: EditorView) {
        const builder = new RangeSetBuilder<Decoration>()
        const { doc } = view.state
        for (const { from, to } of view.visibleRanges) {
          let pos = from
          while (pos <= to) {
            const line = doc.lineAt(pos)
            const text = line.text
            const todo = Todo.parse(text)

            if (todo) {
              if (filterRef.current && !todo.tags.find((t) => t.startsWith(filterRef.current!))) {
                builder.add(line.from, line.from, Decoration.line({ attributes: { style: "display: none" } }))
              }

              // Add completion animation if this line is animating
              if (this.completingLines.has(line.number)) {
                builder.add(line.from, line.from, Decoration.line({ class: "todo-completing" }))
              }

              if (todo.done) {
                builder.add(line.from, line.from, Decoration.line({ class: "todo-completed" }))
              }

              if (todo.dueDate) {
                // Get number of days until due date
                const daysUntilDue = todo.dueDate.diffNow("days").days
                if (daysUntilDue <= -1) {
                  builder.add(line.from, line.to, Decoration.mark({ class: "todo-overdue" }))
                } else if (daysUntilDue <= 0) {
                  builder.add(line.from, line.to, Decoration.mark({ class: "todo-due-today" }))
                }
              }

              // Add checkbox decoration for cursor styling
              const checkboxMatch = text.match(/^(\s*-\s*\[)\s*(\w?)\s*(\]\s+)/)
              if (checkboxMatch) {
                const checkboxEnd = checkboxMatch[0].length
                builder.add(line.from, line.from + checkboxEnd, Decoration.mark({ class: "todo-checkbox" }))
              }

              for (const { type, start, end, attributes } of decorationsFor(todo)) {
                builder.add(
                  line.from + start,
                  line.from + end,
                  Decoration.mark({ class: `todo-${type}`, attributes })
                )
              }
            } else if (/^\s*#+\s/.test(text)) {
              builder.add(line.from, line.to, Decoration.mark({ class: "todo-header" }))
            } else if (text.trim() !== "") {
              builder.add(line.from, line.to, Decoration.mark({ class: "todo-invalid" }))
            }

            pos = line.to + 1
          }
        }
        return builder.finish()
      }
    },
    { decorations: (v) => v.decorations }
  )
}

type TodoAttributes = Record<string, string>
const decorationsFor = (todo: Todo) => {
  const decorations: { type: string; start: number; end: number; attributes: TodoAttributes }[] = []

  let start = 0
  for (const node of todo.nodes) {
    const { type } = node
    const end = start + node.content.length
    const attributes: TodoAttributes = {}
    if (type === "url") {
      attributes["data-url"] = node.url
    }

    decorations.push({ type, start, end, attributes })
    start = end
  }

  return decorations
}
