import React from 'react';
import { CodeBlock } from '../code-block';

/**
 * Props for the MarkdownMessage component.
 */
interface MarkdownMessageProps {
  /** Raw markdown content to render */
  content: string;
  /** Custom CSS classes for the container */
  className?: string;
}

/**
 * Fenced code blocks (```lang\ncode\n```) are extracted before any other
 * regex runs and rendered as real `<CodeBlock>` elements instead of HTML
 * strings — two reasons: (1) `CodeBlock`'s syntax highlighting needs a
 * genuine React element, not a `dangerouslySetInnerHTML` string, and (2) if
 * fenced blocks were left in the string, the bold/italic/inline-code regexes
 * below would run over their contents first and corrupt any code containing
 * `*`, `_`, or backticks (e.g. `x**2`, docstrings, nested template strings).
 */
interface CodeSegment {
  type: 'code';
  language?: string;
  code: string;
}
interface HtmlSegment {
  type: 'html';
  html: string;
}
type Segment = CodeSegment | HtmlSegment;

const FENCE_RE = /```([a-zA-Z0-9_+-]*)[ \t]*\r?\n?([\s\S]*?)```/g;

function splitCodeBlocks(markdown: string): Segment[] {
  const segments: Segment[] = [];
  let lastIndex = 0;
  FENCE_RE.lastIndex = 0;

  let match: RegExpExecArray | null;
  while ((match = FENCE_RE.exec(markdown))) {
    if (match.index > lastIndex) {
      segments.push({ type: 'html', html: convertMarkdownToHtml(markdown.slice(lastIndex, match.index)) });
    }
    segments.push({
      type: 'code',
      language: match[1] || undefined,
      code: match[2].replace(/\n$/, ''),
    });
    lastIndex = FENCE_RE.lastIndex;
  }

  if (lastIndex < markdown.length || segments.length === 0) {
    segments.push({ type: 'html', html: convertMarkdownToHtml(markdown.slice(lastIndex)) });
  }

  return segments;
}

/**
 * Lightweight Markdown → HTML conversion for a single non-code-fence segment.
 *
 * @ai-rules
 * 1. Sanitization: Uses regex for conversion. Be cautious with complex markdown structures.
 * 2. Styling: Injected HTML uses specific Tailwind classes for consistent typography.
 * 3. Security: Result is used with `dangerouslySetInnerHTML`. HTML is escaped
 *    first, so this is only as safe as that escape step — never skip it.
 * 4. Ordering matters: blockquote/heading detection must run on the escaped
 *    `&gt;`/`&lt;` form (the escape step runs first); lists must run before
 *    bold/italic since GFM allows `*` as a bullet marker (the same character
 *    used for italic) — resolving list structure first removes the ambiguity;
 *    and bold must run before italic so `**x**` isn't first read as two
 *    `*x*` matches.
 */
function convertMarkdownToHtml(markdown: string): string {
  let html = markdown;

  // Escape existing HTML
  html = html.replace(/</g, '&lt;').replace(/>/g, '&gt;');

  // Headers (most specific to least, though the trailing required space
  // after the last `#` already prevents `##`/`###` from matching `# `)
  html = html.replace(
    /^#### (.*$)/gim,
    '<h4 class="font-medium text-sm mt-2 mb-1 break-words">$1</h4>'
  );
  html = html.replace(
    /^### (.*$)/gim,
    '<h3 class="font-medium text-sm mt-2 mb-1 break-words">$1</h3>'
  );
  html = html.replace(
    /^## (.*$)/gim,
    '<h2 class="font-medium text-base mt-3 mb-1.5 break-words">$1</h2>'
  );
  html = html.replace(
    /^# (.*$)/gim,
    '<h1 class="font-medium text-lg mt-3 mb-2 break-words">$1</h1>'
  );

  // Blockquotes — `>` was already escaped to `&gt;` above, so match that.
  // Consecutive `&gt;`-prefixed lines are grouped into a single <blockquote>.
  html = html.replace(/(?:^&gt; ?.*$(?:\n|$))+/gim, match => {
    const lines = match
      .trim()
      .split(/\r?\n/)
      .map(line => line.replace(/^&gt; ?/, ''));
    return `<blockquote class="border-l-2 border-border pl-3 my-2 text-muted-foreground italic break-words">${lines.join('<br/>')}</blockquote>`;
  });

  // Lists run BEFORE bold/italic, not after — GFM allows `*` as a bullet
  // marker (alongside `-`/`•`), which is also the italic delimiter. If bold/
  // italic ran first, a bullet line's leading `* ` with no closing `*` on
  // the same line would sit there waiting, and the *next* bullet line's `*`
  // (or a genuine `*italic*` later in the same item) could pair with it,
  // corrupting both. Converting bullet/number markers to `<li>` first removes
  // the ambiguity — plain text search on `*`/`_` after that only ever finds
  // real emphasis markers.
  html = html.replace(/((?:^[•\-*] .*$(?:\n|$))+)/gim, match => {
    const items = match
      .trim()
      .split('\n')
      .map(
        item =>
          `<li class="ml-4 mb-1 list-disc break-words">${item.replace(/^[•\-*]\s+/, '')}</li>`
      )
      .join('');
    return `<ul class="list-disc my-2">${items}</ul>`;
  });

  // Ordered lists - properly wrap in ol
  html = html.replace(/((?:^\d+\. .*$(?:\n|$))+)/gim, match => {
    const items = match
      .trim()
      .split('\n')
      .map(
        item => `<li class="ml-4 mb-1 list-decimal break-words">${item.replace(/^\d+\.\s+/, '')}</li>`
      )
      .join('');
    return `<ol class="list-decimal my-2">${items}</ol>`;
  });

  // Bold (must run before italic, or "**x**" is read as two "*...*" matches)
  html = html.replace(/\*\*(.*?)\*\*/g, '<strong class="font-medium">$1</strong>');

  // Italic
  html = html.replace(/\*(.*?)\*/g, '<em class="italic">$1</em>');

  // Strikethrough (GFM)
  html = html.replace(/~~(.*?)~~/g, '<del class="line-through opacity-70">$1</del>');

  // Links
  html = html.replace(
    /\[([^\]]+)\]\(([^)]+)\)/g,
    '<a href="$2" class="text-[var(--chart-4)] hover:underline break-all" target="_blank" rel="noopener noreferrer">$1</a>'
  );

  // Code inline
  html = html.replace(
    /`([^`]+)`/g,
    '<code class="px-1.5 py-0.5 rounded bg-muted text-xs font-mono break-all inline-block max-w-full">$1</code>'
  );

  // GFM Tables — must run before line-break transforms
  html = html.replace(/((?:^\|.+\|[ \t]*(?:\r?\n|$))+)/gm, match => {
    const rows = match
      .trim()
      .split(/\r?\n/)
      .filter(line => line.trim());
    if (rows.length < 2) return match;

    const isSeparatorRow = /^\|[\s\-:|]+\|$/.test(rows[1].trim());
    if (!isSeparatorRow) return match;

    const parseCells = (row: string) =>
      row
        .split('|')
        .slice(1, -1)
        .map(cell => cell.trim());

    const headerCells = parseCells(rows[0])
      .map(
        cell =>
          `<th class="px-3 py-2 text-left font-medium text-xs uppercase tracking-wide border-b border-border">${cell}</th>`
      )
      .join('');

    const bodyRows = rows
      .slice(2)
      .map(
        row =>
          `<tr class="border-b border-border last:border-0 hover:bg-muted/50">${parseCells(row)
            .map(cell => `<td class="px-3 py-2 text-sm">${cell}</td>`)
            .join('')}</tr>`
      )
      .join('');

    return `<div class="overflow-x-auto my-3 rounded-[var(--radius)] border border-border"><table class="w-full text-sm"><thead class="bg-muted/50"><tr>${headerCells}</tr></thead><tbody>${bodyRows}</tbody></table></div>`;
  });

  // Line breaks (preserve double line breaks as paragraphs)
  html = html.replace(/\n\n/g, '</p><p class="mb-2 break-words">');
  html = html.replace(/\n/g, '<br/>');

  // Wrap in initial paragraph
  html = '<p class="mb-2 break-words">' + html + '</p>';

  // Clean up empty paragraphs
  html = html.replace(/<p[^>]*>\s*<\/p>/g, '');

  return html;
}

/**
 * Lightweight Markdown Parser and Renderer.
 *
 * @description
 * A simplified markdown renderer built for chat messages, where a full-blown
 * parser (react-markdown + remark/rehype plugins) would be overkill. Supports
 * headers (h1-h4), bold, italic, strikethrough, links, inline code, fenced
 * code blocks (rendered via `CodeBlock`, with syntax highlighting), blockquotes,
 * ordered/unordered lists, and GFM tables.
 *
 * @ai-rules
 * 1. Sanitization: Uses regex for conversion. Be cautious with complex markdown structures.
 * 2. Styling: Injected HTML uses specific Tailwind classes for consistent typography.
 * 3. Security: Non-code content uses `dangerouslySetInnerHTML` after escaping `<`/`>`.
 *    Fenced code content is rendered as a React text child (via `CodeBlock`), which
 *    React escapes automatically — never re-escape it manually.
 */
export function MarkdownMessage({ content, className = '' }: MarkdownMessageProps) {
  const segments = splitCodeBlocks(content);

  return (
    <div
      className={`text-sm leading-relaxed break-words overflow-wrap-anywhere max-w-full ${className}`}
    >
      {segments.map((segment, index) =>
        segment.type === 'code' ? (
          <div key={index} className="my-2">
            <CodeBlock code={segment.code} language={segment.language || 'text'} />
          </div>
        ) : (
          <span key={index} dangerouslySetInnerHTML={{ __html: segment.html }} />
        )
      )}
    </div>
  );
}
