/**
 * YamlOverrideEditor — React wrapper around the vanilla `YamlEditor`.
 * @countermeasure-platform/web-components/primitives/yaml-override-editor
 *
 * Designed for the training-data UI's "override YAML" surface where
 * specific tokens (e.g. `__DATASET__`, `__VERSION__`) are placeholders
 * the user is expected to fill in. The wrapper:
 *
 * 1. Mounts a CodeMirror YAML editor in a hosted div.
 * 2. Forwards `value` / `onChange` declaratively (controlled).
 * 3. Renders a chip legend listing each declared placeholder with its
 *    label + optional description below the editor.
 * 4. Decorates matching tokens in the document via the CodeMirror
 *    decorations API so the placeholders render with a non-editable
 *    visual highlight (kept client-side to avoid invasive changes to
 *    the vanilla `YamlEditor` config).
 */

import * as React from 'react'
import { ViewPlugin, Decoration, type DecorationSet, type ViewUpdate } from '@codemirror/view'
import { EditorView } from '@codemirror/view'
import { RangeSetBuilder, StateEffect } from '@codemirror/state'
import { YamlEditor } from '../editors/yaml-editor'
import { cn } from '../react/primitives/utils'

export interface YamlOverridePlaceholder {
  label: string
  description?: string
}

export interface YamlOverrideEditorProps {
  value: string
  onChange: (next: string) => void
  /** Map of placeholder token (e.g. `__DATASET__`) → metadata. */
  placeholders?: Record<string, YamlOverridePlaceholder>
  readOnly?: boolean
  /** Editor pixel height or any CSS length. Defaults to `260`. */
  height?: number | string
  className?: string
}

const placeholderTheme = EditorView.baseTheme({
  '.cmm-yaml-placeholder': {
    backgroundColor: 'hsl(var(--info, 189 94% 33%) / 0.15)',
    color: 'hsl(var(--info, 189 94% 33%))',
    borderRadius: '3px',
    padding: '0 2px',
    fontWeight: '600',
  },
})

function buildPlaceholderPlugin(getKeys: () => string[]) {
  return ViewPlugin.fromClass(
    class {
      decorations: DecorationSet

      constructor(view: EditorView) {
        this.decorations = this.compute(view)
      }

      update(update: ViewUpdate) {
        if (update.docChanged || update.viewportChanged) {
          this.decorations = this.compute(update.view)
        }
      }

      private compute(view: EditorView): DecorationSet {
        const keys = getKeys()
        if (keys.length === 0) return Decoration.none
        // Escape regex metacharacters in keys.
        const escaped = keys.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
        const regex = new RegExp(`(${escaped.join('|')})`, 'g')
        const builder = new RangeSetBuilder<Decoration>()

        for (const { from, to } of view.visibleRanges) {
          const text = view.state.doc.sliceString(from, to)
          let match: RegExpExecArray | null
          regex.lastIndex = 0
          while ((match = regex.exec(text)) !== null) {
            const start = from + match.index
            const end = start + match[0].length
            builder.add(start, end, Decoration.mark({ class: 'cmm-yaml-placeholder' }))
          }
        }

        return builder.finish()
      }
    },
    {
      decorations: v => v.decorations,
    }
  )
}

export function YamlOverrideEditor({
  value,
  onChange,
  placeholders,
  readOnly,
  height = 260,
  className,
}: YamlOverrideEditorProps) {
  const containerRef = React.useRef<HTMLDivElement | null>(null)
  const editorRef = React.useRef<YamlEditor | null>(null)
  const placeholdersRef = React.useRef<Record<string, YamlOverridePlaceholder>>(placeholders ?? {})
  const onChangeRef = React.useRef(onChange)

  React.useEffect(() => {
    placeholdersRef.current = placeholders ?? {}
  }, [placeholders])

  React.useEffect(() => {
    onChangeRef.current = onChange
  }, [onChange])

  // Mount once.
  React.useEffect(() => {
    const container = containerRef.current
    if (!container) return

    const placeholderPlugin = buildPlaceholderPlugin(() => Object.keys(placeholdersRef.current))

    const editor = new YamlEditor({
      container,
      value,
      readOnly: readOnly === true,
      onChange: (next: string) => {
        onChangeRef.current(next)
      },
    })

    // Inject the placeholder decorator plugin + theme into the live view
    // via `StateEffect.appendConfig` so we don't have to fork the vanilla
    // YamlEditor's config interface.
    const view = editor.getView()
    view.dispatch({
      effects: StateEffect.appendConfig.of([placeholderPlugin, placeholderTheme]),
    })

    editorRef.current = editor

    return () => {
      editor.destroy()
      editorRef.current = null
    }
    // Mount-only — controlled props (`value`, `readOnly`, `placeholders`)
    // are pushed via the dedicated effects below.
  }, [])

  // Sync controlled `value` into the editor when it diverges.
  React.useEffect(() => {
    const editor = editorRef.current
    if (!editor) return
    if (editor.getValue() !== value) {
      editor.setValue(value)
    }
  }, [value])

  // Toggle readOnly when the prop changes.
  React.useEffect(() => {
    editorRef.current?.setReadOnly(readOnly === true)
  }, [readOnly])

  const heightStyle = typeof height === 'number' ? `${String(height)}px` : height
  const placeholderEntries = Object.entries(placeholders ?? {})

  return (
    <div data-slot="yaml-override-editor" className={cn('flex flex-col gap-2', className)}>
      <div
        ref={containerRef}
        data-slot="yaml-override-editor-host"
        className="overflow-hidden rounded-md border border-border bg-muted/40"
        style={{ height: heightStyle }}
      />
      {placeholderEntries.length > 0 && (
        <div
          data-slot="yaml-override-editor-legend"
          className="flex flex-wrap gap-1.5"
          aria-label="Placeholders"
        >
          {placeholderEntries.map(([key, meta]) => (
            <span
              key={key}
              title={meta.description}
              className="inline-flex items-center gap-1 rounded-md border border-info/30 bg-info/10 px-2 py-0.5 text-[11px] font-medium text-info"
            >
              <code className="font-mono text-[11px]">{key}</code>
              <span className="text-foreground/80">{meta.label}</span>
            </span>
          ))}
        </div>
      )}
    </div>
  )
}
