/**
 * Shared GFM Markdown Parser
 * GFM-compatible markdown-to-HTML conversion with optional Mermaid diagram support.
 * Handles: headings, links, images, blockquotes, lists, tables,
 * horizontal rules, strikethrough, task lists, code blocks with
 * language labels and copy buttons.
 */

// ============ Types ============

interface CodeBlock {
  language: string
  code: string
  id: string
}

export interface MarkdownParserOptions {
  supportMermaid?: boolean
}

// ============ State ============

let codeBlockCounter = 0

export function resetCodeBlockCounter(): void {
  codeBlockCounter = 0
}

// ============ Main Entry ============

/**
 * Convert markdown text to HTML.
 * The input is assumed to already be HTML-escaped for XSS safety.
 * Code blocks, headings, links, etc. are converted to appropriate HTML.
 */
export function markdownToHtml(escapedText: string, options?: MarkdownParserOptions): string {
  const supportMermaid = options?.supportMermaid ?? false

  // Extract code blocks first to protect them from other transformations
  const codeBlocks: CodeBlock[] = []
  let html = extractCodeBlocks(escapedText, codeBlocks, supportMermaid)

  // Block-level transformations (order matters)
  html = transformHeadings(html)
  html = transformHorizontalRules(html)
  html = transformBlockquotes(html)
  html = transformTables(html)
  html = transformLists(html)

  // Inline transformations
  html = transformStrikethrough(html)
  html = transformBold(html)
  html = transformItalic(html)
  html = transformInlineCode(html)
  html = transformLinks(html)
  html = transformImages(html)
  html = transformTaskLists(html)

  // Line breaks (but not inside block elements)
  html = html.replace(/\n/g, '<br>')

  // Restore code blocks
  html = restoreCodeBlocks(html, codeBlocks)

  return html
}

// ============ Code Block Extraction ============

const CODE_BLOCK_PLACEHOLDER_PREFIX = '\x00CB'
const MERMAID_PLACEHOLDER_PREFIX = '\x00MM'

function extractCodeBlocks(text: string, blocks: CodeBlock[], supportMermaid: boolean): string {
  return text.replace(
    /```(\w*)\n([\s\S]*?)```/g,
    (_match: string, lang: string, code: string): string => {
      // Handle mermaid code blocks
      if (supportMermaid && lang === 'mermaid') {
        const decodedCode = decodeHtmlEntities(code)
        const encodedContent = base64Encode(decodedCode)
        codeBlockCounter += 1
        const placeholderId = `cmm-mermaid-ph-${String(codeBlockCounter)}`
        return `${MERMAID_PLACEHOLDER_PREFIX}${placeholderId}\x01${encodedContent}\x00`
      }

      codeBlockCounter += 1
      const id = `chat-code-${String(codeBlockCounter)}`

      // Decode HTML entities back for code content
      const decodedCode = decodeHtmlEntities(code)

      blocks.push({ language: lang, code: decodedCode, id })
      return `${CODE_BLOCK_PLACEHOLDER_PREFIX}${String(blocks.length - 1)}\x00`
    }
  )
}

function restoreCodeBlocks(html: string, blocks: CodeBlock[]): string {
  // Restore mermaid placeholders
  html = html.replace(
    new RegExp(`${MERMAID_PLACEHOLDER_PREFIX}([^\\x01]+)\\x01([^\\x00]+)\\x00`, 'g'),
    (_match: string, placeholderId: string, encodedContent: string): string =>
      `<div id="${placeholderId}" class="cmm-mermaid-placeholder" data-mermaid-content="${encodedContent}"></div>`
  )

  // Restore regular code blocks
  html = html.replace(
    new RegExp(`${CODE_BLOCK_PLACEHOLDER_PREFIX}(\\d+)\x00`, 'g'),
    (_match: string, indexStr: string): string => {
      const index = Number.parseInt(indexStr, 10)
      const block = blocks[index]
      if (!block) return ''
      return renderCodeBlock(block)
    }
  )

  return html
}

// ============ Code Block Rendering ============

function renderCodeBlock(block: CodeBlock): string {
  const langLabel =
    block.language.length > 0
      ? `<span class="chat-code__lang">${escapeAttr(block.language)}</span>`
      : ''

  // Re-escape for display inside pre/code
  const displayCode = escapeForCodeBlock(block.code)

  return `<div class="chat-code-block" id="${block.id}" data-lang="${escapeAttr(block.language)}">
<div class="chat-code__header">
${langLabel}
<button type="button" class="chat-code__copy" data-copy-code="${block.id}" title="Copy code">
<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 width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
<span class="chat-code__copy-label">Copy</span>
</button>
</div>
<pre><code class="language-${escapeAttr(block.language)}">${displayCode}</code></pre>
</div>`
}

// ============ Block Transformations ============

function transformHeadings(text: string): string {
  // Match lines starting with # (1-6) followed by space and text
  return text.replace(
    /^(#{1,6})\s+(.+)$/gm,
    (_match: string, hashes: string, content: string): string => {
      const level = hashes.length
      return `<h${String(level)} class="chat-md-heading chat-md-h${String(level)}">${content}</h${String(level)}>`
    }
  )
}

function transformHorizontalRules(text: string): string {
  return text.replace(/^(?:---+|\*\*\*+|___+)\s*$/gm, '<hr class="chat-md-hr">')
}

function transformBlockquotes(text: string): string {
  // Match consecutive lines starting with >
  const lines = text.split('\n')
  const result: string[] = []
  let inBlockquote = false

  for (const line of lines) {
    const match = /^&gt;\s?(.*)$/.exec(line)
    if (match) {
      if (!inBlockquote) {
        result.push('<blockquote class="chat-md-blockquote">')
        inBlockquote = true
      }
      result.push(match[1] ?? '')
    } else {
      if (inBlockquote) {
        result.push('</blockquote>')
        inBlockquote = false
      }
      result.push(line)
    }
  }

  if (inBlockquote) {
    result.push('</blockquote>')
  }

  return result.join('\n')
}

function transformTables(text: string): string {
  const lines = text.split('\n')
  const result: string[] = []
  let i = 0

  while (i < lines.length) {
    const line = lines[i] ?? ''
    // Check if this line looks like a table row (starts and ends with |, or contains | between content)
    if (isTableRow(line) && i + 1 < lines.length && isTableSeparator(lines[i + 1] ?? '')) {
      // Found a table! Parse it
      const tableLines: string[] = [line]
      i++ // skip header row
      tableLines.push(lines[i] ?? '') // separator
      i++

      // Collect body rows
      while (i < lines.length && isTableRow(lines[i] ?? '')) {
        tableLines.push(lines[i] ?? '')
        i++
      }

      result.push(renderTable(tableLines))
    } else {
      result.push(line)
      i++
    }
  }

  return result.join('\n')
}

function isTableRow(line: string): boolean {
  const trimmed = line.trim()
  return trimmed.includes('|') && !trimmed.startsWith('|--')
}

function isTableSeparator(line: string): boolean {
  const trimmed = line.trim()
  return /^\|?[\s:]*-{2,}[\s:]*(\|[\s:]*-{2,}[\s:]*)*\|?\s*$/.test(trimmed)
}

function renderTable(lines: string[]): string {
  if (lines.length < 2) return lines.join('\n')

  const headerLine = lines[0] ?? ''
  const headerCells = parseTableRow(headerLine)

  const headerHtml = headerCells.map(cell => `<th>${cell.trim()}</th>`).join('')

  const bodyRows = lines.slice(2)
  const bodyHtml = bodyRows
    .map(row => {
      const cells = parseTableRow(row)
      return `<tr>${cells.map(cell => `<td>${cell.trim()}</td>`).join('')}</tr>`
    })
    .join('')

  return `<table class="chat-md-table"><thead><tr>${headerHtml}</tr></thead><tbody>${bodyHtml}</tbody></table>`
}

function parseTableRow(line: string): string[] {
  let trimmed = line.trim()
  if (trimmed.startsWith('|')) trimmed = trimmed.slice(1)
  if (trimmed.endsWith('|')) trimmed = trimmed.slice(0, -1)
  return trimmed.split('|')
}

function transformLists(text: string): string {
  const lines = text.split('\n')
  const result: string[] = []
  let inUl = false
  let inOl = false

  for (const line of lines) {
    const ulMatch = /^(\s*)[-*+]\s+(.+)$/.exec(line)
    const olMatch = /^(\s*)\d+\.\s+(.+)$/.exec(line)

    if (ulMatch) {
      if (inOl) {
        result.push('</ol>')
        inOl = false
      }
      if (!inUl) {
        result.push('<ul class="chat-md-list">')
        inUl = true
      }
      result.push(`<li>${ulMatch[2] ?? ''}</li>`)
    } else if (olMatch) {
      if (inUl) {
        result.push('</ul>')
        inUl = false
      }
      if (!inOl) {
        result.push('<ol class="chat-md-list">')
        inOl = true
      }
      result.push(`<li>${olMatch[2] ?? ''}</li>`)
    } else {
      if (inUl) {
        result.push('</ul>')
        inUl = false
      }
      if (inOl) {
        result.push('</ol>')
        inOl = false
      }
      result.push(line)
    }
  }

  if (inUl) result.push('</ul>')
  if (inOl) result.push('</ol>')

  return result.join('\n')
}

// ============ Inline Transformations ============

function transformStrikethrough(text: string): string {
  return text.replace(/~~([^~]+)~~/g, '<del>$1</del>')
}

function transformBold(text: string): string {
  return text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
}

function transformItalic(text: string): string {
  return text.replace(/\*([^*]+)\*/g, '<em>$1</em>')
}

function transformInlineCode(text: string): string {
  return text.replace(/`([^`]+)`/g, '<code class="chat-md-inline-code">$1</code>')
}

const SAFE_URL_PATTERN = /^(?:https?:|mailto:|tel:|#|\/)/i

function transformLinks(text: string): string {
  // [text](url) - but not images (which start with !)
  return text.replace(
    /(?<!!)\[([^\]]+)\]\(([^)]+)\)/g,
    (_match: string, linkText: string, url: string) => {
      const trimmedUrl = url.trim()
      if (SAFE_URL_PATTERN.test(trimmedUrl)) {
        return `<a href="${escapeAttr(trimmedUrl)}" class="chat-md-link" target="_blank" rel="noopener noreferrer">${escapeAttr(linkText)}</a>`
      }
      return `<a class="chat-md-link">${escapeAttr(linkText)}</a>`
    }
  )
}

function transformImages(text: string): string {
  return text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_match: string, alt: string, url: string) => {
    const trimmedUrl = url.trim()
    if (/^(?:https?:|data:image\/|\/)/i.test(trimmedUrl)) {
      return `<img src="${escapeAttr(trimmedUrl)}" alt="${escapeAttr(alt)}" class="chat-md-image" loading="lazy" />`
    }
    return `<span class="chat-md-image" role="img" aria-label="${escapeAttr(alt)}">[Image: ${escapeAttr(alt)}]</span>`
  })
}

function transformTaskLists(text: string): string {
  // Transform task list items: - [ ] or - [x]
  return text
    .replace(
      /<li>\[x\]\s*/gi,
      '<li class="chat-md-task chat-md-task--checked"><input type="checkbox" checked disabled /> '
    )
    .replace(/<li>\[ \]\s*/g, '<li class="chat-md-task"><input type="checkbox" disabled /> ')
}

// ============ Utility Functions ============

function decodeHtmlEntities(text: string): string {
  return text
    .replace(/&amp;/g, '&')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
}

function escapeForCodeBlock(text: string): string {
  return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
}

/** Escape a value for safe use in an HTML attribute */
function escapeAttr(text: string): string {
  return text
    .replace(/&/g, '&amp;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
}

/** Base64 encode with proper Unicode handling */
export function base64Encode(str: string): string {
  const bytes = new TextEncoder().encode(str)
  let binary = ''
  for (const byte of bytes) {
    binary += String.fromCharCode(byte)
  }
  return btoa(binary)
}

/** Base64 decode with proper Unicode handling */
export function base64Decode(str: string): string {
  const binary = atob(str)
  const bytes = new Uint8Array(binary.length)
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i)
  }
  return new TextDecoder().decode(bytes)
}

// ============ Test / Public Exports ============

export const markdownParserUtils = {
  transformHeadings,
  transformHorizontalRules,
  transformBlockquotes,
  transformTables,
  transformLists,
  transformStrikethrough,
  transformBold,
  transformItalic,
  transformInlineCode,
  transformLinks,
  transformImages,
  transformTaskLists,
  renderCodeBlock,
  decodeHtmlEntities,
  escapeForCodeBlock,
}
