# Solar engine - v1.0.3.post8 (contain fixed)
# Exports: SOLAR_VERSION, compile_to_python, run_file
from __future__ import annotations

import ast
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable

SOLAR_VERSION = "1.0.3.post9"

@dataclass
class Stmt: ...
@dataclass
class Expr: ...

@dataclass
class Number(Expr):
    value: float

@dataclass
class String(Expr):
    value: str

@dataclass
class Var(Expr):
    name: str

@dataclass
class CallExpr(Expr):
    name: str
    args: list[Expr]

@dataclass
class Let(Stmt):
    name: str
    expr: Expr

@dataclass
class Print(Stmt):
    exprs: list[Expr]

@dataclass
class SolarDef(Stmt):
    name: str
    args: list[str]
    body: list[Stmt]

@dataclass
class CallStmt(Stmt):
    call: CallExpr

@dataclass
class UiStmt(Stmt):
    cmd: str
    args: list[str]
    quoted: list[bool]

@dataclass
class Contain(Stmt):
    module: str

def _tokenize_with_quotes(line: str):
    out = []
    i = 0
    n = len(line)
    while i < n:
        while i < n and line[i].isspace():
            i += 1
        if i >= n:
            break
        if line[i] in ("'", '"'):
            q = line[i]
            i += 1
            buf = []
            while i < n:
                if line[i] == "\" and i + 1 < n:
                    buf.append(line[i + 1])
                    i += 2
                    continue
                if line[i] == q:
                    break
                buf.append(line[i])
                i += 1
            if i >= n or line[i] != q:
                raise SyntaxError("unclosed string")
            i += 1
            out.append(("".join(buf), True))
        else:
            j = i
            while j < n and not line[j].isspace():
                j += 1
            out.append((line[i:j], False))
            i = j
    return out

def _parse_expr(tok, quoted):
    if quoted:
        return String(tok)
    try:
        return Number(int(tok))
    except ValueError:
        try:
            return Number(float(tok))
        except ValueError:
            return Var(tok)

def _parse_line(line, lineno):
    parts_q = _tokenize_with_quotes(line)
    parts = [p for p,_ in parts_q]
    quoted = [q for _,q in parts_q]
    head = parts[0]

    if head == "contain":
        return Contain(parts[1])

    if head == "print":
        return Print([_parse_expr(parts[i], quoted[i]) for i in range(1, len(parts))])

    return CallStmt(CallExpr(head, [_parse_expr(parts[i], quoted[i]) for i in range(1, len(parts))]))

def parse_program(src):
    out = []
    for i, raw in enumerate(src.splitlines(), 1):
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        out.append(_parse_line(line, i))
    return out

def _emit_expr(e):
    if isinstance(e, Number): return repr(e.value)
    if isinstance(e, String): return repr(e.value)
    if isinstance(e, Var): return f"vars_.get({e.name!r})"
    raise TypeError(e)

def compile_to_python(src):
    program = parse_program(src)
    imports = set()
    out = ["# compiled by Solar"]

    for st in program:
        if isinstance(st, Contain):
            imports.add(st.module)

    for m in sorted(imports):
        out.append(f"import {m}")

    out.append("")
    out.append("def __run(vars_, funcs):")

    for st in program:
        if isinstance(st, Contain):
            continue
        if isinstance(st, Print):
            out.append("    print(" + ", ".join(_emit_expr(e) for e in st.exprs) + ")")
        if isinstance(st, CallStmt):
            out.append(f"    funcs[{st.call.name!r}]()")

    return "\n".join(out)

def run_file(path, funcs=None):
    src = Path(path).read_text()
    py = compile_to_python(src)
    env = {}
    exec(py, {"print": print, "__import__": __import__}, env)
    env["__run"]({}, funcs or {})
