// cli.bux — CLI driver for the Bux self-hosting compiler
// Wires together: Lexer → Parser → Sema → HirLower → CBackend
module Cli {

extern func PrintLine(s: String);
extern func Print(s: String);
extern func bux_read_file(path: String) -> String;
extern func bux_write_file(path: String, content: String) -> bool;
extern func bux_file_exists(path: String) -> int;
extern func bux_dir_exists(path: String) -> int;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_mkdir_if_needed(path: String) -> int;
extern func bux_run_nim(nim_file: String, out_bin: String) -> int;
extern func bux_list_dir(dir: String, ext: String, out_count: *int) -> *String;
extern func bux_system(cmd: String) -> int;

func ReadFile(path: String) -> String {
    return bux_read_file(path);
}

func WriteFile(path: String, content: String) -> bool {
    return bux_write_file(path, content);
}

func FileExists(path: String) -> bool {
    return bux_file_exists(path) != 0;
}

func DirExists(path: String) -> bool {
    return bux_dir_exists(path) != 0;
}

// Import the compiler pipeline
// In self-hosting mode, these are compiled together from src/

// ---------------------------------------------------------------------------
// Compile a single .bux source file
// ---------------------------------------------------------------------------

func Cli_Compile(source: String, sourceName: String) -> String {
    // Phase 1: Lex
    PrintLine("  Lexing...");
    let lex: *Lexer = Lexer_Tokenize(source);
    PrintLine("  Lex done");
    if Lexer_DiagCount(lex) > 0 {
        PrintLine("Lex errors:");
        var i: int = 0;
        while i < Lexer_DiagCount(lex) {
            Print("  ");
            PrintLine(lex.diags[i].message);
            i = i + 1;
        }
        return "";
    }

    // Phase 2: Parse
    PrintLine("  Parsing...");
    let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
    if mod == null as *Module {
        PrintLine("Parse failed");
        return "";
    }
    PrintLine("  Parse done");

    // Flatten module wrappers: find module decl and hoist its children
    var decl: *Decl = mod.firstItem;
    while decl != null as *Decl {
        if decl.kind == dkModule && decl.childDecl1 != null as *Decl {
            mod.firstItem = decl.childDecl1;
            break;
        }
        decl = decl.childDecl2;
    }

    // Phase 3: Semantic analysis
    PrintLine("  Sema...");
    let sema: *Sema = Sema_Analyze(mod);
    if Sema_HasError(sema) {
        PrintLine("Sema errors:");
        var i: int = 0;
        while i < Sema_DiagCount(sema) {
            Print("  ");
            PrintLine(sema.diags[i].message);
            i = i + 1;
        }
        return "";
    }
    PrintLine("  Sema done");

    // Phase 4: HIR lowering
    PrintLine("  HIR lowering...");
    let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
    if hirMod == null as *HirModule {
        PrintLine("HIR lowering failed");
        return "";
    }
    Print("HIR funcCount=");
    PrintInt(hirMod.funcCount);

    // Phase 5: C code generation
    let cCode: String = CBackend_Generate(hirMod);

    // Cleanup
    Lexer_Free(lex);
    Sema_Free(sema);

    return cCode;
}

// ---------------------------------------------------------------------------
// Build command
// ---------------------------------------------------------------------------

func Cli_Build(srcPath: String, outPath: String) -> int {
    let source: String = ReadFile(srcPath);
    if source == null as String || String_Eq(source, "") || !FileExists(srcPath) {
        Print("Error: cannot read source file: ");
        PrintLine(srcPath);
        return 1;
    }

    Print("Compiling ");
    PrintLine(srcPath);

    let cCode: String = Cli_Compile(source, srcPath);

    if String_Eq(cCode, "") {
        PrintLine("Compilation failed");
        return 1;
    }

    // Write C output
    let cFile: String = String_Concat(outPath, ".c");
    let ok: bool = WriteFile(cFile, cCode);
    if !ok {
        PrintLine("Error: cannot write output file");
        return 1;
    }
    Print("  → C written to ");
    PrintLine(cFile);

    // Find runtime.c and io.c for linking (rt/ directory)
    var rtPath: String = "rt/runtime.c";
    var ioPath: String = "rt/io.c";
    if !FileExists(rtPath) {
        rtPath = "../rt/runtime.c";
    }
    if !FileExists(ioPath) {
        ioPath = "../rt/io.c";
    }
    if !FileExists(rtPath) {
        rtPath = "../../rt/runtime.c";
    }
    if !FileExists(ioPath) {
        ioPath = "../../rt/io.c";
    }

    // Compile with cc
    PrintLine("Compiling C...");
    var cmdBuf: StringBuilder = StringBuilder_NewCap(512);
    StringBuilder_Append(&cmdBuf, "cc -O2 -pthread -o ");
    StringBuilder_Append(&cmdBuf, outPath);
    StringBuilder_Append(&cmdBuf, " ");
    StringBuilder_Append(&cmdBuf, cFile);
    StringBuilder_Append(&cmdBuf, " ");
    if FileExists(rtPath) {
        StringBuilder_Append(&cmdBuf, rtPath);
        StringBuilder_Append(&cmdBuf, " ");
    }
    if FileExists(ioPath) {
        StringBuilder_Append(&cmdBuf, ioPath);
        StringBuilder_Append(&cmdBuf, " ");
    }
    StringBuilder_Append(&cmdBuf, "-lm");
    StringBuilder_Append(&cmdBuf, " -lcrypto");
    let ccRc: int = bux_system(StringBuilder_Build(&cmdBuf));
    if ccRc != 0 {
        PrintLine("Error: C compilation failed");
        return 1;
    }
    Print("  → Binary: ");
    PrintLine(outPath);
    return 0;
}

// ---------------------------------------------------------------------------
// Check command (compile only, no output)
// ---------------------------------------------------------------------------

func Cli_Check(srcPath: String) -> int {
    Print("Check: ");
    PrintLine(srcPath);
    let source: String = ReadFile(srcPath);
    if source == null as String || String_Eq(source, "") {
        PrintLine("Error: cannot read source file");
        return 1;
    }
    PrintLine("  File read OK");

    // Phase 1: Lex
    PrintLine("  Lexing...");
    let lex: *Lexer = Lexer_Tokenize(source);
    PrintLine("  Lex done");
    if Lexer_DiagCount(lex) > 0 {
        PrintLine("Lex errors found");
        return 1;
    }

    // Phase 2: Parse
    let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
    if mod == null as *Module {
        PrintLine("Parse failed");
        return 1;
    }
    PrintLine("  Parse done");

    // Flatten module wrappers
    var decl2: *Decl = mod.firstItem;
    while decl2 != null as *Decl {
        if decl2.kind == dkModule && decl2.childDecl1 != null as *Decl {
            mod.firstItem = decl2.childDecl1;
            break;
        }
        decl2 = decl2.childDecl2;
    }

    // Phase 3: Sema
    let sema: *Sema = Sema_Analyze(mod);
    if Sema_HasError(sema) {
        PrintLine("Sema errors found");
        var i: int = 0;
        while i < Sema_DiagCount(sema) {
            Print("  line ");
            PrintInt(sema.diags[i].line as int64);
            Print(": ");
            PrintLine(sema.diags[i].message);
            i = i + 1;
        }
        return 1;
    }

    // Phase 4: HIR lowering
    let hirMod: *HirModule = HirLower_LowerModule(mod, sema);

    var dc: *Decl = mod.firstItem;
    while dc != null as *Decl {
        if dc.kind == dkFunc {
            Print("check func ");
            PrintLine(dc.strValue);
        }
        dc = dc.childDecl2;
    }
    PrintLine("Check passed");
    return 0;
}

// ---------------------------------------------------------------------------
// Project build — compile all .bux files in src/ directory
// ---------------------------------------------------------------------------

func Cli_CompileSource(source: String, sourceName: String) -> *HirModule {
    // Phase 1: Lex
    PrintLine("  Lexing...");
    let lex: *Lexer = Lexer_Tokenize(source);
    PrintLine("  Lex done");
    if Lexer_DiagCount(lex) > 0 {
        Print("Lex errors in ");
        PrintLine(sourceName);
        return null as *HirModule;
    }

    // Phase 2: Parse
    let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
    if mod == null as *Module {
        Print("Parse failed for ");
        PrintLine(sourceName);
        return null as *HirModule;
    }

    // Phase 3: Semantic analysis
    let sema: *Sema = Sema_Analyze(mod);
    if Sema_HasError(sema) {
        Print("Sema errors in ");
        PrintLine(sourceName);
        return null as *HirModule;
    }

    // Phase 4: HIR lowering
    let hirMod: *HirModule = HirLower_LowerModule(mod, sema);
    return hirMod;
}

// ---------------------------------------------------------------------------
// Stdlib discovery and merging
// ---------------------------------------------------------------------------

func Cli_FindStdlibDir(projectDir: String) -> String {
    var path: String = bux_path_join(projectDir, "lib");
    if DirExists(path) { return path; }
    path = bux_path_join(projectDir, "../lib");
    if DirExists(path) { return path; }
    path = bux_path_join(projectDir, "../../lib");
    if DirExists(path) { return path; }
    return "";
}

func Cli_DeclName(decl: *Decl) -> String {
    if decl == null as *Decl { return ""; }
    return decl.strValue;
}

func Cli_NameInList(name: String, names: *String, count: int) -> bool {
    if String_Eq(name, "") { return false; }
    var i: int = 0;
    while i < count {
        if String_Eq(names[i], name) {
            return true;
        }
        i = i + 1;
    }
    return false;
}

func Cli_CollectNames(mod: *Module, names: *String, maxCount: int) -> int {
    if mod == null as *Module { return 0; }
    var count: int = 0;
    var decl: *Decl = mod.firstItem;
    while decl != null as *Decl && count < maxCount {
        let next: *Decl = decl.childDecl2;
        if decl.kind == dkModule {
            var inner: *Decl = decl.childDecl1;
            while inner != null as *Decl && count < maxCount {
                let iname: String = Cli_DeclName(inner);
                if !String_Eq(iname, "") {
                    names[count] = iname;
                    count = count + 1;
                }
                inner = inner.childDecl2;
            }
        } else {
            let dname: String = Cli_DeclName(decl);
            if !String_Eq(dname, "") {
                names[count] = dname;
                count = count + 1;
            }
        }
        decl = next;
    }
    return count;
}

// Helper: convert single char int to String
func String_FromChar(c: int) -> String {
    var buf: *char8 = bux_alloc(2) as *char8;
    buf[0] = c as char8;
    buf[1] = 0 as char8;
    return buf as String;
}

// Collect stdlib module paths imported by user code.
// Returns number of unique stdlib files to merge (paths written into outPaths).
func Cli_CollectStdlibImports(mod: *Module, outPaths: *String, maxCount: int, stdlibDir: String) -> int {
    if mod == null as *Module { return 0; }
    var count: int = 0;
    var decl: *Decl = mod.firstItem;
    while decl != null as *Decl && count < maxCount {
        if decl.kind == dkUse {
            let path: String = decl.usePath;
            // Only handle Std::* imports
            if String_StartsWith(path, "Std::") {
                // Extract module name after Std:: (e.g., "Std::Io" -> "Io")
                var j: int = 5; // skip "Std::"
                var modName: String = "";
                while j < 100 {
                    let c: int = path[j] as int;
                    if c == 0 || c == 58 { break; } // null or ':'
                    modName = String_Concat(modName, String_FromChar(c));
                    j = j + 1;
                }
                if !String_Eq(modName, "") {
                    let filePath: String = bux_path_join(stdlibDir, String_Concat(modName, ".bux"));
                    // Check for duplicates
                    var dup: bool = false;
                    var di: int = 0;
                    while di < count {
                        if String_Eq(outPaths[di], filePath) { dup = true; }
                        di = di + 1;
                    }
                    if !dup {
                        outPaths[count] = filePath;
                        count = count + 1;
                    }
                }
            }
        }
        decl = decl.childDecl2;
    }
    return count;
}

func Cli_MergeFileInto(target: *Module, path: String, skipNames: *String, skipCount: int) -> int {
    if !FileExists(path) {
        Print("Error: stdlib file not found: ");
        PrintLine(path);
        return 0;
    }
    let source: String = ReadFile(path);
    if source == null as String || String_Eq(source, "") { return 0; }
    let lex: *Lexer = Lexer_Tokenize(source);
    if Lexer_DiagCount(lex) > 0 { return 0; }
    let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
    if mod == null as *Module { return 0; }

    var added: int = 0;
    var decl: *Decl = mod.firstItem;
    while decl != null as *Decl {
        let next: *Decl = decl.childDecl2;
        decl.childDecl2 = null as *Decl;
        if decl.kind == dkModule {
            var inner: *Decl = decl.childDecl1;
            while inner != null as *Decl {
                let innerNext: *Decl = inner.childDecl2;
                let iname: String = Cli_DeclName(inner);
                if !String_Eq(iname, "") && Cli_NameInList(iname, skipNames, skipCount) {
                    // Skip shadowed stdlib decl
                } else {
                    inner.childDecl2 = null as *Decl;
                    if target.firstItem == null as *Decl {
                        target.firstItem = inner;
                    } else {
                        var last: *Decl = target.firstItem;
                        while last.childDecl2 != null as *Decl {
                            last = last.childDecl2;
                        }
                        last.childDecl2 = inner;
                    }
                    target.itemCount = target.itemCount + 1;
                    added = added + 1;
                }
                inner = innerNext;
            }
        } else {
            let dname: String = Cli_DeclName(decl);
            if !String_Eq(dname, "") && Cli_NameInList(dname, skipNames, skipCount) {
                // Skip shadowed stdlib decl
            } else {
                decl.childDecl2 = null as *Decl;
                if target.firstItem == null as *Decl {
                    target.firstItem = decl;
                } else {
                    var last: *Decl = target.firstItem;
                    while last.childDecl2 != null as *Decl {
                        last = last.childDecl2;
                    }
                    last.childDecl2 = decl;
                }
                target.itemCount = target.itemCount + 1;
                added = added + 1;
            }
        }
        decl = next;
    }
    return added;
}

func Cli_CopyModuleDecls(target: *Module, source: *Module) {
    if source == null as *Module || source.firstItem == null as *Decl { return; }
    // Find last item in target
    var targetLast: *Decl = null as *Decl;
    if target.firstItem != null as *Decl {
        targetLast = target.firstItem;
        while targetLast.childDecl2 != null as *Decl {
            targetLast = targetLast.childDecl2;
        }
    }
    var decl: *Decl = source.firstItem;
    var limit: int = 0;
    while decl != null as *Decl && limit < 10000 {
        let next: *Decl = decl.childDecl2;
        decl.childDecl2 = null as *Decl;
        if target.firstItem == null as *Decl {
            target.firstItem = decl;
            targetLast = decl;
        } else {
            targetLast.childDecl2 = decl;
            targetLast = decl;
        }
        target.itemCount = target.itemCount + 1;
        decl = next;
        limit = limit + 1;
    }
    source.firstItem = null as *Decl;
    source.itemCount = 0;
}

func Cli_BuildProject(projectDir: String) -> int {
    let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
    var outName: String = man.name;
    if String_Eq(outName, "") {
        outName = "bux_out";
    }
    let srcDir: String = bux_path_join(projectDir, "src");
    if !DirExists(srcDir) {
        Print("Error: no src/ directory in ");
        PrintLine(projectDir);
        return 1;
    }

    Print("Scanning ");
    PrintLine(srcDir);

    // List all .bux files in src/
    var fileCount: int = 0;
    let files: *String = bux_list_dir(srcDir, ".bux", &fileCount);
    if fileCount == 0 {
        PrintLine("Error: no .bux files found in src/");
        return 1;
    }

    Print("Found ");
    PrintInt(fileCount);
    PrintLine(" source files");

    // Create user merged module (collect user decls first)
    let userMerged: *Module = bux_alloc(sizeof(Module)) as *Module;
    userMerged.name = "main";
    userMerged.path = "";
    userMerged.itemCount = 0;
    userMerged.firstItem = null as *Decl;

    // Parse each file and merge declarations into userMerged module
    var i: int = 0;
    while i < fileCount {
        let path: String = files[i];
        Print("  Parsing ");
        PrintLine(path);
        let source: String = ReadFile(path);
        if String_Eq(source, "") {
            Print("  Error: cannot read ");
            PrintLine(path);
            return 1;
        }
        let lex: *Lexer = Lexer_Tokenize(source);
        if Lexer_DiagCount(lex) > 0 {
            Print("  Lex errors in ");
            PrintLine(path);
            return 1;
        }
        let mod: *Module = Parser_Parse(lex.tokens, lex.tokenCount);
        if mod == null as *Module {
            Print("  Parse failed for ");
            PrintLine(path);
            return 1;
        }
        // Merge declarations from this module into userMerged
        var decl: *Decl = mod.firstItem;
        var fileDeclCount: int = 0;
        while decl != null as *Decl {
            let next: *Decl = decl.childDecl2;
            decl.childDecl2 = null as *Decl;
            // Flatten module declarations
            if decl.kind == dkModule {
                var inner: *Decl = decl.childDecl1;
                // Find last item to append (preserve original order)
                var last: *Decl = userMerged.firstItem;
                if last != null as *Decl {
                    while last.childDecl2 != null as *Decl {
                        last = last.childDecl2;
                    }
                }
                while inner != null as *Decl {
                    let innerNext: *Decl = inner.childDecl2;
                    inner.childDecl2 = null as *Decl;
                    if userMerged.firstItem == null as *Decl {
                        userMerged.firstItem = inner;
                        last = inner;
                    } else {
                        last.childDecl2 = inner;
                        last = inner;
                    }
                    userMerged.itemCount = userMerged.itemCount + 1;
                    fileDeclCount = fileDeclCount + 1;
                    inner = innerNext;
                }
            } else {
                decl.childDecl2 = null as *Decl;
                var last2: *Decl = userMerged.firstItem;
                if last2 != null as *Decl {
                    while last2.childDecl2 != null as *Decl {
                        last2 = last2.childDecl2;
                    }
                    last2.childDecl2 = decl;
                } else {
                    userMerged.firstItem = decl;
                }
                userMerged.itemCount = userMerged.itemCount + 1;
                fileDeclCount = fileDeclCount + 1;
            }
            decl = next;
        }
        Print("    -> ");
        PrintInt(fileDeclCount as int64);
        PrintLine(" decls merged");
        i = i + 1;
    }

    // Collect user declaration names for shadow detection
    let maxNames: int = 2048;
    let userNames: *String = bux_alloc(maxNames * 8) as *String;
    let userNameCount: int = Cli_CollectNames(userMerged, userNames, maxNames);

    // Create final merged module
    let merged: *Module = bux_alloc(sizeof(Module)) as *Module;
    merged.name = "main";
    merged.path = "";
    merged.itemCount = 0;
    merged.firstItem = null as *Decl;

    // Find and merge ALL stdlib declarations
    let stdlibDir: String = Cli_FindStdlibDir(projectDir);
    if !String_Eq(stdlibDir, "") {
        Print("Stdlib found: ");
        PrintLine(stdlibDir);
        // List all .bux files in lib/
        var libCount: int = 0;
        let libFiles: *String = bux_list_dir(stdlibDir, ".bux", &libCount);
        var stdAdded: int = 0;
        if libCount > 0 {
            var si: int = 0;
            while si < libCount {
                Print("  Merging ");
                PrintLine(libFiles[si]);
                let added: int = Cli_MergeFileInto(merged, libFiles[si], userNames, userNameCount);
                stdAdded = stdAdded + added;
                si = si + 1;
            }
        }
        Print("Stdlib declarations added: ");
        PrintInt(stdAdded);
        PrintLine("");
    }

    // Copy user declarations into merged (user shadows stdlib)
    Cli_CopyModuleDecls(merged, userMerged);

    Print("Merged ");
    PrintInt(merged.itemCount);
    PrintLine(" declarations");

    // Quick diag: scan for parserParsePostfixExpr and parserParsePrimary
    var dd: *Decl = merged.firstItem;
    var foundPPE_fwd: int = 0;
    var foundPPE_real: int = 0;
    var foundPPP_fwd: int = 0;
    var foundPPP_real: int = 0;
    while dd != null as *Decl {
        if dd.kind == dkFunc {
            if String_Eq(dd.strValue, "parserParsePostfixExpr") {
                if dd.refBody != null as *Block { foundPPE_real = foundPPE_real + 1; }
                else { foundPPE_fwd = foundPPE_fwd + 1; }
            }
            if String_Eq(dd.strValue, "parserParsePrimary") {
                if dd.refBody != null as *Block { foundPPP_real = foundPPP_real + 1; }
                else { foundPPP_fwd = foundPPP_fwd + 1; }
            }
        }
        dd = dd.childDecl2;
    }
    Print("  DIAG: parserParsePostfixExpr fwd=");
    PrintInt(foundPPE_fwd as int64);
    Print(" real=");
    PrintInt(foundPPE_real as int64);
    PrintLine("");
    Print("  DIAG: parserParsePrimary fwd=");
    PrintInt(foundPPP_fwd as int64);
    Print(" real=");
    PrintInt(foundPPP_real as int64);
    PrintLine("");

    // Semantic analysis
    PrintLine("Running sema...");
    let sema: *Sema = Sema_Analyze(merged);
    if Sema_HasError(sema) {
        PrintLine("Sema errors found");
        var i: int = 0;
        while i < Sema_DiagCount(sema) {
            Print("  line ");
            PrintInt(sema.diags[i].line as int64);
            Print(": ");
            PrintLine(sema.diags[i].message);
            i = i + 1;
        }
        return 1;
    }

    // HIR lowering
    PrintLine("Lowering to HIR...");
    let hirMod: *HirModule = HirLower_LowerModule(merged, sema);

    // C code generation
    PrintLine("Generating C code...");
    let cCode: String = CBackend_Generate(hirMod);

    // Create build directory
    let buildDir: String = bux_path_join(projectDir, "build");
    discard bux_mkdir_if_needed(buildDir);

    // Write C output
    let cFile: String = bux_path_join(buildDir, "main.c");
    if !WriteFile(cFile, cCode) {
        PrintLine("Error: cannot write main.c");
        return 1;
    }
    Print("C code written to ");
    PrintLine(cFile);

    // Find runtime.c and io.c (look in rt/ directory)
    var rtPath: String = bux_path_join(projectDir, "rt/runtime.c");
    var ioPath: String = bux_path_join(projectDir, "rt/io.c");
    if !FileExists(rtPath) {
        rtPath = bux_path_join(projectDir, "../rt/runtime.c");
    }
    if !FileExists(ioPath) {
        ioPath = bux_path_join(projectDir, "../rt/io.c");
    }
    if !FileExists(rtPath) {
        rtPath = bux_path_join(projectDir, "../../rt/runtime.c");
    }
    if !FileExists(ioPath) {
        ioPath = bux_path_join(projectDir, "../../rt/io.c");
    }

    // Compile with cc
    PrintLine("Compiling C...");
    let outBin: String = bux_path_join(buildDir, outName);
    var ccBuf: StringBuilder = StringBuilder_NewCap(512);
    StringBuilder_Append(&ccBuf, "cc -O2 -pthread -o ");
    StringBuilder_Append(&ccBuf, outBin);
    StringBuilder_Append(&ccBuf, " ");
    StringBuilder_Append(&ccBuf, cFile);
    StringBuilder_Append(&ccBuf, " ");
    if FileExists(rtPath) {
        StringBuilder_Append(&ccBuf, rtPath);
        StringBuilder_Append(&ccBuf, " ");
    }
    if FileExists(ioPath) {
        StringBuilder_Append(&ccBuf, ioPath);
        StringBuilder_Append(&ccBuf, " ");
    }
    StringBuilder_Append(&ccBuf, "-lm");
    StringBuilder_Append(&ccBuf, " -lcrypto");
    let ccRc: int = bux_system(StringBuilder_Build(&ccBuf));
    if ccRc != 0 {
        PrintLine("Error: C compilation failed");
        return 1;
    }

    Print("Build successful: ");
    PrintLine(outBin);
    return 0;
}

// ---------------------------------------------------------------------------
// Run command — build project and execute
// ---------------------------------------------------------------------------

func Cli_RunProject(projectDir: String) -> int {
    let rc: int = Cli_BuildProject(projectDir);
    if rc != 0 {
        return rc;
    }
    let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
    var outName: String = man.name;
    if String_Eq(outName, "") {
        outName = "bux_out";
    }
    let outBin: String = bux_path_join(bux_path_join(projectDir, "build"), outName);
    Print("Running: ");
    PrintLine(outBin);
    return bux_system(outBin);
}

// ---------------------------------------------------------------------------
// Main entry — dispatch based on args
// ---------------------------------------------------------------------------

func Cli_Run(args: *String, argCount: int) -> int {
    if argCount < 2 {
        PrintLine("Bux Self-Hosting Compiler v0.2.0");
        PrintLine("Usage: buxc <command> [args]");
        PrintLine("Commands: build, check, run, project, help, version");
        return 0;
    }

    let cmd: String = args[1];
    if String_Eq(cmd, "version") || String_Eq(cmd, "--version") || String_Eq(cmd, "-v") {
        PrintLine("Bux 0.2.0 (self-hosting bootstrap)");
        return 0;
    }

    if String_Eq(cmd, "help") || String_Eq(cmd, "--help") || String_Eq(cmd, "-h") {
        PrintLine("Bux Self-Hosting Compiler v0.2.0");
        PrintLine("Usage: buxc <command> [args]");
        PrintLine("Commands: build, check, run, project, help, version");
        PrintLine("Pipeline modules:");
        PrintLine("  Lexer      ✅  695 lines");
        PrintLine("  Parser     ✅ 1004 lines");
        PrintLine("  Sema       ✅  393 lines");
        PrintLine("  HirLower   ✅  307 lines");
        PrintLine("  CBackend   ✅  585 lines");
        PrintLine("  Total: 3830 lines of Bux");
        return 0;
    }

    if String_Eq(cmd, "check") {
        if argCount < 3 {
            PrintLine("Usage: buxc check <file.bux>");
            return 1;
        }
        return Cli_Check(args[2]);
    }

    if String_Eq(cmd, "build") {
        let src: String = "src/Main.bux";
        let out: String = "build/main";
        if argCount >= 3 { src = args[2]; }
        if argCount >= 4 { out = args[3]; }
        return Cli_Build(src, out);
    }

    if String_Eq(cmd, "project") {
        let dir: String = ".";
        if argCount >= 3 { dir = args[2]; }
        return Cli_BuildProject(dir);
    }

    if String_Eq(cmd, "run") {
        let dir: String = ".";
        if argCount >= 3 { dir = args[2]; }
        return Cli_RunProject(dir);
    }

    Print("Unknown command: ");
    PrintLine(cmd);
    return 1;
}
}
