// tests/native/test_compiler.el — comprehensive tests for the El compiler pipeline.
//
// Tests the lexer (lexer.el), parser (parser.el), and codegen (codegen.el)
// through the compile() entry point in compiler.el.
//
// Compiled and run via the native test harness:
//   elc --test tests/native/test_compiler.el > /tmp/el_compiler_tests.c
//   gcc -O2 -I runtime /tmp/el_compiler_tests.c runtime/el_runtime.c -lcurl -lpthread -lm -o /tmp/el_compiler_tests
//   /tmp/el_compiler_tests

import "../../el-compiler/src/lexer.el"
import "../../el-compiler/src/parser.el"
import "../../el-compiler/src/codegen.el"
import "../../el-compiler/src/codegen-js.el"
import "../../el-compiler/src/compiler.el"

// ── Lexer helpers ─────────────────────────────────────────────────────────────

fn tok_count(tokens: [Any]) -> Int {
    native_list_len(tokens) / 2
}

// ── Codegen helper: capture compile() stdout to a string ─────────────────────

fn compile_capture(src: String) -> String {
    let tmp: String = "/tmp/el_compiler_test_" + int_to_str(time_now()) + ".c"
    stdout_to_file(tmp)
    compile(src)
    stdout_restore()
    fs_read(tmp)
}

// ── Lexer tests ───────────────────────────────────────────────────────────────

test "lex-empty" {
    let tokens: [Any] = lex("")
    assert tok_count(tokens) == 1, "empty source yields only Eof"
    assert tok_kind(tokens, 0) == "Eof", "single token is Eof"
}

test "lex-whitespace-stripped" {
    let tokens: [Any] = lex("   \t\n\r   ")
    assert tok_count(tokens) == 1, "whitespace-only yields only Eof"
}

test "lex-comment-stripped" {
    let tokens: [Any] = lex("// this is a comment\n// another")
    assert tok_count(tokens) == 1, "comments stripped — only Eof"
}

test "lex-int-literals" {
    let tokens: [Any] = lex("0 1 42 100 999")
    assert tok_count(tokens) == 6, "five int literals + Eof"
    assert tok_kind(tokens, 0) == "Int", "first is Int"
    assert tok_value(tokens, 0) == "0", "value is 0"
    assert tok_kind(tokens, 2) == "Int", "third is Int"
    assert tok_value(tokens, 2) == "42", "value is 42"
    assert tok_kind(tokens, 4) == "Int", "fifth is Int"
    assert tok_value(tokens, 4) == "999", "value is 999"
}

test "lex-float-literals" {
    let tokens: [Any] = lex("3.14 0.0 1.5")
    assert tok_count(tokens) == 4, "three float literals + Eof"
    assert tok_kind(tokens, 0) == "Float", "first is Float"
    assert tok_value(tokens, 0) == "3.14", "value is 3.14"
    assert tok_kind(tokens, 1) == "Float", "second is Float"
    assert tok_value(tokens, 1) == "0.0", "value is 0.0"
}

test "lex-string-literals" {
    let tokens: [Any] = lex("\"hello\" \"world\" \"\"")
    assert tok_count(tokens) == 4, "three string literals + Eof"
    assert tok_kind(tokens, 0) == "Str", "first is Str"
    assert tok_value(tokens, 0) == "hello", "value is hello"
    assert tok_kind(tokens, 2) == "Str", "third is Str"
    assert tok_value(tokens, 2) == "", "empty string value is empty"
}

test "lex-string-escape-newline" {
    let tokens: [Any] = lex("\"hello\\nworld\"")
    assert tok_count(tokens) == 2, "one Str token + Eof"
    assert tok_kind(tokens, 0) == "Str", "is Str"
    let val: String = tok_value(tokens, 0)
    assert str_contains(val, "hello"), "value contains hello"
    assert str_contains(val, "world"), "value contains world"
    assert str_len(val) == 11, "hello + newline + world = 11 chars"
}

test "lex-string-escape-tab" {
    let tokens: [Any] = lex("\"a\\tb\"")
    assert tok_count(tokens) == 2, "one Str + Eof"
    let val: String = tok_value(tokens, 0)
    assert str_len(val) == 3, "a + tab + b = 3 chars"
}

test "lex-string-escape-backslash" {
    let tokens: [Any] = lex("\"a\\\\b\"")
    assert tok_count(tokens) == 2, "one Str + Eof"
    let val: String = tok_value(tokens, 0)
    assert str_len(val) == 3, "a + backslash + b = 3 chars"
}

test "lex-bool-literals" {
    let tokens: [Any] = lex("true false")
    assert tok_count(tokens) == 3, "two Bool tokens + Eof"
    assert tok_kind(tokens, 0) == "Bool", "first is Bool"
    assert tok_value(tokens, 0) == "true", "first is true"
    assert tok_kind(tokens, 1) == "Bool", "second is Bool"
    assert tok_value(tokens, 1) == "false", "second is false"
}

test "lex-identifier" {
    let tokens: [Any] = lex("foo bar _under _123")
    assert tok_count(tokens) == 5, "four idents + Eof"
    assert tok_kind(tokens, 0) == "Ident", "foo is Ident"
    assert tok_value(tokens, 0) == "foo", "value is foo"
    assert tok_kind(tokens, 2) == "Ident", "underscore ident recognized"
    assert tok_value(tokens, 2) == "_under", "value is _under"
}

test "lex-keywords" {
    let tokens: [Any] = lex("let fn if else while for return import type enum match")
    assert tok_count(tokens) == 12, "eleven keywords + Eof"
    assert tok_kind(tokens, 0) == "Let", "let keyword"
    assert tok_kind(tokens, 1) == "Fn", "fn keyword"
    assert tok_kind(tokens, 2) == "If", "if keyword"
    assert tok_kind(tokens, 3) == "Else", "else keyword"
    assert tok_kind(tokens, 4) == "While", "while keyword"
    assert tok_kind(tokens, 5) == "For", "for keyword"
    assert tok_kind(tokens, 6) == "Return", "return keyword"
    assert tok_kind(tokens, 7) == "Import", "import keyword"
    assert tok_kind(tokens, 8) == "Type", "type keyword"
    assert tok_kind(tokens, 9) == "Enum", "enum keyword"
    assert tok_kind(tokens, 10) == "Match", "match keyword"
}

test "lex-more-keywords" {
    let tokens: [Any] = lex("extern break continue")
    assert tok_count(tokens) == 4, "three keywords + Eof"
    assert tok_kind(tokens, 0) == "Extern", "extern keyword"
    assert tok_kind(tokens, 1) == "Break", "break keyword"
    assert tok_kind(tokens, 2) == "Continue", "continue keyword"
}

test "lex-keyword-values" {
    let tokens: [Any] = lex("let fn return")
    assert tok_value(tokens, 0) == "let", "let value is let"
    assert tok_value(tokens, 1) == "fn", "fn value is fn"
    assert tok_value(tokens, 2) == "return", "return value is return"
}

test "lex-arithmetic-operators" {
    let tokens: [Any] = lex("+ - * / %")
    assert tok_count(tokens) == 6, "five ops + Eof"
    assert tok_kind(tokens, 0) == "Plus", "plus"
    assert tok_kind(tokens, 1) == "Minus", "minus"
    assert tok_kind(tokens, 2) == "Star", "star"
    assert tok_kind(tokens, 3) == "Slash", "slash"
    assert tok_kind(tokens, 4) == "Percent", "percent"
}

test "lex-comparison-operators" {
    let tokens: [Any] = lex("== != < > <= >=")
    assert tok_count(tokens) == 7, "six ops + Eof"
    assert tok_kind(tokens, 0) == "EqEq", "eqeq"
    assert tok_value(tokens, 0) == "==", "eqeq value"
    assert tok_kind(tokens, 1) == "NotEq", "noteq"
    assert tok_kind(tokens, 2) == "Lt", "lt"
    assert tok_kind(tokens, 3) == "Gt", "gt"
    assert tok_kind(tokens, 4) == "LtEq", "lteq"
    assert tok_kind(tokens, 5) == "GtEq", "gteq"
}

test "lex-logical-operators" {
    let tokens: [Any] = lex("&& || !")
    assert tok_count(tokens) == 4, "three logical ops + Eof"
    assert tok_kind(tokens, 0) == "And", "and"
    assert tok_value(tokens, 0) == "&&", "and value"
    assert tok_kind(tokens, 1) == "Or", "or"
    assert tok_kind(tokens, 2) == "Not", "not"
}

test "lex-arrow-tokens" {
    let tokens: [Any] = lex("-> =>")
    assert tok_count(tokens) == 3, "arrow + fat-arrow + Eof"
    assert tok_kind(tokens, 0) == "Arrow", "thin arrow"
    assert tok_value(tokens, 0) == "->", "arrow value"
    assert tok_kind(tokens, 1) == "FatArrow", "fat arrow"
}

test "lex-delimiters" {
    let tokens: [Any] = lex("( ) [ ] { } , : ; .")
    assert tok_count(tokens) == 11, "ten delimiters + Eof"
    assert tok_kind(tokens, 0) == "LParen", "lparen"
    assert tok_kind(tokens, 1) == "RParen", "rparen"
    assert tok_kind(tokens, 2) == "LBracket", "lbracket"
    assert tok_kind(tokens, 3) == "RBracket", "rbracket"
    assert tok_kind(tokens, 4) == "LBrace", "lbrace"
    assert tok_kind(tokens, 5) == "RBrace", "rbrace"
    assert tok_kind(tokens, 6) == "Comma", "comma"
    assert tok_kind(tokens, 7) == "Colon", "colon"
    assert tok_kind(tokens, 8) == "Semicolon", "semicolon"
    assert tok_kind(tokens, 9) == "Dot", "dot"
}

test "lex-double-colon" {
    let tokens: [Any] = lex("::")
    assert tok_count(tokens) == 2, "colons + Eof"
    assert tok_kind(tokens, 0) == "ColonColon", "double colon"
    assert tok_value(tokens, 0) == "::", "double colon value"
}

test "lex-dot-dot" {
    let tokens: [Any] = lex(".. ..=")
    assert tok_count(tokens) == 3, "two range tokens + Eof"
    assert tok_kind(tokens, 0) == "DotDot", "dotdot"
    assert tok_kind(tokens, 1) == "DotDotEq", "dotdoteq"
}

test "lex-pipe-operators" {
    let tokens: [Any] = lex("| || |>")
    assert tok_count(tokens) == 4, "three pipe tokens + Eof"
    assert tok_kind(tokens, 0) == "Pipe", "pipe"
    assert tok_kind(tokens, 1) == "Or", "or"
    assert tok_kind(tokens, 2) == "PipeOp", "pipe-op"
}

test "lex-at-and-question" {
    let tokens: [Any] = lex("@ ?")
    assert tok_count(tokens) == 3, "at + question + Eof"
    assert tok_kind(tokens, 0) == "At", "at sign"
    assert tok_kind(tokens, 1) == "QuestionMark", "question mark"
}

test "lex-eof-always-last" {
    let t1: [Any] = lex("x")
    let t2: [Any] = lex("let x = 1")
    let t3: [Any] = lex("")
    let n1: Int = tok_count(t1)
    let n2: Int = tok_count(t2)
    let n3: Int = tok_count(t3)
    assert tok_kind(t1, n1 - 1) == "Eof", "eof last after single ident"
    assert tok_kind(t2, n2 - 1) == "Eof", "eof last after let stmt"
    assert tok_kind(t3, n3 - 1) == "Eof", "eof last after empty"
}

test "lex-string-with-spaces" {
    let tokens: [Any] = lex("\"hello world\"")
    assert tok_count(tokens) == 2, "string with space: 1 Str + Eof"
    assert tok_value(tokens, 0) == "hello world", "internal space preserved"
}

test "lex-multiline-source" {
    let src: String = "let x: Int = 1\nlet y: Int = 2\n"
    let tokens: [Any] = lex(src)
    assert tok_count(tokens) > 5, "multiline source produces multiple tokens"
    assert tok_kind(tokens, 0) == "Let", "first token is Let"
}

test "lex-flat-stride-2-layout" {
    // Verify that the flat stride-2 layout: token i has kind at index 2*i, value at 2*i+1
    let tokens: [Any] = lex("fn foo")
    // tokens[0] = "Fn", tokens[1] = "fn", tokens[2] = "Ident", tokens[3] = "foo", ...
    let raw_len: Int = native_list_len(tokens)
    assert raw_len == 6, "fn + foo + Eof = 3 tokens = 6 raw entries"
    let kind0: String = native_list_get(tokens, 0)
    let val0: String = native_list_get(tokens, 1)
    let kind1: String = native_list_get(tokens, 2)
    let val1: String = native_list_get(tokens, 3)
    assert kind0 == "Fn", "raw[0] is Fn kind"
    assert val0 == "fn", "raw[1] is fn value"
    assert kind1 == "Ident", "raw[2] is Ident kind"
    assert val1 == "foo", "raw[3] is foo value"
}

// ── Parser tests ──────────────────────────────────────────────────────────────

fn get_first_stmt_kind(src: String) -> String {
    let tokens: [Any] = lex(src)
    let stmts: [Map<String, Any>] = parse(tokens)
    if native_list_len(stmts) == 0 { return "" }
    let first: Map<String, Any> = native_list_get(stmts, 0)
    first["stmt"]
}

fn get_first_stmt(src: String) -> Map<String, Any> {
    let tokens: [Any] = lex(src)
    let stmts: [Map<String, Any>] = parse(tokens)
    native_list_get(stmts, 0)
}

test "parse-let-stmt" {
    assert get_first_stmt_kind("let x: Int = 5") == "Let", "let int stmt"
    assert get_first_stmt_kind("let s: String = \"hi\"") == "Let", "let string stmt"
    assert get_first_stmt_kind("let b: Bool = true") == "Let", "let bool stmt"
    let stmt: Map<String, Any> = get_first_stmt("let x: Int = 42")
    let name: String = stmt["name"]
    assert name == "x", "let name is x"
}

test "parse-fn-decl" {
    assert get_first_stmt_kind("fn foo() -> Void { }") == "FnDef", "fn declaration"
    assert get_first_stmt_kind("fn bar(x: Int) -> Int { return x }") == "FnDef", "fn with param"
    let stmt: Map<String, Any> = get_first_stmt("fn foo() -> Void { }")
    let name: String = stmt["name"]
    assert name == "foo", "fn name is foo"
}

test "parse-fn-params" {
    let stmt: Map<String, Any> = get_first_stmt("fn bar(x: Int, y: String) -> Int { return 0 }")
    let params = stmt["params"]
    let n: Int = native_list_len(params)
    assert n == 2, "fn has 2 params"
    let p0: Map<String, Any> = native_list_get(params, 0)
    let p1: Map<String, Any> = native_list_get(params, 1)
    assert p0["name"] == "x", "first param name x"
    assert p1["name"] == "y", "second param name y"
}

test "parse-return-stmt" {
    let tokens: [Any] = lex("fn f() -> Int { return 42 }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let n: Int = native_list_len(body)
    assert n > 0, "fn body non-empty"
    let ret: Map<String, Any> = native_list_get(body, 0)
    assert ret["stmt"] == "Return", "return stmt kind"
}

test "parse-if-stmt" {
    // In El, `if` is an expression. Standalone `if` in a fn body is wrapped
    // as Expr stmt with value.expr == "If".
    let tokens: [Any] = lex("fn f() -> Int { if x > 0 { return 1 } return 0 }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let first_body: Map<String, Any> = native_list_get(body, 0)
    assert first_body["stmt"] == "Expr", "if stmt in fn body is Expr wrapper"
    let val = first_body["value"]
    assert val["expr"] == "If", "Expr wraps If expression"
}

test "parse-if-else" {
    let tokens: [Any] = lex("fn f() -> Int { if x > 0 { return 1 } else { return 0 } }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    // if-else is also an Expr stmt wrapping an If expression
    let expr_stmt: Map<String, Any> = native_list_get(body, 0)
    assert expr_stmt["stmt"] == "Expr", "if-else is Expr stmt"
    let if_node = expr_stmt["value"]
    assert if_node["expr"] == "If", "Expr wraps If expression"
    let has_else: Bool = if_node["has_else"]
    assert has_else, "if-else has else branch"
}

test "parse-while-stmt" {
    let tokens: [Any] = lex("fn f() -> Void { while i < 10 { i = i + 1 } }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let while_node: Map<String, Any> = native_list_get(body, 0)
    assert while_node["stmt"] == "While", "while stmt kind"
}

test "parse-import-stmt" {
    assert get_first_stmt_kind("import \"some/module.el\"") == "Import", "import stmt"
}

test "parse-extern-fn" {
    assert get_first_stmt_kind("extern fn native_op(x: Int) -> Int") == "ExternFn", "extern fn"
}

test "parse-let-int-value" {
    let stmt: Map<String, Any> = get_first_stmt("let n: Int = 99")
    let val = stmt["value"]
    let val_kind: String = val["expr"]
    assert val_kind == "Int", "let value is Int expr"
    let v: String = val["value"]
    assert v == "99", "int literal value 99"
}

test "parse-let-string-value" {
    let stmt: Map<String, Any> = get_first_stmt("let s: String = \"hello\"")
    let val = stmt["value"]
    let val_kind: String = val["expr"]
    assert val_kind == "Str", "let value is Str expr"
    let v: String = val["value"]
    assert v == "hello", "string literal value hello"
}

test "parse-let-bool-value" {
    let stmt: Map<String, Any> = get_first_stmt("let b: Bool = true")
    let val = stmt["value"]
    let val_kind: String = val["expr"]
    assert val_kind == "Bool", "let value is Bool expr"
}

test "parse-binop-expr" {
    let stmt: Map<String, Any> = get_first_stmt("let x: Int = 1 + 2")
    let val = stmt["value"]
    let val_kind: String = val["expr"]
    assert val_kind == "BinOp", "let value is BinOp"
    let op: String = val["op"]
    assert op == "Plus", "binop is Plus"
}

test "parse-call-expr" {
    let tokens: [Any] = lex("fn f() -> Void { println(\"hi\") }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let expr_stmt: Map<String, Any> = native_list_get(body, 0)
    assert expr_stmt["stmt"] == "Expr", "call is Expr stmt"
    let val = expr_stmt["value"]
    let val_kind: String = val["expr"]
    assert val_kind == "Call", "expr is Call"
}

test "parse-multiple-fns" {
    let src: String = "fn a() -> Int { return 1 }\nfn b() -> Int { return 2 }"
    let tokens: [Any] = lex(src)
    let stmts: [Map<String, Any>] = parse(tokens)
    assert native_list_len(stmts) == 2, "two fn declarations parsed"
    let s0: Map<String, Any> = native_list_get(stmts, 0)
    let s1: Map<String, Any> = native_list_get(stmts, 1)
    assert s0["name"] == "a", "first fn name a"
    assert s1["name"] == "b", "second fn name b"
}

test "parse-assign-stmt" {
    let tokens: [Any] = lex("fn f() -> Void { x = 42 }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let a: Map<String, Any> = native_list_get(body, 0)
    assert a["stmt"] == "Assign", "assign stmt kind"
    assert a["name"] == "x", "assign target x"
}

test "parse-for-stmt" {
    let tokens: [Any] = lex("fn f() -> Void { for x in items { println(x) } }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let for_node: Map<String, Any> = native_list_get(body, 0)
    assert for_node["stmt"] == "For", "for stmt kind"
    assert for_node["item"] == "x", "for item is x"
}

test "parse-unary-not" {
    let tokens: [Any] = lex("fn f() -> Bool { return !x }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let ret: Map<String, Any> = native_list_get(body, 0)
    let val = ret["value"]
    assert val["expr"] == "Not", "unary not is Not expr"
}

test "parse-unary-neg" {
    let tokens: [Any] = lex("fn f() -> Int { return -5 }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let ret: Map<String, Any> = native_list_get(body, 0)
    let val = ret["value"]
    assert val["expr"] == "Neg", "unary minus is Neg expr"
}

test "parse-array-literal" {
    let tokens: [Any] = lex("fn f() -> [Int] { return [1, 2, 3] }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let ret: Map<String, Any> = native_list_get(body, 0)
    let val = ret["value"]
    assert val["expr"] == "Array", "array literal is Array expr"
    let elems = val["elems"]
    assert native_list_len(elems) == 3, "array has 3 elements"
}

test "parse-empty-array" {
    let tokens: [Any] = lex("fn f() -> [Int] { return [] }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let ret: Map<String, Any> = native_list_get(body, 0)
    let val = ret["value"]
    assert val["expr"] == "Array", "empty array is Array expr"
    let elems = val["elems"]
    assert native_list_len(elems) == 0, "empty array has 0 elements"
}

test "parse-index-expr" {
    let tokens: [Any] = lex("fn f() -> Any { return arr[0] }")
    let stmts: [Map<String, Any>] = parse(tokens)
    let fn_node: Map<String, Any> = native_list_get(stmts, 0)
    let body = fn_node["body"]
    let ret: Map<String, Any> = native_list_get(body, 0)
    let val = ret["value"]
    assert val["expr"] == "Index", "array index is Index expr"
}

// ── Codegen tests ─────────────────────────────────────────────────────────────

test "codegen-includes" {
    let out: String = compile_capture("fn main() -> Void { }")
    assert str_contains(out, "#include"), "output has #include"
    assert str_contains(out, "el_runtime.h"), "output includes el_runtime.h"
}

test "codegen-int-main" {
    let out: String = compile_capture("fn main() -> Void { }")
    assert str_contains(out, "int main("), "output has int main()"
}

test "codegen-runtime-init" {
    let out: String = compile_capture("fn main() -> Void { }")
    assert str_contains(out, "el_runtime_init_args("), "runtime init in main"
}

test "codegen-void-function-signature" {
    let out: String = compile_capture("fn f() -> Int { return 0 }")
    assert str_contains(out, "f(void)"), "no-param fn uses void signature"
}

test "codegen-function-with-params" {
    let out: String = compile_capture("fn add(x: Int, y: Int) -> Int { return x + y }")
    assert str_contains(out, "add("), "function add in output"
    assert str_contains(out, "el_val_t x"), "param x in output"
    assert str_contains(out, "el_val_t y"), "param y in output"
}

test "codegen-int-literal" {
    let out: String = compile_capture("fn answer() -> Int { return 42 }")
    assert str_contains(out, "42"), "integer literal 42 in output"
    assert str_contains(out, "return"), "return statement in output"
}

test "codegen-string-literal" {
    let out: String = compile_capture("fn greet() -> String { return \"hello\" }")
    assert str_contains(out, "hello"), "string literal hello in output"
}

test "codegen-if-statement" {
    let src: String = "fn check(x: Int) -> Int { if x > 0 { return 1 } return 0 }"
    let out: String = compile_capture(src)
    assert str_contains(out, "if ("), "if statement in C output"
}

test "codegen-if-else" {
    let src: String = "fn check(x: Int) -> Int { if x > 0 { return 1 } else { return 0 } }"
    let out: String = compile_capture(src)
    assert str_contains(out, "if ("), "if in output"
    assert str_contains(out, "} else {"), "else branch in output"
}

test "codegen-while-loop" {
    let src: String = "fn f() -> Int { let i: Int = 0 while i < 10 { i = i + 1 } return i }"
    let out: String = compile_capture(src)
    assert str_contains(out, "while ("), "while loop in C output"
}

test "codegen-let-binding" {
    let src: String = "fn f() -> Int { let n: Int = 5 return n }"
    let out: String = compile_capture(src)
    assert str_contains(out, "el_val_t n"), "let binding in output"
}

test "codegen-function-call" {
    let src: String = "fn f() -> Void { println(\"hi\") }"
    let out: String = compile_capture(src)
    assert str_contains(out, "println("), "function call in output"
}

test "codegen-string-concat" {
    let src: String = "fn f() -> String { let a: String = \"x\" let b: String = \"y\" return a + b }"
    let out: String = compile_capture(src)
    assert str_contains(out, "el_str_concat"), "string concat uses el_str_concat"
}

test "codegen-int-arithmetic" {
    let src: String = "fn f(x: Int, y: Int) -> Int { return x + y }"
    let out: String = compile_capture(src)
    assert !str_contains(out, "el_str_concat(x"), "int add does not use el_str_concat"
}

test "codegen-comparison" {
    let src: String = "fn f(x: Int) -> Bool { return x > 0 }"
    let out: String = compile_capture(src)
    assert str_contains(out, ">"), "comparison in output"
}

test "codegen-string-equality" {
    let src: String = "fn f(s: String) -> Bool { return s == \"hello\" }"
    let out: String = compile_capture(src)
    assert str_contains(out, "str_eq("), "string equality uses str_eq"
}

test "codegen-logical-and" {
    let src: String = "fn f(a: Bool, b: Bool) -> Bool { return a && b }"
    let out: String = compile_capture(src)
    assert str_contains(out, "&&"), "logical and in output"
}

test "codegen-logical-or" {
    let src: String = "fn f(a: Bool, b: Bool) -> Bool { return a || b }"
    let out: String = compile_capture(src)
    assert str_contains(out, "||"), "logical or in output"
}

test "codegen-unary-not" {
    let src: String = "fn f(b: Bool) -> Bool { return !b }"
    let out: String = compile_capture(src)
    assert str_contains(out, "!"), "unary not in output"
}

test "codegen-string-escape-in-c" {
    let src: String = "fn msg() -> String { return \"hello\\nworld\\t!\" }"
    let out: String = compile_capture(src)
    assert str_contains(out, "\\n"), "newline escape in C output"
    assert str_contains(out, "\\t"), "tab escape in C output"
}

test "codegen-many-functions" {
    // Multiple functions — exercises streaming loop + per-function arena scoping
    let src: String = "fn a() -> Int { return 1 }\nfn b() -> Int { return 2 }\nfn c() -> Int { return 3 }\nfn d() -> Int { return 4 }\nfn e() -> Int { return 5 }"
    let out: String = compile_capture(src)
    assert str_contains(out, "el_val_t a("), "function a in output"
    assert str_contains(out, "el_val_t b("), "function b in output"
    assert str_contains(out, "el_val_t c("), "function c in output"
    assert str_contains(out, "el_val_t d("), "function d in output"
    assert str_contains(out, "el_val_t e("), "function e in output"
}

test "codegen-deep-expression" {
    // Deeply nested arithmetic — exercises recursive cg_expr + per-statement arena
    let src: String = "fn deep() -> Int { return 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 }"
    let out: String = compile_capture(src)
    assert str_contains(out, "return"), "deep expr: return present"
    assert str_contains(out, "8"), "deep expr: literal 8 present"
}

test "codegen-forward-declarations" {
    // Functions should have forward declarations before definitions
    let src: String = "fn b() -> Int { return a() }\nfn a() -> Int { return 1 }"
    let out: String = compile_capture(src)
    assert str_contains(out, "el_val_t a("), "function a in output"
    assert str_contains(out, "el_val_t b("), "function b in output"
}

test "codegen-for-loop" {
    let src: String = "fn f() -> Void { let items: [Int] = native_list_empty() for item in items { println(item) } }"
    let out: String = compile_capture(src)
    assert str_contains(out, "for ("), "for loop in C output"
    assert str_contains(out, "el_list_get("), "for loop uses el_list_get"
}

test "codegen-extern-fn" {
    let src: String = "extern fn my_native(x: Int) -> Int\nfn use_it() -> Int { return my_native(1) }"
    let out: String = compile_capture(src)
    assert str_contains(out, "my_native("), "extern fn referenced in output"
}

test "codegen-nested-calls" {
    let src: String = "fn f() -> String { return str_concat(int_to_str(42), \" ok\") }"
    let out: String = compile_capture(src)
    assert str_contains(out, "str_concat"), "nested calls: str_concat in output"
    assert str_contains(out, "int_to_str"), "nested calls: int_to_str in output"
}

// ── Self-host / smoke tests ───────────────────────────────────────────────────

test "compiler-minimal-program" {
    let src: String = "fn main() -> Void { println(\"ok\") }"
    let out: String = compile_capture(src)
    assert str_contains(out, "#include"), "has #include"
    assert str_contains(out, "int main("), "has int main()"
    assert str_contains(out, "println("), "calls println"
    assert str_contains(out, "el_runtime.h"), "links el_runtime.h"
}

test "compiler-pure-library" {
    // No fn main = library mode: codegen_streaming returns before emitting main()
    let src: String = "fn helper(x: Int) -> Int { return x + 1 }"
    let out: String = compile_capture(src)
    assert !str_contains(out, "int main("), "library: no int main"
    assert str_contains(out, "#include"), "library: has includes"
    assert str_contains(out, "helper("), "library: helper function present"
}

test "compiler-multiple-fns-with-main" {
    let src: String = "fn greet(name: String) -> String { return \"Hello \" + name }\nfn main() -> Void { println(greet(\"world\")) }"
    let out: String = compile_capture(src)
    assert str_contains(out, "greet("), "greet in output"
    assert str_contains(out, "int main("), "main in output"
    assert str_contains(out, "println("), "println in output"
}

test "compiler-let-in-main" {
    let src: String = "fn main() -> Void { let x: Int = 42 println(int_to_str(x)) }"
    let out: String = compile_capture(src)
    assert str_contains(out, "el_val_t x"), "let binding x in output"
    assert str_contains(out, "42"), "literal 42 in output"
}

test "compiler-string-concat-chain" {
    let src: String = "fn f() -> String { let a: String = \"x\" let b: String = \"y\" let c: String = \"z\" return a + b + c }"
    let out: String = compile_capture(src)
    assert str_contains(out, "el_str_concat"), "string chain uses el_str_concat"
}

test "compiler-negative-literal" {
    let src: String = "fn f() -> Int { return -42 }"
    let out: String = compile_capture(src)
    assert str_contains(out, "42"), "negative literal value in output"
}

test "compiler-stdint-include" {
    // The generated C should include stdint.h for int64_t
    let src: String = "fn f() -> Int { return 0 }"
    let out: String = compile_capture(src)
    assert str_contains(out, "stdint.h"), "output includes stdint.h"
}
