/**
 * DocMarkdown — o renderer de markdown do DocBrowser. Prosa/tabelas via markdown-it; e
 * fence-dispatch pelos TOKENS do markdown-it (robusto — crase inline na prosa NÃO confunde,
 * ao contrário de um split por regex):
 *
 *   ```tsx preview  → componente RODANDO (sucrase transpila o jsx → createElement, avaliado
 *                     com o `scope`), dentro do <Example> (preview + snippet, tema da casa)
 *   ```<lang>       → <CodeBlock> (highlight + copiar)
 *   (prosa/tabelas) → markdown-it
 *
 * `scope` (default: o opus/ui) = os componentes disponíveis nos blocos `tsx preview`. O
 * `new Function` é aceitável porque o markdown é conteúdo do projeto (confiável), não de terceiro.
 * Só fences de nível 0 viram preview/CodeBlock; fences aninhados caem no HTML do markdown-it.
 */
import * as React from 'react'
import { markdownIt as md } from '../components/primitives/markdown.tsx'
import { transform } from 'sucrase'
import { CodeBlock, Example } from './doc.tsx'
import * as opusUi from '../react.tsx'


// Scope default estável (computado uma vez) — evita re-eval do LiveBlock a cada render. Os blocos
// `tsx preview` enxergam o opus/ui (componentes) + os hooks do React (bare `useState`… além de
// `React.useState`). Ícones (lucide) NÃO entram aqui de propósito: quem monta a doc injeta a paleta
// pelo `scope` (o registry passa o lucide inteiro) — assim o renderer não arrasta o lucide pro
// bundle de TODO consumidor do DocMarkdown; só a superfície que exibe o catálogo paga por ele.
const DEFAULT_SCOPE: Record<string, unknown> = {
  ...opusUi,
  useState: React.useState,
  useEffect: React.useEffect,
  useRef: React.useRef,
  useMemo: React.useMemo,
  useCallback: React.useCallback,
  useId: React.useId,
}

function LiveBlock({
  code,
  scope,
  previewClassName,
}: {
  code: string
  scope: Record<string, unknown>
  previewClassName?: string
}): React.ReactElement {
  const result = React.useMemo<{ Comp?: () => React.ReactNode; error?: string }>(() => {
    try {
      // Exemplo com ESTADO: se o snippet chama `render(<…/>)`, ele roda como CORPO de um
      // componente (os hooks rodam no lugar certo). Bare JSX (uma OU várias irmãs) → embrulho
      // num Fragment antes do render() — várias expressões soltas não passam como um argumento só.
      const hasRender = /\brender\s*\(/.test(code)
      const body = `let __el = null; const render = (e) => { __el = e; };\n${hasRender ? code.trim() : `render(<>${code.trim()}</>)`}\nreturn __el;`
      const js = transform(body, { transforms: ['jsx', 'typescript'], jsxRuntime: 'classic' }).code
      // Só passa pro new Function os identificadores que o snippet realmente cita (o scope tem
      // ~1500 nomes com o lucide) e que são identificador válido (ex.: 'default' fica de fora).
      // React entra SEMPRE — o JSX clássico compila pra React.createElement.
      const entries = Object.entries({ React, ...scope }).filter(
        ([k]) => /^[A-Za-z_$][\w$]*$/.test(k) && k !== 'default' && (k === 'React' || code.includes(k)),
      )
      const fn = new Function(...entries.map(([k]) => k), js)
      const args = entries.map(([, v]) => v)
      // Componente estável (memoizado por [code, scope]): React o chama no render, então os
      // hooks do snippet executam durante um render de verdade — não num eval solto.
      const Comp = (): React.ReactNode => fn(...args) as React.ReactNode
      return { Comp }
    } catch (e) {
      return { error: e instanceof Error ? e.message : String(e) }
    }
  }, [code, scope])
  const Comp = result.Comp
  return (
    <Example code={code.trim()} previewClassName={previewClassName}>
      {result.error ? <span className="text-sm text-red-500">{result.error}</span> : Comp ? <Comp /> : null}
    </Example>
  )
}

export interface DocMarkdownProps {
  content: string
  /** ESTENDE o scope dos blocos ```tsx preview (opus/ui + lucide + hooks). Ex.: um pattern injeta
   *  o palco doc-client. Omitido = só o default. */
  scope?: Record<string, unknown>
}

export function DocMarkdown({ content, scope }: DocMarkdownProps): React.ReactElement {
  // O `scope` ESTENDE o default (não substitui): o pattern injeta o palco doc-client
  // (DocBrowserActionProvider + contratos) sem perder o opus/ui + lucide + hooks. Memoiza pra
  // não re-avaliar os LiveBlocks a cada render (o scope injetado é estável no chamador).
  const liveScope = React.useMemo(() => (scope ? { ...DEFAULT_SCOPE, ...scope } : DEFAULT_SCOPE), [scope])
  // Ignora o frontmatter (---\n…\n---) — a nav já tem o título e o corpo tem o H1. Sem isso,
  // o markdown-it renderiza "title: …" como texto (é robusto pros dois caminhos: registry e folder).
  const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '')
  const tokens = md.parse(body, {})
  const parts: React.ReactNode[] = []
  let buffer: typeof tokens = []
  let key = 0
  const flush = (): void => {
    if (buffer.length === 0) return
    parts.push(<div key={key++} dangerouslySetInnerHTML={{ __html: md.renderer.render(buffer, md.options, {}) }} />)
    buffer = []
  }
  for (const t of tokens) {
    if (t.type === 'fence' && t.level === 0) {
      flush()
      const info = (t.info ?? '').trim()
      const infoTokens = info.split(/\s+/)
      const lang = infoTokens[0] || 'tsx'
      const flags = infoTokens.slice(1)
      // Palco do preview via flags no info (ex.: `tsx preview col md`): `col`/`col-start` = layout
      // em coluna (bloco: Alert, AspectRatio, grupo vertical); um token de largura (`md`, `2xl`…) =
      // cap de `max-w-*` (form/campo que não deve esticar). Viram o previewClassName do Example.
      const dir = flags.includes('col-start') ? 'flex-col items-start' : flags.includes('col') ? 'flex-col items-stretch' : ''
      const widthTok = flags.find((f) => /^(sm|md|lg|xl|2xl|3xl|4xl)$/.test(f))
      const previewClassName = [dir, widthTok ? `max-w-${widthTok}` : ''].filter(Boolean).join(' ') || undefined
      parts.push(
        <div key={key++} className="not-prose">
          {/\b(preview|live)\b/.test(info) ? (
            <LiveBlock code={t.content} scope={liveScope} previewClassName={previewClassName} />
          ) : (
            <CodeBlock code={t.content.replace(/\n$/, '')} lang={lang} />
          )}
        </div>,
      )
    } else {
      buffer.push(t)
    }
  }
  flush()
  return <div className="prose prose-sm max-w-none space-y-4">{parts}</div>
}
