// HyperBook — tiny markdown renderer
// Handles: # / ## / ### headings, ```fenced code (with optional lang),
// - bullet and 1. numbered lists, > blockquotes, `inline code`, *italic*,
// **bold**, [links](url), --- rule, and paragraphs.
// Designed to look right inside the HIBI editorial system.

const { useMemo: useMemoMD } = React;

// ─────────────────────────────────────────────────────────────────────────
// Block parser — pure data; renderer separate.
// ─────────────────────────────────────────────────────────────────────────

function parseBlocks(src) {
  const lines = src.replace(/\r\n/g, '\n').split('\n');
  const blocks = [];
  let i = 0;

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

    // skip blank lines between blocks
    if (!line.trim()) { i++; continue; }

    // fenced code block
    const fence = line.match(/^```([\w-]*)\s*$/);
    if (fence) {
      const lang = fence[1] || '';
      const codeLines = [];
      i++;
      while (i < lines.length && !/^```\s*$/.test(lines[i])) {
        codeLines.push(lines[i]); i++;
      }
      i++; // closing fence
      blocks.push({ type: 'code', lang, value: codeLines.join('\n') });
      continue;
    }

    // headings
    const h = line.match(/^(#{1,3})\s+(.*)$/);
    if (h) {
      blocks.push({ type: 'heading', level: h[1].length, value: h[2].trim() });
      i++; continue;
    }

    // horizontal rule
    if (/^---+\s*$/.test(line)) {
      blocks.push({ type: 'rule' });
      i++; continue;
    }

    // blockquote — group consecutive '>' lines
    if (/^>\s?/.test(line)) {
      const buf = [];
      while (i < lines.length && /^>\s?/.test(lines[i])) {
        buf.push(lines[i].replace(/^>\s?/, ''));
        i++;
      }
      blocks.push({ type: 'quote', value: buf.join(' ') });
      continue;
    }

    // bullet list
    if (/^[-*]\s+/.test(line)) {
      const items = [];
      while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
        items.push(lines[i].replace(/^[-*]\s+/, ''));
        i++;
      }
      blocks.push({ type: 'ul', items });
      continue;
    }

    // ordered list
    if (/^\d+\.\s+/.test(line)) {
      const items = [];
      while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
        items.push(lines[i].replace(/^\d+\.\s+/, ''));
        i++;
      }
      blocks.push({ type: 'ol', items });
      continue;
    }

    // paragraph — collect consecutive non-blank, non-special lines
    const buf = [];
    while (
      i < lines.length &&
      lines[i].trim() &&
      !/^(#{1,3})\s+/.test(lines[i]) &&
      !/^```/.test(lines[i]) &&
      !/^>\s?/.test(lines[i]) &&
      !/^[-*]\s+/.test(lines[i]) &&
      !/^\d+\.\s+/.test(lines[i]) &&
      !/^---+\s*$/.test(lines[i])
    ) {
      buf.push(lines[i]); i++;
    }
    blocks.push({ type: 'p', value: buf.join(' ') });
  }

  return blocks;
}

// ─────────────────────────────────────────────────────────────────────────
// Inline parser — minimal, returns array of React nodes.
// Order matters: code first (so its content isn't re-parsed), then bold,
// italic, links.
// ─────────────────────────────────────────────────────────────────────────

function parseInline(text, theme, keyPrefix = 'i') {
  const out = [];
  let rest = text;
  let key = 0;

  // Tokenize iteratively by finding the *next* match across patterns.
  const patterns = [
    { re: /`([^`]+)`/, kind: 'code' },
    { re: /\*\*([^*]+)\*\*/, kind: 'bold' },
    { re: /\*([^*]+)\*/, kind: 'em' },
    { re: /\[([^\]]+)\]\(([^)]+)\)/, kind: 'link' },
  ];

  while (rest.length) {
    let best = null;
    for (const p of patterns) {
      const m = rest.match(p.re);
      if (m && (best === null || m.index < best.m.index)) best = { p, m };
    }
    if (!best) { out.push(rest); break; }
    const { p, m } = best;
    if (m.index > 0) out.push(rest.slice(0, m.index));
    const k = `${keyPrefix}-${key++}`;
    if (p.kind === 'code') {
      out.push(<code key={k} style={theme.inlineCode}>{m[1]}</code>);
    } else if (p.kind === 'bold') {
      out.push(<strong key={k} style={{ fontWeight: 700, color: theme.fg }}>{m[1]}</strong>);
    } else if (p.kind === 'em') {
      out.push(<em key={k} style={{ fontStyle: 'italic' }}>{m[1]}</em>);
    } else if (p.kind === 'link') {
      out.push(
        <a key={k} href={m[2]} style={{
          color: theme.accent, textDecoration: 'underline',
          textDecorationThickness: '1px', textUnderlineOffset: 2,
        }}>{m[1]}</a>
      );
    }
    rest = rest.slice(m.index + m[0].length);
  }
  return out;
}

// ─────────────────────────────────────────────────────────────────────────
// Theme — passed in by callers so mobile + desktop can override sizes.
// ─────────────────────────────────────────────────────────────────────────

function buildTheme({ lampMode, typeSize, accent, fg, fg2, fg3, scale = 1 }) {
  const border = lampMode ? 'rgba(246,241,229,0.28)' : 'var(--ink)';
  const codeBg = lampMode ? '#0E0B0A' : '#F1EAD7';
  const codeBorder = lampMode ? 'rgba(246,241,229,0.18)' : 'var(--ink)';
  const codeFg = lampMode ? '#F1EAD7' : '#1F1A18';
  return {
    lampMode, accent, fg, fg2, fg3, border, scale, typeSize,
    p: {
      fontFamily: 'var(--font-serif)', fontWeight: 400,
      fontSize: typeSize, lineHeight: 1.62, color: fg2,
      margin: '0 0 22px', textWrap: 'pretty',
    },
    h2: {
      fontFamily: 'var(--font-serif)', fontWeight: 600, fontStyle: 'normal',
      fontSize: Math.round(typeSize * 1.55), lineHeight: 1.15,
      letterSpacing: '-0.01em', color: fg,
      margin: '36px 0 14px', textWrap: 'balance',
    },
    h3: {
      fontFamily: 'var(--font-serif)', fontWeight: 600, fontStyle: 'italic',
      fontSize: Math.round(typeSize * 1.22), lineHeight: 1.22,
      color: fg, margin: '28px 0 10px', textWrap: 'balance',
    },
    h4Eyebrow: {  // a chrome label that sits above h2
      fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '.24em',
      textTransform: 'uppercase', color: accent,
      margin: '0 0 -6px',
    },
    inlineCode: {
      fontFamily: 'var(--font-mono)', fontSize: typeSize * 0.92,
      background: codeBg, color: codeFg,
      padding: '1px 5px 0',
      border: `1px solid ${codeBorder}`,
      borderRadius: 0,
      whiteSpace: 'nowrap',
    },
    codeBlockWrap: {
      margin: '8px 0 24px',
      border: `1.5px solid ${codeBorder}`,
      background: codeBg,
      boxShadow: lampMode ? '3px 3px 0 rgba(217,107,39,0.35)' : '3px 3px 0 var(--mustard)',
      position: 'relative',
    },
    codeChrome: {
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      padding: '4px 10px 3px',
      borderBottom: `1px solid ${codeBorder}`,
      background: lampMode ? '#070605' : '#E8DEC9',
    },
    codeChromeLabel: {
      fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '.22em',
      textTransform: 'uppercase',
      color: lampMode ? 'rgba(246,241,229,0.7)' : 'var(--ink)',
      whiteSpace: 'nowrap',
    },
    codeChromeDot: {
      fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '.22em',
      color: lampMode ? 'rgba(246,241,229,0.45)' : 'var(--ink-mute)',
    },
    codePre: {
      margin: 0, padding: '12px 14px 14px',
      fontFamily: 'var(--font-mono)',
      fontSize: typeSize * 0.92,
      lineHeight: 1.42,
      color: codeFg,
      whiteSpace: 'pre',
      overflowX: 'auto',
    },
    quote: {
      margin: '8px 0 24px',
      paddingLeft: 18,
      borderLeft: `2px solid ${accent}`,
      fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 500,
      fontSize: Math.round(typeSize * 1.05), lineHeight: 1.45, color: fg,
    },
    li: {
      fontFamily: 'var(--font-serif)', fontWeight: 400,
      fontSize: typeSize, lineHeight: 1.5, color: fg2,
      textWrap: 'pretty',
    },
    rule: {
      border: 'none',
      borderTop: `1px solid ${border}`,
      opacity: 0.4,
      margin: '28px 0',
    },
  };
}

// ─────────────────────────────────────────────────────────────────────────
// Markdown component — accepts text and theme; renders blocks.
// Special hooks: dropCap (apply on first paragraph), pullQuoteAfter
// (inject a pull quote element after the Nth paragraph).
// ─────────────────────────────────────────────────────────────────────────

function Markdown({ text, theme, dropCap = false, pullQuoteAfter = -1, pullQuoteEl = null }) {
  const blocks = useMemoMD(() => parseBlocks(text), [text]);

  let paraSeen = 0;
  const out = [];

  blocks.forEach((b, idx) => {
    const key = `b-${idx}`;
    if (b.type === 'p') {
      paraSeen += 1;
      const isFirstPara = paraSeen === 1 && dropCap;
      if (isFirstPara) {
        const first = b.value.charAt(0);
        const rest = b.value.slice(1);
        out.push(
          <p key={key} style={theme.p}>
            <span style={{
              float: 'left',
              fontFamily: 'var(--font-serif)', fontStyle: 'italic', fontWeight: 600,
              fontSize: theme.typeSize * (theme.scale === 'desktop' ? 4.2 : 3.4),
              lineHeight: 0.86,
              color: theme.accent,
              marginRight: theme.scale === 'desktop' ? 10 : 8,
              marginTop: theme.scale === 'desktop' ? 6 : 4,
              marginBottom: -2,
            }}>{first}</span>
            {parseInline(rest, theme, key)}
          </p>
        );
      } else {
        out.push(<p key={key} style={theme.p}>{parseInline(b.value, theme, key)}</p>);
      }
      if (paraSeen === pullQuoteAfter && pullQuoteEl) {
        out.push(React.cloneElement(pullQuoteEl, { key: `${key}-pq` }));
      }
      return;
    }
    if (b.type === 'heading') {
      if (b.level === 1 || b.level === 2) {
        out.push(<h2 key={key} style={theme.h2}>{parseInline(b.value, theme, key)}</h2>);
      } else {
        out.push(<h3 key={key} style={theme.h3}>{parseInline(b.value, theme, key)}</h3>);
      }
      return;
    }
    if (b.type === 'code') {
      out.push(
        <div key={key} style={theme.codeBlockWrap}>
          <div style={theme.codeChrome}>
            <span style={theme.codeChromeLabel}>
              {b.lang ? `code · ${b.lang}` : 'code'}
            </span>
            <span style={theme.codeChromeDot}>●  ●  ●</span>
          </div>
          <pre style={theme.codePre}><code>{b.value}</code></pre>
        </div>
      );
      return;
    }
    if (b.type === 'rule') {
      out.push(<hr key={key} style={theme.rule} />);
      return;
    }
    if (b.type === 'quote') {
      out.push(
        <blockquote key={key} style={theme.quote}>
          {parseInline(b.value, theme, key)}
        </blockquote>
      );
      return;
    }
    if (b.type === 'ul') {
      out.push(
        <ul key={key} style={{
          margin: '0 0 22px', padding: '0 0 0 0',
          listStyle: 'none',
          display: 'flex', flexDirection: 'column', gap: 8,
        }}>
          {b.items.map((t, j) => (
            <li key={`${key}-${j}`} style={{
              display: 'grid', gridTemplateColumns: '18px 1fr', gap: 6,
              alignItems: 'baseline',
            }}>
              <span style={{
                fontFamily: 'var(--font-mono)', fontSize: theme.typeSize * 0.85,
                color: theme.accent, lineHeight: 1.5,
              }}>—</span>
              <span style={theme.li}>{parseInline(t, theme, `${key}-${j}`)}</span>
            </li>
          ))}
        </ul>
      );
      return;
    }
    if (b.type === 'ol') {
      out.push(
        <ol key={key} style={{
          margin: '0 0 22px', padding: 0,
          listStyle: 'none',
          display: 'flex', flexDirection: 'column', gap: 8,
        }}>
          {b.items.map((t, j) => (
            <li key={`${key}-${j}`} style={{
              display: 'grid', gridTemplateColumns: '26px 1fr', gap: 8,
              alignItems: 'baseline',
            }}>
              <span style={{
                fontFamily: 'var(--font-mono)', fontSize: theme.typeSize * 0.85,
                letterSpacing: '.05em',
                color: theme.accent, lineHeight: 1.5,
              }}>{`0${j + 1}`.slice(-2)}</span>
              <span style={theme.li}>{parseInline(t, theme, `${key}-${j}`)}</span>
            </li>
          ))}
        </ol>
      );
      return;
    }
  });

  return <>{out}</>;
}

Object.assign(window, { Markdown, parseBlocks, buildTheme });
