#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";

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

Color :: enum { red; green; blue; }

Shape :: enum {
    circle: f32;
    rect: struct { w, h: f32; };
    none;
}

Overlay :: union {
    f: f32;
    i: i32;
}

Vec2 :: union {
    data: [2]f32;
    struct { x, y: f32; };
}

Defaults :: struct {
    a: i32;
    b: i32 = 99;
    c: i32 = ---;
}

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

OptInner :: struct { val: i32; }

OptOuter :: struct { inner: ?OptInner; }

MyFloat :: f64;

Perms :: enum flags { read; write; execute; }

Status :: enum u8 { ok; err; timeout; }

WindowFlags :: enum flags u32 { vsync :: 64; resizable :: 4; hidden :: 128; }

// --- Top-level functions ---

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

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

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

pair_add :: (a: $T, b: $U) -> i64 {
    cast(i64) a + cast(i64) b
}

typed_sum :: (..args: []i32) -> i32 {
    result := 0;
    for args (it) { result = result + it; }
    result
}

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

void_return :: () {
    return;
}

implicit_return :: (x: i32) -> i32 {
    x * 2
}

early_return :: (x: i32) -> i32 {
    if x > 10 { return 99; }
    x
}

vec3 :: (x: f32, y: f32, z: f32) -> Vector(3, f32) {
    .[x, y, z]
}

point_sum :: (p: Point) -> i32 { p.x + p.y }

// #run compile-time constants

// #run compile-time constants
CT_VAL :: #run add(10, 15);

CT_MUL :: #run mul(6, 7);

CT_CHAIN :: #run add(CT_VAL, 5);

// #run compile-time optional tests

// #run compile-time optional tests
ct_opt_coalesce :: () -> i32 {
    x: ?i32 = 42;
    y: ?i32 = null;
    return (x ?? 0) + (y ?? 99);
}

ct_opt_unwrap :: () -> i32 {
    x: ?i32 = 77;
    return x!;
}

ct_opt_guard :: () -> i32 {
    x: ?i32 = 10;
    if x == null { return -1; }
    return x;
}

CT_OPT_COALESCE :: #run ct_opt_coalesce();

CT_OPT_UNWRAP :: #run ct_opt_unwrap();

CT_OPT_GUARD :: #run ct_opt_guard();

// #insert helpers

// #insert helpers
gen_code :: () -> string {
    return "print(\"insert-ok\\n\");";
}

gen_val :: () -> string {
    return "print(\"insert-gen: {}\\n\", 42);";
}

// --- Error handling (failable functions: sets, raise/try/catch/or/onfail) ---

SmokeErr :: error { Empty, BadDigit, Overflow }

// value-carrying, named set: raise three tags or succeed

// value-carrying, named set: raise three tags or succeed
sm_parse :: (n: i32) -> (i32, !SmokeErr) {
    if n < 0  { raise error.BadDigit; }
    if n == 0 { raise error.Empty; }
    if n > 99 { raise error.Overflow; }
    return n * 2;
}

// pure failable, inferred set (ad-hoc tag minted into `!`)

// pure failable, inferred set (ad-hoc tag minted into `!`)
sm_check :: (ok: bool) -> ! {
    if !ok { raise error.NotReady; }
    return;
}

// multi-value, inferred set: `try` propagates; the SCC pass absorbs SmokeErr

// multi-value, inferred set: `try` propagates; the SCC pass absorbs SmokeErr
sm_pair :: (a: i32, b: i32) -> (i32, i32, !) {
    x := try sm_parse(a);
    y := try sm_parse(b);
    return (x, y);
}

// `catch` block that diverges (logs the tag, then returns a fallback)

// `catch` block that diverges (logs the tag, then returns a fallback)
sm_or_default :: (n: i32) -> i32 {
    return sm_parse(n) catch (e) {
        print("  logged {}\n", e);
        return -1;
    };
}

// `onfail` + `defer` interleave: cleanup runs only on the error path

// `onfail` + `defer` interleave: cleanup runs only on the error path
sm_acquire :: (fail: bool) -> (i32, !) {
    defer  print("  smoke defer A\n");
    onfail print("  smoke onfail B\n");
    if fail { raise error.Acquire; }
    return 7;
}

// `or`-chain: try a, fall to try b; propagate if both fail

// `or`-chain: try a, fall to try b; propagate if both fail
sm_first :: (a: i32, b: i32) -> (i32, !) {
    v := try sm_parse(a) or try sm_parse(b);
    return v;
}

// --- Extern function binding ---

// --- Extern function binding ---
libc :: #library "c";

c_abs :: (n: i32) -> i32 extern libc "abs";

// --- Protocol declarations (Phase 1: static dispatch only) ---

Counter :: protocol {
    inc :: ();
    get :: () -> i32;
}

Summable :: protocol {
    sum :: () -> i32;
}

SimpleCounter :: struct { val: i32; }

impl Counter for SimpleCounter {
    inc :: (self: *SimpleCounter) { self.val += 1; }
    get :: (self: *SimpleCounter) -> i32 { self.val }
}

impl Summable for Point {
    sum :: (self: *Point) -> i32 { self.x + self.y }
}

// Phase 2: #inline protocol for dynamic dispatch

// Phase 2: #inline protocol for dynamic dispatch
Adder :: protocol #inline {
    add :: (n: i32);
    value :: () -> i32;
}

Accumulator :: struct {
    total: i32;
}

impl Adder for Accumulator {
    add :: (self: *Accumulator, n: i32) { self.total += n; }
    value :: (self: *Accumulator) -> i32 { self.total }
}

Doubler :: struct { val: i32; }

impl Adder for Doubler {
    add :: (self: *Doubler, n: i32) { self.val = self.val + n + n; }
    value :: (self: *Doubler) -> i32 { self.val }
}

// Phase 4: default methods

// Phase 4: default methods
Repeater :: protocol {
    say :: (msg: string);
    say_twice :: (msg: string) {
        self.say(msg);
        self.say(msg);
    }
}

Printer :: struct { count: i32; }

impl Repeater for Printer {
    say :: (self: *Printer, msg: string) {
        self.count += 1;
        out(msg);
    }
}

// P4 edge: Chained default→default calls

// 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)
    }
}

ChainImpl :: struct { val: i32; }
impl Chained for ChainImpl {
    base :: (self: *ChainImpl, msg: string) -> i32 {
        self.val += 1;
        msg.len
    }
}

// Phase 5: Self type

// Phase 5: Self type
Eq :: protocol {
    eq :: (other: Self) -> bool;
}

impl Eq for Point {
    eq :: (self: *Point, other: Point) -> bool {
        self.x == other.x and self.y == other.y
    }
}

Cloneable :: protocol {
    clone :: () -> Self;
}

impl Cloneable for Point {
    clone :: (self: *Point) -> Point {
        Point.{ x = self.x, y = self.y }
    }
}

impl Eq for i64 {
    eq :: (self: *i64, other: i64) -> bool {
        self.* == other
    }
}

// Phase 6: Generic constraints

// Phase 6: Generic constraints
are_equal :: ($T: Type/Eq, a: T, b: T) -> bool {
    a.eq(b)
}

Hashable :: protocol {
    hash :: () -> i64;
}

impl Hashable for Point {
    hash :: (self: *Point) -> i64 {
        xx self.x * 31 + xx self.y
    }
}

eq_and_hash :: ($T: Type/Eq/Hashable, a: T, b: T) -> bool {
    if a.hash() != b.hash() { return false; }
    a.eq(b)
}

// P6.4: inline constraint syntax ($T/Protocol)

// P6.4: inline constraint syntax ($T/Protocol)
sum_of_inline :: (a: $T/Summable, b: T) -> i32 {
    a.sum() + b.sum()
}

// Phase 7: Generic struct impls

// Phase 7: Generic struct impls
Pair :: struct ($T: Type) {
    a: T;
    b: T;
}

impl Summable for Pair($T) {
    sum :: (self: *Pair(T)) -> i32 {
        xx self.a + xx self.b
    }
}

// P6.5: Struct type param constraints

// P6.5: Struct type param constraints
SumBox :: struct ($T: Type/Summable) {
    val: T;
}

// ============================================================
// Struct constants test

// ============================================================
// Struct constants test
Phys :: struct {
    x, y: f32;
    GRAVITY :f32: 9.81;
    MAX_SPEED :: 100;
}

// Init block test struct

// Init block test struct
Builder :: struct {
    total: i32;
    count: i32;

    add :: (self: *Builder, val: i32) {
        self.total += val;
        self.count += 1;
    }
}

// Global variable for address-of test

// Global variable for address-of test
g_smoke_val : i32 = 42;

write_to_ptr :: (p: *i32) {
    p.* = 99;
}

main :: () {

    // ========================================================
    // PROTOCOLS (Phase 1: static dispatch)
    // ========================================================
    print("=== Protocols ===\n");

    // P1.1: Basic protocol + impl, direct call on concrete type
    {
        sc := SimpleCounter.{ val = 0 };
        sc.inc();
        sc.inc();
        sc.inc();
        print("P1.1: {}\n", sc.get());
    }

    // P1.2: impl in separate scope (retroactive conformance)
    {
        p := Point.{ x = 10, y = 20 };
        print("P1.2: {}\n", p.sum());
    }

    // P2.1: #inline protocol — xx conversion + dynamic dispatch
    {
        acc := Accumulator.{ total = 0 };
        a : Adder = xx @acc;
        a.add(10);
        a.add(20);
        a.add(12);
        print("P2.1: {}\n", a.value());
    }

    // P2.2: pass protocol value to function
    {
        use_adder :: (a: Adder, n: i32) -> i32 {
            a.add(n);
            a.value()
        }
        acc := Accumulator.{ total = 100 };
        result := use_adder(xx @acc, 50);
        print("P2.2: {}\n", result);
    }

    // P2.3: different impls through same protocol type
    {
        acc := Accumulator.{ total = 0 };
        dbl := Doubler.{ val = 0 };
        a1 : Adder = xx @acc;
        a2 : Adder = xx @dbl;
        a1.add(5);
        a2.add(5);
        print("P2.3: {} {}\n", a1.value(), a2.value());
    }

    // P3.1: vtable-pointer protocol (default, no #inline)
    {
        sc := SimpleCounter.{ val = 0 };
        c : Counter = xx @sc;
        c.inc();
        c.inc();
        c.inc();
        c.inc();
        c.inc();
        print("P3.1: {}\n", c.get());
    }

    // P3.2: vtable protocol passed to function
    {
        use_counter :: (c: Counter) -> i32 {
            c.inc();
            c.inc();
            c.get()
        }
        sc := SimpleCounter.{ val = 10 };
        result := use_counter(xx @sc);
        print("P3.2: {}\n", result);
    }

    // P4.1: default method calls required method (static dispatch)
    {
        pr := Printer.{ count = 0 };
        pr.say_twice("hi ");
        print("\nP4.1: {}\n", pr.count);
    }

    // P4.2: default method via dynamic dispatch (vtable)
    {
        pr := Printer.{ count = 0 };
        r : Repeater = xx @pr;
        r.say_twice("yo ");
        print("\nP4.2: {}\n", pr.count);
    }

    // P4.3: chained default→default calls via vtable
    {
        ci := ChainImpl.{ val = 0 };
        ch : Chained = xx @ci;
        // double_wrap calls wrap twice, wrap calls base once each
        // base("hi") returns 2 (len), wrap adds 1 → 3, double_wrap = 3 + 3 = 6
        result := ch.double_wrap("hi");
        // base was called 2 times (once per wrap call)
        print("P4.3: {} {}\n", result, ci.val);
    }

    // P5.1: Self type in protocol — static dispatch
    {
        p1 := Point.{ x = 1, y = 2 };
        p2 := Point.{ x = 1, y = 2 };
        p3 := Point.{ x = 3, y = 4 };
        print("P5.1: {} {}\n", p1.eq(p2), p1.eq(p3));
    }

    // P5.2: Self in return position
    {
        p := Point.{ x = 10, y = 20 };
        p2 := p.clone();
        print("P5.2: {} {}\n", p2.x, p2.y);
    }

    // P5.5: impl for primitive type
    {
        x := 42;
        y := 42;
        z := 99;
        r1 := x.eq(y);
        r2 := x.eq(z);
        print("P5.5: {} {}\n", r1, r2);
    }

    // P5.3: Self with dynamic dispatch (erased to *void)
    {
        p1 := Point.{ x = 1, y = 2 };
        p2 := Point.{ x = 1, y = 2 };
        p3 := Point.{ x = 3, y = 4 };
        e : Eq = xx p1;
        print("P5.3: {} {}\n", e.eq(p2), e.eq(p3));
    }

    // P6.1: Single constraint — constrained generic function
    {
        p1 := Point.{ x = 1, y = 2 };
        p2 := Point.{ x = 1, y = 2 };
        p3 := Point.{ x = 3, y = 4 };
        print("P6.1: {} {}\n", are_equal(p1, p2), are_equal(p1, p3));
    }

    // P6.2: Constraint with primitive type
    {
        print("P6.2: {} {}\n", are_equal(42, 42), are_equal(42, 99));
    }

    // P6.3: Multiple constraints
    {
        p1 := Point.{ x = 1, y = 2 };
        p2 := Point.{ x = 1, y = 2 };
        p3 := Point.{ x = 3, y = 4 };
        print("P6.3: {} {}\n", eq_and_hash(p1, p2), eq_and_hash(p1, p3));
    }

    // P6.4: inline constraint syntax ($T/Protocol)
    {
        // sum_of_inline uses $T/Summable inline (not $T: Type/Summable)
        p1 := Point.{ x = 10, y = 20 };
        p2 := Point.{ x = 3, y = 7 };
        print("P6.4: {}\n", sum_of_inline(p1, p2));
    }

    // P6.5: Struct type param constraints ($T: Type/Summable)
    {
        box := SumBox(Point).{ val = Point.{ x = 5, y = 15 } };
        print("P6.5: {}\n", box.val.sum());
    }

    // P7.1: impl for generic struct
    {
        p := Pair(i32).{ a = 10, b = 20 };
        print("P7.1: {}\n", p.sum());
    }

    // P7.2: generic struct impl with different type arg
    {
        p1 := Pair(i32).{ a = 3, b = 7 };
        p2 := Pair(i64).{ a = 100, b = 200 };
        print("P7.2: {} {}\n", p1.sum(), p2.sum());
    }

    // P2.4: xx in function return position (tested in standalone test_return.sx)
    // Covered by: make_adder :: (acc: *Accumulator) -> Adder { xx acc; }

    // P2.6: protocol values in arrays
    {
        acc := Accumulator.{ total = 0 };
        dbl := Doubler.{ val = 0 };
        adders : [2]Adder = .[xx @acc, xx @dbl];
        i := 0;
        while i < 2 {
            adders[i].add(5);
            i += 1;
        }
        print("P2.6: {} {}\n", acc.total, dbl.val);
    }

    // P2.7: xx on inline struct literal (no intermediate variable)
    {
        use_adder :: (a: Adder) -> i32 { a.add(10); a.value() }
        result := use_adder(xx Accumulator.{ total = 5 });
        print("P2.7: {}\n", result);
    }

    // P3.3: xx on inline struct literal with vtable protocol
    {
        use_counter :: (c: Counter) -> i32 { c.inc(); c.inc(); c.get() }
        result := use_counter(xx SimpleCounter.{ val = 100 });
        print("P3.3: {}\n", result);
    }
}
