import React, { Fragment } from 'react';
/**
 * Tiny self-contained Markdown-to-React renderer.
 *
 * Built specifically for the AI Visibility article output produced by the
 * agent backend (Gemini-generated long-form posts). Supports the subset
 * we actually see in practice - headings, paragraphs, ordered / unordered
 * lists, blockquotes, fenced code blocks, inline ``code``, **bold**,
 * *italic*, links and images. No third-party markdown library so bundle
 * weight stays minimal.
 *
 * Output is structured React elements - no ``dangerouslySetInnerHTML``.
 */

/** One entry in the TOC derived from article markdown. */
export interface TocEntry {
  level: number;
  text: string;
  slug: string;
}

export const slugifyHeading = (text: string): string => {
  const normalised = text.normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
  const slug = normalised.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');

  return slug || 'section';
};

export const extractHeadings = (source: string): TocEntry[] => {
  if (!source || typeof source !== 'string') return [];
  const out: TocEntry[] = [];
  const seen = new Map<string, number>();
  for (const line of source.replace(/\r\n/g, '\n').split('\n')) {
    const heading = /^(#{1,6})\s+(.*)$/.exec(line);
    if (!heading) continue;
    const level = heading[1].length;
    const text = heading[2]
      .replace(/`([^`]+)`/g, '$1')
      .replace(/\*\*([^*]+)\*\*/g, '$1')
      .replace(/\*([^*]+)\*/g, '$1')
      .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
      .trim();
    if (!text) continue;
    const base = slugifyHeading(text);
    const count = (seen.get(base) ?? 0) + 1;
    seen.set(base, count);
    const slug = count > 1 ? `${base}-${count}` : base;
    out.push({ level, text, slug });
  }

  return out;
};

interface MarkdownViewProps {
  source: string;
}

const renderInline = (text: string, keyPrefix: string): React.ReactNode[] => {
  const tokens: {
    regex: RegExp;
    render: (m: RegExpExecArray, key: string) => React.ReactNode;
  }[] = [
    {
      regex: /!\[([^\]]*)\]\(([^)]+)\)/,
      render: (m, key) => (
        <img
          key={key}
          src={m[2]}
          alt={m[1]}
          style={{ maxWidth: '100%', borderRadius: 8, margin: '8px 0' }}
        />
      ),
    },
    {
      regex: /\[([^\]]+)\]\(([^)]+)\)/,
      render: (m, key) => (
        <a
          key={key}
          href={m[2]}
          target="_blank"
          rel="noreferrer noopener"
          style={{ color: '#B7007C', textDecoration: 'underline' }}
        >
          {m[1]}
        </a>
      ),
    },
    {
      regex: /`([^`]+)`/,
      render: (m, key) => (
        <code
          key={key}
          style={{
            background: '#f4f6f8',
            padding: '1px 6px',
            borderRadius: 4,
            fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
            fontSize: '0.92em',
          }}
        >
          {m[1]}
        </code>
      ),
    },
    {
      regex: /\*\*([^*]+)\*\*/,
      render: (m, key) => <strong key={key}>{m[1]}</strong>,
    },
    {
      regex: /\*([^*]+)\*/,
      render: (m, key) => <em key={key}>{m[1]}</em>,
    },
  ];

  const walk = (input: string, depth: number): React.ReactNode[] => {
    if (!input) return [];
    let earliest: { token: (typeof tokens)[0]; match: RegExpExecArray } | null =
      null;
    for (const token of tokens) {
      const match = token.regex.exec(input);
      if (match && (!earliest || match.index < earliest.match.index)) {
        earliest = { token, match };
      }
    }
    if (!earliest) return [input];

    const before = input.slice(0, earliest.match.index);
    const after = input.slice(earliest.match.index + earliest.match[0].length);
    const node = earliest.token.render(
      earliest.match,
      `${keyPrefix}-${depth}-${earliest.match.index}`
    );

    return [...(before ? [before] : []), node, ...walk(after, depth + 1)];
  };

  return walk(text, 0);
};

const HEADING_STYLES: Record<number, React.CSSProperties> = {
  1: { fontSize: 32, fontWeight: 700, margin: '24px 0 16px', lineHeight: 1.2 },
  2: { fontSize: 24, fontWeight: 700, margin: '32px 0 12px', lineHeight: 1.25 },
  3: { fontSize: 20, fontWeight: 600, margin: '24px 0 10px', lineHeight: 1.3 },
  4: { fontSize: 18, fontWeight: 600, margin: '20px 0 8px' },
  5: { fontSize: 16, fontWeight: 600, margin: '16px 0 6px' },
  6: { fontSize: 14, fontWeight: 600, margin: '14px 0 6px' },
};

const MarkdownView = ({ source }: MarkdownViewProps): JSX.Element => {
  if (!source || typeof source !== 'string') {
    return <></>;
  }
  const blocks: React.ReactNode[] = [];
  const lines = source.replace(/\r\n/g, '\n').split('\n');
  const seenSlugs = new Map<string, number>();

  let i = 0;
  let blockKey = 0;
  while (i < lines.length) {
    const line = lines[i];

    if (/^```/.test(line)) {
      const collected: string[] = [];
      i++;
      while (i < lines.length && !/^```/.test(lines[i])) {
        collected.push(lines[i]);
        i++;
      }
      i++;
      blocks.push(
        <pre
          key={`b-${blockKey++}`}
          style={{
            background: '#f4f6f8',
            padding: 16,
            borderRadius: 8,
            overflowX: 'auto',
            fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
            fontSize: 13,
          }}
        >
          <code>{collected.join('\n')}</code>
        </pre>
      );
      continue;
    }

    if (/^\s*---+\s*$/.test(line)) {
      blocks.push(
        <hr
          key={`b-${blockKey++}`}
          style={{
            border: 'none',
            borderTop: '1px solid #e1e3e5',
            margin: '32px 0',
          }}
        />
      );
      i++;
      continue;
    }

    const heading = /^(#{1,6})\s+(.*)$/.exec(line);
    if (heading) {
      const level = heading[1].length;
      const text = heading[2].trim();
      const base = slugifyHeading(
        text
          .replace(/`([^`]+)`/g, '$1')
          .replace(/\*\*([^*]+)\*\*/g, '$1')
          .replace(/\*([^*]+)\*/g, '$1')
          .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
      );
      const count = (seenSlugs.get(base) ?? 0) + 1;
      seenSlugs.set(base, count);
      const slug = count > 1 ? `${base}-${count}` : base;
      const Tag = `h${level}` as keyof JSX.IntrinsicElements;
      blocks.push(
        <Tag
          key={`b-${blockKey++}`}
          id={slug}
          style={{ ...HEADING_STYLES[level], scrollMarginTop: 16 }}
        >
          {renderInline(text, `h${blockKey}`)}
        </Tag>
      );
      i++;
      continue;
    }

    if (/^>\s?/.test(line)) {
      const collected: string[] = [];
      while (i < lines.length && /^>\s?/.test(lines[i])) {
        collected.push(lines[i].replace(/^>\s?/, ''));
        i++;
      }
      blocks.push(
        <blockquote
          key={`b-${blockKey++}`}
          style={{
            borderLeft: '4px solid #B7007C',
            background: '#fff7fb',
            padding: '12px 16px',
            margin: '16px 0',
            color: '#4d4d4d',
          }}
        >
          {collected.map((text, idx) => (
            <p key={idx} style={{ margin: idx === 0 ? 0 : '8px 0 0' }}>
              {renderInline(text, `bq${blockKey}-${idx}`)}
            </p>
          ))}
        </blockquote>
      );
      continue;
    }

    if (/^\d+\.\s+/.test(line)) {
      const items: string[] = [];
      while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
        items.push(lines[i].replace(/^\d+\.\s+/, ''));
        i++;
      }
      blocks.push(
        <ol
          key={`b-${blockKey++}`}
          style={{ margin: '12px 0 12px 24px', lineHeight: 1.6 }}
        >
          {items.map((item, idx) => (
            <li key={idx} style={{ margin: '4px 0' }}>
              {renderInline(item, `ol${blockKey}-${idx}`)}
            </li>
          ))}
        </ol>
      );
      continue;
    }

    if (/^[-*+]\s+/.test(line)) {
      const items: string[] = [];
      while (i < lines.length && /^[-*+]\s+/.test(lines[i])) {
        items.push(lines[i].replace(/^[-*+]\s+/, ''));
        i++;
      }
      blocks.push(
        <ul
          key={`b-${blockKey++}`}
          style={{ margin: '12px 0 12px 24px', lineHeight: 1.6 }}
        >
          {items.map((item, idx) => (
            <li key={idx} style={{ margin: '4px 0' }}>
              {renderInline(item, `ul${blockKey}-${idx}`)}
            </li>
          ))}
        </ul>
      );
      continue;
    }

    if (line.trim() === '') {
      i++;
      continue;
    }

    const para: string[] = [line];
    i++;
    while (
      i < lines.length &&
      lines[i].trim() !== '' &&
      !/^(#{1,6})\s+/.test(lines[i]) &&
      !/^>\s?/.test(lines[i]) &&
      !/^\d+\.\s+/.test(lines[i]) &&
      !/^[-*+]\s+/.test(lines[i]) &&
      !/^```/.test(lines[i]) &&
      !/^\s*---+\s*$/.test(lines[i])
    ) {
      para.push(lines[i]);
      i++;
    }
    const paraKey = `b-${blockKey++}`;
    blocks.push(
      <p
        key={paraKey}
        style={{ margin: '12px 0', lineHeight: 1.65, fontSize: 16 }}
      >
        {para
          .flatMap((segment, idx) => [
            ...(idx > 0 ? [<br key={`br-${idx}`} />] : []),
            ...renderInline(segment, `${paraKey}-${idx}`),
          ])
          .map((node, idx) => (
            <Fragment key={idx}>{node}</Fragment>
          ))}
      </p>
    );
  }

  return <div style={{ color: '#202223' }}>{blocks}</div>;
};

/**
 * Escape the five HTML special characters so user-supplied markdown text
 * can never inject raw markup into the serialized output.
 */
const escapeHtml = (input: string): string =>
  input
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');

const TABLE_LINE = /^\s*\|.*\|\s*$/;
const TABLE_SEPARATOR = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)+\|?\s*$/;
type TableAlign = 'left' | 'right' | 'center' | null;

const splitTableRow = (line: string): string[] => {
  let s = line.trim();
  if (s.startsWith('|')) s = s.slice(1);
  if (s.endsWith('|')) s = s.slice(0, -1);
  return s.split('|').map(c => c.trim());
};

const columnAlignments = (sepLine: string): TableAlign[] =>
  splitTableRow(sepLine).map(cell => {
    const left = cell.startsWith(':');
    const right = cell.endsWith(':');
    if (left && right) return 'center';
    if (right) return 'right';
    if (left) return 'left';
    return null;
  });

const inlineToHtml = (text: string): string => {
  const tokens: {
    regex: RegExp;
    render: (m: RegExpExecArray) => string;
  }[] = [
    {
      regex: /!\[([^\]]*)\]\(([^)]+)\)/,
      render: m =>
        `<img src="${escapeHtml(m[2])}" alt="${escapeHtml(m[1])}" />`,
    },
    {
      regex: /\[([^\]]+)\]\(([^)]+)\)/,
      render: m =>
        `<a href="${escapeHtml(m[2])}" target="_blank" rel="noreferrer noopener">${inlineToHtml(m[1])}</a>`,
    },
    {
      regex: /`([^`]+)`/,
      render: m => `<code>${escapeHtml(m[1])}</code>`,
    },
    {
      regex: /\*\*([^*]+)\*\*/,
      render: m => `<strong>${inlineToHtml(m[1])}</strong>`,
    },
    {
      regex: /\*([^*]+)\*/,
      render: m => `<em>${inlineToHtml(m[1])}</em>`,
    },
  ];

  const walk = (input: string): string => {
    if (!input) return '';
    let earliest: { token: (typeof tokens)[0]; match: RegExpExecArray } | null =
      null;
    for (const token of tokens) {
      const match = token.regex.exec(input);
      if (match && (!earliest || match.index < earliest.match.index)) {
        earliest = { token, match };
      }
    }
    if (!earliest) return escapeHtml(input);
    const before = input.slice(0, earliest.match.index);
    const after = input.slice(earliest.match.index + earliest.match[0].length);
    return (
      escapeHtml(before) + earliest.token.render(earliest.match) + walk(after)
    );
  };

  return walk(text);
};

/**
 * Serialize markdown to a clean HTML string suitable for pasting into a
 * WordPress / Gutenberg post body or any CMS that accepts safe inline
 * HTML. Output is a sequence of stacked block elements (h1..h6, p,
 * ul/ol, blockquote, pre/code, hr, table) - no outer html/body wrapper,
 * no scripts. Heading ids match the slugs produced by
 * {@link slugifyHeading} so anchor links round-trip with the in-app
 * preview.
 */
export const markdownToHtml = (source: string): string => {
  if (!source || typeof source !== 'string') return '';
  const out: string[] = [];
  const lines = source.replace(/\r\n/g, '\n').split('\n');
  const seenSlugs = new Map<string, number>();

  let i = 0;
  while (i < lines.length) {
    const line = lines[i];

    if (/^```/.test(line)) {
      const collected: string[] = [];
      i++;
      while (i < lines.length && !/^```/.test(lines[i])) {
        collected.push(lines[i]);
        i++;
      }
      i++;
      out.push(`<pre><code>${escapeHtml(collected.join('\n'))}</code></pre>`);
      continue;
    }

    if (/^\s*---+\s*$/.test(line)) {
      out.push('<hr />');
      i++;
      continue;
    }

    const heading = /^(#{1,6})\s+(.*)$/.exec(line);
    if (heading) {
      const level = heading[1].length;
      const text = heading[2].trim();
      const base = slugifyHeading(
        text
          .replace(/`([^`]+)`/g, '$1')
          .replace(/\*\*([^*]+)\*\*/g, '$1')
          .replace(/\*([^*]+)\*/g, '$1')
          .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1')
      );
      const count = (seenSlugs.get(base) ?? 0) + 1;
      seenSlugs.set(base, count);
      const slug = count > 1 ? `${base}-${count}` : base;
      out.push(
        `<h${level} id="${escapeHtml(slug)}">${inlineToHtml(text)}</h${level}>`
      );
      i++;
      continue;
    }

    if (/^>\s?/.test(line)) {
      const collected: string[] = [];
      while (i < lines.length && /^>\s?/.test(lines[i])) {
        collected.push(lines[i].replace(/^>\s?/, ''));
        i++;
      }
      const inner = collected.map(t => `<p>${inlineToHtml(t)}</p>`).join('');
      out.push(`<blockquote>${inner}</blockquote>`);
      continue;
    }

    if (/^\d+\.\s+/.test(line)) {
      const items: string[] = [];
      while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
        items.push(lines[i].replace(/^\d+\.\s+/, ''));
        i++;
      }
      out.push(
        `<ol>${items.map(t => `<li>${inlineToHtml(t)}</li>`).join('')}</ol>`
      );
      continue;
    }

    if (/^[-*+]\s+/.test(line)) {
      const items: string[] = [];
      while (i < lines.length && /^[-*+]\s+/.test(lines[i])) {
        items.push(lines[i].replace(/^[-*+]\s+/, ''));
        i++;
      }
      out.push(
        `<ul>${items.map(t => `<li>${inlineToHtml(t)}</li>`).join('')}</ul>`
      );
      continue;
    }

    if (
      TABLE_LINE.test(line) &&
      i + 1 < lines.length &&
      TABLE_SEPARATOR.test(lines[i + 1])
    ) {
      const headers = splitTableRow(line);
      const aligns = columnAlignments(lines[i + 1]);
      i += 2;
      const bodyRows: string[][] = [];
      while (i < lines.length && TABLE_LINE.test(lines[i])) {
        bodyRows.push(splitTableRow(lines[i]));
        i++;
      }
      const styleFor = (a: TableAlign): string =>
        a ? ` style="text-align:${a}"` : '';
      const head = headers
        .map((h, idx) => `<th${styleFor(aligns[idx])}>${inlineToHtml(h)}</th>`)
        .join('');
      const body = bodyRows
        .map(
          row =>
            `<tr>${row
              .map(
                (c, cIdx) =>
                  `<td${styleFor(aligns[cIdx])}>${inlineToHtml(c)}</td>`
              )
              .join('')}</tr>`
        )
        .join('');
      out.push(
        `<table><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table>`
      );
      continue;
    }

    if (line.trim() === '') {
      i++;
      continue;
    }

    const para: string[] = [line];
    i++;
    while (
      i < lines.length &&
      lines[i].trim() !== '' &&
      !/^(#{1,6})\s+/.test(lines[i]) &&
      !/^>\s?/.test(lines[i]) &&
      !/^\d+\.\s+/.test(lines[i]) &&
      !/^[-*+]\s+/.test(lines[i]) &&
      !/^```/.test(lines[i]) &&
      !/^\s*---+\s*$/.test(lines[i]) &&
      !(
        TABLE_LINE.test(lines[i]) &&
        i + 1 < lines.length &&
        TABLE_SEPARATOR.test(lines[i + 1])
      )
    ) {
      para.push(lines[i]);
      i++;
    }
    out.push(`<p>${para.map(inlineToHtml).join('<br />')}</p>`);
  }

  return out.join('\n');
};

/**
 * Render a markdown source into a plain-text string suitable for the
 * clipboard's ``text/plain`` slot. Reuses {@link markdownToHtml} so any future
 * formatting tweak only has to land in one place, then collapses the resulting
 * tags into characters. Block elements (``<p>``, ``<li>``, ``<h1..6>``,
 * ``<br />``) become single newlines so a paste into Word / Notepad / Gmail
 * keeps its paragraph breaks instead of collapsing into one run-on line.
 *
 * @param {string} source - Markdown source text.
 * @returns {string} Plain-text rendering with paragraph breaks preserved.
 */
export const markdownToPlainText = (source: string): string => {
  if (!source || typeof source !== 'string') return '';
  if (typeof window === 'undefined' || typeof DOMParser === 'undefined') {
    return markdownToHtml(source)
      .replace(/<br\s*\/?>/gi, '\n')
      .replace(/<\/(p|div|li|h[1-6])>/gi, '\n')
      .replace(/<[^>]+>/g, '')
      .replace(/&nbsp;/g, ' ')
      .replace(/&amp;/g, '&')
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')
      .replace(/&quot;/g, '"')
      .replace(/\n{3,}/g, '\n\n')
      .trim();
  }
  const htmlWithBreaks = markdownToHtml(source)
    .replace(/<br\s*\/?>/gi, '\n')
    .replace(/<\/(p|div|li|h[1-6])>/gi, '$&\n');
  const parsed = new DOMParser().parseFromString(htmlWithBreaks, 'text/html');
  const text = parsed.body.textContent || '';

  return text.replace(/\n{3,}/g, '\n\n').trim();
};

export default MarkdownView;
