/**
 * React wrapper around CodeMirror 6 for the CalcPad editor.
 *
 * Integrates the CalcPad language mode, error display,
 * and debounced evaluation via the WASM engine Web Worker.
 */

import { useRef, useEffect, useCallback } from 'react'
import { EditorState } from '@codemirror/state'
import {
  EditorView,
  lineNumbers,
  drawSelection,
  highlightActiveLine,
  keymap,
} from '@codemirror/view'
import {
  syntaxHighlighting,
  bracketMatching,
  indentOnInput,
  HighlightStyle,
} from '@codemirror/language'
import { tags } from '@lezer/highlight'
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
import { calcpadLanguage } from './calcpad-language.ts'
import { errorDisplayExtension, setErrorsEffect, type LineError } from './error-display.ts'
import { stripedLinesExtension } from './inline-results.ts'
import { formatPreviewExtension, formatPreviewCompartment, formatPreviewEnabled } from './format-preview.ts'
import type { EngineLineResult } from '../engine/types.ts'

export interface CalcEditorProps {
  /** Initial document content */
  initialDoc?: string
  /** Called when the document text changes (debounced internally) */
  onDocChange?: (lines: string[]) => void
  /** Engine evaluation results to display as errors */
  results?: EngineLineResult[]
  /** Debounce delay in ms before triggering onDocChange */
  debounceMs?: number
  /** Called with the EditorView once created (null on cleanup) */
  onViewReady?: (view: EditorView | null) => void
  /** Enable live preview formatting */
  formatPreview?: boolean
}

/**
 * CalcPad editor component built on CodeMirror 6.
 * Handles syntax highlighting, line numbers, and error underlines.
 */
export function CalcEditor({
  initialDoc = '',
  onDocChange,
  results,
  debounceMs = 50,
  onViewReady,
  formatPreview = true,
}: CalcEditorProps) {
  const containerRef = useRef<HTMLDivElement>(null)
  const viewRef = useRef<EditorView | null>(null)
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)

  // Stable callback ref for doc changes
  const onDocChangeRef = useRef(onDocChange)
  onDocChangeRef.current = onDocChange

  const scheduleEval = useCallback((view: EditorView) => {
    if (timerRef.current) clearTimeout(timerRef.current)
    timerRef.current = setTimeout(() => {
      timerRef.current = null
      const doc = view.state.doc.toString()
      const lines = doc.split('\n')
      onDocChangeRef.current?.(lines)
    }, debounceMs)
  }, [debounceMs])

  // Create editor on mount
  useEffect(() => {
    if (!containerRef.current) return

    const updateListener = EditorView.updateListener.of((update) => {
      if (update.docChanged && viewRef.current) {
        scheduleEval(viewRef.current)
      }
    })

    const state = EditorState.create({
      doc: initialDoc,
      extensions: [
        lineNumbers(),
        drawSelection(),
        highlightActiveLine(),
        bracketMatching(),
        indentOnInput(),
        history(),
        keymap.of([...defaultKeymap, ...historyKeymap]),
        syntaxHighlighting(calcpadHighlight),
        calcpadLanguage(),
        errorDisplayExtension(),
        stripedLinesExtension(),
        formatPreviewExtension(formatPreview),
        updateListener,
        calcpadEditorTheme,
      ],
    })

    const view = new EditorView({
      state,
      parent: containerRef.current,
    })

    viewRef.current = view
    onViewReady?.(view)

    // Trigger initial evaluation
    const doc = view.state.doc.toString()
    onDocChangeRef.current?.(doc.split('\n'))

    return () => {
      if (timerRef.current) clearTimeout(timerRef.current)
      view.destroy()
      viewRef.current = null
      onViewReady?.(null)
    }
    // initialDoc intentionally excluded — we only set it once on mount
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [scheduleEval])

  // Toggle format preview mode
  useEffect(() => {
    const view = viewRef.current
    if (!view) return
    view.dispatch({
      effects: formatPreviewCompartment.reconfigure(
        formatPreview ? formatPreviewEnabled : [],
      ),
    })
  }, [formatPreview])

  // Push engine results into the error display
  useEffect(() => {
    const view = viewRef.current
    if (!view || !results) return

    const errors: LineError[] = []

    for (let i = 0; i < results.length; i++) {
      const lineNum = i + 1
      const result = results[i]

      if (result.type === 'error' && result.error) {
        // Map to document positions
        if (lineNum <= view.state.doc.lines) {
          const docLine = view.state.doc.line(lineNum)
          errors.push({
            from: docLine.from,
            to: docLine.to,
            message: result.error,
          })
        }
      }
    }

    view.dispatch({
      effects: [
        setErrorsEffect.of(errors),
      ],
    })
  }, [results])

  return <div ref={containerRef} className="calc-editor" />
}

/**
 * Syntax highlighting using CSS variables for theme integration.
 */
const calcpadHighlight = HighlightStyle.define([
  { tag: tags.number, color: 'var(--syntax-number)' },
  { tag: tags.operator, color: 'var(--syntax-operator)' },
  { tag: tags.variableName, color: 'var(--syntax-variable)' },
  { tag: tags.function(tags.variableName), color: 'var(--syntax-function)' },
  { tag: tags.keyword, color: 'var(--syntax-keyword)' },
  { tag: tags.lineComment, color: 'var(--syntax-comment)', fontStyle: 'italic' },
  { tag: tags.heading, color: 'var(--syntax-heading)', fontWeight: '700' },
  { tag: tags.definitionOperator, color: 'var(--syntax-operator)' },
  { tag: tags.special(tags.variableName), color: 'var(--syntax-function)' },
  { tag: tags.constant(tags.variableName), color: 'var(--syntax-number)', fontWeight: '600' },
  { tag: tags.paren, color: 'var(--syntax-operator)' },
  { tag: tags.punctuation, color: 'var(--syntax-operator)' },
])

/**
 * Base theme for the CalcPad editor.
 */
const calcpadEditorTheme = EditorView.baseTheme({
  '&': {
    height: '100%',
    fontSize: 'var(--editor-font-size, 15px)',
    fontFamily: 'var(--mono, ui-monospace, Consolas, "Courier New", monospace)',
  },
  '.cm-scroller': {
    overflow: 'auto',
  },
  '.cm-content': {
    padding: '8px 0',
    minHeight: '100%',
  },
  '.cm-line': {
    padding: '0 12px',
    lineHeight: '1.6',
    position: 'relative',
    textAlign: 'var(--cm-text-align, left)',
  },
  '.cm-gutters': {
    backgroundColor: 'transparent',
    borderRight: 'none',
  },
  '.cm-lineNumbers .cm-gutterElement': {
    padding: '0 6px 0 12px',
    color: 'var(--text, #9ca3af)',
    opacity: '0.4',
    fontSize: 'calc(var(--editor-font-size, 15px) - 2px)',
    minWidth: '32px',
  },
  '.cm-activeLineGutter .cm-gutterElement': {
    opacity: '1',
    fontWeight: '600',
  },
  '.cm-stripe': {
    backgroundColor: 'var(--stripe, rgba(0, 0, 0, 0.02))',
  },
  '.cm-activeLine': {
    backgroundColor: 'var(--accent-bg, rgba(99, 102, 241, 0.04))',
  },
  '.cm-selectionBackground': {
    backgroundColor: 'var(--accent-bg, rgba(99, 102, 241, 0.15)) !important',
  },
  '.cm-focused': {
    outline: 'none',
  },
})
