import type { SaveFileMessage } from "../shared/types"
import { scrollback } from "./dom"
import { send } from "./websocket"
import { focusInput } from "./focus"

const INDENT_SIZE = 2

export function initEditor() {
  document.addEventListener("input", handleAdjustHeight)
  focusTextareaOnCreation()
}

function handleAdjustHeight(e: Event) {
  const target = e.target as HTMLElement
  if (target?.matches(".editor"))
    adjustHeight(target as HTMLTextAreaElement)
}

function adjustHeight(editor: HTMLTextAreaElement) {
  editor.style.height = "auto"
  editor.style.height = editor.scrollHeight + "px"
}

function focusTextareaOnCreation() {
  const observer = new MutationObserver(mutations => {
    for (const m of mutations)
      for (const node of Array.from(m.addedNodes))
        if (node instanceof HTMLElement && node.childNodes[0] instanceof HTMLElement && node.childNodes[0].matches("textarea")) {
          const editor = node.childNodes[0]
          editor.focus()
          editor.addEventListener("keydown", keydownHandler)
          return
        }
  })

  observer.observe(scrollback, { childList: true })
}

function keydownHandler(e: KeyboardEvent) {
  const editor = e.target as HTMLTextAreaElement

  if (e.key === "Tab") {
    e.preventDefault()
    if (e.shiftKey)
      removeTab(editor)
    else
      insertTab(editor)
  } else if (e.ctrlKey && e.key === "c") {
    focusInput()
  } else if ((e.ctrlKey && e.key === "s") || (e.ctrlKey && e.key === "Enter")) {
    e.preventDefault()
    send({
      id: editor.dataset.path || "/tmp.txt",
      type: "save-file",
      data: editor.value
    } as SaveFileMessage)
  } else if (e.key === "{") {
    if (editor.selectionStart !== editor.selectionEnd) {
      insertAroundSelection(editor, '{', '}')
      e.preventDefault()
    } else {
      setTimeout(() => insertAfterCaret(editor, "}"), 0)
    }
  } else if (e.key === "}" && isNextChar(editor, '}')) {
    moveOneRight(editor)
    e.preventDefault()

  } else if (e.key === "[") {
    if (editor.selectionStart !== editor.selectionEnd) {
      insertAroundSelection(editor, '[', ']')
      e.preventDefault()
    } else {
      setTimeout(() => insertAfterCaret(editor, "]"), 0)
    }
  } else if (e.key === "]" && isNextChar(editor, ']')) {
    moveOneRight(editor)
    e.preventDefault()

  } else if (e.key === "(") {
    if (editor.selectionStart !== editor.selectionEnd) {
      insertAroundSelection(editor, '(', ')')
      e.preventDefault()
    } else {
      setTimeout(() => insertAfterCaret(editor, ")"), 0)
    }
  } else if (e.key === ")" && isNextChar(editor, ')')) {
    moveOneRight(editor)
    e.preventDefault()
  } else if (e.key === '"') {
    if (isNextChar(editor, '"')) {
      moveOneRight(editor)
      e.preventDefault()
    } else if (editor.selectionStart !== editor.selectionEnd) {
      insertAroundSelection(editor, '"', '"')
      e.preventDefault()
    } else {
      setTimeout(() => insertAfterCaret(editor, '"'), 0)
    }
  } else if (e.key === "Enter") {
    indentNewlineForBraces(e, editor)
  }
}

function moveOneRight(editor: HTMLTextAreaElement) {
  const pos = editor.selectionStart
  editor.selectionStart = editor.selectionEnd = pos + 1
}

function insertTab(editor: HTMLTextAreaElement) {
  const start = editor.selectionStart
  const end = editor.selectionEnd
  const lineStart = editor.value.lastIndexOf("\n", start - 1) + 1

  editor.value = editor.value.slice(0, lineStart) + "  " + editor.value.slice(lineStart)
  editor.selectionStart = start + INDENT_SIZE
  editor.selectionEnd = end + INDENT_SIZE
}

function removeTab(editor: HTMLTextAreaElement) {
  const start = editor.selectionStart
  const end = editor.selectionEnd
  const lineStart = editor.value.lastIndexOf("\n", start - 1) + 1

  if (editor.value.slice(lineStart, lineStart + 2) === "  ") {
    editor.value = editor.value.slice(0, lineStart) + editor.value.slice(lineStart + 2)
    editor.selectionStart = start - INDENT_SIZE
    editor.selectionEnd = end - INDENT_SIZE
  }
}

function insertAfterCaret(editor: HTMLTextAreaElement, char: string) {
  const pos = editor.selectionStart
  editor.value = editor.value.slice(0, pos) + char + editor.value.slice(pos)
  editor.selectionStart = editor.selectionEnd = pos
}

function isNextChar(editor: HTMLTextAreaElement, char: string): boolean {
  return editor.value[editor.selectionStart] === char
}

function indentNewlineForBraces(e: KeyboardEvent, editor: HTMLTextAreaElement) {
  e.preventDefault()

  if (isBetween(editor, "{", "}") || isBetween(editor, "[", "]")) {
    setTimeout(() => insertMoreIndentedNewline(editor), 0)
  } else {
    setTimeout(() => insertIndentedNewline(editor), 0)
  }
}

function isBetween(editor: HTMLTextAreaElement, start: string, end: string): boolean {
  const pos = editor.selectionStart
  const val = editor.value

  if (pos <= 0 || pos >= val.length) return false
  return val[pos - 1] === start && val[pos] === end
}

function insertIndentedNewline(editor: HTMLTextAreaElement) {
  const pos = editor.selectionStart

  const before = editor.value.slice(0, pos)

  const prevLineStart = before.lastIndexOf("\n", pos - 1) + 1
  const prevLine = before.slice(prevLineStart)

  let leading = 0
  while (prevLine[leading] === " ") leading++

  const indent = " ".repeat(leading)

  const insert = "\n" + indent
  editor.value = editor.value.slice(0, pos) + insert + editor.value.slice(pos)

  const newPos = pos + insert.length
  editor.selectionStart = editor.selectionEnd = newPos

  adjustHeight(editor)
}

function insertAroundSelection(editor: HTMLTextAreaElement, before: string, after: string) {
  const start = editor.selectionStart
  const end = editor.selectionEnd

  editor.value = editor.value.slice(0, start) + before + editor.value.slice(start, end) + after + editor.value.slice(end)
}

function insertMoreIndentedNewline(editor: HTMLTextAreaElement) {
  const pos = editor.selectionStart

  const before = editor.value.slice(0, pos)

  const prevLineStart = before.lastIndexOf("\n", pos - 1) + 1
  const prevLine = before.slice(prevLineStart)

  let leading = 0
  while (prevLine[leading] === " ") leading++

  const oldIndent = " ".repeat(leading)
  const newIndent = " ".repeat(leading + INDENT_SIZE)

  const insert = "\n" + newIndent + "\n" + oldIndent
  editor.value = editor.value.slice(0, pos) + insert + editor.value.slice(pos)

  const newPos = pos + insert.length
  editor.selectionStart = editor.selectionEnd = newPos - 1 - oldIndent.length

  adjustHeight(editor)
}
