"""Template rendering for ticket summary and description.

Supports a simple Mustache-style syntax:

- ``{{var}}`` — variable interpolation
- ``{{#each var}}...{{this}}...{{/each}}`` — list iteration

Nesting of ``{{#each}}`` blocks is not supported and will produce
incorrect output — the inner ``{{/each}}`` closes the outer block.
"""

from __future__ import annotations

import re
from typing import Any

from specwright.parser.models import SpecDocument, SpecSection
from specwright.sync.mapping import TemplateConfig


def _build_context(
    section: SpecSection,
    doc: SpecDocument,
    spec_url: str = "",
) -> dict[str, Any]:
    """Build the template variable context."""
    return {
        "section.title": section.title,
        "section.content": section.content,
        "section.section_number": section.section_number or "",
        "section.depth": str(section.depth),
        "section.id": section.id,
        "section.acceptance_criteria": [ac.text for ac in section.acceptance_criteria],
        "spec.title": doc.frontmatter.title,
        "spec.owner": doc.frontmatter.owner,
        "spec.team": doc.frontmatter.team,
        "spec.tags": doc.frontmatter.tags,
        "spec_url": spec_url,
    }


def _substitute_vars(text: str, context: dict[str, Any]) -> str:
    """Positional substitution of ``{{var}}`` tokens.

    Walks the text left-to-right, replacing each ``{{var}}`` with its
    context value. Values are inserted literally (never re-scanned),
    which prevents injection from user content.
    """
    parts: list[str] = []
    last_end = 0
    for m in re.finditer(r"\{\{([^#/].*?)\}\}", text):
        parts.append(text[last_end : m.start()])
        var_name = m.group(1).strip()
        value = context.get(var_name, "")
        if isinstance(value, list):
            parts.append(", ".join(str(v) for v in value))
        else:
            parts.append(str(value))
        last_end = m.end()
    parts.append(text[last_end:])
    return "".join(parts)


def _render(template: str, context: dict[str, Any]) -> str:
    """Render a template with the given context.

    Uses positional substitution: template tokens are replaced left-to-right
    with context values. Values are never re-scanned for template syntax,
    preventing injection from spec content.
    """

    # ── Phase 1: expand {{#each}} blocks ─────────────────
    # Each block's body is rendered per-item with {{this}} replaced
    # positionally (not via string replace, to prevent re-scanning).
    parts: list[str] = []
    last_end = 0

    for m in re.finditer(r"\{\{#each\s+(.+?)\}\}(.*?)\{\{/each\}\}", template, flags=re.DOTALL):
        # Add text before this block (may contain {{var}} tokens)
        parts.append(_substitute_vars(template[last_end : m.start()], context))

        var_name = m.group(1).strip()
        body = m.group(2)
        items = context.get(var_name, [])
        if isinstance(items, list):
            for item in items:
                # Replace {{this}} positionally within the body
                item_context = {**context, "this": str(item)}
                parts.append(_substitute_vars(body, item_context))
        last_end = m.end()

    # Add remaining text after last block
    parts.append(_substitute_vars(template[last_end:], context))

    return "".join(parts)


# ─── Public API ──────────────────────────────────────────


DEFAULT_SUMMARY_TEMPLATE = "[{{section.section_number}}] {{section.title}}"
DEFAULT_DESCRIPTION_TEMPLATE = "{{section.content}}"


def render_summary(
    section: SpecSection,
    doc: SpecDocument,
    config: TemplateConfig | None = None,
    spec_url: str = "",
) -> str:
    """Render ticket summary from template or default."""
    template = (config.summary if config else None) or DEFAULT_SUMMARY_TEMPLATE
    context = _build_context(section, doc, spec_url)
    return _render(template, context)


def render_description(
    section: SpecSection,
    doc: SpecDocument,
    config: TemplateConfig | None = None,
    spec_url: str = "",
) -> str:
    """Render ticket description from template or default.

    When using the default template, truncates to 2000 chars
    to match current behavior.
    """
    if config and config.description:
        context = _build_context(section, doc, spec_url)
        return _render(config.description, context)

    # Default: section content truncated (matches current behavior)
    return section.content[:2000]
