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

export interface MarkdownCitation {
  /** Citation id token matched in the source text — e.g. `doc:42#§3`. */
  id: string;
  /** Display label shown inside the chip. */
  label: string;
  /** Optional click handler — typically opens the source document. */
  onClick?: () => void;
}

export interface MarkdownProps {
  /** Markdown source string. */
  source: string;
  /**
   * Optional citation registry. Inline `[cite:<id>]` tokens are
   * replaced with `<CitationChip>`-styled inline chips that look up
   * the id here. Missing ids render as `[?]` to keep the failure mode
   * obvious.
   */
  citations?: MarkdownCitation[];
  className?: string;
  style?: CSSProperties;
}

/**
 * `<Markdown>` — pure-JS, dependency-free subset Markdown renderer with
 * inline citation support.
 *
 * Supports: # / ## / ### headings, **bold**, *italic*, `inline code`,
 * ```fenced code```, - / 1. lists, [link](url), `[cite:<id>]`.
 *
 * Tradeoffs: no GFM tables, no HTML pass-through (intentional for
 * safety), no nested lists. Hosts that need richer rendering should
 * compose a markdown pipeline (remark/rehype) and feed the result here
 * via a custom renderer.
 *
 * Wave 9.E.1.
 */
export function Markdown({
  source,
  citations,
  className,
  style,
}: MarkdownProps) {
  const blocks = parseBlocks(source);
  const map = new Map<string, MarkdownCitation>(
    (citations ?? []).map((c) => [c.id, c]),
  );
  return (
    <div
      data-testid="bl-markdown"
      className={className}
      style={{ fontSize: 14, lineHeight: 1.6, ...style }}
    >
      {blocks.map((b, i) => renderBlock(b, i, map))}
    </div>
  );
}

type Block =
  | { kind: 'heading'; level: 1 | 2 | 3; text: string }
  | { kind: 'paragraph'; text: string }
  | { kind: 'code'; lang: string; text: string }
  | { kind: 'list'; ordered: boolean; items: string[] };

function parseBlocks(source: string): Block[] {
  const lines = source.replace(/\r\n/g, '\n').split('\n');
  const out: Block[] = [];
  let i = 0;
  while (i < lines.length) {
    const line = lines[i];
    if (line === undefined) {
      i += 1;
      continue;
    }
    // Fenced code block.
    if (line.startsWith('```')) {
      const lang = line.slice(3).trim();
      const buf: string[] = [];
      i += 1;
      while (i < lines.length && !(lines[i] ?? '').startsWith('```')) {
        buf.push(lines[i] ?? '');
        i += 1;
      }
      out.push({ kind: 'code', lang, text: buf.join('\n') });
      i += 1; // skip closing fence
      continue;
    }
    // Heading.
    const h = /^(#{1,3})\s+(.+)$/.exec(line);
    if (h) {
      const level = h[1]?.length as 1 | 2 | 3;
      out.push({ kind: 'heading', level, text: h[2] ?? '' });
      i += 1;
      continue;
    }
    // List.
    if (/^[-*]\s+/.test(line) || /^\d+\.\s+/.test(line)) {
      const ordered = /^\d+\.\s+/.test(line);
      const items: string[] = [];
      while (
        i < lines.length &&
        ((ordered && /^\d+\.\s+/.test(lines[i] ?? '')) ||
          (!ordered && /^[-*]\s+/.test(lines[i] ?? '')))
      ) {
        const m = ordered
          ? /^\d+\.\s+(.*)$/.exec(lines[i] ?? '')
          : /^[-*]\s+(.*)$/.exec(lines[i] ?? '');
        items.push(m?.[1] ?? '');
        i += 1;
      }
      out.push({ kind: 'list', ordered, items });
      continue;
    }
    // Blank line.
    if (line.trim() === '') {
      i += 1;
      continue;
    }
    // Paragraph — consume until blank line or block start.
    const buf: string[] = [];
    while (
      i < lines.length &&
      (lines[i] ?? '').trim() !== '' &&
      !(lines[i] ?? '').startsWith('```') &&
      !/^(#{1,3})\s+/.test(lines[i] ?? '') &&
      !/^[-*]\s+/.test(lines[i] ?? '') &&
      !/^\d+\.\s+/.test(lines[i] ?? '')
    ) {
      buf.push(lines[i] ?? '');
      i += 1;
    }
    out.push({ kind: 'paragraph', text: buf.join('\n') });
  }
  return out;
}

function renderBlock(b: Block, key: number, cites: Map<string, MarkdownCitation>): ReactNode {
  switch (b.kind) {
    case 'heading': {
      const Tag = (`h${b.level}` as 'h1' | 'h2' | 'h3') as 'h1';
      const sizes = { 1: 22, 2: 18, 3: 16 } as const;
      return (
        <Tag
          key={key}
          style={{
            fontSize: sizes[b.level],
            fontWeight: 600,
            margin: '14px 0 6px',
          }}
        >
          {renderInline(b.text, cites)}
        </Tag>
      );
    }
    case 'paragraph':
      return (
        <p key={key} style={{ margin: '6px 0' }}>
          {renderInline(b.text, cites)}
        </p>
      );
    case 'code':
      return (
        <pre
          key={key}
          data-testid="bl-markdown-code"
          data-lang={b.lang || undefined}
          style={{
            margin: '8px 0',
            padding: 12,
            borderRadius: 10,
            background: 'var(--bl-surface-muted, #f6f6f6)',
            color: 'var(--bl-text-primary, #111)',
            fontFamily:
              'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
            fontSize: 12,
            lineHeight: 1.55,
            overflow: 'auto',
          }}
        >
          {b.text}
        </pre>
      );
    case 'list': {
      const Tag = b.ordered ? 'ol' : 'ul';
      return (
        <Tag
          key={key}
          style={{ margin: '6px 0 6px 20px', paddingLeft: 8 }}
        >
          {b.items.map((it, j) => (
            <li key={j} style={{ margin: '2px 0' }}>
              {renderInline(it, cites)}
            </li>
          ))}
        </Tag>
      );
    }
    default:
      return null;
  }
}

/**
 * Inline-token renderer. Tokens recognised:
 *   `code`            inline code
 *   **bold**          bold
 *   *italic*          italic
 *   [text](url)       link (opens in same tab, target=_self)
 *   [cite:<id>]       citation chip (resolved from registry)
 *
 * Implementation is a single pass with regex alternation — order
 * matters (inline code first to protect from formatting consumption).
 */
function renderInline(text: string, cites: Map<string, MarkdownCitation>): ReactNode[] {
  const out: ReactNode[] = [];
  // Tokenizer — single regex picks the FIRST matching pattern.
  const tokenRe =
    /(`[^`]+`)|(\*\*[^*]+\*\*)|(\*[^*]+\*)|(\[cite:[^\]]+\])|(\[[^\]]+\]\([^)]+\))/g;
  let last = 0;
  let m: RegExpExecArray | null;
  let key = 0;
  while ((m = tokenRe.exec(text))) {
    if (m.index > last) {
      out.push(text.slice(last, m.index));
    }
    const full = m[0];
    if (full.startsWith('`')) {
      out.push(
        <code
          key={key++}
          style={{
            background: 'var(--bl-surface-muted, #f0f0f0)',
            padding: '0 4px',
            borderRadius: 4,
            fontFamily: 'ui-monospace, monospace',
            fontSize: 12,
          }}
        >
          {full.slice(1, -1)}
        </code>,
      );
    } else if (full.startsWith('**')) {
      out.push(
        <strong key={key++}>{full.slice(2, -2)}</strong>,
      );
    } else if (full.startsWith('[cite:')) {
      const id = full.slice('[cite:'.length, -1);
      const c = cites.get(id);
      out.push(
        <CitationChip
          key={key++}
          id={id}
          label={c?.label}
          onClick={c?.onClick}
          missing={!c}
        />,
      );
    } else if (full.startsWith('[')) {
      const lm = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(full);
      if (lm) {
        out.push(
          <a
            key={key++}
            href={lm[2]}
            style={{
              color: 'var(--bl-accent, #6366f1)',
              textDecoration: 'underline',
              textUnderlineOffset: 2,
            }}
          >
            {lm[1]}
          </a>,
        );
      } else {
        out.push(full);
      }
    } else if (full.startsWith('*')) {
      out.push(<em key={key++}>{full.slice(1, -1)}</em>);
    }
    last = m.index + full.length;
  }
  if (last < text.length) {
    out.push(text.slice(last));
  }
  return out;
}

function CitationChip({
  id,
  label,
  onClick,
  missing,
}: {
  id: string;
  label?: string;
  onClick?: () => void;
  missing: boolean;
}) {
  const text = missing ? '[?]' : label ?? id;
  return (
    <button
      type="button"
      onClick={onClick}
      data-testid="bl-markdown-citation"
      data-id={id}
      data-missing={missing ? 'true' : 'false'}
      aria-label={`Citation ${id}`}
      disabled={!onClick}
      style={{
        display: 'inline-block',
        margin: '0 2px',
        padding: '0 6px',
        borderRadius: 999,
        border: '1px solid color-mix(in srgb, var(--bl-accent, #6366f1) 35%, transparent)',
        background: missing
          ? 'var(--bl-surface-muted, #f0f0f0)'
          : 'color-mix(in srgb, var(--bl-accent, #6366f1) 14%, transparent)',
        color: missing
          ? 'var(--bl-text-tertiary, #888)'
          : 'var(--bl-accent, #4338ca)',
        font: 'inherit',
        fontSize: 11,
        fontWeight: 500,
        cursor: onClick ? 'pointer' : 'default',
        verticalAlign: 'baseline',
      }}
    >
      {text}
    </button>
  );
}
