/**
 * Lightweight markdown parser + syntax highlighter.
 * Zero dependencies — regex-based.
 */

// ── HTML escape ──

function esc(s: string): string {
  return s
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
}

// ── Syntax highlighting ──

const KEYWORDS_JS = new Set([
  'const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while',
  'do', 'switch', 'case', 'break', 'continue', 'new', 'delete', 'typeof',
  'instanceof', 'void', 'this', 'class', 'extends', 'super', 'import',
  'export', 'default', 'from', 'as', 'async', 'await', 'yield', 'try',
  'catch', 'finally', 'throw', 'of', 'in', 'with', 'debugger',
  // TS extras
  'type', 'interface', 'enum', 'namespace', 'declare', 'abstract',
  'implements', 'readonly', 'keyof', 'infer', 'extends', 'satisfies',
])

const KEYWORDS_PY = new Set([
  'def', 'class', 'return', 'if', 'elif', 'else', 'for', 'while', 'break',
  'continue', 'import', 'from', 'as', 'try', 'except', 'finally', 'raise',
  'with', 'yield', 'lambda', 'pass', 'del', 'and', 'or', 'not', 'is', 'in',
  'True', 'False', 'None', 'async', 'await', 'nonlocal', 'global', 'assert',
])

function highlightJSON(code: string): string {
  return esc(code).replace(
    /("(?:[^"\\]|\\.)*")\s*(:)|("(?:[^"\\]|\\.)*")|(\b(?:true|false|null)\b)|(\b-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)/g,
    (_, key, colon, str, bool, num) => {
      if (key) return `<span class="hl-key">${key}</span>${colon}`
      if (str) return `<span class="hl-str">${str}</span>`
      if (bool) return `<span class="hl-bool">${bool}</span>`
      if (num) return `<span class="hl-num">${num}</span>`
      return _
    }
  )
}

function highlightYAML(code: string): string {
  return esc(code).split('\n').map(line => {
    // Comments
    if (/^\s*#/.test(line)) return `<span class="hl-comment">${line}</span>`
    // Key: value
    return line.replace(
      /^(\s*)([\w.-]+)(:)/,
      (_, ws, k, c) => `${ws}<span class="hl-key">${k}</span>${c}`
    ).replace(
      /:\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
      (m, s) => m.replace(s, `<span class="hl-str">${s}</span>`)
    ).replace(
      /\b(true|false|null|~)\b/g,
      '<span class="hl-bool">$1</span>'
    ).replace(
      /\b(\d+(?:\.\d+)?)\b/g,
      '<span class="hl-num">$1</span>'
    )
  }).join('\n')
}

function highlightTOML(code: string): string {
  return esc(code).split('\n').map(line => {
    // Comments
    if (/^\s*#/.test(line)) return `<span class="hl-comment">${line}</span>`
    // Section headers [section]
    if (/^\s*\[/.test(line)) return `<span class="hl-section">${line}</span>`
    // Key = value
    return line.replace(
      /^(\s*)([\w.-]+)(\s*=)/,
      (_, ws, k, eq) => `${ws}<span class="hl-key">${k}</span>${eq}`
    ).replace(
      /=\s*("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
      (m, s) => m.replace(s, `<span class="hl-str">${s}</span>`)
    ).replace(
      /\b(true|false)\b/g,
      '<span class="hl-bool">$1</span>'
    ).replace(
      /\b(\d+(?:\.\d+)?)\b/g,
      '<span class="hl-num">$1</span>'
    )
  }).join('\n')
}

function highlightJS(code: string, keywords: Set<string>): string {
  const escaped = esc(code)
  const tokens: { start: number; end: number; html: string }[] = []

  // Collect token positions to avoid overlapping replacements
  // Strings (double & single & template)
  const strRe = /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g
  let m: RegExpExecArray | null
  while ((m = strRe.exec(escaped))) {
    tokens.push({ start: m.index, end: m.index + m[0].length, html: `<span class="hl-str">${m[0]}</span>` })
  }

  // Line comments
  const commentRe = /(\/\/.*)/gm
  while ((m = commentRe.exec(escaped))) {
    // Only if not inside a string token
    const s = m.index
    if (!tokens.some(t => s >= t.start && s < t.end)) {
      tokens.push({ start: s, end: s + m[0].length, html: `<span class="hl-comment">${m[0]}</span>` })
    }
  }

  // Numbers
  const numRe = /\b(\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/g
  while ((m = numRe.exec(escaped))) {
    const s = m.index
    if (!tokens.some(t => s >= t.start && s < t.end)) {
      tokens.push({ start: s, end: s + m[0].length, html: `<span class="hl-num">${m[0]}</span>` })
    }
  }

  // Keywords
  const kwRe = /\b([a-zA-Z_]\w*)\b/g
  while ((m = kwRe.exec(escaped))) {
    const s = m.index
    if (keywords.has(m[1]) && !tokens.some(t => s >= t.start && s < t.end)) {
      tokens.push({ start: s, end: s + m[0].length, html: `<span class="hl-kw">${m[0]}</span>` })
    }
  }

  // Sort by start position and build output
  tokens.sort((a, b) => a.start - b.start)
  let result = ''
  let cursor = 0
  for (const tok of tokens) {
    if (tok.start < cursor) continue // overlapping, skip
    result += escaped.slice(cursor, tok.start) + tok.html
    cursor = tok.end
  }
  result += escaped.slice(cursor)
  return result
}

function highlightBash(code: string): string {
  return esc(code).split('\n').map(line => {
    // Comments
    if (/^\s*#/.test(line)) return `<span class="hl-comment">${line}</span>`
    return line
      .replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g, '<span class="hl-str">$1</span>')
      .replace(/\b(sudo|apt|npm|bun|yarn|pnpm|git|cd|ls|rm|cp|mv|mkdir|cat|echo|export|source|chmod|curl|wget|docker|pip|python|node)\b/g, '<span class="hl-kw">$1</span>')
  }).join('\n')
}

function highlightCSS(code: string): string {
  return esc(code)
    .replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="hl-comment">$1</span>')
    .replace(/([\w-]+)\s*:/g, '<span class="hl-key">$1</span>:')
    .replace(/(#[0-9a-fA-F]{3,8})\b/g, '<span class="hl-num">$1</span>')
    .replace(/(\d+(?:\.\d+)?(?:px|rem|em|%|vh|vw|s|ms)?)\b/g, '<span class="hl-num">$1</span>')
}

function highlightHTML(code: string): string {
  return esc(code)
    .replace(/(&lt;\/?)([\w-]+)/g, '$1<span class="hl-kw">$2</span>')
    .replace(/\s([\w-]+)(=)/g, ' <span class="hl-key">$1</span>$2')
    .replace(/("(?:[^"\\]|\\.)*")/g, '<span class="hl-str">$1</span>')
    .replace(/(&lt;!--[\s\S]*?--&gt;)/g, '<span class="hl-comment">$1</span>')
}

function highlightGeneric(code: string): string {
  return esc(code)
    .replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g, '<span class="hl-str">$1</span>')
    .replace(/(#.*|\/\/.*)/gm, '<span class="hl-comment">$1</span>')
    .replace(/\b(\d+(?:\.\d+)?)\b/g, '<span class="hl-num">$1</span>')
}

type LangAlias = string

const LANG_MAP: Record<string, (code: string) => string> = {
  json: highlightJSON,
  jsonc: highlightJSON,
  yaml: highlightYAML,
  yml: highlightYAML,
  toml: highlightTOML,
  javascript: c => highlightJS(c, KEYWORDS_JS),
  js: c => highlightJS(c, KEYWORDS_JS),
  jsx: c => highlightJS(c, KEYWORDS_JS),
  typescript: c => highlightJS(c, KEYWORDS_JS),
  ts: c => highlightJS(c, KEYWORDS_JS),
  tsx: c => highlightJS(c, KEYWORDS_JS),
  vue: c => highlightHTML(c), // close enough
  python: c => highlightJS(c, KEYWORDS_PY),
  py: c => highlightJS(c, KEYWORDS_PY),
  bash: highlightBash,
  sh: highlightBash,
  shell: highlightBash,
  zsh: highlightBash,
  css: highlightCSS,
  scss: highlightCSS,
  html: highlightHTML,
  xml: highlightHTML,
  svg: highlightHTML,
}

export function highlightCode(code: string, lang?: string): string {
  ensureStyles()
  const fn = lang ? LANG_MAP[lang.toLowerCase()] : undefined
  return fn ? fn(code) : highlightGeneric(code)
}

/** Inject global highlight + markdown styles (idempotent). */
let _stylesInjected = false
export function ensureStyles(): void {
  if (_stylesInjected || typeof document === 'undefined') return
  _stylesInjected = true
  if (document.getElementById('md-highlight-styles')) return
  const style = document.createElement('style')
  style.id = 'md-highlight-styles'
  style.textContent = MARKDOWN_STYLES
  document.head.appendChild(style)
}

// ── Markdown parser ──

/**
 * Parse a markdown string into HTML.
 * Supports: headers, bold, italic, inline code, code blocks (with highlighting),
 * links, images, blockquotes, unordered lists, ordered lists, horizontal rules.
 */
export function parseMarkdown(md: string): string {
  // Extract fenced code blocks first (protect from inline parsing)
  const codeBlocks: string[] = []
  let text = md.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang: string, code: string) => {
    const rawCode = code.replace(/\n$/, '')
    const highlighted = highlightCode(rawCode, lang || undefined)
    const langLabel = lang ? `<span class="code-lang">${esc(lang)}</span>` : ''
    const encoded = rawCode.replace(/&/g, '&amp;').replace(/"/g, '&quot;')
    const copyBtn = `<button class="md-copy-btn" data-code="${encoded}" onclick="(function(b){var c=b.getAttribute('data-code').replace(/&amp;/g,'&').replace(/&quot;/g,'\"');navigator.clipboard.writeText(c).then(function(){b.textContent='Copied!';setTimeout(function(){b.textContent='Copy'},1500)})})(this)">Copy</button>`
    const idx = codeBlocks.length
    codeBlocks.push(
      `<div class="md-code-block">${langLabel}${copyBtn}<pre class="md-pre"><code>${highlighted}</code></pre></div>`
    )
    return `\x00CODE${idx}\x00`
  })

  // Split into lines for block-level parsing
  const lines = text.split('\n')
  const out: string[] = []
  let i = 0

  while (i < lines.length) {
    const line = lines[i]

    // Code block placeholder
    const codeMatch = line.match(/^\x00CODE(\d+)\x00$/)
    if (codeMatch) {
      out.push(codeBlocks[parseInt(codeMatch[1])])
      i++
      continue
    }

    // Horizontal rule
    if (/^(\s*[-*_]\s*){3,}$/.test(line)) {
      out.push('<hr class="md-hr"/>')
      i++
      continue
    }

    // Headers
    const hMatch = line.match(/^(#{1,6})\s+(.+)$/)
    if (hMatch) {
      const level = hMatch[1].length
      out.push(`<h${level} class="md-h${level}">${inlineParse(esc(hMatch[2]))}</h${level}>`)
      i++
      continue
    }

    // Blockquote
    if (/^>\s?/.test(line)) {
      const quoteLines: string[] = []
      while (i < lines.length && /^>\s?/.test(lines[i])) {
        quoteLines.push(lines[i].replace(/^>\s?/, ''))
        i++
      }
      out.push(`<blockquote class="md-blockquote">${parseMarkdown(quoteLines.join('\n'))}</blockquote>`)
      continue
    }

    // Unordered list
    if (/^\s*[-*+]\s+/.test(line)) {
      const items: string[] = []
      while (i < lines.length && /^\s*[-*+]\s+/.test(lines[i])) {
        items.push(lines[i].replace(/^\s*[-*+]\s+/, ''))
        i++
      }
      out.push(`<ul class="md-ul">${items.map(it => `<li>${inlineParse(esc(it))}</li>`).join('')}</ul>`)
      continue
    }

    // Ordered list
    if (/^\s*\d+[.)]\s+/.test(line)) {
      const items: string[] = []
      while (i < lines.length && /^\s*\d+[.)]\s+/.test(lines[i])) {
        items.push(lines[i].replace(/^\s*\d+[.)]\s+/, ''))
        i++
      }
      out.push(`<ol class="md-ol">${items.map(it => `<li>${inlineParse(esc(it))}</li>`).join('')}</ol>`)
      continue
    }

    // Empty line
    if (line.trim() === '') {
      i++
      continue
    }

    // Regular paragraph
    const paraLines: string[] = []
    while (i < lines.length && lines[i].trim() !== '' && !/^#{1,6}\s/.test(lines[i]) && !/^\x00CODE/.test(lines[i]) && !/^>\s?/.test(lines[i]) && !/^\s*[-*+]\s+/.test(lines[i]) && !/^\s*\d+[.)]\s+/.test(lines[i]) && !/^(\s*[-*_]\s*){3,}$/.test(lines[i])) {
      paraLines.push(lines[i])
      i++
    }
    if (paraLines.length) {
      out.push(`<p class="md-p">${inlineParse(esc(paraLines.join('\n')))}</p>`)
    }
  }

  return out.join('\n')
}

/** Parse inline markdown elements (bold, italic, code, links, images). */
function inlineParse(html: string): string {
  return html
    // Images ![alt](src)
    .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img class="md-img" alt="$1" src="$2"/>')
    // Links [text](url)
    .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a class="md-link" href="$2" target="_blank" rel="noopener">$1</a>')
    // Bold + italic ***text***
    .replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>')
    // Bold **text** or __text__
    .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
    .replace(/__(.+?)__/g, '<strong>$1</strong>')
    // Italic *text* or _text_
    .replace(/\*(.+?)\*/g, '<em>$1</em>')
    .replace(/_(.+?)_/g, '<em>$1</em>')
    // Strikethrough ~~text~~
    .replace(/~~(.+?)~~/g, '<del>$1</del>')
    // Inline code `code`
    .replace(/`([^`]+)`/g, '<code class="md-inline-code">$1</code>')
    // Line breaks
    .replace(/\n/g, '<br/>')
}

// ── CSS for the renderer (inject once) ──

export const MARKDOWN_STYLES = `
/* ── Markdown rendered content ── */
.md-content h1, .md-content h2, .md-content h3,
.md-content h4, .md-content h5, .md-content h6 {
  margin: 0.5em 0 0.25em;
  color: var(--text-primary);
  line-height: 1.3;
}
.md-content .md-h1 { font-size: 1.3em; border-bottom: 1px solid var(--border-color); padding-bottom: 0.2em; }
.md-content .md-h2 { font-size: 1.15em; }
.md-content .md-h3 { font-size: 1.05em; }
.md-content .md-h4, .md-content .md-h5, .md-content .md-h6 { font-size: 1em; }

.md-content .md-p {
  margin: 0.3em 0;
  line-height: 1.6;
}

.md-content strong { font-weight: 600; color: var(--text-primary); }
.md-content em { font-style: italic; }
.md-content del { text-decoration: line-through; opacity: 0.6; }

.md-content .md-link {
  color: #0ea5e9;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.md-content .md-link:hover { border-bottom-color: #0ea5e9; }

.md-content .md-inline-code {
  background: rgba(99, 102, 241, 0.1);
  color: #e879f9;
  padding: 0.1em 0.35em;
  border-radius: 4px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  font-size: 0.9em;
}

.md-content .md-code-block {
  position: relative;
  margin: 0.4em 0;
  border: 1px solid var(--border-color);
  border-radius: 6px;
  overflow: hidden;
  background: var(--bg-secondary, #1a1a2e);
}

.md-content .code-lang {
  position: absolute;
  top: 0;
  right: 0;
  font-size: 9px;
  padding: 0.15em 0.5em;
  color: var(--text-muted);
  background: rgba(255,255,255,0.04);
  border-bottom-left-radius: 4px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}

.md-content .md-copy-btn {
  position: absolute;
  top: 0;
  right: 0;
  font-size: 10px;
  padding: 0.2em 0.5em;
  color: var(--text-muted, #94a3b8);
  background: rgba(255,255,255,0.06);
  border: none;
  border-bottom-left-radius: 4px;
  cursor: pointer;
  font-family: inherit;
  opacity: 0;
  transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease;
  z-index: 1;
}

.md-content .md-code-block:hover .md-copy-btn { opacity: 1; }
.md-content .md-copy-btn:hover { color: var(--text-primary, #e2e8f0); background: rgba(255,255,255,0.1); }

/* Hide lang label when copy button is visible */
.md-content .md-code-block:hover .code-lang { opacity: 0; }

.md-content .md-pre {
  margin: 0;
  padding: 0.6em 0.75em;
  font-size: 12px;
  line-height: 1.2;
  overflow-x: auto;
  font-family: 'Consolas', 'Lucida Console', 'SF Mono', 'Fira Code', monospace;
  color: var(--text-secondary);
  letter-spacing: 0;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.md-content .md-pre code {
  white-space: pre;
  word-spacing: 0;
  tab-size: 4;
}

.md-content .md-blockquote {
  margin: 0.4em 0;
  padding: 0.3em 0.75em;
  border-left: 3px solid var(--accent, #6366f1);
  background: rgba(99, 102, 241, 0.04);
  border-radius: 0 4px 4px 0;
  color: var(--text-secondary);
}

.md-content .md-ul, .md-content .md-ol {
  margin: 0.3em 0;
  padding-left: 1.5em;
}

.md-content .md-ul li, .md-content .md-ol li {
  margin: 0.15em 0;
  line-height: 1.5;
}

.md-content .md-hr {
  border: none;
  border-top: 1px solid var(--border-color);
  margin: 0.6em 0;
}

/* ── Syntax highlighting tokens ── */
.hl-key { color: #c084fc; }
.hl-str { color: #4ade80; }
.hl-num { color: #fb923c; }
.hl-bool { color: #38bdf8; }
.hl-kw { color: #c084fc; font-weight: 500; }
.hl-comment { color: #64748b; font-style: italic; }
.hl-section { color: #f59e0b; font-weight: 600; }
`
