import React from "react";

interface MarkdownBlock {
  type: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "code" | "blockquote" | "ul" | "ol" | "hr" | "p" | "table";
  content: string;
  language?: string;
  items?: string[];
  headers?: string[];
  rows?: string[][];
}

interface ParseState {
  blocks: MarkdownBlock[];
  currentCodeBlock: { language: string; lines: string[] } | null;
  currentList: { type: "ul" | "ol"; items: string[] } | null;
  currentTableLines: string[];
  currentParagraphLines: string[];
}

interface InlineToken {
  type: "text" | "bold" | "italic" | "code" | "link";
  text: string;
  url?: string;
}

const flushParagraph = (state: ParseState): void => {
  if (state.currentParagraphLines.length > 0) {
    state.blocks.push({
      type: "p",
      content: state.currentParagraphLines.join(" ").trim(),
    });
    state.currentParagraphLines = [];
  }
};

const flushList = (state: ParseState): void => {
  if (state.currentList) {
    state.blocks.push({
      type: state.currentList.type,
      content: "",
      items: state.currentList.items,
    });
    state.currentList = null;
  }
};

const flushTable = (state: ParseState): void => {
  if (state.currentTableLines.length > 0) {
    const rawLines = state.currentTableLines;
    state.currentTableLines = [];

    // Filter out separator lines like |---|---|
    const parsedRows = rawLines
      .filter(line => !/^[|\s-:]+$/.test(line.trim()))
      .map(line => {
        const cells = line.split('|').map(c => c.trim());
        // Remove empty lead/trail cells from leading/trailing pipes
        if (cells.length > 0 && cells[0] === '') cells.shift();
        if (cells.length > 0 && cells[cells.length - 1] === '') cells.pop();
        return cells;
      })
      .filter(row => row.length > 0);

    if (parsedRows.length > 0) {
      const headers = parsedRows[0];
      const rows = parsedRows.slice(1);
      state.blocks.push({
        type: "table",
        content: "",
        headers,
        rows,
      });
    }
  }
};

const handleCodeBlock = (trimmed: string, state: ParseState): boolean => {
  if (trimmed.startsWith("```")) {
    if (state.currentCodeBlock) {
      state.blocks.push({
        type: "code",
        content: state.currentCodeBlock.lines.join("\n"),
        language: state.currentCodeBlock.language,
      });
      state.currentCodeBlock = null;
    } else {
      flushParagraph(state);
      flushList(state);
      flushTable(state);
      const language = trimmed.slice(3).trim();
      state.currentCodeBlock = { language, lines: [] };
    }
    return true;
  }
  return false;
};

const handleHeading = (line: string, state: ParseState): boolean => {
  const match = line.match(/^(#{1,6})\s+(.*)$/);
  if (match) {
    flushParagraph(state);
    flushList(state);
    flushTable(state);
    const level = match[1].length;
    state.blocks.push({
      type: `h${level}` as any,
      content: match[2].trim(),
    });
    return true;
  }
  return false;
};

const handleBlockquote = (trimmed: string, state: ParseState): boolean => {
  if (trimmed.startsWith(">")) {
    flushParagraph(state);
    flushList(state);
    flushTable(state);
    state.blocks.push({
      type: "blockquote",
      content: trimmed.replace(/^>\s*/, ""),
    });
    return true;
  }
  return false;
};

const handleLists = (line: string, state: ParseState): boolean => {
  const ulMatch = line.match(/^(\s*)([-*+])\s+(.*)$/);
  if (ulMatch) {
    flushParagraph(state);
    flushTable(state);
    const content = ulMatch[3].trim();
    if (state.currentList && state.currentList.type === "ul") {
      state.currentList.items.push(content);
    } else {
      flushList(state);
      state.currentList = { type: "ul", items: [content] };
    }
    return true;
  }

  const olMatch = line.match(/^(\s*)(\d+)\.\s+(.*)$/);
  if (olMatch) {
    flushParagraph(state);
    flushTable(state);
    const content = olMatch[3].trim();
    if (state.currentList && state.currentList.type === "ol") {
      state.currentList.items.push(content);
    } else {
      flushList(state);
      state.currentList = { type: "ol", items: [content] };
    }
    return true;
  }

  return false;
};

const handleTableLine = (trimmed: string, state: ParseState): boolean => {
  // Check if line contains markdown table pipes
  if (trimmed.includes("|") && (trimmed.startsWith("|") || trimmed.includes(" | ") || /^[-|\s:]+$/.test(trimmed))) {
    flushParagraph(state);
    flushList(state);
    state.currentTableLines.push(trimmed);
    return true;
  }
  return false;
};

const handleLine = (line: string, state: ParseState): void => {
  const trimmed = line.trim();

  if (handleCodeBlock(trimmed, state)) {
    return;
  }

  if (state.currentCodeBlock) {
    state.currentCodeBlock.lines.push(line);
    return;
  }

  if (trimmed === "---" || trimmed === "***" || trimmed === "___") {
    flushParagraph(state);
    flushList(state);
    flushTable(state);
    state.blocks.push({ type: "hr", content: "" });
    return;
  }

  if (handleHeading(line, state) || handleBlockquote(trimmed, state)) {
    return;
  }

  if (handleLists(line, state)) {
    return;
  }

  if (handleTableLine(trimmed, state)) {
    return;
  }

  if (trimmed === "") {
    flushParagraph(state);
    flushList(state);
    flushTable(state);
    return;
  }

  flushList(state);
  flushTable(state);
  state.currentParagraphLines.push(line);
};

/**
 * Preprocesses raw markdown text to split single-line concatenated markdown table rows and merge split table rows across lines.
 */
const sanitizeMarkdownText = (rawText: string): string => {
  if (!rawText) return "";

  const lines = rawText.split("\n");
  const processedLines: string[] = [];

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const trimmed = line.trim();

    // If the line starts with a pipe but doesn't end with one, it is likely split across linebreaks
    if (trimmed.startsWith("|") && !trimmed.endsWith("|")) {
      let merged = line;
      while (i + 1 < lines.length) {
        const nextLine = lines[i + 1];
        const nextTrimmed = nextLine.trim();
        merged += " " + nextTrimmed;
        i++;
        if (nextTrimmed.endsWith("|")) {
          break;
        }
      }
      processedLines.push(merged);
    } else {
      processedLines.push(line);
    }
  }

  let formatted = processedLines.join("\n");

  // Split inline concatenated table rows like "| Col 1 | Col 2 | | :--- | :--- | | Val 1 | Val 2 |"
  formatted = formatted.replace(/\|\s*\|\s*:-/g, "|\n| :-");
  formatted = formatted.replace(/\|\s*\|\s*([A-Za-z0-9_*`])/g, "|\n| $1");
  formatted = formatted.replace(/([^\n|])\s*(\|[\s\S]+?\|)\s*([^\n|])/g, "$1\n\n$2\n\n$3");

  // Clean up repeated linebreaks
  formatted = formatted.replace(/\n{3,}/g, "\n\n");
  return formatted;
};

export const parseMarkdown = (text: string): MarkdownBlock[] => {
  const sanitized = sanitizeMarkdownText(text);
  const lines = sanitized.split("\n");
  const state: ParseState = {
    blocks: [],
    currentCodeBlock: null,
    currentList: null,
    currentTableLines: [],
    currentParagraphLines: [],
  };

  for (let i = 0; i < lines.length; i++) {
    handleLine(lines[i], state);
  }

  flushParagraph(state);
  flushList(state);
  flushTable(state);

  return state.blocks;
};

const parseInlineLinks = (tokens: InlineToken[]): InlineToken[] => {
  const updated: InlineToken[] = [];
  for (const part of tokens) {
    if (part.type === "text") {
      const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
      let lastIndex = 0;
      let match;
      while ((match = regex.exec(part.text)) !== null) {
        const before = part.text.substring(lastIndex, match.index);
        if (before) updated.push({ type: "text", text: before });
        updated.push({ type: "link", text: match[1], url: match[2] });
        lastIndex = regex.lastIndex;
      }
      const after = part.text.substring(lastIndex);
      if (after) updated.push({ type: "text", text: after });
    } else {
      updated.push(part);
    }
  }
  return updated;
};

const parseInlineUrls = (tokens: InlineToken[]): InlineToken[] => {
  const updated: InlineToken[] = [];
  for (const part of tokens) {
    if (part.type === "text") {
      const regex = /(https?:\/\/[^\s)]+)/g;
      let lastIndex = 0;
      let match;
      while ((match = regex.exec(part.text)) !== null) {
        const before = part.text.substring(lastIndex, match.index);
        if (before) updated.push({ type: "text", text: before });
        updated.push({ type: "link", text: match[1], url: match[1] });
        lastIndex = regex.lastIndex;
      }
      const after = part.text.substring(lastIndex);
      if (after) updated.push({ type: "text", text: after });
    } else {
      updated.push(part);
    }
  }
  return updated;
};

const parseInlineBold = (tokens: InlineToken[]): InlineToken[] => {
  const updated: InlineToken[] = [];
  for (const part of tokens) {
    if (part.type === "text") {
      const regex = /\*\*([^*]+)\*\*/g;
      let lastIndex = 0;
      let match;
      while ((match = regex.exec(part.text)) !== null) {
        const before = part.text.substring(lastIndex, match.index);
        if (before) updated.push({ type: "text", text: before });
        updated.push({ type: "bold", text: match[1] });
        lastIndex = regex.lastIndex;
      }
      const after = part.text.substring(lastIndex);
      if (after) updated.push({ type: "text", text: after });
    } else {
      updated.push(part);
    }
  }
  return updated;
};

const parseInlineCode = (tokens: InlineToken[]): InlineToken[] => {
  const updated: InlineToken[] = [];
  for (const part of tokens) {
    if (part.type === "text") {
      const regex = /`([^`]+)`/g;
      let lastIndex = 0;
      let match;
      while ((match = regex.exec(part.text)) !== null) {
        const before = part.text.substring(lastIndex, match.index);
        if (before) updated.push({ type: "text", text: before });
        updated.push({ type: "code", text: match[1] });
        lastIndex = regex.lastIndex;
      }
      const after = part.text.substring(lastIndex);
      if (after) updated.push({ type: "text", text: after });
    } else {
      updated.push(part);
    }
  }
  return updated;
};

const parseInlineItalic = (tokens: InlineToken[]): InlineToken[] => {
  const updated: InlineToken[] = [];
  for (const part of tokens) {
    if (part.type === "text") {
      const regex = /\*([^*]+)\*/g;
      let lastIndex = 0;
      let match;
      while ((match = regex.exec(part.text)) !== null) {
        const before = part.text.substring(lastIndex, match.index);
        if (before) updated.push({ type: "text", text: before });
        updated.push({ type: "italic", text: match[1] });
        lastIndex = regex.lastIndex;
      }
      const after = part.text.substring(lastIndex);
      if (after) updated.push({ type: "text", text: after });
    } else {
      updated.push(part);
    }
  }
  return updated;
};

export const renderInlineText = (text: string, isDark: boolean = false): React.ReactNode[] => {
  if (!text) return [];

  let tokens: InlineToken[] = [{ type: "text", text }];
  tokens = parseInlineLinks(tokens);
  tokens = parseInlineUrls(tokens);
  tokens = parseInlineBold(tokens);
  tokens = parseInlineCode(tokens);
  tokens = parseInlineItalic(tokens);

  return tokens.map((part, idx) => {
    switch (part.type) {
      case "bold":
        return <strong key={idx} className={isDark ? "font-extrabold text-white" : "font-extrabold text-ink-900"}>{part.text}</strong>;
      case "italic":
        return <em key={idx} className={isDark ? "italic text-slate-200" : "italic text-ink-800"}>{part.text}</em>;
      case "code":
        return (
          <code key={idx} className={isDark ? "bg-slate-900 border border-slate-700 text-emerald-300 rounded px-1.5 py-0.5 text-xs font-mono" : "bg-ink-100 border border-ink-200 rounded px-1.5 py-0.5 text-xs font-mono text-emerald-700"}>
            {part.text}
          </code>
        );
      case "link":
        return (
          <a
            key={idx}
            href={part.url}
            target="_blank"
            rel="noopener noreferrer"
            className={isDark ? "text-emerald-400 hover:text-emerald-300 font-semibold underline break-all inline-flex items-center gap-0.5" : "text-primary-600 hover:text-primary-800 font-semibold underline break-all inline-flex items-center gap-0.5"}
          >
            {part.text}
          </a>
        );
      default:
        return <span key={idx}>{part.text}</span>;
    }
  });
};

const renderListBlock = (block: MarkdownBlock, key: string, isDark: boolean): React.ReactNode => {
  const Component = block.type === "ul" ? "ul" : "ol";
  const listClass = block.type === "ul" 
    ? `list-disc pl-5 space-y-1.5 my-2 text-xs ${isDark ? 'text-slate-100' : 'text-ink-800'}`
    : `list-decimal pl-5 space-y-1.5 my-2 text-xs ${isDark ? 'text-slate-100' : 'text-ink-800'}`;
  return (
    <Component key={key} className={listClass}>
      {block.items?.map((item, idx) => (
        <li key={idx}>{renderInlineText(item, isDark)}</li>
      ))}
    </Component>
  );
};

const renderHeadingBlock = (block: MarkdownBlock, key: string, isDark: boolean): React.ReactNode => {
  const level = block.type.slice(1);
  const classes: Record<string, string> = {
    "1": `text-lg sm:text-xl font-extrabold ${isDark ? 'text-white' : 'text-ink-900'} mt-4 mb-2 border-b border-slate-700/50 pb-1`,
    "2": `text-base sm:text-lg font-extrabold ${isDark ? 'text-white' : 'text-ink-900'} mt-3 mb-1.5`,
    "3": `text-sm sm:text-base font-bold ${isDark ? 'text-emerald-400' : 'text-ink-900'} mt-2.5 mb-1`,
    "4": `text-xs sm:text-sm font-bold ${isDark ? 'text-emerald-300' : 'text-ink-800'} mt-2 mb-1`,
    "5": `text-xs font-bold ${isDark ? 'text-slate-200' : 'text-ink-800'} mt-1.5 mb-1`,
    "6": `text-xs font-bold ${isDark ? 'text-slate-300' : 'text-ink-700'} mt-1 mb-0.5`,
  };
  const Component = block.type as any;
  return (
    <Component key={key} className={classes[level] || ""}>
      {renderInlineText(block.content, isDark)}
    </Component>
  );
};

const renderTableBlock = (block: MarkdownBlock, key: string, isDark: boolean): React.ReactNode => {
  if (!block.headers || block.headers.length === 0) return null;

  return (
    <div key={key} className="my-3 w-full overflow-x-auto rounded-xl border border-slate-800 bg-slate-950/90 shadow-xl max-w-full">
      <table className="w-full text-left border-collapse min-w-[320px]">
        <thead>
          <tr className="bg-slate-900/90 border-b border-slate-800 text-[11px] font-extrabold text-emerald-400 uppercase tracking-wider">
            {block.headers.map((h, i) => (
              <th key={i} className="py-2.5 px-3 border-r last:border-r-0 border-slate-800/80 font-bold whitespace-nowrap">
                {renderInlineText(h, isDark)}
              </th>
            ))}
          </tr>
        </thead>
        <tbody className="divide-y divide-slate-800/60 text-xs">
          {(block.rows || []).map((row, rIdx) => (
            <tr key={rIdx} className="hover:bg-slate-900/60 transition-colors odd:bg-slate-950/40 even:bg-slate-900/30">
              {row.map((cell, cIdx) => (
                <td key={cIdx} className="py-2.5 px-3 border-r last:border-r-0 border-slate-800/60 text-slate-200 leading-relaxed font-sans">
                  {renderInlineText(cell, isDark)}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

const renderBlock = (block: MarkdownBlock, index: number, isDark: boolean): React.ReactNode => {
  const key = `${block.type}-${index}`;
  if (block.type.startsWith("h") && block.type.length === 2 && block.type !== "hr") {
    return renderHeadingBlock(block, key, isDark);
  }
  switch (block.type) {
    case "table":
      return renderTableBlock(block, key, isDark);
    case "blockquote":
      return (
        <blockquote key={key} className={`border-l-4 border-emerald-500 pl-3 py-1 italic ${isDark ? 'bg-slate-900/60 text-slate-200' : 'bg-primary-50/20 text-ink-700'} my-2 rounded-r-lg`}>
          {renderInlineText(block.content, isDark)}
        </blockquote>
      );
    case "ul":
    case "ol":
      return renderListBlock(block, key, isDark);
    case "code":
      return (
        <div key={key} className="my-3 rounded-xl border border-slate-800 bg-slate-950 text-slate-100 p-3 overflow-x-auto shadow-inner relative group select-text max-w-full">
          {block.language && (
            <div className="absolute right-3 top-2 text-[9px] uppercase font-bold text-slate-400 select-none">
              {block.language}
            </div>
          )}
          <pre className="font-mono text-xs leading-relaxed overflow-x-auto">
            {block.content}
          </pre>
        </div>
      );
    case "hr":
      return <hr key={key} className={`my-4 ${isDark ? 'border-slate-800' : 'border-ink-200'}`} />;
    default:
      return (
        <p key={key} className={`text-xs ${isDark ? 'text-slate-100' : 'text-ink-800'} leading-relaxed my-1.5`}>
          {renderInlineText(block.content, isDark)}
        </p>
      );
  }
};

export interface MarkdownViewerProps {
  markdown: string;
  variant?: 'light' | 'dark' | 'auto';
}

export const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ markdown, variant = 'auto' }) => {
  const blocks = parseMarkdown(markdown);
  const isDark = variant === 'dark';
  return (
    <div className={`w-full text-left select-text font-sans leading-relaxed break-words overflow-hidden ${isDark ? 'text-slate-100' : 'text-ink-800'}`}>
      {blocks.map((block, idx) => renderBlock(block, idx, isDark))}
    </div>
  );
};

export default MarkdownViewer;
