"""@import handler — thin dispatcher with plugin architecture.

Syntax:
    @import "data/file.csv"              → default: code display with line numbers
    @import "data/file.csv" as table     → load doc_builder/render_table.py, call render()
    @import "data/file.json" as registers → load doc_builder/render_registers.py
    @import "data/file.yaml" as requirements → load doc_builder/render_requirements.py

How 'as' works:
    as <name>  →  importlib loads doc_builder/render_<name>.py
                  calls render(filepath) → returns HTML string

    No 'as'    →  default code viewer with line numbers

Each project provides its own render_<name>.py scripts in doc_builder/.
Built-in renderers (table, bga, address, registers, requirements) are
included and can be copied between projects.
"""
import importlib
import re
import traceback
from pathlib import Path


IMPORT_RE = re.compile(
    r'^@import\s+"([^"]+)"(?:\s+using\s+([\w.]+))?\s*$',
    re.MULTILINE,
)


def handle(import_path, renderer_name, base_dir, process_chapter_fn):
    """Resolve @import and dispatch to renderer.

    Args:
        import_path: file path from @import directive
        renderer_name: name after 'as' (e.g. 'table', 'registers'), or None
        base_dir: project root for path resolution
        process_chapter_fn: callback for recursive .md processing

    Returns:
        HTML string
    """
    fp = (base_dir / import_path).resolve()
    if not fp.exists():
        return (
            f'<div class="warning">'
            f'@import file not found: {import_path}'
            f'</div>'
        )

    # .md files are always processed recursively (no renderer script)
    if fp.suffix.lower() == '.md':
        md_text = fp.read_text(encoding='utf-8')
        return process_chapter_fn(md_text)

    # No as specified → default: code display with line numbers
    if not renderer_name:
        return _render_default(fp)

    # as <name> → load renderer script and call render()
    try:
        renderer = _load_renderer(renderer_name, base_dir)
        return renderer(fp)
    except RendererNotFound:
        # Graceful fallback: show default code view + note
        note = (
            f'<div class="note">'
            f'Renderer script <code>doc_builder/{renderer_name}</code> not found. '
            f'Showing default code view. '
            f'Create this file with a <code>render(filepath)</code> function '
            f'to customize rendering.'
            f'</div>'
        )
        return note + _render_default(fp)
    except Exception as e:
        return (
            f'<div class="warning">'
            f'@import renderer error ({import_path} as {renderer_name}): {e}'
            f'<pre>{traceback.format_exc()}</pre>'
            f'</div>'
        )


def process_imports(md_text, base_dir, process_chapter_fn):
    """Replace all @import directives in md_text with rendered HTML."""
    def _replace(match):
        path = match.group(1)
        hint = match.group(2)  # may be None
        return handle(path, hint, base_dir, process_chapter_fn)
    return IMPORT_RE.sub(_replace, md_text)


# ---- Renderer plugin loader ----

class RendererNotFound(Exception):
    """Raised when a render_<name>.py script does not exist."""
    pass


def _load_renderer(name, base_dir):
    """Load a renderer script from doc_builder/<name>.

    The module must export: render(filepath) → HTML string
    """
    doc_builder = base_dir / 'doc_builder'
    script = doc_builder / name

    if not script.exists():
        raise RendererNotFound(
            f'doc_builder/{name} not found'
        )

    spec = importlib.util.spec_from_file_location(
        name.replace('.', '_'), str(script)
    )
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)

    if not hasattr(mod, 'render'):
        raise RendererNotFound(
            f'doc_builder/{name} has no render() function'
        )

    return mod.render


# ---- Default renderer: code with line numbers ----

def _render_default(filepath):
    """Display file content as code with line numbers."""
    try:
        with open(filepath, encoding='utf-8') as f:
            lines = f.readlines()
    except UnicodeDecodeError:
        return '<div class="warning">Cannot display binary file.</div>'

    from html import escape
    ext = filepath.suffix.lstrip('.').upper()
    html = [
        f'<div class="code-block">',
        f'<div class="code-header">{filepath.name} ({len(lines)} lines)</div>',
        f'<pre class="code-lines">',
    ]
    for i, line in enumerate(lines, 1):
        # escape HTML but keep the line content
        escaped = escape(line.rstrip('\n\r'))
        html.append(
            f'<span class="ln">{i:4d}</span> '
            f'<span class="lc">{escaped}</span>'
        )
    html.append('</pre></div>')
    return '\n'.join(html)
