// Mini Interpreter - Stress tests complex ADTs, recursion, and pattern matching
//
// This demonstrates:
// - Abstract syntax trees
// - Recursive evaluation
// - Pattern matching on complex types
// - Error handling

// Value types in our mini language
type Value =
    | VInt(Int)
    | VBool(Bool)
    | VString(String)
    | VUnit

// Expression AST
type Expr =
    | EInt(Int)
    | EBool(Bool)
    | EString(String)
    | EVar(String)
    | EAdd(Expr, Expr)
    | ESub(Expr, Expr)
    | EMul(Expr, Expr)
    | EDiv(Expr, Expr)
    | EEq(Expr, Expr)
    | ELt(Expr, Expr)
    | EGt(Expr, Expr)
    | EIf(Expr, Expr, Expr)
    | ELet(String, Expr, Expr)

// Environment (variable bindings)
type Env =
    | EmptyEnv
    | ExtendEnv(String, Value, Env)

// Environment operations
fn envLookup(env: Env, name: String): Option<Value> =
    match env {
        EmptyEnv => None,
        ExtendEnv(n, v, rest) => if n == name then Some(v) else envLookup(rest, name)
    }

fn envExtend(env: Env, name: String, value: Value): Env =
    ExtendEnv(name, value, env)

// Value pretty printing
fn valueToString(v: Value): String =
    match v {
        VInt(n) => toString(n),
        VBool(b) => if b then "true" else "false",
        VString(s) => "\"" + s + "\"",
        VUnit => "()"
    }

// Main evaluator
fn eval(expr: Expr, env: Env): Result<Value, String> =
    match expr {
        EInt(n) => Ok(VInt(n)),
        EBool(b) => Ok(VBool(b)),
        EString(s) => Ok(VString(s)),
        EVar(name) => match envLookup(env, name) {
            None => Err("Unbound variable: " + name),
            Some(v) => Ok(v)
        },
        EAdd(left, right) => evalBinOp(left, right, env, fn(a: Int, b: Int): Int => a + b),
        ESub(left, right) => evalBinOp(left, right, env, fn(a: Int, b: Int): Int => a - b),
        EMul(left, right) => evalBinOp(left, right, env, fn(a: Int, b: Int): Int => a * b),
        EDiv(left, right) => evalDiv(left, right, env),
        EEq(left, right) => evalCompare(left, right, env, fn(a: Int, b: Int): Bool => a == b),
        ELt(left, right) => evalCompare(left, right, env, fn(a: Int, b: Int): Bool => a < b),
        EGt(left, right) => evalCompare(left, right, env, fn(a: Int, b: Int): Bool => a > b),
        EIf(cond, thenBranch, elseBranch) => evalIf(cond, thenBranch, elseBranch, env),
        ELet(name, valueExpr, bodyExpr) => evalLet(name, valueExpr, bodyExpr, env)
    }

fn evalBinOp(left: Expr, right: Expr, env: Env, op: fn(Int, Int): Int): Result<Value, String> =
    match eval(left, env) {
        Err(e) => Err(e),
        Ok(leftVal) => match eval(right, env) {
            Err(e) => Err(e),
            Ok(rightVal) => match (leftVal, rightVal) {
                (VInt(a), VInt(b)) => Ok(VInt(op(a, b))),
                (_, _) => Err("Type error: expected Int")
            }
        }
    }

fn evalDiv(left: Expr, right: Expr, env: Env): Result<Value, String> =
    match eval(left, env) {
        Err(e) => Err(e),
        Ok(leftVal) => match eval(right, env) {
            Err(e) => Err(e),
            Ok(rightVal) => match (leftVal, rightVal) {
                (VInt(a), VInt(b)) => if b == 0 then Err("Division by zero") else Ok(VInt(a / b)),
                (_, _) => Err("Type error: expected Int")
            }
        }
    }

fn evalCompare(left: Expr, right: Expr, env: Env, op: fn(Int, Int): Bool): Result<Value, String> =
    match eval(left, env) {
        Err(e) => Err(e),
        Ok(leftVal) => match eval(right, env) {
            Err(e) => Err(e),
            Ok(rightVal) => match (leftVal, rightVal) {
                (VInt(a), VInt(b)) => Ok(VBool(op(a, b))),
                (_, _) => Err("Type error: expected Int")
            }
        }
    }

fn evalIf(cond: Expr, thenBranch: Expr, elseBranch: Expr, env: Env): Result<Value, String> =
    match eval(cond, env) {
        Err(e) => Err(e),
        Ok(condVal) => match condVal {
            VBool(true) => eval(thenBranch, env),
            VBool(false) => eval(elseBranch, env),
            _ => Err("Condition must be boolean")
        }
    }

fn evalLet(name: String, valueExpr: Expr, bodyExpr: Expr, env: Env): Result<Value, String> =
    match eval(valueExpr, env) {
        Err(e) => Err(e),
        Ok(value) => eval(bodyExpr, envExtend(env, name, value))
    }

// Run a program and print result
fn runProgram(expr: Expr): Unit with {Console} =
    match eval(expr, EmptyEnv) {
        Ok(value) => Console.print("=> " + valueToString(value)),
        Err(e) => Console.print("Error: " + e)
    }

// Test programs
fn program1(): Expr = ELet("x", EInt(10), ELet("y", EInt(20), EAdd(EVar("x"), EVar("y"))))

fn program2(): Expr = ELet("a", EInt(5), EMul(EVar("a"), EAdd(EVar("a"), EInt(1))))

fn program3(): Expr = EIf(EGt(EInt(10), EInt(5)), EString("yes"), EString("no"))

fn program4(): Expr = ELet("n", EInt(5), ELet("a", EMul(EVar("n"), ESub(EVar("n"), EInt(1))), ELet("b", EMul(EVar("a"), ESub(EVar("n"), EInt(2))), ELet("c", EMul(EVar("b"), ESub(EVar("n"), EInt(3))), EMul(EVar("c"), ESub(EVar("n"), EInt(4)))))))

fn program5(): Expr = EDiv(EInt(10), EInt(0))

fn program6(): Expr = EVar("undefined")

// Main
fn main(): Unit with {Console} = {
    Console.print("========================================")
    Console.print("       MINI INTERPRETER DEMO")
    Console.print("========================================")
    Console.print("")

    Console.print("Program 1: let x = 10 in let y = 20 in x + y")
    runProgram(program1())
    Console.print("")

    Console.print("Program 2: let a = 5 in a * (a + 1)")
    runProgram(program2())
    Console.print("")

    Console.print("Program 3: if 10 > 5 then \"yes\" else \"no\"")
    runProgram(program3())
    Console.print("")

    Console.print("Program 4: 5! computed iteratively")
    runProgram(program4())
    Console.print("")

    Console.print("Program 5: Division by zero error")
    runProgram(program5())
    Console.print("")

    Console.print("Program 6: Unbound variable error")
    runProgram(program6())
}

let output = run main() with {}
