import type { ReactNode } from "react";

export function renderPlainMarkdownText(text: string, keyPrefix: string): ReactNode[] {
  const nodes: ReactNode[] = [];
  const pattern = /https?:\/\/[^\s)]+/g;
  let cursor = 0;
  let match: RegExpExecArray | null;

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

    const url = match[0];
    nodes.push(
      <a key={`${keyPrefix}-link-${match.index}`} href={url} target="_blank" rel="noreferrer">
        {url}
      </a>,
    );
    cursor = match.index + url.length;
  }

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

  return nodes.length > 0 ? nodes : [text];
}

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

  while ((match = pattern.exec(text)) !== null) {
    const token = match[0];
    if (match.index > cursor) {
      nodes.push(...renderPlainMarkdownText(text.slice(cursor, match.index), `${keyPrefix}-text-${cursor}`));
    }

    if (token.startsWith("`") && token.endsWith("`")) {
      nodes.push(<code key={`${keyPrefix}-code-${match.index}`}>{token.slice(1, -1)}</code>);
    } else if ((token.startsWith("**") && token.endsWith("**")) || (token.startsWith("__") && token.endsWith("__"))) {
      nodes.push(<strong key={`${keyPrefix}-strong-${match.index}`}>{renderMarkdownInline(token.slice(2, -2), `${keyPrefix}-strong-${match.index}`)}</strong>);
    } else if (token.startsWith("~~") && token.endsWith("~~")) {
      nodes.push(<del key={`${keyPrefix}-del-${match.index}`}>{renderMarkdownInline(token.slice(2, -2), `${keyPrefix}-del-${match.index}`)}</del>);
    } else if (token.startsWith("[") && token.includes("](") && token.endsWith(")")) {
      const labelEnd = token.indexOf("](");
      const label = token.slice(1, labelEnd);
      const href = token.slice(labelEnd + 2, -1);
      nodes.push(
        <a key={`${keyPrefix}-md-link-${match.index}`} href={href} target="_blank" rel="noreferrer">
          {renderMarkdownInline(label, `${keyPrefix}-md-link-label-${match.index}`)}
        </a>,
      );
    } else {
      nodes.push(
        <a key={`${keyPrefix}-raw-link-${match.index}`} href={token} target="_blank" rel="noreferrer">
          {token}
        </a>,
      );
    }

    cursor = match.index + token.length;
  }

  if (cursor < text.length) {
    nodes.push(...renderPlainMarkdownText(text.slice(cursor), `${keyPrefix}-text-${cursor}`));
  }

  return nodes.length > 0 ? nodes : [text];
}

export function splitMarkdownTableRow(line: string): string[] {
  return line
    .trim()
    .replace(/^\|/, "")
    .replace(/\|$/, "")
    .split("|")
    .map((cell) => cell.trim());
}

export function isMarkdownTableDivider(line: string): boolean {
  return /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line);
}

export function isMarkdownBlockStart(line: string, nextLine?: string): boolean {
  const trimmed = line.trim();
  return (
    trimmed === "" ||
    /^```/.test(trimmed) ||
    /^#{1,4}\s+/.test(trimmed) ||
    /^>\s?/.test(trimmed) ||
    /^[-*+]\s+/.test(trimmed) ||
    /^\d+[.)]\s+/.test(trimmed) ||
    /^[-*_]{3,}$/.test(trimmed) ||
    (line.includes("|") && !!nextLine && isMarkdownTableDivider(nextLine))
  );
}

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

  while (index < lines.length) {
    const line = lines[index] ?? "";
    const trimmed = line.trim();

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

    const fenceMatch = trimmed.match(/^```(\S+)?/);
    if (fenceMatch) {
      const language = fenceMatch[1] || "";
      const codeLines: string[] = [];
      index += 1;
      while (index < lines.length && !lines[index].trim().startsWith("```")) {
        codeLines.push(lines[index]);
        index += 1;
      }
      if (index < lines.length) index += 1;
      blocks.push(
        <figure key={`code-${index}`} className="ai-chat-markdown-code">
          {language ? <figcaption>{language}</figcaption> : null}
          <pre>
            <code>{codeLines.join("\n")}</code>
          </pre>
        </figure>,
      );
      continue;
    }

    const headingMatch = trimmed.match(/^(#{1,4})\s+(.+)$/);
    if (headingMatch) {
      const level = Math.min(headingMatch[1].length, 4);
      const Tag = `h${level}` as keyof JSX.IntrinsicElements;
      blocks.push(<Tag key={`heading-${index}`}>{renderMarkdownInline(headingMatch[2], `heading-${index}`)}</Tag>);
      index += 1;
      continue;
    }

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

    if (line.includes("|") && index + 1 < lines.length && isMarkdownTableDivider(lines[index + 1])) {
      const headers = splitMarkdownTableRow(line);
      const rows: string[][] = [];
      index += 2;
      while (index < lines.length && lines[index].includes("|") && lines[index].trim()) {
        rows.push(splitMarkdownTableRow(lines[index]));
        index += 1;
      }
      blocks.push(
        <div key={`table-${index}`} className="ai-chat-markdown-table-wrap">
          <table>
            <thead>
              <tr>
                {headers.map((header, cellIndex) => (
                  <th key={`table-head-${cellIndex}`}>{renderMarkdownInline(header, `table-head-${cellIndex}`)}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {rows.map((row, rowIndex) => (
                <tr key={`table-row-${rowIndex}`}>
                  {headers.map((_, cellIndex) => (
                    <td key={`table-cell-${rowIndex}-${cellIndex}`}>
                      {renderMarkdownInline(row[cellIndex] || "", `table-cell-${rowIndex}-${cellIndex}`)}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>,
      );
      continue;
    }

    if (/^>\s?/.test(trimmed)) {
      const quoteLines: string[] = [];
      while (index < lines.length && /^>\s?/.test(lines[index].trim())) {
        quoteLines.push(lines[index].trim().replace(/^>\s?/, ""));
        index += 1;
      }
      blocks.push(
        <blockquote key={`quote-${index}`}>
          {quoteLines.map((quoteLine, quoteIndex) => (
            <p key={`quote-line-${quoteIndex}`}>{renderMarkdownInline(quoteLine, `quote-${index}-${quoteIndex}`)}</p>
          ))}
        </blockquote>,
      );
      continue;
    }

    const unorderedMatch = trimmed.match(/^[-*+]\s+(.+)$/);
    const orderedMatch = trimmed.match(/^\d+[.)]\s+(.+)$/);
    if (unorderedMatch || orderedMatch) {
      const ordered = Boolean(orderedMatch);
      const ListTag = ordered ? "ol" : "ul";
      const items: string[] = [];
      while (index < lines.length) {
        const itemMatch = ordered ? lines[index].trim().match(/^\d+[.)]\s+(.+)$/) : lines[index].trim().match(/^[-*+]\s+(.+)$/);
        if (!itemMatch) break;
        items.push(itemMatch[1]);
        index += 1;
      }
      blocks.push(
        <ListTag key={`list-${index}`}>
          {items.map((item, itemIndex) => (
            <li key={`list-item-${itemIndex}`}>{renderMarkdownInline(item, `list-${index}-${itemIndex}`)}</li>
          ))}
        </ListTag>,
      );
      continue;
    }

    const paragraphLines: string[] = [line];
    index += 1;
    while (index < lines.length && !isMarkdownBlockStart(lines[index], lines[index + 1])) {
      paragraphLines.push(lines[index]);
      index += 1;
    }
    const paragraphText = paragraphLines.join("\n").trim();
    blocks.push(<p key={`paragraph-${index}`}>{renderMarkdownInline(paragraphText, `paragraph-${index}`)}</p>);
  }

  return blocks;
}
