"""Storyboard JSON template engine.

Templates live in ``skills/media-screenshot/templates/<name>.json``.
Each template is a regular storyboard JSON optionally prefixed with a ``_meta``
block describing the variables it accepts. ``{{var_name}}`` placeholders inside
string values are replaced; when a string consists of a single placeholder its
substituted value is auto-coerced to int / float / bool / null so e.g.
``"scale": "{{zoom_scale}}"`` becomes a number after rendering.
"""
from __future__ import annotations

import json
import re
import sys
from pathlib import Path
from typing import Any

_TEMPLATES_DIR = Path(__file__).resolve().parent.parent.parent / "templates"
_PLACEHOLDER_RE = re.compile(r"\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}")
_SOLE_PLACEHOLDER_RE = re.compile(
    r"^\s*\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}\s*$"
)


def _coerce(s: str) -> Any:
    low = s.strip().lower()
    if low == "true":
        return True
    if low == "false":
        return False
    if low in ("null", "none"):
        return None
    try:
        return int(s)
    except ValueError:
        pass
    try:
        return float(s)
    except ValueError:
        pass
    return s


def _substitute(value: Any, params: dict[str, str]) -> Any:
    if isinstance(value, str):
        m = _SOLE_PLACEHOLDER_RE.match(value)
        if m:
            key = m.group(1)
            if key not in params:
                raise SystemExit(f"模板缺少参数：{key}")
            return _coerce(str(params[key]))

        def repl(m: re.Match) -> str:
            key = m.group(1)
            if key not in params:
                raise SystemExit(f"模板缺少参数：{key}")
            return str(params[key])

        return _PLACEHOLDER_RE.sub(repl, value)
    if isinstance(value, list):
        return [_substitute(v, params) for v in value]
    if isinstance(value, dict):
        return {k: _substitute(v, params) for k, v in value.items()}
    return value


def list_templates() -> list[Path]:
    if not _TEMPLATES_DIR.exists():
        return []
    return sorted(_TEMPLATES_DIR.glob("*.json"))


def load_template(name: str) -> dict:
    """Resolve a template name (or path) to its parsed JSON."""
    p = Path(name)
    if not p.exists():
        p = _TEMPLATES_DIR / f"{name}.json"
    if not p.exists():
        avail = ", ".join(t.stem for t in list_templates()) or "(none)"
        raise SystemExit(f"模板不存在：{name}\n可用模板：{avail}")
    try:
        return json.loads(p.read_text())
    except json.JSONDecodeError as e:
        raise SystemExit(f"模板 JSON 解析失败 {p}: {e}")


def render_template(name: str, params: dict[str, str]) -> dict:
    """Load, validate against ``_meta.params``, render, return the storyboard."""
    tpl = load_template(name)
    meta = tpl.pop("_meta", {})
    declared = meta.get("params", {}) or {}

    final: dict[str, str] = {}
    for key, spec in declared.items():
        if isinstance(spec, dict) and "default" in spec:
            final[key] = str(spec["default"])
    for key, val in params.items():
        if declared and key not in declared:
            print(
                f"[template] 警告：参数 {key} 未在 _meta.params 中声明",
                file=sys.stderr,
            )
        final[key] = val
    for key, spec in declared.items():
        if isinstance(spec, dict) and spec.get("required") and key not in final:
            raise SystemExit(
                f"模板 {name} 要求参数 {key}（{spec.get('description', '')}）"
            )
    return _substitute(tpl, final)


def parse_param_pairs(pairs: list[str] | None) -> dict[str, str]:
    out: dict[str, str] = {}
    for raw in pairs or []:
        if "=" not in raw:
            raise SystemExit(f'--param 需要 "key=value" 格式：{raw}')
        k, v = raw.split("=", 1)
        out[k.strip()] = v
    return out
