/**
 * Live Preview extension for CodeMirror.
 *
 * When enabled, hides markdown syntax markers and applies visual formatting:
 * - `# text` → heading (bold, larger)
 * - `**text**` → bold
 * - `*text*` → italic
 * - `// text` → comment (dimmed, italic)
 *
 * The active line always shows raw markdown for editing.
 * Toggle with the compartment to switch between raw and preview modes.
 */

import {
  EditorView,
  Decoration,
  ViewPlugin,
  type DecorationSet,
  type ViewUpdate,
  WidgetType,
} from '@codemirror/view'
import { Compartment, type Extension } from '@codemirror/state'

// Invisible widget to replace hidden syntax markers
class HiddenMarker extends WidgetType {
  toDOM() {
    const span = document.createElement('span')
    span.style.display = 'none'
    return span
  }
}

const hiddenWidget = Decoration.replace({ widget: new HiddenMarker() })

const headingMark = Decoration.mark({ class: 'cm-fmt-heading' })
const boldMark = Decoration.mark({ class: 'cm-fmt-bold' })
const italicMark = Decoration.mark({ class: 'cm-fmt-italic' })
const commentMark = Decoration.mark({ class: 'cm-fmt-comment' })

function buildDecorations(view: EditorView): DecorationSet {
  const decorations: { from: number; to: number; dec: Decoration }[] = []
  const doc = view.state.doc
  const activeLine = doc.lineAt(view.state.selection.main.head).number

  for (let i = 1; i <= doc.lines; i++) {
    const line = doc.line(i)
    const text = line.text
    const isActive = i === activeLine

    // Headings: # text
    const headingMatch = text.match(/^(#{1,6})\s/)
    if (headingMatch) {
      if (!isActive) {
        // Hide the # prefix
        decorations.push({
          from: line.from,
          to: line.from + headingMatch[0].length,
          dec: hiddenWidget,
        })
      }
      // Style the rest as heading
      decorations.push({
        from: line.from + headingMatch[0].length,
        to: line.to,
        dec: headingMark,
      })
      continue
    }

    // Comments: // text
    if (text.trimStart().startsWith('//')) {
      const offset = text.indexOf('//')
      if (!isActive) {
        // Hide the // prefix
        decorations.push({
          from: line.from + offset,
          to: line.from + offset + 2 + (text[offset + 2] === ' ' ? 1 : 0),
          dec: hiddenWidget,
        })
      }
      decorations.push({
        from: line.from,
        to: line.to,
        dec: commentMark,
      })
      continue
    }

    // Bold: **text** (only on non-active lines)
    if (!isActive) {
      const boldRegex = /\*\*(.+?)\*\*/g
      let match
      while ((match = boldRegex.exec(text)) !== null) {
        const start = line.from + match.index
        // Hide opening **
        decorations.push({ from: start, to: start + 2, dec: hiddenWidget })
        // Bold the content
        decorations.push({ from: start + 2, to: start + 2 + match[1].length, dec: boldMark })
        // Hide closing **
        decorations.push({ from: start + 2 + match[1].length, to: start + match[0].length, dec: hiddenWidget })
      }
    }

    // Italic: *text* (only on non-active lines, avoid **bold**)
    if (!isActive) {
      const italicRegex = /(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g
      let match
      while ((match = italicRegex.exec(text)) !== null) {
        const start = line.from + match.index
        // Hide opening *
        decorations.push({ from: start, to: start + 1, dec: hiddenWidget })
        // Italic the content
        decorations.push({ from: start + 1, to: start + 1 + match[1].length, dec: italicMark })
        // Hide closing *
        decorations.push({ from: start + 1 + match[1].length, to: start + match[0].length, dec: hiddenWidget })
      }
    }

    // Color labels: [color:text] — always show color, even on active line
    {
      const colorRegex = /\[(red|orange|yellow|green|blue|purple):(.+?)\]/g
      let match
      while ((match = colorRegex.exec(text)) !== null) {
        const start = line.from + match.index
        const color = match[1]
        const content = match[2]
        if (!isActive) {
          // Non-active lines: hide syntax, show only colored content
          decorations.push({ from: start, to: start + color.length + 2, dec: hiddenWidget })
          decorations.push({
            from: start + color.length + 2,
            to: start + color.length + 2 + content.length,
            dec: Decoration.mark({ class: `cm-fmt-color-${color}` }),
          })
          decorations.push({
            from: start + match[0].length - 1,
            to: start + match[0].length,
            dec: hiddenWidget,
          })
        } else {
          // Active line: keep syntax visible but color the entire expression
          decorations.push({
            from: start,
            to: start + match[0].length,
            dec: Decoration.mark({ class: `cm-fmt-color-${color}` }),
          })
        }
      }
    }
  }

  // Sort by position (required by RangeSet)
  decorations.sort((a, b) => a.from - b.from || a.to - b.to)

  return Decoration.set(decorations.map(d => d.dec.range(d.from, d.to)))
}

const formatPreviewPlugin = ViewPlugin.fromClass(
  class {
    decorations: DecorationSet
    constructor(view: EditorView) {
      this.decorations = buildDecorations(view)
    }
    update(update: ViewUpdate) {
      if (update.docChanged || update.selectionSet || update.viewportChanged) {
        this.decorations = buildDecorations(update.view)
      }
    }
  },
  { decorations: (v) => v.decorations },
)

const formatPreviewTheme = EditorView.baseTheme({
  '.cm-fmt-heading': {
    fontWeight: '700',
    fontSize: '1.15em',
    color: 'var(--text-h)',
  },
  '.cm-fmt-bold': {
    fontWeight: '700',
  },
  '.cm-fmt-italic': {
    fontStyle: 'italic',
  },
  '.cm-fmt-comment': {
    fontStyle: 'italic',
    opacity: '0.5',
  },
  '.cm-fmt-color-red': { color: '#ef4444' },
  '.cm-fmt-color-orange': { color: '#f97316' },
  '.cm-fmt-color-yellow': { color: '#eab308' },
  '.cm-fmt-color-green': { color: '#22c55e' },
  '.cm-fmt-color-blue': { color: '#3b82f6' },
  '.cm-fmt-color-purple': { color: '#a855f7' },
})

// Empty extension for "raw" mode
const noopExtension: Extension = []

export const formatPreviewCompartment = new Compartment()

/** The extensions to use when preview is enabled */
export const formatPreviewEnabled = [formatPreviewPlugin, formatPreviewTheme]

export function formatPreviewExtension(enabled: boolean): Extension {
  return formatPreviewCompartment.of(
    enabled ? formatPreviewEnabled : noopExtension,
  )
}
