/**
 * CodeBlock - Static code display with syntax highlighting and copy functionality
 * Uses CodeMirror 6 for rendering with syntax highlighting in read-only mode.
 * @countermeasure/web-components/display/code-block
 */

import { EditorView, lineNumbers as cmLineNumbers } from '@codemirror/view'
import { EditorState, type Extension } from '@codemirror/state'
import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language'
import { editorTheme, tfHighlightStyle } from '../../editors/base'
import { json } from '../../editors/languages/json'
import { yaml } from '../../editors/languages/yaml'
import { sql } from '../../editors/languages/sql'
import { fluxl } from '../../editors/languages/fluxl'
import { yaral } from '../../editors/languages/yaral'
import { yara } from '../../editors/languages/yara'
import { resolveContainer, uniqueId } from '../../primitives/utils'
import { type CodeBlockConfig } from './types'

export type { CodeBlockConfig } from './types'

/** SVG icon for the copy button */
const COPY_ICON =
  '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>'

/** Resolve a language string to a CodeMirror 6 language extension */
function resolveLanguage(language: string | undefined): Extension {
  switch (language) {
    case 'json':
      return json()
    case 'yaml':
    case 'yml':
      return yaml()
    case 'sql':
      return sql()
    case 'fluxl':
      return fluxl()
    case 'yaral':
      return yaral()
    case 'yara':
      return yara()
    default:
      return []
  }
}

/** CodeMirror theme overrides for read-only code blocks */
const codeBlockTheme = EditorView.theme({
  '&': {
    fontSize: '13px',
  },
  '&.cm-focused': {
    outline: 'none',
  },
  '.cm-content': {
    padding: '12px 0',
    cursor: 'text',
  },
  '.cm-gutters': {
    cursor: 'default',
  },
  '.cm-activeLine': {
    backgroundColor: 'transparent',
  },
  '.cm-activeLineGutter': {
    backgroundColor: 'transparent',
  },
})

export class CodeBlock {
  public readonly el: HTMLDivElement

  private readonly codeBlockId: string
  private readonly showLineNumbers: boolean
  private readonly showCopyButton: boolean
  private readonly maxHeight: string | undefined
  private readonly language: string | undefined
  private currentCode: string

  private editorView: EditorView | null = null
  private editorContainer: HTMLDivElement | null = null
  private copyBtnEl: HTMLButtonElement | null = null
  private copyTimeoutId: ReturnType<typeof setTimeout> | null = null

  constructor(config: CodeBlockConfig) {
    this.codeBlockId = uniqueId('cmm-code-block')
    this.currentCode = config.code
    this.language = config.language
    this.showLineNumbers = config.showLineNumbers === true
    this.showCopyButton = config.showCopyButton !== false
    this.maxHeight = config.maxHeight

    // Create root element
    this.el = document.createElement('div')
    this.el.id = this.codeBlockId
    this.el.className = 'cmm-code-block'

    if (config.className) {
      this.el.classList.add(config.className)
    }

    this.buildContent()

    // Mount if container provided
    if (config.container !== undefined) {
      const parent = resolveContainer(config.container)
      parent.appendChild(this.el)
    }
  }

  /** Update the displayed code */
  setCode(code: string): void {
    this.currentCode = code
    if (this.editorView) {
      this.editorView.dispatch({
        changes: {
          from: 0,
          to: this.editorView.state.doc.length,
          insert: code,
        },
      })
    }
  }

  /** Remove the code block from the DOM and clean up */
  destroy(): void {
    if (this.copyTimeoutId !== null) {
      clearTimeout(this.copyTimeoutId)
      this.copyTimeoutId = null
    }
    if (this.editorView) {
      this.editorView.destroy()
      this.editorView = null
    }
    this.el.remove()
  }

  private buildContent(): void {
    // Header with language label and copy button
    if (this.language !== undefined || this.showCopyButton) {
      const header = document.createElement('div')
      header.className = 'cmm-code-block__header'

      const langLabel = document.createElement('span')
      langLabel.className = 'cmm-code-block__language'
      langLabel.textContent = this.language ?? ''
      header.appendChild(langLabel)

      if (this.showCopyButton) {
        const copyBtn = document.createElement('button')
        copyBtn.className = 'cmm-code-block__copy'
        copyBtn.setAttribute('aria-label', 'Copy code')
        copyBtn.type = 'button'
        copyBtn.innerHTML = `${COPY_ICON} Copy`

        copyBtn.addEventListener('click', () => {
          this.handleCopy()
        })

        header.appendChild(copyBtn)
        this.copyBtnEl = copyBtn
      }

      this.el.appendChild(header)
    }

    // CodeMirror editor container
    this.editorContainer = document.createElement('div')
    this.editorContainer.className = 'cmm-code-block__editor'

    if (this.maxHeight !== undefined) {
      this.editorContainer.style.maxHeight = this.maxHeight
      this.editorContainer.style.overflow = 'auto'
    }

    // Build CodeMirror extensions
    const extensions: Extension[] = [
      EditorState.readOnly.of(true),
      EditorView.editable.of(false),
      syntaxHighlighting(tfHighlightStyle),
      syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
      editorTheme,
      codeBlockTheme,
      resolveLanguage(this.language),
    ]

    if (this.showLineNumbers) {
      extensions.push(cmLineNumbers())
    }

    const state = EditorState.create({
      doc: this.currentCode,
      extensions,
    })

    this.editorView = new EditorView({
      state,
      parent: this.editorContainer,
    })

    this.el.appendChild(this.editorContainer)
  }

  private handleCopy(): void {
    void navigator.clipboard.writeText(this.currentCode).then(() => {
      if (this.copyBtnEl) {
        this.copyBtnEl.innerHTML = `${COPY_ICON} Copied!`
        this.copyBtnEl.classList.add('cmm-code-block__copy--copied')

        if (this.copyTimeoutId !== null) {
          clearTimeout(this.copyTimeoutId)
        }

        this.copyTimeoutId = setTimeout(() => {
          if (this.copyBtnEl) {
            this.copyBtnEl.innerHTML = `${COPY_ICON} Copy`
            this.copyBtnEl.classList.remove('cmm-code-block__copy--copied')
          }
          this.copyTimeoutId = null
        }, 2000)
      }
    })
  }
}
