/**
 * JsonViewer - JSON viewer/editor with formatting and validation
 * @countermeasure/web-components/editors
 */

import { type Extension } from '@codemirror/state'
import { foldGutter, foldKeymap } from '@codemirror/language'
import { linter, lintGutter, type Diagnostic } from '@codemirror/lint'
import { type EditorView, keymap } from '@codemirror/view'
import { BaseEditor } from './base'
import { json } from './languages/json'
import { type JsonViewerConfig } from './types'

/** Lint JSON content and return parse error diagnostics */
function jsonLinter(view: EditorView): Diagnostic[] {
  const content = view.state.doc.toString()
  if (!content.trim()) {
    return []
  }

  try {
    JSON.parse(content)
    return []
  } catch (err) {
    const message = err instanceof SyntaxError ? err.message : 'Invalid JSON'

    // Try to extract position from the error message
    const posMatch = /position (\d+)/.exec(message)
    const pos = posMatch?.[1] ? parseInt(posMatch[1], 10) : 0
    const safePos = Math.min(pos, view.state.doc.length)

    return [
      {
        from: safePos,
        to: Math.min(safePos + 1, view.state.doc.length),
        severity: 'error',
        message,
      },
    ]
  }
}

export class JsonViewer extends BaseEditor {
  declare private editable: boolean

  constructor(config: JsonViewerConfig) {
    const isEditable = config.editable === true
    const formatOnLoad = config.formatOnLoad !== false

    const extraExtensions: Extension[] = [
      // JSON language
      json(),

      // Code folding for JSON structure
      foldGutter(),
      keymap.of(foldKeymap),

      // JSON validation
      linter(jsonLinter, { delay: 500 }),
      lintGutter(),
    ]

    // Format the initial value if requested
    let initialValue = config.value ?? ''
    if (formatOnLoad && initialValue.trim()) {
      try {
        const parsed: unknown = JSON.parse(initialValue)
        initialValue = JSON.stringify(parsed, null, 2)
      } catch {
        // Keep original value if not valid JSON
      }
    }

    super(
      {
        ...config,
        value: initialValue,
        readOnly: !isEditable,
      },
      extraExtensions
    )

    this.editable = isEditable
  }

  /** Pretty-print the JSON content */
  format(): void {
    const content = this.getValue()
    if (!content.trim()) {
      return
    }

    try {
      const parsed: unknown = JSON.parse(content)
      this.setValue(JSON.stringify(parsed, null, 2))
    } catch {
      // Can't format invalid JSON
    }
  }

  /** Minify the JSON content */
  minify(): void {
    const content = this.getValue()
    if (!content.trim()) {
      return
    }

    try {
      const parsed: unknown = JSON.parse(content)
      this.setValue(JSON.stringify(parsed))
    } catch {
      // Can't minify invalid JSON
    }
  }

  /** Check if the current content is valid JSON */
  validate(): boolean {
    try {
      JSON.parse(this.getValue())
      return true
    } catch {
      return false
    }
  }

  /** Get the parsed JSON value, or null if invalid */
  getParsed(): unknown {
    try {
      return JSON.parse(this.getValue()) as unknown
    } catch {
      return null
    }
  }

  /** Set the JSON content from a value (auto-formats) */
  setJson(value: unknown): void {
    this.setValue(JSON.stringify(value, null, 2))
  }

  /** Toggle between editable and read-only */
  setEditable(editable: boolean): void {
    this.editable = editable
    this.setReadOnly(!editable)
  }
}
