/**
 * Inline error display for CalcPad.
 * Adapted from epic/9-2-codemirror-6-editor.
 *
 * Shows red underline decorations and gutter markers for lines with errors.
 */

import {
  Decoration,
  type DecorationSet,
  GutterMarker,
  gutter,
  EditorView,
  hoverTooltip,
  type Tooltip,
} from '@codemirror/view'
import { StateField, StateEffect, type Extension, RangeSet } from '@codemirror/state'

// --- State Effects ---

export interface LineError {
  from: number  // absolute start position in document
  to: number    // absolute end position in document
  message: string
}

export const setErrorsEffect = StateEffect.define<LineError[]>()

// --- Error Gutter Marker ---

class ErrorGutterMarker extends GutterMarker {
  override toDOM(): HTMLElement {
    const span = document.createElement('span')
    span.className = 'cm-error-marker'
    span.textContent = '\u26A0' // Warning sign
    return span
  }

  override eq(other: GutterMarker): boolean {
    return other instanceof ErrorGutterMarker
  }
}

const errorMarkerInstance = new ErrorGutterMarker()

// --- Error Underline Decorations (StateField) ---

const errorUnderlineMark = Decoration.mark({ class: 'cm-error-underline' })

export const errorDecorationsField = StateField.define<DecorationSet>({
  create() {
    return Decoration.none
  },

  update(decos, tr) {
    for (const effect of tr.effects) {
      if (effect.is(setErrorsEffect)) {
        if (effect.value.length === 0) {
          return Decoration.none
        }
        const ranges = effect.value
          .filter((e) => e.from < e.to)
          .sort((a, b) => a.from - b.from)
          .map((e) => errorUnderlineMark.range(e.from, e.to))
        return RangeSet.of(ranges)
      }
    }

    if (tr.docChanged) {
      return decos.map(tr.changes)
    }

    return decos
  },

  provide(field) {
    return EditorView.decorations.from(field)
  },
})

// --- Error Lines Set (for gutter) ---

export const errorLinesField = StateField.define<Set<number>>({
  create() {
    return new Set()
  },

  update(lines, tr) {
    for (const effect of tr.effects) {
      if (effect.is(setErrorsEffect)) {
        const newLines = new Set<number>()
        for (const error of effect.value) {
          const lineNumber = tr.state.doc.lineAt(error.from).number
          newLines.add(lineNumber)
        }
        return newLines
      }
    }
    return lines
  },
})

// --- Error Messages (for tooltips) ---

export const errorMessagesField = StateField.define<Map<number, string>>({
  create() {
    return new Map()
  },
  update(msgs, tr) {
    for (const effect of tr.effects) {
      if (effect.is(setErrorsEffect)) {
        const newMsgs = new Map<number, string>()
        for (const error of effect.value) {
          const lineNumber = tr.state.doc.lineAt(error.from).number
          newMsgs.set(lineNumber, error.message)
        }
        return newMsgs
      }
    }
    return msgs
  },
})

// --- Error Tooltip (hover) ---

const errorTooltip = hoverTooltip((view, pos) => {
  const line = view.state.doc.lineAt(pos)
  const errorMessages = view.state.field(errorMessagesField)
  const msg = errorMessages.get(line.number)
  if (!msg) return null

  return {
    pos: line.from,
    end: line.to,
    above: false,
    create() {
      const dom = document.createElement('div')
      dom.className = 'cm-error-tooltip'
      dom.textContent = msg
      return { dom }
    },
  } satisfies Tooltip
})

// --- Error Gutter ---

export const errorGutter = gutter({
  class: 'cm-error-gutter',
  lineMarker(view, line) {
    const lineNumber = view.state.doc.lineAt(line.from).number
    const errorLines = view.state.field(errorLinesField)
    return errorLines.has(lineNumber) ? errorMarkerInstance : null
  },
  lineMarkerChange(update) {
    return update.transactions.some((tr) =>
      tr.effects.some((e) => e.is(setErrorsEffect)),
    )
  },
})

// --- Base Theme ---

export const errorBaseTheme = EditorView.baseTheme({
  '.cm-error-underline': {
    textDecoration: 'underline wavy var(--error, red)',
    textDecorationThickness: '1.5px',
  },
  '.cm-error-marker': {
    color: 'var(--error, #e53e3e)',
    fontSize: '14px',
    cursor: 'pointer',
  },
  '.cm-error-gutter': {
    width: '20px',
  },
  '.cm-error-tooltip': {
    backgroundColor: 'var(--bg-secondary, #f8f9fa)',
    color: 'var(--error, #e53e3e)',
    border: '1px solid var(--border, #e5e4e7)',
    borderRadius: '4px',
    padding: '4px 8px',
    fontSize: '12px',
    fontFamily: 'var(--sans, system-ui)',
    maxWidth: '300px',
    boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
  },
  '.cm-tooltip': {
    border: 'none',
    backgroundColor: 'transparent',
  },
})

/**
 * Creates the error display extension bundle.
 */
export function errorDisplayExtension(): Extension {
  return [
    errorDecorationsField,
    errorLinesField,
    errorMessagesField,
    errorGutter,
    errorTooltip,
    errorBaseTheme,
  ]
}
