"""Minimal Go-template renderer for Jackett YAML definitions.

Supports:
  - Variable interpolation: ``{{ .Foo.Bar }}``
  - Conditionals: ``{{ if .X }}...{{ else }}...{{ end }}``
  - Function-style conditions: ``{{ if and .X .Y }}...{{ end }}``
  - Range loops: ``{{ range .Items }}{{ . }}{{ end }}``
  - Function calls: ``{{ join .Items "sep" }}``, ``{{ re_replace .V "p" "r" }}``
"""

import re

from lark import Lark, Token, Tree
from lark.visitors import Interpreter

_GRAMMAR = r"""
start: segment*

?segment: text
        | interpolation
        | if_block
        | range_block

text: /[^{]+|{(?!{)/
interpolation: "{{" (func_call | dotvar) "}}"

if_block: "{{" "if" condition "}}" segment* else_block? "{{" "end" "}}"
else_block: "{{" "else" "}}" segment*

range_block: "{{" "range" dotvar "}}" segment* "{{" "end" "}}"

?condition: func_call | dotvar

func_call: FUNC_NAME arg+
FUNC_NAME: "and" | "join" | "re_replace"
arg: dotvar | STRING
dotvar: DOTVAR
DOTVAR: /\.[A-Za-z_][A-Za-z0-9_.]*/
      | "."

STRING: /\"[^\"]*\"/

%ignore /[ \t\n\r]+/
"""

_parser = Lark(_GRAMMAR, parser="earley", ambiguity="resolve")


def _resolve(path: str, ctx: dict) -> object:
    """Resolve a dotted path like ``.Config.sort`` against a context dict."""
    if path == ".":
        return ctx.get(".", "")
    segments = path.lstrip(".").split(".")
    cur: object = ctx
    for seg in segments:
        if isinstance(cur, dict) and seg in cur:
            cur = cur[seg]
        else:
            return ""
    return cur


class _Renderer(Interpreter):
    """Evaluate a parse tree against a context dict, producing a string."""

    def __init__(self, ctx: dict) -> None:
        self._ctx = ctx

    def start(self, tree: Tree) -> str:
        return "".join(str(self.visit(child)) for child in tree.children)

    def text(self, tree: Tree) -> str:
        return str(tree.children[0])

    def interpolation(self, tree: Tree) -> str:
        return str(self._eval(tree.children[0]))

    def if_block(self, tree: Tree) -> str:
        condition = tree.children[0]
        cond_value = self._eval(condition)

        has_else = (
            len(tree.children) > 1
            and isinstance(tree.children[-1], Tree)
            and tree.children[-1].data == "else_block"
        )
        body = tree.children[1:-1] if has_else else tree.children[1:]
        else_body = tree.children[-1].children if has_else else []

        if cond_value:
            return "".join(str(self.visit(child)) for child in body)
        return "".join(str(self.visit(child)) for child in else_body)

    def range_block(self, tree: Tree) -> str:
        items_path = str(tree.children[0].children[0])
        items = _resolve(items_path, self._ctx)
        body = tree.children[1:]

        if not items or not isinstance(items, list):
            return ""

        parts: list[str] = []
        saved = self._ctx.copy()
        for item in items:
            self._ctx["."] = item
            parts.append("".join(str(self.visit(child)) for child in body))
        self._ctx.clear()
        self._ctx.update(saved)
        return "".join(parts)

    def _eval(self, node: Tree) -> object:
        """Evaluate a condition/expression node to its value."""
        if node.data == "dotvar":
            return _resolve(str(node.children[0]), self._ctx)
        if node.data == "func_call":
            return self._eval_func(node)
        return ""

    def _eval_func(self, node: Tree) -> object:
        """Evaluate a function call node."""
        name = str(node.children[0])
        args = [self._eval_arg(child) for child in node.children[1:]]
        match name:
            case "and":
                return self._func_and(args)
            case "join":
                return self._func_join(args)
            case "re_replace":
                return self._func_re_replace(args)
            case _:
                return ""

    def _eval_arg(self, node: Tree) -> object:
        """Evaluate a function argument (dotvar or string literal)."""
        child = node.children[0]
        if isinstance(child, Tree) and child.data == "dotvar":
            return _resolve(str(child.children[0]), self._ctx)
        if isinstance(child, Token):
            return str(child)[1:-1]
        return str(child)

    @staticmethod
    def _func_and(args: list) -> object:
        """Go template ``and`` — returns the last arg if all are truthy."""
        for a in args:
            if not a:
                return a
        return args[-1] if args else True

    @staticmethod
    def _func_join(args: list) -> str:
        """Go template ``join`` — join a list with a separator."""
        items = args[0] if args else []
        sep = args[1] if len(args) > 1 else ""
        if isinstance(items, list):
            return sep.join(str(i) for i in items)
        return str(items)

    @staticmethod
    def _func_re_replace(args: list) -> str:
        """Go template ``re_replace`` — regex substitution."""
        value = str(args[0]) if args else ""
        pattern = str(args[1]) if len(args) > 1 else ""
        replacement = str(args[2]) if len(args) > 2 else ""
        return re.sub(pattern, replacement, value)


def render(template: str, context: dict) -> str:
    """Parse and render a Go-style template string.

    Args:
        template: The template string to render.
        context: The context dictionary for variable resolution.

    Returns:
        The rendered template string.

    """
    tree = _parser.parse(template)
    return _Renderer(context).start(tree)
