"""
Nexa Language — Python→Nexa Source Migrator v24
nexa migrate script.py --output script.nexa
Transpiles Python source to idiomatic Nexa syntax.
"""
from __future__ import annotations
import ast
import re
import os


class PythonToNexaMigrator(ast.NodeVisitor):
    def __init__(self):
        self._lines: list[str] = []
        self._indent = 0
        self._warnings: list[str] = []

    # ── Helpers ───────────────────────────────────────────────────────────────
    def _emit(self, line: str):
        self._lines.append("    " * self._indent + line)

    def _emit_blank(self):
        self._lines.append("")

    def _warn(self, msg: str):
        self._warnings.append(f"  ⚠️  {msg}")

    def _expr(self, node) -> str:
        """Convert a Python AST expression node to Nexa string."""
        if isinstance(node, ast.Constant):
            if isinstance(node.value, str):
                return repr(node.value)
            return str(node.value)
        if isinstance(node, ast.Name):
            name = node.id
            if name == "True": return "true"
            if name == "False": return "false"
            if name == "None": return "null"
            return name
        if isinstance(node, ast.BinOp):
            ops = {ast.Add: "+", ast.Sub: "-", ast.Mult: "*", ast.Div: "/",
                   ast.Mod: "%", ast.Pow: "**", ast.BitAnd: "&",
                   ast.BitOr: "|", ast.BitXor: "^"}
            op = ops.get(type(node.op), "?")
            return f"{self._expr(node.left)} {op} {self._expr(node.right)}"
        if isinstance(node, ast.BoolOp):
            op = "and" if isinstance(node.op, ast.And) else "or"
            return f" {op} ".join(self._expr(v) for v in node.values)
        if isinstance(node, ast.UnaryOp):
            if isinstance(node.op, ast.Not): return f"not {self._expr(node.operand)}"
            if isinstance(node.op, ast.USub): return f"-{self._expr(node.operand)}"
            return self._expr(node.operand)
        if isinstance(node, ast.Compare):
            ops = {ast.Eq: "==", ast.NotEq: "!=", ast.Lt: "<", ast.LtE: "<=",
                   ast.Gt: ">", ast.GtE: ">=", ast.In: "in", ast.NotIn: "not in"}
            parts = [self._expr(node.left)]
            for op, comp in zip(node.ops, node.comparators):
                parts.append(ops.get(type(op), "?"))
                parts.append(self._expr(comp))
            return " ".join(parts)
        if isinstance(node, ast.Call):
            func = self._expr(node.func)
            args = [self._expr(a) for a in node.args]
            kwargs = [f"{k.arg}={self._expr(k.value)}" for k in node.keywords]
            return f"{func}({', '.join(args + kwargs)})"
        if isinstance(node, ast.Attribute):
            return f"{self._expr(node.value)}.{node.attr}"
        if isinstance(node, ast.Subscript):
            return f"{self._expr(node.value)}[{self._expr(node.slice)}]"
        if isinstance(node, ast.List):
            return "[" + ", ".join(self._expr(e) for e in node.elts) + "]"
        if isinstance(node, ast.Dict):
            pairs = (f"{self._expr(k)}: {self._expr(v)}"
                     for k, v in zip(node.keys, node.values) if k is not None)
            return "{" + ", ".join(pairs) + "}"
        if isinstance(node, ast.Tuple):
            return "[" + ", ".join(self._expr(e) for e in node.elts) + "]"  # Nexa uses lists
        if isinstance(node, ast.IfExp):
            return f"{self._expr(node.body)} if {self._expr(node.test)} else {self._expr(node.orelse)}"
        if isinstance(node, ast.Lambda):
            params = ", ".join(a.arg for a in node.args.args)
            return f"def({params}) => {self._expr(node.body)}"
        if isinstance(node, ast.JoinedStr):
            # f-string
            parts = []
            for v in node.values:
                if isinstance(v, ast.Constant): parts.append(str(v.value))
                else: parts.append("{" + self._expr(v.value if hasattr(v, 'value') else v) + "}")
            return 'f"' + "".join(parts) + '"'
        if isinstance(node, ast.ListComp):
            return f"[{self._expr(node.elt)} for {node.generators[0].target.id} in {self._expr(node.generators[0].iter)}]"
        # Fallback: unparse
        try:
            return ast.unparse(node)
        except Exception:
            return "<expr>"

    # ── Statement visitors ────────────────────────────────────────────────────

    def visit_Module(self, node):
        self._emit(f'// Migrated from Python by nexa migrate')
        self._emit_blank()
        self.generic_visit(node)

    def visit_FunctionDef(self, node):
        self._emit_blank()
        # Decorators
        decorators = ""
        for dec in node.decorator_list:
            self._emit(f"@{self._expr(dec)}")
        # Params
        args = node.args
        params = []
        n_defaults = len(args.defaults)
        n_args = len(args.args)
        for i, arg in enumerate(args.args):
            pname = arg.arg
            ann = f": {ast.unparse(arg.annotation)}" if arg.annotation else ""
            def_idx = i - (n_args - n_defaults)
            if def_idx >= 0:
                pname += f" = {self._expr(args.defaults[def_idx])}"
            params.append(pname + ann)

        ret = f" -> {ast.unparse(node.returns)}" if node.returns else ""
        async_kw = "async " if isinstance(node, ast.AsyncFunctionDef) else ""
        self._emit(f"{async_kw}def {node.name}({', '.join(params)}){ret} {{")
        self._indent += 1

        # Docstring
        if (node.body and isinstance(node.body[0], ast.Expr)
                and isinstance(node.body[0].value, ast.Constant)
                and isinstance(node.body[0].value.value, str)):
            self._emit(f'"""{node.body[0].value.value}"""')
            body = node.body[1:]
        else:
            body = node.body

        for stmt in body:
            self.visit(stmt)
        self._indent -= 1
        self._emit("}")

    visit_AsyncFunctionDef = visit_FunctionDef

    def visit_ClassDef(self, node):
        self._emit_blank()
        bases = ", ".join(self._expr(b) for b in node.bases)
        implements = f" implements {bases}" if bases else ""
        self._emit(f"class {node.name}{implements} {{")
        self._indent += 1
        for stmt in node.body:
            self.visit(stmt)
        self._indent -= 1
        self._emit("}")

    def visit_Return(self, node):
        val = self._expr(node.value) if node.value else "null"
        self._emit(f"return {val}")

    def visit_Assign(self, node):
        targets = ", ".join(self._expr(t) for t in node.targets)
        self._emit(f"let {targets} = {self._expr(node.value)}")

    def visit_AnnAssign(self, node):
        name = self._expr(node.target)
        ann = ast.unparse(node.annotation)
        val = f" = {self._expr(node.value)}" if node.value else ""
        self._emit(f"let {name}: {ann}{val}")

    def visit_AugAssign(self, node):
        ops = {ast.Add: "+=", ast.Sub: "-=", ast.Mult: "*=", ast.Div: "/=", ast.Mod: "%="}
        op = ops.get(type(node.op), "=")
        self._emit(f"{self._expr(node.target)} {op} {self._expr(node.value)}")

    def visit_If(self, node):
        self._emit(f"if {self._expr(node.test)} {{")
        self._indent += 1
        for stmt in node.body: self.visit(stmt)
        self._indent -= 1
        if node.orelse:
            if len(node.orelse) == 1 and isinstance(node.orelse[0], ast.If):
                self._emit("} else ")
                self.visit(node.orelse[0])
                return
            self._emit("} else {")
            self._indent += 1
            for stmt in node.orelse: self.visit(stmt)
            self._indent -= 1
        self._emit("}")

    def visit_For(self, node):
        var = self._expr(node.target)
        it  = self._expr(node.iter)
        self._emit(f"for {var} in {it} {{")
        self._indent += 1
        for stmt in node.body: self.visit(stmt)
        self._indent -= 1
        self._emit("}")

    def visit_While(self, node):
        self._emit(f"while {self._expr(node.test)} {{")
        self._indent += 1
        for stmt in node.body: self.visit(stmt)
        self._indent -= 1
        self._emit("}")

    def visit_With(self, node):
        self._warn(f"'with' statement not supported in Nexa — using plain block")
        self._emit("// with (context manager) — manual cleanup needed")
        self._emit("{")
        self._indent += 1
        for stmt in node.body: self.visit(stmt)
        self._indent -= 1
        self._emit("}")

    def visit_Try(self, node):
        self._emit("try {")
        self._indent += 1
        for stmt in node.body: self.visit(stmt)
        self._indent -= 1
        for handler in node.handlers:
            exc = handler.type.id if isinstance(handler.type, ast.Name) else "Exception"
            var = handler.name or "e"
            self._emit(f"}} catch ({var}: {exc}) {{")
            self._indent += 1
            for stmt in handler.body: self.visit(stmt)
            self._indent -= 1
        self._emit("}")

    def visit_Raise(self, node):
        if node.exc:
            self._emit(f"throw {self._expr(node.exc)}")
        else:
            self._emit("throw \"re-raise\"")

    def visit_Import(self, node):
        for alias in node.names:
            asname = f" as {alias.asname}" if alias.asname else ""
            self._emit(f"import {alias.name}{asname}")

    def visit_ImportFrom(self, node):
        names = ", ".join(a.name + (f" as {a.asname}" if a.asname else "") for a in node.names)
        self._emit(f"from {node.module} import {{ {names} }}")

    def visit_Expr(self, node):
        val = self._expr(node.value)
        if val not in ("...", "pass"):
            self._emit(val)

    def visit_Pass(self, node): self._emit("// pass")
    def visit_Break(self, node): self._emit("break")
    def visit_Continue(self, node): self._emit("continue")
    def visit_Global(self, node): self._warn(f"global {', '.join(node.names)} — not needed in Nexa")
    def visit_Nonlocal(self, node): self._warn(f"nonlocal {', '.join(node.names)} — closures auto-capture in Nexa")

    def get_output(self) -> str:
        return "\n".join(self._lines)


def migrate_python_to_nexa(source_path: str, output_path: str = None) -> str:
    """Read a Python file and convert it to Nexa syntax."""
    src = open(source_path, encoding="utf-8").read()
    try:
        tree = ast.parse(src)
    except SyntaxError as e:
        print(f"  ❌ Python syntax error: {e}")
        return ""

    migrator = PythonToNexaMigrator()
    migrator.visit(tree)
    nexa_code = migrator.get_output()

    if output_path:
        with open(output_path, "w", encoding="utf-8") as f:
            f.write(nexa_code)
        print(f"  ✅ Migrated {os.path.basename(source_path)} → {output_path}")
    else:
        print(nexa_code)

    if migrator._warnings:
        print("\n  ⚠️  Migration Warnings:")
        for w in migrator._warnings:
            print(w)

    return nexa_code
