import { Fragment, type ReactNode } from 'react';

/** Turns literal `\n` / `\t` sequences in API markdown into real newlines and tabs. */
export function normalizeContractMarkdownNewlines(markdown: string): string {
  return markdown.replaceAll('\\n', '\n').replaceAll('\\t', '\t');
}

type Block =
  | { type: 'h1'; text: string }
  | { type: 'h2'; text: string }
  | { type: 'h3'; text: string }
  | { type: 'hr' }
  | { type: 'p'; text: string }
  | { type: 'blockquote'; lines: string[] }
  | { type: 'ul'; items: string[] }
  | { type: 'ol'; items: string[] }
  | { type: 'table'; headers: string[]; rows: string[][] };

function splitTableRow(line: string): string[] {
  const t = line.trim();
  const core = t.startsWith('|') ? t.slice(1) : t;
  const endTrimmed = core.endsWith('|') ? core.slice(0, -1) : core;
  return endTrimmed.split('|').map(cell => cell.trim());
}

function isTableSeparatorLine(line: string): boolean {
  const t = line.trim();
  if (!t.includes('|') || !t.includes('-')) {
    return false;
  }
  const cells = splitTableRow(t);
  return cells.every(cell => /^:?-{3,}:?$/.test(cell.trim()));
}

function isTableBlockStart(lines: string[], index: number): boolean {
  if (index + 1 >= lines.length) {
    return false;
  }
  const header = lines[index].trim();
  if (!header.includes('|')) {
    return false;
  }
  return isTableSeparatorLine(lines[index + 1]);
}

function isBlankLine(line: string): boolean {
  return line.trim() === '';
}

export function parseMarkdownBlocks(markdown: string): Block[] {
  const lines = markdown.split(/\r?\n/);
  const blocks: Block[] = [];
  let i = 0;

  while (i < lines.length) {
    const line = lines[i];
    if (isBlankLine(line)) {
      i++;
      continue;
    }

    const t = line.trim();

    if (/^---+$/.test(t)) {
      blocks.push({ type: 'hr' });
      i++;
      continue;
    }

    if (t.startsWith('### ')) {
      blocks.push({ type: 'h3', text: t.slice(4) });
      i++;
      continue;
    }
    if (t.startsWith('## ') && !t.startsWith('###')) {
      blocks.push({ type: 'h2', text: t.slice(3) });
      i++;
      continue;
    }
    if (t.startsWith('# ') && !t.startsWith('##')) {
      blocks.push({ type: 'h1', text: t.slice(2) });
      i++;
      continue;
    }

    if (t.startsWith('>')) {
      const quoteLines: string[] = [];
      while (i < lines.length) {
        const L = lines[i];
        if (isBlankLine(L)) break;
        const tr = L.trim();
        if (!tr.startsWith('>')) break;
        quoteLines.push(tr.replace(/^>\s?/, ''));
        i++;
      }
      blocks.push({ type: 'blockquote', lines: quoteLines });
      continue;
    }

    if (/^\d+\.\s/.test(t)) {
      const items: string[] = [];
      while (i < lines.length) {
        const L = lines[i];
        if (isBlankLine(L)) break;
        const tr = L.trim();
        if (!/^\d+\.\s/.test(tr)) break;
        items.push(tr.replace(/^\d+\.\s*/, ''));
        i++;
      }
      blocks.push({ type: 'ol', items });
      continue;
    }

    if (t.startsWith('- ')) {
      const items: string[] = [];
      while (i < lines.length) {
        const L = lines[i];
        if (isBlankLine(L)) break;
        const tr = L.trim();
        if (!tr.startsWith('- ')) break;
        items.push(tr.slice(2));
        i++;
      }
      blocks.push({ type: 'ul', items });
      continue;
    }

    if (isTableBlockStart(lines, i)) {
      const headers = splitTableRow(lines[i].trim());
      i += 2;
      const rows: string[][] = [];
      while (i < lines.length) {
        const L = lines[i];
        if (isBlankLine(L)) {
          break;
        }
        const tr = L.trim();
        if (!tr.includes('|')) {
          break;
        }
        if (isTableSeparatorLine(tr)) {
          i++;
          continue;
        }
        rows.push(splitTableRow(tr));
        i++;
      }
      blocks.push({ type: 'table', headers, rows });
      continue;
    }

    while (i < lines.length) {
      const L = lines[i];
      if (isBlankLine(L)) break;
      const tr = L.trim();
      if (
        /^---+$/.test(tr) ||
        /^#{1,3}\s/.test(tr) ||
        tr.startsWith('>') ||
        tr.startsWith('- ') ||
        /^\d+\.\s/.test(tr) ||
        isTableBlockStart(lines, i)
      ) {
        break;
      }
      blocks.push({ type: 'p', text: tr });
      i++;
    }
  }

  return blocks;
}

function findSingleAsteriskOpen(text: string, from: number): number {
  for (let i = from; i < text.length; i++) {
    if (text[i] !== '*') continue;
    if (text[i + 1] === '*') {
      i++;
      continue;
    }
    return i;
  }
  return -1;
}

function findSingleAsteriskClose(text: string, from: number): number {
  for (let j = from; j < text.length; j++) {
    if (text[j] !== '*') continue;
    if (text[j + 1] === '*') {
      j++;
      continue;
    }
    return j;
  }
  return -1;
}

export function wrapInlineChildren(nodes: ReactNode[]): ReactNode {
  if (nodes.length === 0) return null;
  if (nodes.length === 1) return nodes[0];
  return <Fragment>{nodes}</Fragment>;
}

export function parseMarkdownInline(
  text: string,
  keyPrefix: string,
): ReactNode[] {
  const nodes: ReactNode[] = [];
  let pos = 0;
  let keyInc = 0;
  const nextKey = () => `${keyPrefix}-${keyInc++}`;

  while (pos < text.length) {
    const boldOpen = text.indexOf('**', pos);
    const starOpen = findSingleAsteriskOpen(text, pos);

    let boldClose = -1;
    if (boldOpen !== -1) {
      boldClose = text.indexOf('**', boldOpen + 2);
    }

    const useBold =
      boldOpen !== -1 &&
      boldClose !== -1 &&
      (starOpen === -1 || boldOpen <= starOpen);

    if (useBold) {
      if (boldOpen > pos) {
        const chunk = text.slice(pos, boldOpen);
        if (chunk) nodes.push(chunk);
      }
      const inner = text.slice(boldOpen + 2, boldClose);
      const k = nextKey();
      nodes.push(
        <strong key={k}>
          {wrapInlineChildren(parseMarkdownInline(inner, `${k}-in`))}
        </strong>,
      );
      pos = boldClose + 2;
      continue;
    }

    if (starOpen !== -1) {
      const starClose = findSingleAsteriskClose(text, starOpen + 1);
      if (starClose !== -1) {
        if (starOpen > pos) {
          const chunk = text.slice(pos, starOpen);
          if (chunk) nodes.push(chunk);
        }
        const inner = text.slice(starOpen + 1, starClose);
        const k = nextKey();
        nodes.push(
          <em key={k}>
            {wrapInlineChildren(parseMarkdownInline(inner, `${k}-in`))}
          </em>,
        );
        pos = starClose + 1;
        continue;
      }
    }

    const rest = text.slice(pos);
    if (rest) nodes.push(rest);
    break;
  }

  return nodes;
}
