import { marked, type Tokens, type RendererObject } from 'marked';
import { highlight } from 'cli-highlight';

// Helper to convert HTML to terminal-friendly text
function convertHtmlToTerminal(text: string): string {
  return text
    // Line breaks
    .replace(/<br\s*\/?>/gi, '\n')
    // Block elements to newlines
    .replace(/<\/(?:p|div|section|article)>/gi, '\n')
    .replace(/<(?:p|div|section|article)[^>]*>/gi, '')
    // Inline formatting - bold
    .replace(/<(?:b|strong)[^>]*>(.*?)<\/(?:b|strong)>/gi, '\x1b[1m$1\x1b[0m')
    // Inline formatting - italic
    .replace(/<(?:i|em)[^>]*>(.*?)<\/(?:i|em)>/gi, '\x1b[3m$1\x1b[0m')
    // Inline formatting - code
    .replace(/<code[^>]*>(.*?)<\/code>/gi, '\x1b[36m$1\x1b[0m')
    // Links - show text with URL
    .replace(/<a[^>]*href=["']([^"']*)["'][^>]*>(.*?)<\/a>/gi, '\x1b[4m$2\x1b[0m (\x1b[34m$1\x1b[0m)')
    // Headings
    .replace(/<h[1-6][^>]*>(.*?)<\/h[1-6]>/gi, '\x1b[1m\x1b[4m$1\x1b[0m\n')
    // Lists
    .replace(/<li[^>]*>(.*?)<\/li>/gi, '  • $1\n')
    .replace(/<\/?(?:ul|ol)[^>]*>/gi, '')
    // Blockquotes
    .replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gi, '  │ $1\n')
    // Horizontal rules
    .replace(/<hr\s*\/?>/gi, '─'.repeat(40) + '\n')
    // Strip any remaining HTML tags
    .replace(/<[^>]+>/g, '');
}

// Custom renderer as an object for marked.use()
// Methods that contain inline children use this.parser.parseInline(tokens)
// Methods that contain block children use this.parser.parse(tokens)
const terminalRenderer: RendererObject = {
  code({ text, lang }: Tokens.Code): string {
    try {
      return '\n' + highlight(text, { language: lang || 'plaintext' }) + '\n';
    } catch {
      return '\n' + text + '\n';
    }
  },

  codespan({ text }: Tokens.Codespan): string {
    return `\x1b[36m${text}\x1b[0m`; // Cyan for inline code
  },

  strong({ text }: Tokens.Strong): string {
    return `\x1b[1m${text}\x1b[0m`; // Bold
  },

  em({ text }: Tokens.Em): string {
    return `\x1b[3m${text}\x1b[0m`; // Italic
  },

  paragraph({ tokens }: Tokens.Paragraph): string {
    return this.parser.parseInline(tokens) + '\n';
  },

  list({ items, ordered, start }: Tokens.List): string {
    let body = '';
    for (let i = 0; i < items.length; i++) {
      const item = items[i];
      const bullet = ordered ? `${(start || 1) + i}. ` : '  • ';
      const content = this.parser.parse(item.tokens).replace(/\n$/, '');
      body += bullet + content + '\n';
    }
    return body;
  },

  listitem({ tokens }: Tokens.ListItem): string {
    return this.parser.parse(tokens);
  },

  heading({ tokens, depth }: Tokens.Heading): string {
    const text = this.parser.parseInline(tokens);
    const prefix = depth <= 2 ? '' : '#'.repeat(depth) + ' ';
    return `\x1b[1m\x1b[4m${prefix}${text}\x1b[0m\n`; // Bold + underline
  },

  link({ text, href }: Tokens.Link): string {
    return `\x1b[4m${text}\x1b[0m (\x1b[34m${href}\x1b[0m)`; // Underline text, blue URL
  },

  blockquote({ tokens }: Tokens.Blockquote): string {
    const body = this.parser.parse(tokens);
    return body.split('\n').map((line: string) => `  │ ${line}`).join('\n') + '\n';
  },

  table({ header, rows, align }: Tokens.Table): string {
    // Render cell contents
    const headerTexts = header.map((cell: Tokens.TableCell) =>
      this.parser.parseInline(cell.tokens),
    );
    const rowTexts = rows.map((row: Tokens.TableCell[]) =>
      row.map((cell: Tokens.TableCell) => this.parser.parseInline(cell.tokens)),
    );

    // Calculate column widths (strip ANSI for measurement)
    const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*m/g, '');
    const colWidths = headerTexts.map((h: string, i: number) => {
      const cellWidths = rowTexts.map((row: string[]) => stripAnsi(row[i] || '').length);
      return Math.max(stripAnsi(h).length, ...cellWidths);
    });

    // Pad cell to width respecting ANSI codes
    const pad = (text: string, width: number, alignment: string | null) => {
      const visible = stripAnsi(text).length;
      const diff = width - visible;
      if (diff <= 0) {return text;}
      if (alignment === 'right') {return ' '.repeat(diff) + text;}
      if (alignment === 'center') {
        const left = Math.floor(diff / 2);
        return ' '.repeat(left) + text + ' '.repeat(diff - left);
      }
      return text + ' '.repeat(diff);
    };

    // Build header row
    const headerRow = ' ' + headerTexts.map((h: string, i: number) =>
      `\x1b[1m${pad(h, colWidths[i], align[i])}\x1b[0m`,
    ).join(' │ ');

    // Build separator
    const separator = '─' + colWidths.map((w: number) => '─'.repeat(w)).join('─┼─') + '─';

    // Build data rows
    const dataRows = rowTexts.map((row: string[]) =>
      ' ' + row.map((cell: string, i: number) =>
        pad(cell, colWidths[i], align[i]),
      ).join(' │ '),
    );

    return [headerRow, separator, ...dataRows].join('\n') + '\n';
  },

  hr(): string {
    return '─'.repeat(40) + '\n';
  },

  html({ text }: Tokens.HTML | Tokens.Tag): string {
    return decodeHtmlEntities(convertHtmlToTerminal(text));
  },
};

// Configure marked with our renderer
marked.use({ renderer: terminalRenderer });

function decodeHtmlEntities(text: string): string {
  return text
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&amp;/g, '&')
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/&nbsp;/g, ' ');
}

export function renderMarkdown(text: string): string {
  try {
    const rendered = marked.parse(text);
    // marked.parse can return string | Promise<string>, we only use sync
    if (typeof rendered === 'string') {
      return decodeHtmlEntities(rendered).trim();
    }
    return text;
  } catch {
    return text;
  }
}
