#import "modules/std.sx";
#import "modules/std/mem.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";

Point :: struct { x, y: i32; }

OptNode :: struct {
    value: i32;
    next: ?i32;
}

OptInner :: struct { val: i32; }

OptOuter :: struct { inner: ?OptInner; }

add :: (a: i32, b: i32) -> i32 { a + b }

mul :: (a: i32, b: i32) -> i32 { a * b }

identity :: (x: $T) -> T { x }

apply :: (f: (i32, i32) -> i32, x: i32, y: i32) -> i32 {
    f(x, y)
}

// P4 edge: Chained default→default calls
Chained :: protocol {
    base :: (msg: string) -> i32;
    wrap :: (msg: string) -> i32 {
        self.base(msg) + 1
    }
    double_wrap :: (msg: string) -> i32 {
        self.wrap(msg) + self.wrap(msg)
    }
}

main :: () {

    // --- Tuple Operators ---
    {
        print("=== Tuple Operators ===\n");

        // Equality
        print("{}\n", (1, 2) == (1, 2));     // true
        print("{}\n", (1, 2) == (1, 3));     // false
        print("{}\n", (1, 2) != (1, 3));     // true
        print("{}\n", (1, 2) != (1, 2));     // false

        // Concatenation
        c := (1, 2) + (3, 4);
        print("{}\n", c.0);    // 1
        print("{}\n", c.1);    // 2
        print("{}\n", c.2);    // 3
        print("{}\n", c.3);    // 4

        // Repetition
        r := (1, 2) * 3;
        print("{}\n", r.0);    // 1
        print("{}\n", r.1);    // 2
        print("{}\n", r.2);    // 1
        print("{}\n", r.3);    // 2
        print("{}\n", r.4);    // 1
        print("{}\n", r.5);    // 2

        // Lexicographic comparison
        print("{}\n", (1, 2) < (1, 3));      // true
        print("{}\n", (1, 3) < (1, 2));      // false
        print("{}\n", (1, 2) < (1, 2));      // false
        print("{}\n", (1, 2) <= (1, 2));     // true
        print("{}\n", (2, 0) > (1, 9));      // true
        print("{}\n", (1, 2) >= (1, 2));     // true

        // Membership
        print("{}\n", 2 in (1, 2, 3));      // true
        print("{}\n", 5 in (1, 2, 3));      // false
    }

    // --- Directory imports ---
    {
        print("--- directory imports ---\n");
        print("{}\n", pkg.add(3, 4));         // 7
        print("{}\n", pkg.mul(5, 6));         // 30
        print("{}\n", pkg.hello());           // hello from testpkg
        print("{}\n", pkg.cwd_greet());       // cwd-import-ok
    }

    // --- Pipe operator ---
    {
        print("--- pipe operator ---\n");
        // Basic: a |> f(b) → f(a, b)
        print("{}\n", 3 |> pkg.add(4));           // 7
        print("{}\n", 5 |> pkg.mul(6));           // 30

        // Chaining: a |> f(b) |> g(c) → g(f(a, b), c)
        print("{}\n", 3 |> pkg.add(4) |> pkg.mul(2));  // 14

        // With non-namespaced functions
        print("{}\n", "hello" |> concat(" world"));     // hello world

        // Chained string ops
        print("{}\n", "piped" |> concat(" ok") |> concat("!"));  // piped ok!
    }

    // ── alloc_slice ──────────────────────────────────────────
    {
        items := alloc_slice(i64, 5);
        items[0] = 10;
        items[1] = 20;
        items[2] = 30;
        items[3] = 40;
        items[4] = 50;
        print("alloc len: {}\n", items.len);         // alloc len: 5
        print("alloc[0]: {}\n", items[0]);            // alloc[0]: 10
        print("alloc[4]: {}\n", items[4]);            // alloc[4]: 50

        // alloc_slice with u8
        bytes := alloc_slice(u8, 3);
        bytes[0] = 65;
        bytes[1] = 66;
        bytes[2] = 67;
        print("bytes len: {}\n", bytes.len);          // bytes len: 3
    }

    // ========================================================
    // ALLOCATORS
    // ========================================================
    print("--- allocators ---\n");

    // ── GPA ─────────────────────────────────────────────────
    {
        gpa := GPA.init();
        a : Allocator = xx gpa;
        p1 := a.alloc_bytes(64);
        p2 := a.alloc_bytes(128);
        print("gpa allocs: {}\n", gpa.alloc_count);         // gpa allocs: 2
        a.dealloc_bytes(p1);
        a.dealloc_bytes(p2);
        print("gpa final: {}\n", gpa.alloc_count);          // gpa final: 0
    }

    // ── Arena backed by GPA (multi-chunk) ───────────────────
    {
        gpa3 := GPA.init();
        arena := Arena.init(xx gpa3, 32);
        a : Allocator = xx arena;
        // First chunk fits 80 usable bytes
        a1 := a.alloc_bytes(40);
        a2 := a.alloc_bytes(40);
        // Counts: just the first chunk = 1. Arena.init returns the
        // state by value; the local IS the Arena struct, no parent
        // allocation for the state itself.
        print("arena chunks: {}\n", gpa3.alloc_count);        // arena chunks: 1
        // Overflow → new chunk
        a3 := a.alloc_bytes(16);
        print("arena overflow: {}\n", gpa3.alloc_count);      // arena overflow: 2
        // Verify memory works across chunks
        p1 : [*]u8 = xx a1;
        p3 : [*]u8 = xx a3;
        p1[0] = 42;
        p3[0] = 99;
        print("arena a1: {}\n", p1[0]);                       // arena a1: 42
        print("arena a3: {}\n", p3[0]);                       // arena a3: 99
        // Reset retains the first chunk
        arena.reset();
        print("arena reset idx: {}\n", arena.end_index);      // arena reset idx: 0
        print("arena reset gpa: {}\n", gpa3.alloc_count);     // arena reset gpa: 1
        // Deinit frees all chunks (caller's local is the state — no
        // dealloc of the struct itself).
        arena.deinit();
        print("arena deinit: {}\n", gpa3.alloc_count);        // arena deinit: 0
    }

    // ── BufAlloc from stack array ───────────────────────────
    {
        stack_buf : [128]u8 = ---;
        buf := BufAlloc.init(@stack_buf[0], 128);
        a : Allocator = xx buf;
        b1 := a.alloc_bytes(24);
        b2 := a.alloc_bytes(24);
        print("buf pos: {}\n", buf.pos);                    // buf pos: 48
        b3 := a.alloc_bytes(200);
        b3_i : i64 = xx b3;
        print("buf overflow: {}\n", b3_i);                  // buf overflow: 0
        buf.reset();
        print("buf reset: {}\n", buf.pos);                  // buf reset: 0
    }

    {
        if 1 == (1,) {
            print("1 == (1)\n");
        }

        if (1,) == (1) {
            print("(1) == 1\n");
        }

        if (1,) == 1 {
            print("1 == 1\n");
        }
    }

    // ========================================================
    // OPTIONALS
    // ========================================================
    print("--- optionals ---\n");

    // Basic optional creation and null
    {
        x: ?i32 = 42;
        y: ?i32 = null;
        print("opt x: {}\n", x);                           // opt x: 42
        print("opt y: {}\n", y);                            // opt y: null
    }

    // Force unwrap
    {
        x: ?i32 = 10;
        val := x!;
        print("unwrap: {}\n", val);                         // unwrap: 10
    }

    // Null coalescing
    {
        x: ?i32 = 42;
        y: ?i32 = null;
        a := x ?? 0;
        b := y ?? 99;
        print("coalesce a: {}\n", a);                       // coalesce a: 42
        print("coalesce b: {}\n", b);                       // coalesce b: 99

        // Chained ?? (right-associative): a ?? b ?? c
        z: ?i32 = null;
        c := x ?? y ?? 0;
        d := z ?? y ?? 99;
        e := z ?? z ?? 0;
        print("chained ?? c: {}\n", c);                     // chained ?? c: 42
        print("chained ?? d: {}\n", d);                     // chained ?? d: 99
        print("chained ?? e: {}\n", e);                     // chained ?? e: 0
    }

    // If-binding (safe unwrap)
    {
        x: ?i32 = 7;
        y: ?i32 = null;
        if val := x {
            print("if-bind x: {}\n", val);                  // if-bind x: 7
        }
        if val := y {
            print("if-bind y: should not print\n");
        } else {
            print("if-bind y: none\n");                     // if-bind y: none
        }
    }

    // Pattern matching on optionals
    {
        check :: (v: ?i32) -> i32 {
            return if v == {
                case .some: (val) { val }
                case .none: { 0 }
            };
        }
        a: ?i32 = 55;
        b: ?i32 = null;
        print("match some: {}\n", check(a));                // match some: 55
        print("match none: {}\n", check(b));                // match none: 0
    }

    // Optional with implicit wrapping
    {
        opt_wrap :: (n: i32) -> ?i32 {
            if n > 0 {
                return n;
            }
            return null;
        }
        r1 := opt_wrap(5);
        r2 := opt_wrap(0);
        print("wrap pos: {}\n", r1);                        // wrap pos: 5
        print("wrap neg: {}\n", r2);                        // wrap neg: null
    }

    // Struct field defaults for ?T
    {
        n := OptNode.{ value = 10 };
        print("opt field default: {}\n", n.next);           // opt field default: null
        m := OptNode.{ value = 20, next = 42 };
        print("opt field set: {}\n", m.next);               // opt field set: 42
    }

    // ?T as function parameter
    {
        opt_process :: (val: ?i32) -> i32 {
            return val ?? 0;
        }
        a: ?i32 = 42;
        b: ?i32 = null;
        print("opt param a: {}\n", opt_process(a));         // opt param a: 42
        print("opt param b: {}\n", opt_process(b));         // opt param b: 0
        print("opt param 7: {}\n", opt_process(7));         // opt param 7: 7
    }

    // Assignment to optional variable (f32 → ?f32)
    {
        iw: ?f32 = null;
        w: f32 = 42.5;
        iw = w;
        print("opt reassign: {}\n", iw ?? 0.0);                // opt reassign: 42.5

        // Assignment of computed value to optional
        iw2: ?f32 = null;
        a: ?f32 = 10.0;
        if v := a { iw2 = v + 5.0; }
        print("opt compute assign: {}\n", iw2 ?? 0.0);         // opt compute assign: 15.0

        // Re-assign optional back to null
        iw2 = null;
        print("opt re-null: {}\n", iw2 ?? 99.0);               // opt re-null: 99.0
    }

    // Generic function with ?T return
    {
        first_pos :: ($T: Type, a: T, b: T) -> ?T {
            if a > 0 { return a; }
            if b > 0 { return b; }
            return null;
        }
        print("generic opt 1: {}\n", first_pos(i32, 5, 10));  // generic opt 1: 5
        print("generic opt 2: {}\n", first_pos(i32, 0, 7));   // generic opt 2: 7
        print("generic opt 3: {}\n", first_pos(i32, 0, 0));   // generic opt 3: null
    }

    // Optional chaining (?.)
    {
        p: ?OptNode = OptNode.{ value = 10, next = 20 };
        q: ?OptNode = null;
        print("chain some: {}\n", p?.value ?? 0);           // chain some: 10
        print("chain none: {}\n", q?.value ?? 0);           // chain none: 0
        print("chain print: {}\n", p?.next);                 // chain print: 20
        print("chain null: {}\n", q?.next);                  // chain null: null

        // Chained: obj.field?.field
        o1 := OptOuter.{ inner = OptInner.{ val = 99 } };
        o2 := OptOuter.{ inner = null };
        print("deep chain 1: {}\n", o1.inner?.val ?? 0);    // deep chain 1: 99
        print("deep chain 2: {}\n", o2.inner?.val ?? 0);    // deep chain 2: 0
    }

    // Flow-sensitive narrowing
    {
        x: ?i32 = 42;
        y: ?i32 = null;

        // if x != null → x is narrowed to i32
        if x != null {
            print("narrow x: {}\n", x);                        // narrow x: 42
        }

        // if y != null → not entered
        if y != null {
            print("should not print\n");
        } else {
            print("narrow y else: null\n");                    // narrow y else: null
        }

        // if x == null ... else → else-branch narrowed
        if x == null {
            print("should not print\n");
        } else {
            print("narrow else x: {}\n", x);                  // narrow else x: 42
        }
    }

    // Guard narrowing
    {
        guard_fn :: (v: ?i32) -> i32 {
            if v == null { return 0; }
            return v;
        }
        print("guard some: {}\n", guard_fn(42));               // guard some: 42
        print("guard none: {}\n", guard_fn(null));             // guard none: 0
    }

    // Compound narrowing: && chains
    {
        a: ?i32 = 10;
        b: ?i32 = 20;
        c: ?i32 = null;
        if a != null and b != null {
            print("and both: {} {}\n", a, b);                  // and both: 10 20
        }
        if a != null and c != null {
            print("should not print\n");
        } else {
            print("and one null\n");                           // and one null
        }
    }

    // Compound guard narrowing: || chains
    {
        guard2 :: (a: ?i32, b: ?i32) -> i32 {
            if a == null or b == null { return 0; }
            return a + b;
        }
        print("or guard: {}\n", guard2(3, 4));                 // or guard: 7
        print("or guard null: {}\n", guard2(3, null));         // or guard null: 0
    }

    // Nested if narrowing
    {
        a: ?i32 = 10;
        b: ?i32 = 20;
        if a != null {
            if b != null {
                print("nested narrow: {} {}\n", a, b);         // nested narrow: 10 20
            }
        }
    }

    // Guard narrowing used in loop
    {
        guard_loop :: (v: ?i32) -> i32 {
            if v == null { return 0; }
            sum := 0;
            i := 0;
            while i < v {
                sum = sum + 1;
                i = i + 1;
            }
            return sum;
        }
        print("guard loop: {}\n", guard_loop(3));              // guard loop: 3
    }

    // --- block-body lambdas ---
    {
        // block-body lambda with return type
        clamp := (x: i64, lo: i64, hi: i64) -> i64 {
            if x < lo { return lo; }
            if x > hi { return hi; }
            return x;
        };
        print("block-lambda: {}\n", clamp(50, 0, 100));   // block-lambda: 50
        print("block-lambda: {}\n", clamp(-10, 0, 100));  // block-lambda: 0
        print("block-lambda: {}\n", clamp(999, 0, 100));  // block-lambda: 100

        // block-body lambda without return type annotation
        greet := (name: string) {
            print("hello {}\n", name);
        };
        greet("block");  // hello block
    }

    // --- named params in function types ---
    {
        // Named params are documentation only — ignored for type identity
        apply_named :: (f: (x: i32, y: i32) -> i32, a: i32, b: i32) -> i32 {
            return f(a, b);
        }
        add :: (a: i32, b: i32) -> i32 { return a + b; }
        print("named-fn-type: {}\n", apply_named(add, 3, 4));  // named-fn-type: 7
    }

    // --- xx on function pointers ---
    {
        MyEnv :: struct { n: i32; }
        typed_fn :: (e: *MyEnv, x: i32) -> i32 {
            return x + e.n;
        }
        // xx cast: (*MyEnv, i32) -> i32  →  (*void, i32) -> i32
        f : (*void, i32) -> i32 = xx typed_fn;
        env := MyEnv.{ n = 100 };
        print("xx-fnptr: {}\n", f(xx @env, 42));  // xx-fnptr: 142
    }

    // --- closure type: construct and access fields ---
    {
        dummy_fn :: (env: *void, x: i32) -> i32 {
            return x * 2;
        }
        fn_ptr : *void = xx dummy_fn;
        null_env : *void = xx 0;
        c : Closure(i32) -> i32 = .{ fn_ptr = fn_ptr, env = null_env };
        print("closure-type: fn_ptr-nonnull={}\n", c.fn_ptr != null_env);
        print("closure-type: env-null={}\n", c.env == null_env);
    }

    // --- closure calling convention ---
    {
        Env :: struct { n: i32; }
        impl_fn :: (env: *void, x: i32) -> i32 {
            e : *Env = xx env;
            return x + e.n;
        }
        env := Env.{ n = 5 };
        fn_ptr : *void = xx impl_fn;
        env_ptr : *void = xx @env;
        c : Closure(i32) -> i32 = .{ fn_ptr = fn_ptr, env = env_ptr };
        print("closure-call: {}\n", c(10));
    }

    // --- auto-promotion: bare fn → Closure ---
    {
        double :: (x: i32) -> i32 { return x * 2; }
        apply :: (f: Closure(i32) -> i32, x: i32) -> i32 { return f(x); }
        print("auto-promote: {}\n", apply(double, 10));

        // Named function to Closure variable
        f : Closure(i32) -> i32 = double;
        print("auto-promote-var: {}\n", f(5));
    }

    // --- closure() intrinsic ---
    {
        // capture scalar
        n := 42;
        f := closure((x: i32) => x + n);
        print("closure-capture: {}\n", f(10));

        // capture by value is a snapshot
        m := 5;
        g := closure((x: i32) => x + m);
        m = 100;
        print("closure-snapshot: {}\n", g(10));

        // no captures (null env)
        h := closure((x: i32) => x * 2);
        print("closure-nocap: {}\n", h(7));

        // multiple captures
        a := 10;
        b := 20;
        multi := closure((x: i32) => x + a + b);
        print("closure-multi: {}\n", multi(3));

        // block-body closure with return
        offset := 50;
        clamp := closure((x: i64) -> i64 {
            if x < 0 { return 0; }
            if x > 100 { return 100; }
            return x + offset;
        });
        r1 : i64 = clamp(10);
        r2 : i64 = clamp(0 - 5);
        r3 : i64 = clamp(999);
        print("closure-block: {}\n", r1);
        print("closure-block: {}\n", r2);
        print("closure-block: {}\n", r3);

        // void closure
        tag := "LOG";
        logger := closure((msg: string) {
            print("[{}] {}\n", tag, msg);
        });
        logger("hello");

        // pass closure to higher-order function
        dbl :: (x: i32) -> i32 { return x * 2; }
        apply_cl :: (f2: Closure(i32) -> i32, x: i32) -> i32 { return f2(x); }
        factor : i32 = 3;
        print("closure-hof: {}\n", apply_cl(closure((x: i32) -> i32 => x * factor), 10));

        // auto-promoted bare fn passed alongside closures
        print("closure-hof-bare: {}\n", apply_cl(dbl, 10));

        // C5.A2: capture f32
        scale := 2.5;
        f_f32 := closure((x: f32) -> f32 => x * scale);
        print("closure-f32: {}\n", f_f32(4.0));

        // C5.A3: capture bool
        verbose := true;
        f_bool := closure((msg: string) {
            if verbose { print("closure-bool: {}\n", msg); }
        });
        f_bool("hello");

        // C5.B3: two params
        base : i32 = 100;
        f_2p := closure((x: i32, y: i32) -> i32 => x + y + base);
        print("closure-2p: {}\n", f_2p(3, 4));

        // C5.B4: three params
        bias : i32 = 1;
        f_3p := closure((a: i32, b: i32, c2: i32) -> i32 => a + b + c2 + bias);
        print("closure-3p: {}\n", f_3p(10, 20, 30));

        // C5.B5: mixed param types (string + i32)
        extra : i32 = 5;
        f_mix := closure((name: string, age: i32) {
            print("closure-mix: {} is {}\n", name, age + extra);
        });
        f_mix("Alice", 30);

        // C5.C3: return bool
        threshold : i32 = 100;
        f_rbool := closure((x: i32) -> bool { return x > threshold; });
        print("closure-rbool: {} {}\n", f_rbool(50), f_rbool(200));

        // C5.D3: reduce / fold
        reduce :: (arr: []i32, f3: Closure(i32, i32) -> i32, init: i32) -> i32 {
            acc := init;
            i : i64 = 0;
            while i < arr.len { acc = f3(acc, arr[i]); i += 1; }
            return acc;
        }
        r_nums : []i32 = .[1, 2, 3, 4, 5];
        r_bonus : i32 = 100;
        r_total := reduce(r_nums, closure((acc: i32, x: i32) -> i32 => acc + x), r_bonus);
        print("closure-reduce: {}\n", r_total);

        // C5.G1: factory function
        make_adder :: (n: i32) -> Closure(i32) -> i32 {
            return closure((x: i32) -> i32 => x + n);
        }
        add5 := make_adder(5);
        add10 := make_adder(10);
        print("closure-factory: {} {}\n", add5(100), add10(100));

        // C5.A5: capture struct
        origin := Point.{ x = 10, y = 20 };
        f_st := closure(() {
            print("closure-struct: {} {}\n", origin.x, origin.y);
        });
        f_st();

        // C5.H1: closure captures another closure
        inner_n := 10;
        inner_cl := closure((x: i64) -> i64 => x + inner_n);
        outer_cl := closure((x: i64) -> i64 => inner_cl(x) * 2);
        print("closure-compose: {}\n", outer_cl(5));

        // C5.M7: multiple closures from same scope capture independently
        shared : i32 = 10;
        cl_a := closure((x: i32) -> i32 => x + shared);
        cl_b := closure((x: i32) -> i32 => x * shared);
        print("closure-indep: {} {}\n", cl_a(5), cl_b(5));

        // C6: optional closures
        f_none : ?Closure(i64) -> i64 = null;
        if h := f_none {
            print("should not print: {}\n", h(1));
        } else {
            print("opt-closure: none\n");
        }

        opt_n := 10;
        f_some : ?Closure(i64) -> i64 = closure((x: i64) -> i64 => x + opt_n);
        if h := f_some {
            print("opt-closure: {}\n", h(5));
        } else {
            print("should not print\n");
        }

        // Struct with optional closure callback
        Btn :: struct { label: string; on_click: ?Closure(i64) -> void; }
        btn_x := 99;
        btn_cl := closure((id: i64) {
            print("opt-closure-btn: {} {}\n", id, btn_x);
        });
        btn1 := Btn.{ label = "OK", on_click = btn_cl };
        btn2 := Btn.{ label = "Cancel", on_click = null };
        if h := btn1.on_click { h(1); }
        if h := btn2.on_click { h(2); } else { print("opt-closure-btn: null\n"); }

        // C5.A6: capture pointer (shared mutable state)
        count_a6 : i32 = 0;
        p_a6 := @count_a6;
        inc_fn := closure(() { p_a6.* += 1; });
        inc_fn(); inc_fn(); inc_fn();
        print("closure-ptr: {}\n", count_a6);

        // C5.A9: capture enum value (as i32 tag)
        c_a9 : i32 = 2;  // simulate enum tag
        f_a9 := closure(() -> i32 => c_a9);
        print("closure-enum: {}\n", f_a9());

        // C5.C4: return string
        tag_c4 := "INFO";
        f_c4 := closure((msg: string) -> string => format("[{}] {}", tag_c4, msg));
        print("closure-rstr: {}\n", f_c4("ok"));

        // C5.C5: return struct
        off_c5 := Point.{ x = 10, y = 20 };
        f_c5 := closure((p: Point) -> Point => Point.{ x = p.x + off_c5.x, y = p.y + off_c5.y });
        res_c5 := f_c5(Point.{ x = 1, y = 2 });
        print("closure-rstruct: {} {}\n", res_c5.x, res_c5.y);

        // C5.G2: factory with multiple captures
        make_linear :: (m: i32, b: i32) -> Closure(i32) -> i32 {
            return closure((x: i32) -> i32 => m * x + b);
        }
        lin := make_linear(3, 7);
        print("closure-linear: {}\n", lin(10));

        // C5.G3: factory returning clamper
        make_clamper :: (lo: i32, hi: i32) -> Closure(i32) -> i32 {
            return closure((x: i32) -> i32 {
                if x < lo { return lo; }
                if x > hi { return hi; }
                return x;
            });
        }
        clamp_fn := make_clamper(0, 255);
        cv1 : i32 = xx -10;
        cv2 : i32 = 100;
        cv3 : i32 = 999;
        print("closure-clamp: {} {} {}\n", clamp_fn(cv1), clamp_fn(cv2), clamp_fn(cv3));

        // C5.H2: compose
        compose :: (f_h2: Closure(i32) -> i32, g_h2: Closure(i32) -> i32) -> Closure(i32) -> i32 {
            return closure((x: i32) -> i32 => f_h2(g_h2(x)));
        }
        one_h2 : i32 = 1;
        two_h2 : i32 = 2;
        add1_h2 := closure((x: i32) -> i32 => x + one_h2);
        mul2_h2 := closure((x: i32) -> i32 => x * two_h2);
        composed := compose(mul2_h2, add1_h2);
        print("closure-compose2: {}\n", composed(5));

        // C5.H3: chain of closures
        ch_k1 : i32 = 1;
        ch_k2 : i32 = 2;
        ch_k10 : i32 = 10;
        ch_a := closure((x: i32) -> i32 => x + ch_k1);
        ch_b := closure((x: i32) -> i32 => ch_a(x) * ch_k2);
        ch_c := closure((x: i32) -> i32 => ch_b(x) + ch_k10);
        print("closure-chain: {}\n", ch_c(5));

        // C5.D1: map
        map_cl :: (arr: [*]i32, cnt: i64, f_map: Closure(i32) -> i32, result: [*]i32) {
            i := 0;
            while i < cnt { result[i] = f_map(arr[i]); i += 1; }
        }
        map_src : [5]i32 = .[1, 2, 3, 4, 5];
        map_dst : [5]i32 = .[0, 0, 0, 0, 0];
        factor_d1 : i32 = 3;
        map_cl(xx @map_src, 5, closure((x: i32) -> i32 => x * factor_d1), xx @map_dst);
        print("closure-map: {} {} {} {} {}\n", map_dst[0], map_dst[1], map_dst[2], map_dst[3], map_dst[4]);

        // C5.D2: filter
        filter_cl :: (arr: [*]i32, cnt: i64, pred: Closure(i32) -> bool, result: [*]i32) -> i64 {
            j := 0;
            i := 0;
            while i < cnt {
                if pred(arr[i]) { result[j] = arr[i]; j += 1; }
                i += 1;
            }
            return j;
        }
        min_val : i32 = 3;
        filt_dst : [5]i32 = .[0, 0, 0, 0, 0];
        kept := filter_cl(xx @map_src, 5, closure((x: i32) -> bool => x >= min_val), xx @filt_dst);
        print("closure-filter: {} [{} {} {}]\n", kept, filt_dst[0], filt_dst[1], filt_dst[2]);

        // C5.D4: sort comparator (bubble sort)
        sort_cl :: (arr: [*]i32, cnt: i64, less: Closure(i32, i32) -> bool) {
            i := 0;
            while i < cnt {
                j := 0;
                while j < cnt - 1 - i {
                    if less(arr[j + 1], arr[j]) {
                        tmp := arr[j];
                        arr[j] = arr[j + 1];
                        arr[j + 1] = tmp;
                    }
                    j += 1;
                }
                i += 1;
            }
        }
        sort_arr : [5]i32 = .[5, 3, 1, 4, 2];
        descending := true;
        sort_cl(xx @sort_arr, 5, closure((a: i32, b: i32) -> bool {
            if descending { return a > b; }
            return a < b;
        }));
        print("closure-sort: {} {} {} {} {}\n", sort_arr[0], sort_arr[1], sort_arr[2], sort_arr[3], sort_arr[4]);

        // C5.D5: for_each with index
        for_each_cl :: (arr: [*]i32, cnt: i64, f_fe: Closure(i32, i64) -> void) {
            i : i64 = 0;
            while i < cnt { f_fe(arr[i], i); i += 1; }
        }
        fe_label := "item";
        fe_arr : [3]i32 = .[10, 20, 30];
        for_each_cl(xx @fe_arr, 3, closure((val: i32, idx: i64) {
            print("closure-fe: {} {}={}\n", fe_label, idx, val);
        }));

        // C5.D6: find
        find_cl :: (arr: [*]i32, cnt: i64, pred_f: Closure(i32) -> bool) -> i64 {
            i : i64 = 0;
            while i < cnt {
                if pred_f(arr[i]) { return i; }
                i += 1;
            }
            return -1;
        }
        target : i32 = 30;
        found_idx := find_cl(xx @fe_arr, 3, closure((x: i32) -> bool => x == target));
        print("closure-find: {}\n", found_idx);

        // C5.D7: any
        any_cl :: (arr: [*]i32, cnt: i64, pred_a: Closure(i32) -> bool) -> bool {
            i : i64 = 0;
            while i < cnt {
                if pred_a(arr[i]) { return true; }
                i += 1;
            }
            return false;
        }
        has_big := any_cl(xx @fe_arr, 3, closure((x: i32) -> bool => x > 100));
        has_20 := any_cl(xx @fe_arr, 3, closure((x: i32) -> bool => x == 20));
        print("closure-any: {} {}\n", has_big, has_20);

        // C5.E4: auto-promotion in struct field assignment
        Widget :: struct { transform: Closure(i32) -> i32; }
        negate_fn :: (x: i32) -> i32 { return 0 - x; }
        w_e4 := Widget.{ transform = negate_fn };
        print("closure-struct-field: {}\n", w_e4.transform(5));

        // C5.F1: single closure callback in struct
        Button :: struct {
            label: string;
            on_press: Closure(i32) -> void;
        }
        btn_x2 := 99;
        btn_cb := closure((id: i32) {
            print("closure-btn: {} {}\n", id, btn_x2);
        });
        btn3 := Button.{ label = "OK", on_press = btn_cb };
        btn3.on_press(1);

        // C5.J1: stateful counter via pointer capture
        state_j1 : i32 = 0;
        p_j1 := @state_j1;
        inc_j1 := closure(() -> i32 { p_j1.* += 1; return p_j1.*; });
        print("closure-counter: {} {} {}\n", inc_j1(), inc_j1(), inc_j1());

        // C5.J2: stateful accumulator
        state_j2 : i32 = 100;
        p_j2 := @state_j2;
        acc_j2 := closure((x: i32) -> i32 { p_j2.* += x; return p_j2.*; });
        print("closure-acc: {} {}\n", acc_j2(5), acc_j2(10));

        // C5.K2: block-body with local variables and loops
        base_k2 : i32 = 100;
        sum_fn := closure((items: [*]i32, cnt: i64) -> i32 {
            total : i32 = 0;
            i : i64 = 0;
            while i < cnt {
                total += items[i];
                i += 1;
            }
            return total + base_k2;
        });
        k2_arr : [5]i32 = .[1, 2, 3, 4, 5];
        print("closure-loop: {}\n", sum_fn(xx @k2_arr, 5));

        // C5.M3: reassigning a closure variable
        n_m3 : i32 = 1;
        f_m3 := closure((x: i32) -> i32 => x + n_m3);
        print("closure-reassign: {}\n", f_m3(10));
        m_m3 : i32 = 2;
        f_m3 = closure((x: i32) -> i32 => x * m_m3);
        print("closure-reassign: {}\n", f_m3(10));

        // C5.M6b: snapshot verified with struct capture
        pt_m6 := Point.{ x = 5, y = 10 };
        f_m6 := closure(() -> i32 => pt_m6.x + pt_m6.y);
        pt_m6 = Point.{ x = 99, y = 99 };
        print("closure-snapstruct: {}\n", f_m6());

        // C5.M2: closure capturing auto-promoted closure
        double_m2 :: (x: i32) -> i32 { return x * 2; }
        base_m2 : Closure(i32) -> i32 = double_m2;
        n_m2 : i32 = 1;
        f_m2 := closure((x: i32) -> i32 => base_m2(x) + n_m2);
        print("closure-cap-promoted: {}\n", f_m2(5));

        // C5.M5: immediately invoked closure (via temp var)
        n_m5 : i32 = 5;
        iife := closure((x: i32) -> i32 => x + n_m5);
        result_m5 := iife(10);
        print("closure-iife: {}\n", result_m5);

        // C5.F2: optional callback (none)
        Toggle :: struct { on_change: ?Closure(bool) -> void; }
        t_f2 := Toggle.{ on_change = null };
        if h := t_f2.on_change { h(true); } else { print("closure-toggle: none\n"); }

        // C5.F3: optional callback (some)
        t_f3_cb := closure((enabled: bool) { print("closure-toggle: {}\n", enabled); });
        t_f3 := Toggle.{ on_change = t_f3_cb };
        if h := t_f3.on_change { h(true); }

        // C5.F5: callback receiving caller context
        Panel :: struct {
            title: string;
            on_resize: Closure(string, i32, i32) -> void;
        }
        p_f5_cb := closure((title: string, w: i32, h: i32) {
            print("closure-panel: {} {}x{}\n", title, w, h);
        });
        p_f5 := Panel.{ title = "main", on_resize = p_f5_cb };
        p_f5.on_resize(p_f5.title, 800, 600);

        // C5.E6: protocol value passed through multiple function calls
        step3 :: (a: Allocator) -> *void { a.alloc_bytes(8) }
        step2 :: (a: Allocator) -> *void { step3(a) }
        step1 :: (a: Allocator) -> *void { step2(a) }
        gpa_e6 := GPA.init();
        a_e6 : Allocator = xx gpa_e6;
        ptr_e6 := step1(a_e6);
        print("closure-chain-call: {}\n", ptr_e6 != null);
        a_e6.dealloc_bytes(ptr_e6);

        // C5.I1: creating closures in a loop (each captures different value)
        // TEMPORARILY DISABLED — closure-in-loop causes infinite loop (index_gep element size issue?)
        // cl_arr : [5]Closure(i32) -> i32 = ---;
        // i_loop := 0;
        // while i_loop < 5 {
        //     val_loop : i32 = xx (i_loop * 10);
        //     cl_arr[i_loop] = closure((x: i32) -> i32 => x + val_loop);
        //     i_loop += 1;
        // }
        // I2: calling closures from array
        // tmp_cl := cl_arr[0]; print("closure-loop-0: {}\n", tmp_cl(1));
        // tmp_cl = cl_arr[1]; print("closure-loop-1: {}\n", tmp_cl(1));
        // tmp_cl = cl_arr[4]; print("closure-loop-4: {}\n", tmp_cl(1));

        // C5.M4: closure in conditional expression (via temp var)
        use_fast := true;
        k_fast : i32 = 2;
        k_slow : i32 = 10;
        f_fast := closure((x: i32) -> i32 => x * k_fast);
        f_slow := closure((x: i32) -> i32 => x + k_slow);
        f_cond : Closure(i32) -> i32 = if use_fast then f_fast else f_slow;
        print("closure-cond: {}\n", f_cond(5));

        // C5.F4: multiple callbacks on one struct
        Form :: struct {
            on_submit: ?Closure() -> void;
            on_cancel: ?Closure() -> void;
        }
        msg_f4 := "submitted";
        sub_cb := closure(() { print("closure-form: {}\n", msg_f4); });
        form_f4 := Form.{ on_submit = sub_cb, on_cancel = null };
        if h := form_f4.on_submit { h(); }
        if h := form_f4.on_cancel { h(); } else { print("closure-form: no cancel\n"); }

        // C5.L3: auto-promoted closure env is null (no free needed)
        double_l3 :: (x: i32) -> i32 { return x * 2; }
        f_l3 : Closure(i32) -> i32 = double_l3;
        print("closure-null-env: {}\n", f_l3.env == null);

        // C5.A7: capture slice (fat pointer like string)
        sl_a7 : [3]i32 = .[10, 20, 30];
        ptr_a7 : [*]i32 = xx @sl_a7;
        f_a7 := closure((i: i64) -> i32 => ptr_a7[i]);
        print("closure-slice: {} {} {}\n", f_a7(0), f_a7(1), f_a7(2));

        // C5.L1: arena bulk free (closures allocated on arena, freed in bulk)
        gpa_l1 := GPA.init();
        arena_l1 := Arena.init(xx gpa_l1, 4096);
        push Context.{ allocator = xx arena_l1 } {
            n_l1 : i32 = 5;
            f_l1 := closure((x: i32) -> i32 => x + n_l1);
            print("closure-arena: {}\n", f_l1(10));
        }
        arena_l1.deinit();

        // C5.L2: GPA manual free (verify env alloc/dealloc)
        gpa_l2 := GPA.init();
        a_l2 : Allocator = xx gpa_l2;
        n_l2 : i32 = 7;
        result_l2 : i32 = 0;
        push Context.{ allocator = a_l2 } {
            f_l2 := closure((x: i32) -> i32 => x + n_l2);
            result_l2 = f_l2(10);
            a_l2.dealloc_bytes(f_l2.env);
        }
        print("closure-gpa: {} allocs={}\n", result_l2, gpa_l2.alloc_count);

        // C5.A10: capture optional
        val_a10 : ?i32 = 42;
        f_a10 := closure(() -> i32 {
            if v := val_a10 { return v; }
            return 0;
        });
        print("closure-opt: {}\n", f_a10());

        // C5.C6: return optional
        limit_c6 : i32 = 100;
        f_c6 := closure((x: i32) -> ?i32 {
            if x > limit_c6 { return null; }
            return x;
        });
        r1_c6 := f_c6(50);
        r2_c6 := f_c6(200);
        if v := r1_c6 { print("closure-ropt: {}\n", v); }
        if v := r2_c6 { print("should-not-print\n"); } else { print("closure-ropt: none\n"); }

        // C5.M8: array of closures with mixed origins
        double_m8 :: (x: i32) -> i32 { return x * 2; }
        n_m8 : i32 = 10;
        fns_m8 : [3]Closure(i32) -> i32 = ---;
        fns_m8[0] = double_m8;                                // auto-promoted
        fns_m8[1] = closure((x: i32) -> i32 => x + n_m8);    // captured
        fns_m8[2] = closure((x: i32) -> i32 => x * x);       // no capture
        tmp_m8 := fns_m8[0]; print("closure-mixed: {}\n", tmp_m8(5));
        tmp_m8 = fns_m8[1]; print("closure-mixed: {}\n", tmp_m8(5));
        tmp_m8 = fns_m8[2]; print("closure-mixed: {}\n", tmp_m8(5));

        // C5.E1: independent closures from same factory (each has own env)
        mk_e1 :: (n: i32) -> Closure(i32) -> i32 {
            return closure((x: i32) -> i32 => x * n);
        }
        f1_e1 := mk_e1(2);
        f2_e1 := mk_e1(3);
        f3_e1 := mk_e1(4);
        print("closure-factory-indep: {} {} {}\n", f1_e1(10), f2_e1(10), f3_e1(10));

        // C5.E2: deep chain — closure capturing closure capturing closure
        v_e2 : i32 = 1;
        k2_e2 : i32 = 2;
        k100_e2 : i32 = 100;
        f0_e2 := closure((x: i32) -> i32 => x + v_e2);
        f1_e2 := closure((x: i32) -> i32 => f0_e2(x) * k2_e2);
        f2_e2 := closure((x: i32) -> i32 => f1_e2(x) + k100_e2);
        print("closure-deep-chain: {}\n", f2_e2(10));

        // C5.E3: many captures (stress env struct)
        c1_e3 : i32 = 1;
        c2_e3 : i32 = 2;
        c3_e3 : i32 = 3;
        c4_e3 : i32 = 4;
        c5_e3 : i32 = 5;
        c6_e3 : i32 = 6;
        c7_e3 : i32 = 7;
        c8_e3 : i32 = 8;
        big_env := closure(() -> i32 => c1_e3 + c2_e3 + c3_e3 + c4_e3 + c5_e3 + c6_e3 + c7_e3 + c8_e3);
        print("closure-8cap: {}\n", big_env());

        // C5.E5: closure with many parameters (4 params)
        multi_param := closure((a: i32, b: i32, c: i32, d: i32) -> i32 => a + b + c + d);
        a_e5 : i32 = 1; b_e5 : i32 = 2; c_e5 : i32 = 3; d_e5 : i32 = 4;
        print("closure-4param: {}\n", multi_param(a_e5, b_e5, c_e5, d_e5));

        // C5.E7: two closures sharing the same captured pointer
        shared : i32 = 0;
        shared_p := @shared;
        inc_shared := closure(() { shared_p.* += 1; });
        add5_shared := closure(() { shared_p.* += 5; });
        inc_shared();
        add5_shared();
        inc_shared();
        print("closure-shared-ptr: {}\n", shared);

        // C5.E8: closure with f64 arithmetic
        pi_e8 : f64 = 3.14159;
        area_fn := closure((r: f64) -> f64 => pi_e8 * r * r);
        a_e8 := area_fn(10.0);
        print("closure-f64: {}\n", a_e8 > 314.0);

        // C5.E9: zero-capture closure (env should be null, like auto-promoted)
        no_cap := closure((x: i32) -> i32 => x * x);
        print("closure-zerocap: {} {}\n", no_cap(7), no_cap.env == null);

        // C5.E10: closure capturing and calling struct method
        pt_e10 := Point.{ x = 3, y = 4 };
        p_e10 := @pt_e10;
        get_xy := closure(() -> i32 => p_e10.x + p_e10.y);
        print("closure-struct-method: {}\n", get_xy());

        // C5.E11: multiple closures from same factory with different captures
        fns_e11 : [3]Closure(i32) -> i32 = ---;
        i_e11 := 0;
        while i_e11 < 3 {
            multiplier : i32 = xx (i_e11 + 1);
            fns_e11[i_e11] = closure((x: i32) -> i32 => x * multiplier);
            i_e11 += 1;
        }
        t_e11 := fns_e11[0]; print("closure-multi-factory: {}\n", t_e11(10));
        t_e11 = fns_e11[1]; print("closure-multi-factory: {}\n", t_e11(10));
        t_e11 = fns_e11[2]; print("closure-multi-factory: {}\n", t_e11(10));

        // C5.E12: closure capturing bool
        flag_e12 := true;
        check_fn := closure((x: i32) -> bool {
            if flag_e12 { return x > 0; }
            return x < 0;
        });
        pos_e12 : i32 = 5;
        neg_e12 : i32 = xx -3;
        print("closure-bool-cap: {} {}\n", check_fn(pos_e12), check_fn(neg_e12));

        // C5.E13: closure as argument to another closure
        apply_fn := closure((f_app: Closure(i32) -> i32, val: i32) -> i32 => f_app(val));
        k_e13 : i32 = 100;
        inner_fn := closure((x: i32) -> i32 => x + k_e13);
        print("closure-as-arg: {}\n", apply_fn(inner_fn, 42));

        // C5.E14: closure capturing string and formatting
        prefix_e14 := "hello";
        greet_fn := closure((name: string) -> string => format("{} {}", prefix_e14, name));
        print("closure-strfmt: {}\n", greet_fn("world"));

        // C5.E15: reassigning shared pointer target between closure calls
        val_e15 : i32 = 10;
        p_e15 := @val_e15;
        read_fn := closure(() -> i32 => p_e15.*);
        print("closure-ptr-before: {}\n", read_fn());
        val_e15 = 42;
        print("closure-ptr-after: {}\n", read_fn());

        // C5.E16: closure returning negative value
        off_e16 : i32 = 100;
        neg_fn := closure((x: i32) -> i32 => x - off_e16);
        val_e16 : i32 = 30;
        print("closure-neg: {}\n", neg_fn(val_e16));

        // C5.E17: closure with protocol value capture (#inline protocol)
        gpa_e17 := GPA.init();
        a_e17 : Allocator = xx gpa_e17;
        alloc_fn := closure((size: i64) -> *void => a_e17.alloc_bytes(size));
        ptr_e17 := alloc_fn(32);
        print("closure-proto-cap: {}\n", ptr_e17 != null);
        a_e17.dealloc_bytes(ptr_e17);

        // C5.E18: chained factory — compose two factories
        make_scaler :: (factor: i32) -> Closure(i32) -> i32 {
            return closure((x: i32) -> i32 => x * factor);
        }
        make_offset :: (off: i32) -> Closure(i32) -> i32 {
            return closure((x: i32) -> i32 => x + off);
        }
        s_fn := make_scaler(3);
        o_fn := make_offset(7);
        // manually compose: scale then offset
        print("closure-chain-factory: {}\n", o_fn(s_fn(10)));

        // C5.E19: closure in while loop condition helper
        threshold : i32 = 50;
        above_fn := closure((x: i32) -> bool => x >= threshold);
        vals_e19 : [5]i32 = .[10, 30, 50, 70, 90];
        count_above : i32 = 0;
        idx_e19 : i64 = 0;
        while idx_e19 < 5 {
            if above_fn(vals_e19[idx_e19]) { count_above += 1; }
            idx_e19 += 1;
        }
        print("closure-while-cond: {}\n", count_above);

        // ---- Inferred closure parameter types ----

        // CI.1: inferred params from typed variable
        f_ci1 : Closure(i32, i32) -> i32 = closure((a, b) => a + b);
        a_ci1 : i32 = 3;
        b_ci1 : i32 = 4;
        print("closure-infer: {}\n", f_ci1(a_ci1, b_ci1));

        // CI.2: inferred params from function argument
        apply_ci :: (f: Closure(i32) -> i32, x: i32) -> i32 { return f(x); }
        k_ci : i32 = 10;
        v_ci : i32 = 5;
        print("closure-infer-arg: {}\n", apply_ci(closure((x) => x + k_ci), v_ci));

        // CI.3: inferred with block body
        h_ci : Closure(i32, i32) -> i32 = closure((a, b) { return a * b; });
        print("closure-infer-block: {}\n", h_ci(a_ci1, b_ci1));

        // CI.4: inferred with captures
        cap_ci : i32 = 100;
        f_ci4 : Closure(i32) -> i32 = closure((x) => x + cap_ci);
        print("closure-infer-cap: {}\n", f_ci4(v_ci));

        // CI.5: inferred in factory return
        mk_ci :: (n: i32) -> Closure(i32) -> i32 { return closure((x) => x * n); }
        f_ci5 := mk_ci(7);
        print("closure-infer-factory: {}\n", f_ci5(v_ci));

        // CI.6: inferred with higher-order (closure taking closure)
        compose_ci :: (f: Closure(i32) -> i32, g: Closure(i32) -> i32) -> Closure(i32) -> i32 {
            return closure((x: i32) -> i32 => f(g(x)));
        }
        one_ci : i32 = 1;
        two_ci : i32 = 2;
        c_ci := compose_ci(closure((x) => x + one_ci), closure((x) => x * two_ci));
        print("closure-infer-compose: {}\n", c_ci(v_ci));

        // CI.7: inferred void return
        msg_ci := "infer-void";
        cb_ci : Closure(i32) -> void = closure((x) { print("closure-{}: {}\n", msg_ci, x); });
        cb_ci(42);
    }
}
