// 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_getcwd() -> String;
    extern func bux_path_join(a: String, b: String) -> String;
    extern func bux_path_parent(path: 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;
    extern func bux_getenv(name: String) -> String;
    extern func bux_setenv(name: String, value: String) -> int;
    extern func bux_strlen(s: String) -> uint;
    extern func bux_str_slice(s: String, start: uint, len: uint) -> String;

    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;
    }

    // ---------------------------------------------------------------------------
    // Diagnostic formatting (Rust-style errors with snippets)
    // ---------------------------------------------------------------------------

    struct Diagnostic {
        message: String;
        line: uint32;
        column: uint32;
        severity: int;
    }

    /* Read a single line from a file (1-based). Returns "" on error or EOF. */
    func Diagnostic_GetLine(path: String, lineNum: uint32) -> String {
        let content: String = bux_read_file(path);
        if String_Eq(content, "") { return ""; }
        /* bux_str_split_part uses 0-based index */
        return bux_str_split_part(content, "\n", lineNum - 1);
    }

    /* Simple substring check for help hints */
    func Diagnostic_MsgContains(msg: String, needle: String) -> bool {
        return bux_str_contains(msg, needle) != 0;
    }

    /* Actionable help for common error messages */
    func Diagnostic_Hint(msg: String) -> String {
        if Diagnostic_MsgContains(msg, "cannot assign") {
            return "ensure the right-hand side type matches the left-hand side";
        }
        if Diagnostic_MsgContains(msg, "undeclared identifier") {
            return "check the spelling, or import the symbol from the right module";
        }
        if Diagnostic_MsgContains(msg, "too few arguments") {
            return "compare the call with the function's parameter list";
        }
        if Diagnostic_MsgContains(msg, "too many arguments") {
            return "compare the call with the function's parameter list";
        }
        if Diagnostic_MsgContains(msg, "use of moved value") {
            return "the value was moved; clone it or restructure ownership";
        }
        if Diagnostic_MsgContains(msg, "expected expression") {
            return "the previous statement may be incomplete (missing value or ';')";
        }
        if Diagnostic_MsgContains(msg, "duplicate symbol") {
            return "rename one of the definitions or remove the duplicate";
        }
        return "";
    }

    /* Print a diagnostic in Rust-style format:
    *   error: <message>
    *     --> <path>:<line>:<col>
    *      |
    *   42 | <source_line>
    *      | <spaces>^
    *      = help: <hint>
    */
    func Diagnostic_Print(diag: *Diagnostic, sourcePath: String) {
        /* Severity prefix */
        if diag.severity == 0 {
            Print("error: ");
        } else if diag.severity == 1 {
            Print("warning: ");
        } else {
            Print("note: ");
        }
        PrintLine(diag.message);

        /* Location header */
        Print("  --> ");
        Print(sourcePath);
        Print(":");
        PrintInt(diag.line as int64);
        Print(":");
        PrintInt(diag.column as int64);
        PrintLine("");

        /* Source snippet */
        let lineText: String = Diagnostic_GetLine(sourcePath, diag.line);
        if !String_Eq(lineText, "") {
            let lineNumStr: String = String_FromInt(diag.line as int64);

            Print("   |");
            PrintLine("");
            Print(" ");
            Print(lineNumStr);
            Print(" | ");
            PrintLine(lineText);

            /* Underline (multi-char for identifiers/string tokens) */
            Print("   | ");
            var i: uint32 = 0;
            while i < diag.column - 1 && i < 120 {
                Print(" ");
                i = i + 1;
            }
            /* Estimate token length from the source line */
            var ulen: uint = 1;
            let col0: uint = diag.column - 1;
            let lineLen: uint = String_Len(lineText);
            if col0 < lineLen {
                let first: String = String_Chars(lineText, col0);
                if String_Eq(first, "\"") || String_Eq(first, "`") || String_Eq(first, "'") {
                var j: uint = col0 + 1;
                while j < lineLen {
                    let cj: String = String_Chars(lineText, j);
                    if String_Eq(cj, first) {
                        ulen = j - col0 + 1;
                        break;
                    }
                    j = j + 1;
                }
            } else {
                var j: uint = col0;
                while j < lineLen {
                    let cj: String = String_Chars(lineText, j);
                    if String_Contains("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", cj) {
                        j = j + 1;
                    } else {
                        break;
                    }
                }
                if j > col0 {
                    ulen = j - col0;
                }
            }
        }
        var k: uint = 0;
        while k < ulen {
            Print("^");
            k = k + 1;
        }
        PrintLine("");
    }

    /* Helpful hint when we recognize the error */
    let hint: String = Diagnostic_Hint(diag.message);
    if !String_Eq(hint, "") {
        Print("   = help: ");
        PrintLine(hint);
    }
}

// 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, targetTriple: String) -> String {
    // Phase 1: Lex
    PrintLine("  Lexing...");
    let lex: *Lexer = Lexer_Tokenize(source);
    PrintLine("  Lex done");
    if Lexer_DiagCount(lex) > 0 {
        var i: int = 0;
        while i < Lexer_DiagCount(lex) {
            let diag: Diagnostic = Diagnostic {
                message: lex.diags[i].message,
                line: lex.diags[i].line,
                column: lex.diags[i].column,
                severity: 0,
            };
            Diagnostic_Print(&diag, sourceName);
            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) {
        var i: int = 0;
        while i < Sema_DiagCount(sema) {
            let diag: Diagnostic = Diagnostic {
                message: sema.diags[i].message,
                line: sema.diags[i].line,
                column: sema.diags[i].column,
                severity: 0,
            };
            Diagnostic_Print(&diag, sourceName);
            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, targetTriple: String, isRelease: bool) -> int {
    // If building the standard project entry point, use full project build
    // which merges stdlib and supports multi-file projects
    if String_Eq(srcPath, "src/Main.bux") || String_Eq(srcPath, "src/main.bux") {
        let rc: int = Cli_BuildProject(".", targetTriple, isRelease);
        if rc == 0 && !String_Eq(outPath, "build/main") {
            // Rename output if custom path was requested
            bux_system(String_Concat(String_Concat("mv build/main ", outPath), " 2>/dev/null || true"));
        }
        return rc;
    }

    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, targetTriple);

    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 or clang for cross-compilation
    // Default: -O0 -g (debug, matches bootstrap). --release: -O2 -DNDEBUG.
    PrintLine("Compiling C...");
    var cmdBuf: StringBuilder = StringBuilder_NewCap(512);
    var optFlags: String = "-O0 -g";
    if isRelease {
        optFlags = "-O2 -DNDEBUG";
    }
    let extraCf: String = bux_getenv("BUX_CFLAGS");
    if extraCf != null as String && !String_Eq(extraCf, "") {
        optFlags = String_Concat(optFlags, " ");
        optFlags = String_Concat(optFlags, extraCf);
    }
    if !String_Eq(targetTriple, "") {
        StringBuilder_Append(&cmdBuf, "clang ");
        StringBuilder_Append(&cmdBuf, optFlags);
        StringBuilder_Append(&cmdBuf, " -pthread -Wl,--build-id=none -target ");
        StringBuilder_Append(&cmdBuf, targetTriple);
        StringBuilder_Append(&cmdBuf, " ");
    } else {
        StringBuilder_Append(&cmdBuf, "cc ");
        StringBuilder_Append(&cmdBuf, optFlags);
        StringBuilder_Append(&cmdBuf, " -pthread -Wl,--build-id=none ");
    }
    StringBuilder_Append(&cmdBuf, "-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 {
        var i: int = 0;
        while i < Lexer_DiagCount(lex) {
            let diag: Diagnostic = Diagnostic {
                message: lex.diags[i].message,
                line: lex.diags[i].line,
                column: lex.diags[i].column,
                severity: 0,
            };
            Diagnostic_Print(&diag, srcPath);
            i = i + 1;
        }
        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) {
        var i: int = 0;
        while i < Sema_DiagCount(sema) {
            let diag: Diagnostic = Diagnostic {
                message: sema.diags[i].message,
                line: sema.diags[i].line,
                column: sema.diags[i].column,
                severity: 0,
            };
            Diagnostic_Print(&diag, srcPath);
            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 {
    // Allow tests and temp packages to inherit the parent project's stdlib.
    let envPath: String = bux_getenv("BUX_STDLIB");
    if !String_Eq(envPath, "") && DirExists(envPath) && FileExists(bux_path_join(envPath, "Fs.bux")) {
        return envPath;
    }

    var path: String = bux_path_join(projectDir, "lib");
    if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { return path; }
    path = bux_path_join(projectDir, "../lib");
    if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { return path; }
    path = bux_path_join(projectDir, "../../lib");
    if DirExists(path) && FileExists(bux_path_join(path, "Fs.bux")) { 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;
}

/* Count lines in a string (split by newline) */
func Cli_CountLines(content: String) -> uint {
    return bux_str_split_count(content, "\n");
}

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

// ---------------------------------------------------------------------------
// New — create a new Bux project
// ---------------------------------------------------------------------------

func Cli_New(name: String) -> int {
    var root: String = name;
    // If name starts with /, use as absolute path; otherwise join with cwd
    if !String_StartsWith(name, "/") {
        let cwd: String = bux_getcwd();
        root = bux_path_join(cwd, name);
    }
    // Extract package name from path (last component)
    var pkgName: String = name;
    if String_StartsWith(name, "/") {
        var i: int = 0;
        while i < 256 {
            let c: int = name[i] as int;
            if c == 0 { break; }
            i = i + 1;
        }
        var j: int = i - 1;
        while j >= 0 {
            let c: int = name[j] as int;
            if c == 47 {
                pkgName = "";
                var k: int = j + 1;
                while k < i {
                    pkgName = String_Concat(pkgName, String_FromChar(name[k] as int));
                    k = k + 1;
                }
                break;
            }
            j = j - 1;
        }
    }
    if DirExists(root) {
        Print("Error: directory '"); Print(pkgName); PrintLine("' already exists");
        return 1;
    }
    let srcDir: String = bux_path_join(root, "src");
    let mk1: int = bux_mkdir_if_needed(root);
    let mk2: int = bux_mkdir_if_needed(srcDir);
    let tomlPath: String = bux_path_join(root, "bux.toml");
    let tomlContent: String = "[Package]\nName    = \"";
    tomlContent = String_Concat(tomlContent, pkgName);
    tomlContent = String_Concat(tomlContent, "\"\nVersion = \"0.1.0\"\nType    = \"bin\"\n\n[Build]\nOutput = \"Bin\"\n");
    let w1: bool = WriteFile(tomlPath, tomlContent);
    let mainPath: String = bux_path_join(srcDir, "Main.bux");
    let mainContent: String = "import Std::Io::PrintLine;\n\nfunc Main() -> int {\n    PrintLine(\"Hello, Bux!\");\n    return 0;\n}\n";
    let w2: bool = WriteFile(mainPath, mainContent);
    if !w1 || !w2 || mk1 != 0 || mk2 != 0 {
        Print("Error: could not create project '"); Print(pkgName); PrintLine("'");
        return 1;
    }
    Print("Created Bux package '"); Print(pkgName); PrintLine("'");
    return 0;
}

// ---------------------------------------------------------------------------
// Init — initialize a Bux project in the current directory
// ---------------------------------------------------------------------------

func Cli_Init() -> int {
    let cwd: String = bux_getcwd();
    let tomlPath: String = bux_path_join(cwd, "bux.toml");
    if FileExists(tomlPath) {
        PrintLine("Error: bux.toml already exists");
        return 1;
    }
    let parent: String = bux_path_parent(cwd);
    var name: String = "";
    if !String_Eq(parent, "/") {
        var i: int = 0;
        while i < 256 {
            let c: int = cwd[i] as int;
            if c == 0 { break; }
            i = i + 1;
        }
        // Extract last path component
        var j: int = i - 1;
        while j >= 0 {
            let c: int = cwd[j] as int;
            if c == 47 { // '/'
                var k: int = j + 1;
                while k < i {
                    name = String_Concat(name, String_FromChar(cwd[k] as int));
                    k = k + 1;
                }
                break;
            }
            j = j - 1;
        }
        if String_Eq(name, "") { name = "untitled"; }
    } else {
        name = "untitled";
    }
    let tomlContent: String = "[Package]\nName    = \"";
    tomlContent = String_Concat(tomlContent, name);
    tomlContent = String_Concat(tomlContent, "\"\nVersion = \"0.1.0\"\nType    = \"bin\"\n\n[Build]\nOutput = \"Bin\"\n");
    discard WriteFile(tomlPath, tomlContent);
    let srcDir: String = bux_path_join(cwd, "src");
    if !DirExists(srcDir) {
        discard bux_mkdir_if_needed(srcDir);
    }
    Print("Initialized Bux package '"); Print(name); PrintLine("'");
    return 0;
}

// ---------------------------------------------------------------------------
// Test command — build and run tests
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// Add — add a dependency to bux.toml
// ---------------------------------------------------------------------------

func Cli_Add(pkgName: String, url: String) -> int {
    let cwd: String = bux_getcwd();
    let tomlPath: String = bux_path_join(cwd, "bux.toml");
    if !FileExists(tomlPath) {
        PrintLine("Error: no bux.toml found");
        return 1;
    }
    var man: Manifest = Manifest_Load(tomlPath);
    if Manifest_HasDep(man, pkgName) {
        Print("Error: dependency '"); Print(pkgName); PrintLine("' already exists");
        return 1;
    }
    let added: bool = Manifest_AddDep(&man, pkgName, url);
    if !added {
        PrintLine("Error: cannot add dependency (max 8)");
        return 1;
    }
    let tomlContent: String = Manifest_ToString(man);
    if !WriteFile(tomlPath, tomlContent) {
        PrintLine("Error: cannot write bux.toml");
        return 1;
    }
    Print("Added dependency: "); Print(pkgName); Print(" = "); PrintLine(url);
    return 0;
}

// ---------------------------------------------------------------------------
// Remove — remove a dependency from bux.toml
// ---------------------------------------------------------------------------

func Cli_Remove(pkgName: String) -> int {
    let cwd: String = bux_getcwd();
    let tomlPath: String = bux_path_join(cwd, "bux.toml");
    if !FileExists(tomlPath) {
        PrintLine("Error: no bux.toml found");
        return 1;
    }
    var man: Manifest = Manifest_Load(tomlPath);
    if !Manifest_HasDep(man, pkgName) {
        Print("Error: dependency '"); Print(pkgName); PrintLine("' not found");
        return 1;
    }
    let removed: bool = Manifest_RemoveDep(&man, pkgName);
    if !removed {
        PrintLine("Error: cannot remove dependency");
        return 1;
    }
    let tomlContent: String = Manifest_ToString(man);
    if !WriteFile(tomlPath, tomlContent) {
        PrintLine("Error: cannot write bux.toml");
        return 1;
    }
    Print("Removed dependency: "); PrintLine(pkgName);
    return 0;
}

// ---------------------------------------------------------------------------
// Fetch — download dependencies into deps/
// ---------------------------------------------------------------------------

func Cli_Fetch() -> int {
    let cwd: String = bux_getcwd();
    let tomlPath: String = bux_path_join(cwd, "bux.toml");
    if !FileExists(tomlPath) {
        PrintLine("Error: no bux.toml found");
        return 1;
    }
    let man: Manifest = Manifest_Load(tomlPath);
    if man.depCount == 0 {
        PrintLine("No dependencies to fetch");
        return 0;
    }
    let depsDir: String = bux_path_join(cwd, "deps");
    discard bux_mkdir_if_needed(depsDir);
    var i: int = 0;
    while i < man.depCount {
        var depName: String = "";
        var depUrl: String = "";
        if i == 0 { depName = man.depName0; depUrl = man.depUrl0; }
        else if i == 1 { depName = man.depName1; depUrl = man.depUrl1; }
        else if i == 2 { depName = man.depName2; depUrl = man.depUrl2; }
        else if i == 3 { depName = man.depName3; depUrl = man.depUrl3; }
        else if i == 4 { depName = man.depName4; depUrl = man.depUrl4; }
        else if i == 5 { depName = man.depName5; depUrl = man.depUrl5; }
        else if i == 6 { depName = man.depName6; depUrl = man.depUrl6; }
        else if i == 7 { depName = man.depName7; depUrl = man.depUrl7; }
        let depPath: String = bux_path_join(depsDir, depName);
        if DirExists(depPath) {
            Print("Updating "); PrintLine(depName);
            let cmd: String = String_Concat("cd \"", String_Concat(depPath, "\" && git pull --quiet 2>/dev/null"));
            discard bux_system(cmd);
        } else {
            Print("Fetching "); Print(depName); Print(" from "); PrintLine(depUrl);
            let cmd: String = String_Concat("git clone --quiet \"", String_Concat(depUrl, String_Concat("\" \"", String_Concat(depPath, "\""))));
            let rc: int = bux_system(cmd);
            if rc != 0 {
                Print("Error: failed to fetch "); PrintLine(depName);
            }
        }
        i = i + 1;
    }
    PrintLine("Fetch complete");
    return 0;
}

// ---------------------------------------------------------------------------
// Doc command — Markdown from /// comments (D.4)
// ---------------------------------------------------------------------------

func Cli_DocIsDeclStart(line: String) -> bool {
    if String_StartsWith(line, "func ") { return true; }
    if String_StartsWith(line, "pub func ") { return true; }
    if String_StartsWith(line, "extern func ") { return true; }
    if String_StartsWith(line, "const func ") { return true; }
    if String_StartsWith(line, "async func ") { return true; }
    if String_StartsWith(line, "struct ") { return true; }
    if String_StartsWith(line, "pub struct ") { return true; }
    if String_StartsWith(line, "enum ") { return true; }
    if String_StartsWith(line, "interface ") { return true; }
    if String_StartsWith(line, "module ") { return true; }
    if String_StartsWith(line, "type ") { return true; }
    return false;
}

func Cli_DocExtractName(line: String) -> String {
    // Skip leading keywords
    var s: String = line;
    if String_StartsWith(s, "pub ") { s = bux_str_slice(s, 4, bux_strlen(s) - 4); }
    if String_StartsWith(s, "extern ") { s = bux_str_slice(s, 7, bux_strlen(s) - 7); }
    if String_StartsWith(s, "const ") { s = bux_str_slice(s, 6, bux_strlen(s) - 6); }
    if String_StartsWith(s, "async ") { s = bux_str_slice(s, 6, bux_strlen(s) - 6); }
    if String_StartsWith(s, "func ") { s = bux_str_slice(s, 5, bux_strlen(s) - 5); }
    else if String_StartsWith(s, "struct ") { s = bux_str_slice(s, 7, bux_strlen(s) - 7); }
    else if String_StartsWith(s, "enum ") { s = bux_str_slice(s, 5, bux_strlen(s) - 5); }
    else if String_StartsWith(s, "interface ") { s = bux_str_slice(s, 10, bux_strlen(s) - 10); }
    else if String_StartsWith(s, "module ") { s = bux_str_slice(s, 7, bux_strlen(s) - 7); }
    else if String_StartsWith(s, "type ") { s = bux_str_slice(s, 5, bux_strlen(s) - 5); }
    // Take until space, <, (, {, :, ;
    var i: uint = 0;
    let n: uint = bux_strlen(s);
    while i < n {
        let ch: String = bux_str_slice(s, i, 1);
        if String_Eq(ch, " ") || String_Eq(ch, "<") || String_Eq(ch, "(") ||
        String_Eq(ch, "{") || String_Eq(ch, ":") || String_Eq(ch, ";") {
            break;
        }
        i = i + 1;
    }
    if i == 0 { return s; }
    return bux_str_slice(s, 0, i);
}

func Cli_DocProcessFile(path: String, outSb: *StringBuilder) -> int {
    let source: String = ReadFile(path);
    if source == null as String || String_Eq(source, "") { return 0; }
    var itemCount: int = 0;
    var pending: String = "";
    var hasPending: bool = false;
    let lineCount: uint = bux_str_split_count(source, "\n");
    // Drop trailing empty split artifact
    var nLines: uint = lineCount;
    if nLines > 0 {
        let last: String = bux_str_split_part(source, "\n", nLines - 1);
        if String_Eq(last, "") { nLines = nLines - 1; }
    }
    var li: uint = 0;
    var wroteHeader: bool = false;
    while li < nLines {
        let raw: String = bux_str_split_part(source, "\n", li);
        let line: String = String_Trim(raw);
        if String_StartsWith(line, "///") {
        var body: String = bux_str_slice(line, 3, bux_strlen(line) - 3);
        if String_StartsWith(body, " ") {
            body = bux_str_slice(body, 1, bux_strlen(body) - 1);
        }
        if hasPending {
            pending = String_Concat(pending, String_Concat("\n", body));
        } else {
            pending = body;
            hasPending = true;
        }
        li = li + 1;
        continue;
    }
    if String_Eq(line, "") || String_StartsWith(line, "@[") {
        li = li + 1;
        continue;
    }
    if hasPending && Cli_DocIsDeclStart(line) {
        if !wroteHeader {
            StringBuilder_Append(outSb, "## `");
            StringBuilder_Append(outSb, Cli_FileNameFromPath(path));
            StringBuilder_Append(outSb, "`\n\n");
            StringBuilder_Append(outSb, "_Source: `");
            StringBuilder_Append(outSb, path);
            StringBuilder_Append(outSb, "`_\n\n");
            wroteHeader = true;
        }
        let name: String = Cli_DocExtractName(line);
        StringBuilder_Append(outSb, "### `");
        StringBuilder_Append(outSb, name);
        StringBuilder_Append(outSb, "`\n\n");
        StringBuilder_Append(outSb, "```bux\n");
        StringBuilder_Append(outSb, line);
        StringBuilder_Append(outSb, "\n```\n\n");
        StringBuilder_Append(outSb, pending);
        StringBuilder_Append(outSb, "\n\n");
        itemCount = itemCount + 1;
        hasPending = false;
        pending = "";
        li = li + 1;
        continue;
    }
    if String_StartsWith(line, "//") {
    li = li + 1;
    continue;
}
// Other code clears pending
hasPending = false;
pending = "";
li = li + 1;
}
return itemCount;
}

func Cli_Doc(dir: String, outPath: String) -> int {
    var sb: StringBuilder = StringBuilder_NewCap(16384);
    StringBuilder_Append(&sb, "# API Reference\n\n");
    StringBuilder_Append(&sb, "Generated by `bux doc` from `///` documentation comments.\n\n");
    var total: int = 0;
    if FileExists(dir) {
        total = total + Cli_DocProcessFile(dir, &sb);
    } else if DirExists(dir) {
        var fileCount: int = 0;
        let files: *String = bux_list_dir(dir, ".bux", &fileCount);
        var i: int = 0;
        while i < fileCount {
            total = total + Cli_DocProcessFile(files[i], &sb);
            i = i + 1;
        }
    } else {
        Print("Error: path not found: ");
        PrintLine(dir);
        StringBuilder_Free(&sb);
        return 1;
    }
    let md: String = StringBuilder_Build(&sb);
    StringBuilder_Free(&sb);
    if String_Eq(outPath, "") {
        Print(md);
    } else {
        if !WriteFile(outPath, md) {
            Print("Error: cannot write ");
            PrintLine(outPath);
            return 1;
        }
        Print("Wrote ");
        PrintInt(total as int64);
        Print(" documented items → ");
        PrintLine(outPath);
    }
    if total == 0 {
        PrintLine("warning: no /// documented declarations found");
    }
    return 0;
}

// ---------------------------------------------------------------------------
// Fmt command — format source files (write or --check for CI)
// ---------------------------------------------------------------------------

func Cli_Fmt(dir: String, checkOnly: bool) -> int {
    // If dir is a file, format/check just that file
    if FileExists(dir) {
        if checkOnly {
            if Fmt_CheckFile(dir) == 0 {
                Print("  ok ");
                PrintLine(dir);
                return 0;
            }
            Print("  would reformat ");
            PrintLine(dir);
            return 1;
        }
        Print("Formatting ");
        PrintLine(dir);
        if Fmt_FormatFile(dir) {
            PrintLine("  OK");
            return 0;
        }
        return 1;
    }
    // Otherwise format/check all .bux files in directory
    if checkOnly {
        Print("Checking format in ");
    } else {
        Print("Formatting ");
    }
    PrintLine(dir);
    var fileCount: int = 0;
    let files: *String = bux_list_dir(dir, ".bux", &fileCount);
    if fileCount == 0 {
        PrintLine("No .bux files found");
        return 1;
    }
    var i: int = 0;
    var okCount: int = 0;
    var changeCount: int = 0;
    while i < fileCount {
        if checkOnly {
            if Fmt_CheckFile(files[i]) == 0 {
                okCount = okCount + 1;
            } else {
                Print("  would reformat ");
                PrintLine(files[i]);
                changeCount = changeCount + 1;
            }
        } else {
            Print("  ");
            PrintLine(files[i]);
            if Fmt_FormatFile(files[i]) {
                okCount = okCount + 1;
            }
        }
        i = i + 1;
    }
    if checkOnly {
        Print("fmt --check: ");
        PrintInt(changeCount as int64);
        Print(" would reformat, ");
        PrintInt(okCount as int64);
        PrintLine(" ok");
        if changeCount > 0 { return 1; }
        return 0;
    }
    Print("Formatted "); PrintInt(okCount as int64); Print("/"); PrintInt(fileCount as int64); PrintLine(" files");
    return 0;
}

func Cli_FileNameFromPath(path: String) -> String {
    let len: int = bux_strlen(path) as int;
    var i: int = len - 1;
    while i >= 0 {
        let ch: String = bux_str_slice(path, i as uint, 1);
        if String_Eq(ch, "/") {
            return bux_str_slice(path, (i + 1) as uint, (len - i - 1) as uint);
        }
        i = i - 1;
    }
    return path;
}

func Cli_StripExtension(name: String) -> String {
    let len: int = bux_strlen(name) as int;
    var i: int = len - 1;
    while i >= 0 {
        let ch: String = bux_str_slice(name, i as uint, 1);
        if String_Eq(ch, ".") {
            return bux_str_slice(name, 0, i as uint);
        }
        i = i - 1;
    }
    return name;
}

func Cli_Test(projectDir: String, filter: String) -> int {
    Print("Testing project: ");
    PrintLine(projectDir);
    if !String_Eq(filter, "") {
        Print("Filter: ");
        PrintLine(filter);
    }

    // Without --filter, build and run the project's own Main first.
    // With --filter, only run matching tests/*.bux files.
    if String_Eq(filter, "") {
        let mainRc: int = Cli_BuildProject(projectDir, "", false);
        if mainRc != 0 {
            PrintLine("Main test build failed");
            return mainRc;
        }
        let man: Manifest = Manifest_Load(bux_path_join(projectDir, "bux.toml"));
        var mainName: String = man.name;
        if String_Eq(mainName, "") { mainName = "bux_out"; }
        let mainBin: String = bux_path_join(bux_path_join(projectDir, "build"), mainName);
        if !FileExists(mainBin) {
            Print("Error: test binary not found: ");
            PrintLine(mainBin);
            return 1;
        }
        let mainResult: int = bux_system(mainBin);
        if mainResult != 0 {
            Print("Main tests failed (exit code ");
            PrintInt(mainResult as int64);
            PrintLine(")");
            return mainResult;
        }
        PrintLine("Main tests passed");
    }

    // Propagate the project's stdlib to temp test packages.
    let stdlibDir: String = Cli_FindStdlibDir(projectDir);
    if !String_Eq(stdlibDir, "") {
        discard bux_setenv("BUX_STDLIB", stdlibDir);
    }

    // Run individual test files from tests/ directory
    let testsDir: String = bux_path_join(projectDir, "tests");
    if !DirExists(testsDir) {
        PrintLine("No tests/ directory found");
        return 0;
    }
    var testCount: int = 0;
    let testFiles: *String = bux_list_dir(testsDir, ".bux", &testCount);
    if testCount == 0 {
        PrintLine("No .bux test files found in tests/");
        return 0;
    }

    PrintLine("┌──────────────────────────────┬────────┐");
    PrintLine("│ Test                         │ Status │");
    PrintLine("├──────────────────────────────┼────────┤");

    var passed: int = 0;
    var failed: int = 0;
    var skipped: int = 0;
    var i: int = 0;
    while i < testCount {
        let testPath: String = testFiles[i];
        let fileName: String = Cli_FileNameFromPath(testPath);
        let testName: String = Cli_StripExtension(fileName);

        // --filter: only run tests whose name contains the filter substring
        if !String_Eq(filter, "") {
            if !String_Contains(testName, filter) {
                skipped = skipped + 1;
                i = i + 1;
                continue;
            }
        }

        Print("│ ");
        Print(testName);
        // Pad status column roughly (name may be long)
        Print(" ... ");

        // Create temp package for this test file
        let tmpDir: String = bux_path_join(bux_path_join(projectDir, "build"), "_test_tmp");
        let tmpSrc: String = bux_path_join(tmpDir, "src");
        discard bux_mkdir_if_needed(tmpDir);
        discard bux_mkdir_if_needed(tmpSrc);

        let source: String = ReadFile(testPath);
        if String_Eq(source, "") {
            PrintLine("FAIL │");
            failed = failed + 1;
            i = i + 1;
            continue;
        }
        let tmpMain: String = bux_path_join(tmpSrc, "Main.bux");
        if !WriteFile(tmpMain, source) {
            PrintLine("FAIL │");
            failed = failed + 1;
            i = i + 1;
            continue;
        }

        let tmpToml: String = bux_path_join(tmpDir, "bux.toml");
        var tomlContent: String = "[Package]\nName    = \"_test_tmp\"\nVersion = \"0.1.0\"\nType    = \"bin\"\n\n[Build]\nOutput = \"Bin\"\n";
        if !WriteFile(tmpToml, tomlContent) {
            PrintLine("FAIL │");
            failed = failed + 1;
            i = i + 1;
            continue;
        }

        // Copy runtime shims so the temp package links even when nested deep.
        if !String_Eq(stdlibDir, "") {
            let rtDir: String = bux_path_join(bux_path_parent(stdlibDir), "rt");
            let tmpRtDir: String = bux_path_join(tmpDir, "rt");
            discard bux_mkdir_if_needed(tmpRtDir);
            let rtSrc: String = bux_path_join(rtDir, "runtime.c");
            let rtDst: String = bux_path_join(tmpRtDir, "runtime.c");
            if FileExists(rtSrc) && !FileExists(rtDst) {
                discard WriteFile(rtDst, ReadFile(rtSrc));
            }
            let ioSrc: String = bux_path_join(rtDir, "io.c");
            let ioDst: String = bux_path_join(tmpRtDir, "io.c");
            if FileExists(ioSrc) && !FileExists(ioDst) {
                discard WriteFile(ioDst, ReadFile(ioSrc));
            }
        }

        let buildRc: int = Cli_BuildProject(tmpDir, "", false);
        if buildRc != 0 {
            PrintLine("FAIL │");
            failed = failed + 1;
            i = i + 1;
            continue;
        }
        let testBin: String = bux_path_join(bux_path_join(tmpDir, "build"), "_test_tmp");
        if !FileExists(testBin) {
            PrintLine("FAIL │");
            failed = failed + 1;
            i = i + 1;
            continue;
        }
        let runRc: int = bux_system(testBin);
        if runRc == 0 {
            PrintLine("PASS │");
            passed = passed + 1;
        } else {
            Print("FAIL │");
            PrintLine("");
            failed = failed + 1;
        }
        i = i + 1;
    }

    PrintLine("└──────────────────────────────┴────────┘");
    Print("Results: ");
    PrintInt(passed as int64);
    Print(" passed, ");
    PrintInt(failed as int64);
    Print(" failed");
    if skipped > 0 {
        Print(", ");
        PrintInt(skipped as int64);
        Print(" skipped");
    }
    PrintLine("");
    if !String_Eq(filter, "") && passed == 0 && failed == 0 {
        Print("No tests matching filter '");
        Print(filter);
        PrintLine("'");
        return 1;
    }
    if failed > 0 { return 1; }
    return 0;
}

func Cli_BuildProject(projectDir: String, targetTriple: String, isRelease: bool) -> 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 {
            var li: int = 0;
            while li < Lexer_DiagCount(lex) {
                let diag: Diagnostic = Diagnostic {
                    message: lex.diags[li].message,
                    line: lex.diags[li].line,
                    column: lex.diags[li].column,
                    severity: 0,
                };
                Diagnostic_Print(&diag, path);
                li = li + 1;
            }
            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("");
    }

    // Merge dependency declarations (deps shadow stdlib, user shadows deps)
    if man.depCount > 0 {
        let depsDir: String = bux_path_join(projectDir, "deps");
        var di: int = 0;
        while di < man.depCount {
            var depName: String = "";
            if di == 0 { depName = man.depName0; }
            else if di == 1 { depName = man.depName1; }
            else if di == 2 { depName = man.depName2; }
            else if di == 3 { depName = man.depName3; }
            else if di == 4 { depName = man.depName4; }
            else if di == 5 { depName = man.depName5; }
            else if di == 6 { depName = man.depName6; }
            else if di == 7 { depName = man.depName7; }
            let depSrcDir: String = bux_path_join(bux_path_join(depsDir, depName), "src");
            if DirExists(depSrcDir) {
                var depFileCount: int = 0;
                let depFiles: *String = bux_list_dir(depSrcDir, ".bux", &depFileCount);
                if depFileCount > 0 {
                    Print("Merging dependency: ");
                    PrintLine(depName);
                    var dfi: int = 0;
                    while dfi < depFileCount {
                        Print("  Merging ");
                        PrintLine(depFiles[dfi]);
                        let added: int = Cli_MergeFileInto(merged, depFiles[dfi], userNames, userNameCount);
                        Print("    -> ");
                        PrintInt(added as int64);
                        PrintLine(" decls added");
                        dfi = dfi + 1;
                    }
                }
            }
            di = di + 1;
        }
    }

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

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

    // Semantic analysis
    PrintLine("Running sema...");
    let sema: *Sema = Sema_Analyze(merged);
    if Sema_HasError(sema) {
        var i: int = 0;
        while i < Sema_DiagCount(sema) {
            let diag: Diagnostic = Diagnostic {
                message: sema.diags[i].message,
                line: sema.diags[i].line,
                column: sema.diags[i].column,
                severity: 0,
            };
            /* Try to guess which source file has this line */
            let errLine: uint32 = sema.diags[i].line;
            let mainSrc: String = bux_path_join(projectDir, "src/Main.bux");
            let mainContent: String = bux_read_file(mainSrc);
            var sourcePath: String = "<merged>";
            if !String_Eq(mainContent, "") {
                let mainLines: uint = Cli_CountLines(mainContent);
                if errLine <= mainLines {
                    sourcePath = mainSrc;
                }
            }
            Diagnostic_Print(&diag, sourcePath);
            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 — default debug (-O0 -g); --release → -O2 -DNDEBUG
    PrintLine("Compiling C...");
    let outBin: String = bux_path_join(buildDir, outName);
    var ccBuf: StringBuilder = StringBuilder_NewCap(512);
    var optFlags2: String = "-O0 -g";
    if isRelease {
        optFlags2 = "-O2 -DNDEBUG";
    }
    let extraCf2: String = bux_getenv("BUX_CFLAGS");
    if extraCf2 != null as String && !String_Eq(extraCf2, "") {
        optFlags2 = String_Concat(optFlags2, " ");
        optFlags2 = String_Concat(optFlags2, extraCf2);
    }
    if !String_Eq(targetTriple, "") {
        StringBuilder_Append(&ccBuf, "clang ");
        StringBuilder_Append(&ccBuf, optFlags2);
        StringBuilder_Append(&ccBuf, " -pthread -Wl,--build-id=none -target ");
        StringBuilder_Append(&ccBuf, targetTriple);
        StringBuilder_Append(&ccBuf, " ");
    } else {
        StringBuilder_Append(&ccBuf, "cc ");
        StringBuilder_Append(&ccBuf, optFlags2);
        StringBuilder_Append(&ccBuf, " -pthread -Wl,--build-id=none ");
    }
    StringBuilder_Append(&ccBuf, "-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, targetTriple: String, isRelease: bool) -> int {
    let rc: int = Cli_BuildProject(projectDir, targetTriple, isRelease);
    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 {
    /* Scan for --target and --release flags before processing command */
    var targetTriple: String = "";
    var isRelease: bool = false;
    var ai: int = 1;
    while ai < argCount {
        if String_Eq(args[ai], "--target") && ai + 1 < argCount {
            targetTriple = args[ai + 1];
            /* Remove --target and its value from args by shifting */
            var j: int = ai;
            while j + 2 < argCount {
                args[j] = args[j + 2];
                j = j + 1;
            }
            argCount = argCount - 2;
            ai = ai - 1;
        } else if String_Eq(args[ai], "--release") {
            isRelease = true;
            /* Remove --release from args by shifting */
            var j: int = ai;
            while j + 1 < argCount {
                args[j] = args[j + 1];
                j = j + 1;
            }
            argCount = argCount - 1;
            ai = ai - 1;
        }
        ai = ai + 1;
    }

    if argCount < 2 {
        PrintLine("Bux Self-Hosting Compiler v0.2.0");
        PrintLine("Usage: buxc <command> [args]");
        PrintLine("Commands: build, check, new, init, add, remove, fetch, fmt, doc, test, run, project, help, version");
        PrintLine("  test --filter <name>   Only run tests/*.bux whose name contains <name>");
        PrintLine("  fmt  --check [path]    Exit 1 if any file would be reformatted (CI)");
        PrintLine("  doc  --out file.md [path]  API docs from /// comments");
        PrintLine("  --release              Optimize (-O2 -DNDEBUG); default is -O0 -g");
        PrintLine("  BUX_CFLAGS             Extra flags appended to cc");
        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, new, init, add, remove, fetch, fmt, doc, test, run, project, help, version");
        PrintLine("  test --filter <name>   Only run tests/*.bux whose name contains <name>");
        PrintLine("  fmt  --check [path]    Exit 1 if any file would be reformatted (CI)");
        PrintLine("  doc  --out file.md [path]  API docs from /// comments");
        PrintLine("  --release              Optimize (-O2 -DNDEBUG); default is -O0 -g");
        PrintLine("  BUX_CFLAGS             Extra flags appended to cc");
        PrintLine("Pipeline modules:");
        PrintLine("  Lexer      ✅");
        PrintLine("  Parser     ✅");
        PrintLine("  Sema       ✅");
        PrintLine("  HirLower   ✅");
        PrintLine("  CBackend   ✅");
        return 0;
    }

    if String_Eq(cmd, "new") {
        if argCount < 3 {
            PrintLine("Usage: buxc new <name>");
            return 1;
        }
        return Cli_New(args[2]);
    }

    if String_Eq(cmd, "init") {
        return Cli_Init();
    }

    if String_Eq(cmd, "add") {
        if argCount < 4 {
            PrintLine("Usage: buxc add <name> <url>");
            return 1;
        }
        return Cli_Add(args[2], args[3]);
    }

    if String_Eq(cmd, "remove") {
        if argCount < 3 {
            PrintLine("Usage: buxc remove <name>");
            return 1;
        }
        return Cli_Remove(args[2]);
    }

    if String_Eq(cmd, "fetch") {
        return Cli_Fetch();
    }

    if String_Eq(cmd, "fmt") {
        var dir: String = ".";
        var checkOnly: bool = false;
        var fi: int = 2;
        while fi < argCount {
            if String_Eq(args[fi], "--check") {
                checkOnly = true;
            } else {
                dir = args[fi];
            }
            fi = fi + 1;
        }
        return Cli_Fmt(dir, checkOnly);
    }

    if String_Eq(cmd, "doc") {
        var dir: String = "lib";
        var outPath: String = "";
        var di: int = 2;
        while di < argCount {
            if String_Eq(args[di], "--out") && di + 1 < argCount {
                outPath = args[di + 1];
                di = di + 1;
            } else if String_StartsWith(args[di], "--out=") {
                outPath = bux_str_slice(args[di], 6, bux_strlen(args[di]) - 6);
            } else if !String_StartsWith(args[di], "-") {
                dir = args[di];
            }
            di = di + 1;
        }
        return Cli_Doc(dir, outPath);
    }

    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, targetTriple, isRelease);
    }

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

    if String_Eq(cmd, "test") {
        var dir: String = ".";
        var filter: String = "";
        var ti: int = 2;
        while ti < argCount {
            if String_Eq(args[ti], "--filter") && ti + 1 < argCount {
                filter = args[ti + 1];
                ti = ti + 1;
            } else if String_StartsWith(args[ti], "--filter=") {
                // --filter=name form
                filter = bux_str_slice(args[ti], 9, bux_strlen(args[ti]) - 9);
            } else if !String_StartsWith(args[ti], "-") {
                dir = args[ti];
            }
            ti = ti + 1;
        }
        return Cli_Test(dir, filter);
    }

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

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