"""
transpiler.py — Prose-to-Python Transpiler

Converts a Prose AST (from parser.py) directly into Python source code.
The generated `.py` file runs at native CPython speed — up to 100x faster
than the tree-walking interpreter.

Usage (via prose.py):
    python prose.py build myscript.prose
    → generates myscript.py
    python myscript.py
"""

from typing import Any, List
from .parser import *


class Transpiler:
    """
    Walks the prose AST and emits equivalent Python source code.
    """

    def __init__(self):
        self.indent_level = 0
        self.output: List[str] = []

    def _indent(self) -> str:
        return "    " * self.indent_level

    def _emit(self, text: str):
        self.output.append(self._indent() + text)

    # ─── Public entry point ────────────────────────────────────────────────────

    def transpile(self, program: List[Any]) -> str:
        self.output.clear()
        self.indent_level = 0

        # ── Preamble ─────────────────────────────────────────────────────────
        self._emit("# Auto-generated by the Prose transpiler — do not edit by hand")
        self._emit("import sys, os, time, math, random")
        self._emit("try:")
        self._emit("    import urllib.request, json")
        self._emit("except ImportError:")
        self._emit("    pass")
        self._emit("")
        self._emit("# ── prose standard-library polyfills ────────────────────")
        self._emit("def _plain_say(*parts):")
        self._emit("    print(''.join(str(p) for p in parts))")
        self._emit("def _plain_ask(prompt=''):")
        self._emit("    return input(prompt if prompt else '')")
        self._emit("def _plain_len(obj): return len(obj) if hasattr(obj,'__len__') else 0")
        self._emit("def _plain_type(obj, kind):")
        self._emit("    if kind == 'number': return isinstance(obj,(int,float)) and not isinstance(obj,bool)")
        self._emit("    if kind == 'text': return isinstance(obj,str)")
        self._emit("    if kind == 'list': return isinstance(obj,list)")
        self._emit("    if kind == 'boolean': return isinstance(obj,bool)")
        self._emit("    return False")
        self._emit("# prose stdlib aliases (matches interpreter behaviour)")
        self._emit("time_now = time.time")
        self._emit("math_sin = math.sin; math_cos = math.cos; math_tan = math.tan")
        self._emit("math_log = math.log; math_log10 = math.log10; math_exp = math.exp")
        self._emit("math_pi = math.pi")
        self._emit("random_int = random.randint; random_float = random.random")
        self._emit("# ─────────────────────────────────────────────────────────")
        self._emit("")

        for stmt in program:
            self._stmt(stmt)

        return "\n".join(self.output)

    # ─── Statement dispatcher ─────────────────────────────────────────────────

    def _stmt(self, node: Any):
        t = type(node).__name__

        if t == "LetStmt":
            self._emit(f"{node.name} = {self._expr(node.expr)}")

        elif t == "LetResultStmt":
            # Let X be the result of calling func with args
            args_s = ", ".join(self._expr(a) for a in node.args)
            self._emit(f"{node.variable} = {node.func_name}({args_s})")

        elif t == "SayStmt":
            # SayStmt.parts may be [one BinOp] or [multiple literals]
            # Use _plain_say which str()-coerces everything safely
            if len(node.parts) == 1:
                self._emit(f"_plain_say({self._expr(node.parts[0])})")
            else:
                parts = ", ".join(self._expr(p) for p in node.parts)
                self._emit(f"_plain_say({parts})")

        elif t == "DisplayStmt":
            self._emit(f"print({self._expr(node.expr)})")

        elif t == "AskStmt":
            # AskStmt.variable — no prompt stored in AST
            self._emit(f"{node.variable} = _plain_ask()")

        elif t == "GiveBackStmt":
            self._emit(f"return {self._expr(node.expr)}")

        elif t == "FunctionDef":
            params = ", ".join(p.name for p in node.params)
            prefix = "async " if getattr(node, "is_async", False) else ""
            self._emit(f"{prefix}def {node.name}({params}):")
            self.indent_level += 1
            if not node.body:
                self._emit("pass")
            for s in node.body:
                self._stmt(s)
            self.indent_level -= 1
            self._emit("")

        elif t == "CallStmt":
            args_s = ", ".join(self._expr(a) for a in node.args)
            self._emit(f"{node.name}({args_s})")

        elif t == "IfStmt":
            self._emit(f"if {self._cond(node.condition)}:")
            self.indent_level += 1
            if not node.then_body:
                self._emit("pass")
            for s in node.then_body:
                self._stmt(s)
            self.indent_level -= 1
            if node.else_body:
                self._emit("else:")
                self.indent_level += 1
                for s in node.else_body:
                    self._stmt(s)
                self.indent_level -= 1

        elif t == "WhileStmt":
            self._emit(f"while {self._cond(node.condition)}:")
            self.indent_level += 1
            if not node.body:
                self._emit("pass")
            for s in node.body:
                self._stmt(s)
            self.indent_level -= 1

        elif t == "RepeatStmt":
            count = self._expr(node.count)
            self._emit(f"for _i in range(int({count})):")
            self.indent_level += 1
            if not node.body:
                self._emit("pass")
            for s in node.body:
                self._stmt(s)
            self.indent_level -= 1

        elif t == "ForEachStmt":
            self._emit(f"for {node.var} in {self._expr(node.iterable)}:")
            self.indent_level += 1
            if not node.body:
                self._emit("pass")
            for s in node.body:
                self._stmt(s)
            self.indent_level -= 1

        elif t == "RangeLoopStmt":
            start = self._expr(node.from_expr)
            end   = self._expr(node.to_expr)
            step  = self._expr(node.step_expr) if node.step_expr else "1"
            self._emit(f"for {node.var_name} in range(int({start}), int({end}) + 1, int({step})):")
            self.indent_level += 1
            if not node.body:
                self._emit("pass")
            for s in node.body:
                self._stmt(s)
            self.indent_level -= 1

        elif t == "AddToListStmt":
            self._emit(f"{node.list_name}.append({self._expr(node.value)})")

        elif t == "RemoveFromListStmt":
            # index is 1-based in prose
            self._emit(f"{node.list_name}.pop(int({self._expr(node.index)}) - 1)")

        elif t == "RemoveFromListStmt":
            self._emit(f"{node.list_name}.remove({self._expr(node.value)})")

        elif t == "SortList":
            self._emit(f"{node.list_name}.sort()")

        elif t == "StopLoopStmt":
            self._emit("break")

        elif t == "SkipStmt":
            self._emit("continue")

        elif t == "ImportStmt":
            # best-effort: map common modules
            fp = getattr(node, "file_expr", None)
            if fp:
                mod = self._expr(fp).strip("'\"")
                alias = f" as {node.alias}" if node.alias else ""
                self._emit(f"import {mod}{alias}")

        elif t == "TryCatchStmt":
            self._emit("try:")
            self.indent_level += 1
            for s in node.try_body:
                self._stmt(s)
            self.indent_level -= 1
            self._emit(f"except Exception as {node.error_var or '_err'}:")
            self.indent_level += 1
            if not node.catch_body:
                self._emit("pass")
            for s in node.catch_body:
                self._stmt(s)
            self.indent_level -= 1

        elif t == "MethodCallStmt":
            args_s = ", ".join(self._expr(a) for a in node.args)
            obj = self._expr(node.obj_expr)
            self._emit(f"{obj}.{node.method_name}({args_s})")

        elif t == "SetPropertyStmt":
            obj = self._expr(node.obj_expr)
            self._emit(f"{obj}.{node.prop_name} = {self._expr(node.value_expr)}")

        else:
            self._emit(f"# [TODO: transpile {type(node).__name__}]")

    # ─── Expression transpiler ────────────────────────────────────────────────

    def _expr(self, node: Any) -> str:
        t = type(node).__name__

        if t == "NumberLiteral":
            # keep int if whole number
            v = node.value
            return str(int(v)) if isinstance(v, float) and v.is_integer() else str(v)

        if t == "StringLiteral":
            return repr(node.value)

        if t in ("BoolLiteral",):
            return "True" if node.value else "False"

        if t == "NoneLiteral":
            return "None"

        if t == "Identifier":
            return node.name

        if t == "BinOp":
            ls = self._expr(node.left)
            rs = self._expr(node.right)
            op = node.op
            if op == "plus":
                # prose 'plus' auto-coerces to str if either side is a str literal
                left_is_str  = type(node.left).__name__  == "StringLiteral"
                right_is_str = type(node.right).__name__ == "StringLiteral"
                if left_is_str or right_is_str:
                    return f"(str({ls}) + str({rs}))"
                return f"({ls} + {rs})"
            ops = {
                "minus": "-", "times": "*",
                "divided_by": "/", "modulo": "%",
            }
            op_sym = ops.get(op, op)
            return f"({ls} {op_sym} {rs})"

        if t == "UnaryMinus":
            return f"(-{self._expr(node.operand)})"

        if t == "ListLiteral":
            elems = ", ".join(self._expr(e) for e in node.elements)
            return f"[{elems}]"

        if t == "ListAccess":
            return f"{self._expr(node.list_expr)}[int({self._expr(node.index_expr)})-1]"

        if t == "LengthOf":
            return f"_plain_len({self._expr(node.expr)})"

        if t == "UppercaseOf":
            return f"str({self._expr(node.expr)}).upper()"

        if t == "LowercaseOf":
            return f"str({self._expr(node.expr)}).lower()"

        if t == "TrimOf":
            return f"str({self._expr(node.expr)}).strip()"

        if t == "ContainsExpr":
            return f"({self._expr(node.needle)} in {self._expr(node.haystack)})"

        if t == "StartsWithExpr":
            return f"str({self._expr(node.haystack)}).startswith(str({self._expr(node.needle)}))"

        if t == "EndsWithExpr":
            return f"str({self._expr(node.haystack)}).endswith(str({self._expr(node.needle)}))"

        if t == "ReplaceIn":
            return f"str({self._expr(node.source)}).replace(str({self._expr(node.find)}), str({self._expr(node.replacement)}))"

        if t == "SplitBy":
            return f"str({self._expr(node.source)}).split(str({self._expr(node.delimiter)}))"

        if t == "JoinWith":
            return f"str({self._expr(node.separator)}).join(str(x) for x in {self._expr(node.list_expr)})"

        if t == "RoundOf":
            places = self._expr(node.places) if node.places else "0"
            return f"round({self._expr(node.expr)}, int({places}))"

        if t == "AbsOf":
            return f"abs({self._expr(node.expr)})"

        if t == "SqrtOf":
            return f"math.sqrt({self._expr(node.expr)})"

        if t == "FloorOf":
            return f"math.floor({self._expr(node.expr)})"

        if t == "CeilingOf":
            return f"math.ceil({self._expr(node.expr)})"

        if t == "PowerOf":
            return f"({self._expr(node.base)} ** {self._expr(node.exp)})"

        if t == "MinOf":
            return f"min({self._expr(node.left)}, {self._expr(node.right)})"

        if t == "MaxOf":
            return f"max({self._expr(node.left)}, {self._expr(node.right)})"

        if t == "RandomBetween":
            return f"random.randint(int({self._expr(node.low)}), int({self._expr(node.high)}))"

        if t == "AsNumber":
            return f"float({self._expr(node.expr)})"

        if t == "AsText":
            return f"str({self._expr(node.expr)})"

        if t == "TypeCheck":
            return f"_plain_type({self._expr(node.expr)}, '{node.expected}')"

        if t == "LetResultStmt":
            args_s = ", ".join(self._expr(a) for a in node.args)
            return f"{node.func_name}({args_s})"

        if t == "CallStmt":
            args_s = ", ".join(self._expr(a) for a in node.args)
            return f"{node.name}({args_s})"

        if t == "MethodCallStmt":
            args_s = ", ".join(self._expr(a) for a in node.args)
            return f"{self._expr(node.obj_expr)}.{node.method_name}({args_s})"

        if t == "PropertyAccessExpr":
            return f"{self._expr(node.obj_expr)}.{node.prop_name}"

        if t == "IndexOf":
            return f"({self._expr(node.list_expr)}.index({self._expr(node.item)}) + 1)"

        if t == "SayStmt":
            parts = " + ".join(f"str({self._expr(p)})" for p in node.parts)
            return parts

        if t == "InterpolatedString":
            parts = []
            for p in node.parts:
                if type(p).__name__ == "StringLiteral":
                    parts.append(repr(p.value))
                else:
                    parts.append(f"str({self._expr(p)})")
            return " + ".join(parts) if parts else "''"

        if t == "AllWhereExpr":
            cond = self._cond(node.condition)
            return f"[{node.var_name} for {node.var_name} in {self._expr(node.source_expr)} if {cond}]"

        # Fallback
        return f"__UNSUPPORTED_{t}__"

    # ─── Condition transpiler ─────────────────────────────────────────────────

    def _cond(self, node: Any) -> str:
        t = type(node).__name__

        if t == "CompoundCondition":
            op = "and" if node.connective == "and" else "or"
            return f"({self._cond(node.left)} {op} {self._cond(node.right)})"

        if t == "Condition":
            left = self._expr(node.left)
            op = node.op

            if op.startswith("is_"):
                kind = op[3:]
                return f"_plain_type({left}, '{kind}')"

            right = self._expr(node.right)
            sym = {
                "greater_than": ">", "less_than": "<",
                "greater_equal": ">=", "less_equal": "<=",
                "equals": "==", "not_equals": "!=",
            }.get(op, "==")
            return f"({left} {sym} {right})"

        # Boolean expression used directly as condition
        return self._expr(node)
