/**
 * BaseEditor - Core editor class wrapping CodeMirror 6
 * @countermeasure/web-components/editors
 */

import {
  EditorView,
  drawSelection,
  dropCursor,
  highlightActiveLine,
  highlightActiveLineGutter,
  highlightSpecialChars,
  keymap,
  lineNumbers,
  placeholder as cmPlaceholder,
  type ViewUpdate,
} from '@codemirror/view'
import { Compartment, EditorState, type Extension } from '@codemirror/state'
import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands'
import {
  HighlightStyle,
  bracketMatching,
  indentOnInput,
  syntaxHighlighting,
  defaultHighlightStyle,
} from '@codemirror/language'
import { closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete'
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
import { tags as t } from '@lezer/highlight'
import { type BaseEditorConfig } from './types'

/** Resolve a container reference to an HTMLElement */
export function resolveContainer(container: HTMLElement | string): HTMLElement {
  if (typeof container === 'string') {
    const el = document.querySelector<HTMLElement>(container)
    if (!el) {
      throw new Error(`Editor container not found: ${container}`)
    }
    return el
  }
  return container
}

/** Custom syntax highlighting using CSS variables for theme adaptability */
const tfHighlightStyle = HighlightStyle.define([
  { tag: t.keyword, color: 'var(--cmm-editor-keyword, #7c3aed)' },
  { tag: t.operator, color: 'var(--cmm-editor-operator, #0891b2)' },
  {
    tag: [t.string, t.special(t.string)],
    color: 'var(--cmm-editor-string, #059669)',
  },
  { tag: t.number, color: 'var(--cmm-editor-number, #d97706)' },
  {
    tag: t.comment,
    color: 'var(--cmm-editor-comment, #6b7280)',
    fontStyle: 'italic',
  },
  { tag: t.variableName, color: 'var(--cmm-editor-variable, #1e40af)' },
  {
    tag: [t.function(t.variableName)],
    color: 'var(--cmm-editor-function, #2563eb)',
  },
  { tag: t.typeName, color: 'var(--cmm-editor-type, #0891b2)' },
  { tag: t.propertyName, color: 'var(--cmm-editor-property, #7c3aed)' },
  { tag: t.bool, color: 'var(--cmm-editor-atom, #d97706)' },
  { tag: t.null, color: 'var(--cmm-editor-atom, #d97706)' },
  { tag: t.atom, color: 'var(--cmm-editor-atom, #d97706)' },
  { tag: t.meta, color: 'var(--cmm-editor-meta, #6b7280)' },
  { tag: t.labelName, color: 'var(--cmm-editor-label, #2563eb)' },
  { tag: t.namespace, color: 'var(--cmm-editor-namespace, #0891b2)' },
  { tag: t.regexp, color: 'var(--cmm-editor-regexp, #dc2626)' },
  { tag: t.bracket, color: 'var(--cmm-editor-bracket, #6b7280)' },
  { tag: t.punctuation, color: 'var(--cmm-editor-punctuation, #6b7280)' },
  { tag: t.tagName, color: 'var(--cmm-editor-tag, #dc2626)' },
  { tag: t.attributeName, color: 'var(--cmm-editor-attribute, #d97706)' },
])

/** Editor chrome theme using CSS variables */
const editorTheme = EditorView.theme({
  '&': {
    fontSize: '14px',
    backgroundColor: 'var(--cmm-bg-primary, #ffffff)',
    color: 'var(--cmm-text-primary, #0f172a)',
    borderRadius: 'var(--radius-sm, 0.25rem)',
  },
  '&.cm-focused': {
    outline: '2px solid var(--cmm-accent, #2563eb)',
    outlineOffset: '-2px',
  },
  '.cm-content': {
    padding: '8px 0',
    caretColor: 'var(--cmm-text-primary, #0f172a)',
    fontFamily: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace",
  },
  '.cm-cursor, .cm-dropCursor': {
    borderLeftColor: 'var(--cmm-text-primary, #0f172a)',
    borderLeftWidth: '2px',
  },
  '.cm-gutters': {
    backgroundColor: 'var(--cmm-bg-secondary, #f8fafc)',
    color: 'var(--cmm-text-muted, #64748b)',
    borderRight: '1px solid var(--cmm-border, #e2e8f0)',
    fontFamily: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace",
    fontSize: '13px',
  },
  '.cm-activeLineGutter': {
    backgroundColor: 'transparent',
    color: 'var(--cmm-text-secondary, #334155)',
  },
  '&.cm-focused .cm-selectionBackground, .cm-selectionBackground': {
    backgroundColor: 'var(--cmm-editor-selection, rgba(59, 130, 246, 0.2))',
  },
  '.cm-activeLine': {
    backgroundColor: 'var(--cmm-editor-active-line, rgba(0, 0, 0, 0.04))',
  },
  '.cm-matchingBracket': {
    backgroundColor: 'var(--cmm-editor-bracket-match, rgba(59, 130, 246, 0.3))',
    outline: '1px solid var(--cmm-accent, #2563eb)',
  },
  '.cm-selectionMatch': {
    backgroundColor: 'var(--cmm-editor-selection-match, rgba(34, 197, 94, 0.15))',
  },
  '.cm-foldPlaceholder': {
    backgroundColor: 'var(--cmm-bg-tertiary, #f1f5f9)',
    border: '1px solid var(--cmm-border, #e2e8f0)',
    color: 'var(--cmm-text-muted, #64748b)',
  },
  '.cm-tooltip': {
    backgroundColor: 'var(--cmm-bg-secondary, #f8fafc)',
    border: '1px solid var(--cmm-border, #e2e8f0)',
    color: 'var(--cmm-text-primary, #0f172a)',
    borderRadius: '6px',
    boxShadow: 'var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1))',
  },
  '.cm-tooltip-autocomplete > ul > li[aria-selected]': {
    backgroundColor: 'var(--cmm-accent, #2563eb)',
    color: 'white',
  },
  '.cm-panels': {
    backgroundColor: 'var(--cmm-bg-secondary, #f8fafc)',
    color: 'var(--cmm-text-primary, #0f172a)',
  },
  '.cm-panels.cm-panels-top': {
    borderBottom: '1px solid var(--cmm-border, #e2e8f0)',
  },
  '.cm-panels.cm-panels-bottom': {
    borderTop: '1px solid var(--cmm-border, #e2e8f0)',
  },
  '.cm-textfield': {
    backgroundColor: 'var(--cmm-bg-primary, #ffffff)',
    border: '1px solid var(--cmm-border, #e2e8f0)',
    borderRadius: '4px',
    padding: '2px 6px',
    fontSize: '13px',
    color: 'var(--cmm-text-primary, #0f172a)',
  },
  '.cm-button': {
    backgroundColor: 'var(--cmm-bg-tertiary, #f1f5f9)',
    border: '1px solid var(--cmm-border, #e2e8f0)',
    borderRadius: '4px',
    padding: '2px 8px',
    fontSize: '13px',
    cursor: 'pointer',
    color: 'var(--cmm-text-primary, #0f172a)',
  },
})

export class BaseEditor {
  protected view: EditorView
  protected container: HTMLElement
  protected readOnlyCompartment = new Compartment()
  protected lineNumbersCompartment = new Compartment()
  protected wordWrapCompartment = new Compartment()
  private onChangeCallback: ((value: string) => void) | null

  constructor(config: BaseEditorConfig, extraExtensions: Extension[] = []) {
    this.container = resolveContainer(config.container)
    this.onChangeCallback = config.onChange ?? null

    const showLineNumbers = config.lineNumbers !== false
    const wordWrap = config.wordWrap === true
    const readOnly = config.readOnly === true
    const tabSize = config.tabSize ?? 2

    const extensions: Extension[] = [
      // Core editing
      highlightSpecialChars(),
      history(),
      drawSelection(),
      dropCursor(),
      EditorState.allowMultipleSelections.of(true),
      indentOnInput(),
      bracketMatching(),
      closeBrackets(),
      highlightActiveLine(),
      highlightSelectionMatches(),

      // Syntax highlighting (custom with CSS vars, default as fallback)
      syntaxHighlighting(tfHighlightStyle),
      syntaxHighlighting(defaultHighlightStyle, { fallback: true }),

      // Editor chrome theme
      editorTheme,

      // Keymaps
      keymap.of([
        ...closeBracketsKeymap,
        ...defaultKeymap,
        ...searchKeymap,
        ...historyKeymap,
        indentWithTab,
      ]),

      // Tab size
      EditorState.tabSize.of(tabSize),

      // Controlled by compartments
      this.lineNumbersCompartment.of(
        showLineNumbers ? [lineNumbers(), highlightActiveLineGutter()] : []
      ),
      this.wordWrapCompartment.of(wordWrap ? EditorView.lineWrapping : []),
      this.readOnlyCompartment.of(readOnly ? EditorState.readOnly.of(true) : []),

      // Placeholder
      ...(config.placeholder ? [cmPlaceholder(config.placeholder)] : []),

      // Change listener
      EditorView.updateListener.of((update: ViewUpdate) => {
        if (update.docChanged && this.onChangeCallback) {
          this.onChangeCallback(update.state.doc.toString())
        }
      }),

      // Extra extensions from subclasses
      ...extraExtensions,
    ]

    const state = EditorState.create({
      doc: config.value ?? '',
      extensions,
    })

    this.view = new EditorView({
      state,
      parent: this.container,
    })
  }

  /** Get the full editor content */
  getValue(): string {
    return this.view.state.doc.toString()
  }

  /** Replace the entire editor content */
  setValue(value: string): void {
    this.view.dispatch({
      changes: {
        from: 0,
        to: this.view.state.doc.length,
        insert: value,
      },
    })
  }

  /** Toggle read-only mode */
  setReadOnly(readOnly: boolean): void {
    this.view.dispatch({
      effects: this.readOnlyCompartment.reconfigure(readOnly ? EditorState.readOnly.of(true) : []),
    })
  }

  /** Toggle line numbers */
  setLineNumbers(show: boolean): void {
    this.view.dispatch({
      effects: this.lineNumbersCompartment.reconfigure(
        show ? [lineNumbers(), highlightActiveLineGutter()] : []
      ),
    })
  }

  /** Toggle word wrap */
  setWordWrap(wrap: boolean): void {
    this.view.dispatch({
      effects: this.wordWrapCompartment.reconfigure(wrap ? EditorView.lineWrapping : []),
    })
  }

  /** Focus the editor */
  focus(): void {
    this.view.focus()
  }

  /** Blur the editor */
  blur(): void {
    this.view.contentDOM.blur()
  }

  /** Get the total number of lines */
  getLineCount(): number {
    return this.view.state.doc.lines
  }

  /** Scroll to a specific line number (1-indexed) */
  scrollToLine(line: number): void {
    const safeLine = Math.min(Math.max(1, line), this.view.state.doc.lines)
    const lineInfo = this.view.state.doc.line(safeLine)
    this.view.dispatch({
      effects: EditorView.scrollIntoView(lineInfo.from, { y: 'center' }),
    })
  }

  /** Get the underlying CodeMirror EditorView */
  getView(): EditorView {
    return this.view
  }

  /** Set the onChange callback */
  setOnChange(callback: ((value: string) => void) | null): void {
    this.onChangeCallback = callback
  }

  /** Destroy the editor and remove it from the DOM */
  destroy(): void {
    this.view.destroy()
  }
}

export { editorTheme, tfHighlightStyle }
