import type { ReactNode } from "react"

interface RenderMarkdownContentOptions {
  messageId: string
  copiedToken: string
  copyIcon: ReactNode
  copiedIcon: ReactNode
  onCopy: (token: string, text: string) => void | Promise<void>
}

function formatCodeLanguageLabel(language: string): string {
  const normalized = language.trim().toLowerCase()
  if (!normalized) {
    return "Text"
  }

  const aliases: Record<string, string> = {
    js: "JavaScript",
    ts: "TypeScript",
    jsx: "React JSX",
    tsx: "React TSX",
    py: "Python",
    sh: "Shell",
    yml: "YAML",
    md: "Markdown"
  }

  return aliases[normalized] ?? normalized.charAt(0).toUpperCase() + normalized.slice(1)
}

function renderMarkdownInline(text: string, keyPrefix: string): ReactNode[] {
  const nodes: ReactNode[] = []
  const pattern = /`([^`]+)`|\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)|\*\*([^*]+)\*\*|__([^_]+)__|\*([^*\n]+)\*|_([^_\n]+)_/g
  let cursor = 0
  let match: RegExpExecArray | null
  let tokenIndex = 0

  while ((match = pattern.exec(text)) !== null) {
    if (match.index > cursor) {
      nodes.push(text.slice(cursor, match.index))
    }

    const key = `${keyPrefix}-${tokenIndex}`
    if (match[1]) {
      nodes.push(<code key={key} className="markdown-inline-code">{match[1]}</code>)
    } else if (match[2] && match[3]) {
      nodes.push(
        <a key={key} href={match[3]} target="_blank" rel="noreferrer" className="markdown-link">
          {renderMarkdownInline(match[2], `${key}-link`)}
        </a>
      )
    } else if (match[4] || match[5]) {
      nodes.push(<strong key={key}>{renderMarkdownInline(match[4] || match[5] || "", `${key}-strong`)}</strong>)
    } else if (match[6] || match[7]) {
      nodes.push(<em key={key}>{renderMarkdownInline(match[6] || match[7] || "", `${key}-em`)}</em>)
    }

    cursor = pattern.lastIndex
    tokenIndex += 1
  }

  if (cursor < text.length) {
    nodes.push(text.slice(cursor))
  }

  return nodes
}

function renderParagraphLines(lines: string[], keyPrefix: string): ReactNode[] {
  return lines.flatMap((line, lineIndex) => [
    lineIndex > 0 ? <br key={`${keyPrefix}-br-${lineIndex}`} /> : null,
    ...renderMarkdownInline(line, `${keyPrefix}-line-${lineIndex}`)
  ])
}

function renderMarkdownHeading(level: number, key: string, children: ReactNode[]) {
  switch (level) {
    case 1:
      return <h1 key={key}>{children}</h1>
    case 2:
      return <h2 key={key}>{children}</h2>
    case 3:
      return <h3 key={key}>{children}</h3>
    case 4:
      return <h4 key={key}>{children}</h4>
    case 5:
      return <h5 key={key}>{children}</h5>
    default:
      return <h6 key={key}>{children}</h6>
  }
}

function renderMarkdownBlocks(text: string, keyPrefix: string): ReactNode[] {
  const normalized = text.replace(/\r\n?/g, "\n")
  const lines = normalized.split("\n")
  const blocks: ReactNode[] = []
  let index = 0

  const isBlockBoundary = (line: string) => {
    const trimmed = line.trim()
    return !trimmed
      || /^#{1,6}\s+/.test(trimmed)
      || /^>\s?/.test(trimmed)
      || /^[-*_]{3,}\s*$/.test(trimmed)
      || /^\s*[-*]\s+/.test(trimmed)
      || /^\s*\d+\.\s+/.test(trimmed)
  }

  while (index < lines.length) {
    const current = lines[index]
    const trimmed = current.trim()

    if (!trimmed) {
      index += 1
      continue
    }

    const headingMatch = /^(#{1,6})\s+(.*)$/.exec(trimmed)
    if (headingMatch) {
      const level = Math.min(headingMatch[1].length, 6)
      blocks.push(
        renderMarkdownHeading(
          level,
          `${keyPrefix}-heading-${index}`,
          renderMarkdownInline(headingMatch[2], `${keyPrefix}-heading-${index}`)
        )
      )
      index += 1
      continue
    }

    if (/^[-*_]{3,}\s*$/.test(trimmed)) {
      blocks.push(<hr key={`${keyPrefix}-rule-${index}`} className="markdown-rule" />)
      index += 1
      continue
    }

    if (/^>\s?/.test(trimmed)) {
      const quoteLines: string[] = []
      while (index < lines.length && /^>\s?/.test(lines[index].trim())) {
        quoteLines.push(lines[index].replace(/^\s*>\s?/, ""))
        index += 1
      }
      blocks.push(
        <blockquote key={`${keyPrefix}-quote-${index}`} className="markdown-quote">
          <p>{renderParagraphLines(quoteLines, `${keyPrefix}-quote-${index}`)}</p>
        </blockquote>
      )
      continue
    }

    if (/^\s*[-*]\s+/.test(trimmed)) {
      const items: string[] = []
      while (index < lines.length && /^\s*[-*]\s+/.test(lines[index].trim())) {
        items.push(lines[index].replace(/^\s*[-*]\s+/, ""))
        index += 1
      }
      blocks.push(
        <ul key={`${keyPrefix}-ul-${index}`} className="markdown-list markdown-list-unordered">
          {items.map((item, itemIndex) => (
            <li key={`${keyPrefix}-ul-${index}-${itemIndex}`}>{renderMarkdownInline(item, `${keyPrefix}-ul-item-${itemIndex}`)}</li>
          ))}
        </ul>
      )
      continue
    }

    if (/^\s*\d+\.\s+/.test(trimmed)) {
      const items: string[] = []
      while (index < lines.length && /^\s*\d+\.\s+/.test(lines[index].trim())) {
        items.push(lines[index].replace(/^\s*\d+\.\s+/, ""))
        index += 1
      }
      blocks.push(
        <ol key={`${keyPrefix}-ol-${index}`} className="markdown-list markdown-list-ordered">
          {items.map((item, itemIndex) => (
            <li key={`${keyPrefix}-ol-${index}-${itemIndex}`}>{renderMarkdownInline(item, `${keyPrefix}-ol-item-${itemIndex}`)}</li>
          ))}
        </ol>
      )
      continue
    }

    const paragraphLines: string[] = []
    while (index < lines.length && !isBlockBoundary(lines[index])) {
      paragraphLines.push(lines[index])
      index += 1
    }
    blocks.push(
      <p key={`${keyPrefix}-p-${index}`} className="markdown-paragraph">
        {renderParagraphLines(paragraphLines, `${keyPrefix}-p-${index}`)}
      </p>
    )
  }

  return blocks
}

export function renderMarkdownContent(content: string, options: RenderMarkdownContentOptions): ReactNode[] {
  const normalized = content.replace(/\r\n?/g, "\n")
  const blocks: ReactNode[] = []
  const fencePattern = /```([^\n`]*)\n([\s\S]*?)```/g
  let cursor = 0
  let blockIndex = 0
  let match: RegExpExecArray | null

  while ((match = fencePattern.exec(normalized)) !== null) {
    const proseBeforeFence = normalized.slice(cursor, match.index)
    if (proseBeforeFence.trim()) {
      blocks.push(...renderMarkdownBlocks(proseBeforeFence, `${options.messageId}-md-${blockIndex}`))
      blockIndex += 1
    }

    const language = (match[1] || "").trim().split(/\s+/)[0] || ""
    const code = match[2].replace(/\n$/, "")
    const copyToken = `message:${options.messageId}:code:${blockIndex}`
    blocks.push(
      <div key={`${options.messageId}-code-${blockIndex}`} className="markdown-code-block">
        <div className="markdown-code-toolbar">
          <span>{formatCodeLanguageLabel(language)}</span>
          <button
            type="button"
            className={"markdown-code-copy" + (options.copiedToken === copyToken ? " copied" : "")}
            aria-label="复制代码"
            title="复制代码"
            onClick={() => void options.onCopy(copyToken, code)}
          >
            {options.copiedToken === copyToken ? options.copiedIcon : options.copyIcon}
          </button>
        </div>
        <pre className="markdown-code-pre">
          <code>{code}</code>
        </pre>
      </div>
    )
    cursor = match.index + match[0].length
    blockIndex += 1
  }

  const trailingProse = normalized.slice(cursor)
  if (trailingProse.trim()) {
    blocks.push(...renderMarkdownBlocks(trailingProse, `${options.messageId}-md-${blockIndex}`))
  }

  return blocks.length > 0
    ? blocks
    : [
        <p key={`${options.messageId}-md-empty`} className="markdown-paragraph">
          {renderParagraphLines([normalized], `${options.messageId}-md-empty`)}
        </p>
      ]
}
