"""Code block renderer — standalone pre-markdown step.

Usage in build pipeline:
    md_text = render_code_blocks(md_text, project_dir)
    html = markdown.markdown(md_text, extensions=[...])

Syntax in chapters:
    ```schemdraw
    import schemdraw
    ...
    ```

Runs BEFORE markdown parsing, so avoids priority conflicts with
fenced_code / codehilite extensions.

Renders live in doc_builder/renderers/<tag>.py.
Each exports: render(code_text, project_dir) -> HTML string.
"""
import importlib.util
import re
from pathlib import Path
from html import unescape as html_unescape

CODE_BLOCK_RE = re.compile(
    r'(?:^|\n)```(\w+)\s*\n(.*?)\n```(?:\n|$)',
    re.DOTALL,
)

PASSTHROUGH = {'mermaid', 'wavedrom', 'python', 'bash', 'json', 'yaml', 'ascii'}


def _load_renderer(lang, project_dir):
    rp = project_dir / 'doc_builder' / 'renderers' / f'{lang}.py'
    if not rp.exists():
        return None
    spec = importlib.util.spec_from_file_location(
        f'render_{lang}', str(rp)
    )
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return getattr(mod, 'render', None)


def render_code_blocks(text, project_dir):
    """Find ```lang blocks, dispatch to renderers, replace inline."""
    result = []
    last_end = 0

    for match in CODE_BLOCK_RE.finditer(text):
        lang = match.group(1)
        code = match.group(2)

        result.append(text[last_end:match.start()])

        if lang in PASSTHROUGH:
            result.append(match.group(0))
        else:
            renderer = _load_renderer(lang, project_dir)
            if renderer is None:
                result.append(match.group(0))
            else:
                try:
                    code_text = html_unescape(code.strip())
                    html = renderer(code_text, project_dir)
                    result.append(f'\n{html}\n')
                except Exception as e:
                    result.append(
                        f'\n<div class="warning">'
                        f'{lang} render error: {e}'
                        f'</div>\n'
                    )

        last_end = match.end()

    result.append(text[last_end:])
    return ''.join(result)
