// nim_backend.bux — Nim transpiler backend
// Generates Nim code from the HIR. Much simpler than C backend.

import Std::String;
import Std::Io::{Print, PrintLine};

module NimBackend {

// ---------------------------------------------------------------------------
// Type → Nim type name
// ---------------------------------------------------------------------------

func NimBackend_TypeToNim(kind: int) -> String {
    if kind == tyVoid { return "void"; }
    if kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32 { return "bool"; }
    if kind == tyChar8 { return "char"; }
    if kind == tyChar16 { return "uint16"; }
    if kind == tyChar32 { return "uint32"; }
    if kind == tyStr { return "string"; }
    if kind == tyInt8 { return "int8"; }
    if kind == tyInt16 { return "int16"; }
    if kind == tyInt32 { return "int32"; }
    if kind == tyInt64 { return "int64"; }
    if kind == tyInt { return "int"; }
    if kind == tyUInt8 { return "uint8"; }
    if kind == tyUInt16 { return "uint16"; }
    if kind == tyUInt32 { return "uint32"; }
    if kind == tyUInt64 { return "uint64"; }
    if kind == tyUInt { return "uint"; }
    if kind == tyFloat32 { return "float32"; }
    if kind == tyFloat64 { return "float64"; }
    if kind == tyPointer { return "pointer"; }
    return "int";
}

// ---------------------------------------------------------------------------
// Operator → Nim
// ---------------------------------------------------------------------------

func NimBackend_OpToNim(op: int) -> String {
    if op == tkPlus { return "+"; }
    if op == tkMinus { return "-"; }
    if op == tkStar { return "*"; }
    if op == tkSlash { return "/"; }
    if op == tkPercent { return "%"; }
    if op == tkEq { return "=="; }
    if op == tkNe { return "!="; }
    if op == tkLt { return "<"; }
    if op == tkLe { return "<="; }
    if op == tkGt { return ">"; }
    if op == tkGe { return ">="; }
    if op == tkAmpAmp { return "and"; }
    if op == tkPipePipe { return "or"; }
    if op == tkBang { return "not"; }
    if op == tkAmp { return "and"; }
    if op == tkPipe { return "or"; }
    if op == tkCaret { return "xor"; }
    if op == tkShl { return "shl"; }
    if op == tkShr { return "shr"; }
    if op == tkAssign { return "="; }
    return "?";
}

// ---------------------------------------------------------------------------
// Emitter
// ---------------------------------------------------------------------------

struct NBEmitter {
    sb: StringBuilder;
    indent: int;
}

func NBE_Init() -> NBEmitter {
    var sb: StringBuilder = StringBuilder_NewCap(4096);
    return NBEmitter { sb: sb, indent: 0 };
}

func NBE_EmitIndent(nbe: *NBEmitter) {
    var i: int = 0;
    while i < nbe.indent {
        StringBuilder_Append(&nbe.sb, "    ");
        i = i + 1;
    }
}

func NBE_Emit(nbe: *NBEmitter, text: String) {
    NBE_EmitIndent(nbe);
    StringBuilder_Append(&nbe.sb, text);
}

func NBE_EmitLine(nbe: *NBEmitter, text: String) {
    NBE_Emit(nbe, text);
    StringBuilder_Append(&nbe.sb, "\n");
}

func NBE_Build(nbe: *NBEmitter) -> String {
    return StringBuilder_Build(&nbe.sb);
}

func NBE_Append(nbe: *NBEmitter, text: String) {
    StringBuilder_Append(&nbe.sb, text);
}

func NBE_AppendLine(nbe: *NBEmitter, text: String) {
    StringBuilder_Append(&nbe.sb, text);
    StringBuilder_Append(&nbe.sb, "\n");
}

func NBE_AppendChar(nbe: *NBEmitter, c: char8) {
    var tmp: *char8 = bux_alloc(2) as *char8;
    tmp[0] = c;
    tmp[1] = 0 as char8;
    StringBuilder_Append(&nbe.sb, tmp);
}

// ---------------------------------------------------------------------------
// Expression emission
// ---------------------------------------------------------------------------

func NBE_EmitExpr(nbe: *NBEmitter, node: *HirNode) {
    if node == null as *HirNode { return; }
    let kind: int = node.kind;

    // Literal
    if kind == hLit {
        if node.intValue == tkNull {
            NBE_Append(nbe, "nil");
        } else if node.intValue == tkStringLiteral {
            // Nim string: emit quoted with escapes
            NBE_Append(nbe, "\"");
            var s: String = node.strValue;
            if s != null as String {
                var slen: int = bux_strlen(s) as int;
                var start: int = 0;
                var end: int = slen;
                // Strip outer quotes if present
                if slen >= 2 {
                    var fc: char8 = s[0];
                    var lc: char8 = s[slen - 1];
                    if fc == 34 as char8 && lc == 34 as char8 {
                        start = 1;
                        end = slen - 1;
                    }
                }
                var i: int = start;
                while i < end {
                    var c: char8 = s[i];
                    if c == 10 as char8 { NBE_Append(nbe, "\\n"); }       // \n
                    else if c == 13 as char8 { NBE_Append(nbe, "\\r"); }  // \r
                    else if c == 9 as char8 { NBE_Append(nbe, "\\t"); }   // \t
                    else if c == 34 as char8 { NBE_Append(nbe, "\\\""); } // \"
                    else if c == 92 as char8 { NBE_Append(nbe, "\\\\"); } // \\
                    else { NBE_AppendChar(nbe, c); }
                    i = i + 1;
                }
            }
            NBE_Append(nbe, "\"");
        } else if node.intValue == tkBoolLiteral {
            NBE_Append(nbe, node.strValue);
        } else {
            NBE_Append(nbe, node.strValue);
        }
        return;
    }

    // Variable
    if kind == hVar {
        NBE_Append(nbe, NimBackend_EscapeIdent(node.strValue));
        return;
    }

    // Binary
    if kind == hBinary {
        // Special case: String == null → check for empty string
        if node.intValue == tkEq || node.intValue == tkNe {
            var isNullCmp: bool = false;
            var other: *HirNode = null as *HirNode;
            if node.child1 != null as *HirNode && node.child1.kind == hLit && node.child1.intValue == tkNull {
                isNullCmp = true;
                other = node.child2;
            }
            if !isNullCmp && node.child2 != null as *HirNode && node.child2.kind == hLit && node.child2.intValue == tkNull {
                isNullCmp = true;
                other = node.child1;
            }
            if isNullCmp && other != null as *HirNode && other.typeKind == tyStr {
                if node.intValue == tkEq {
                    NBE_EmitExpr(nbe, other);
                    NBE_Append(nbe, " == \"\"");
                } else {
                    NBE_EmitExpr(nbe, other);
                    NBE_Append(nbe, " != \"\"");
                }
                return;
            }
        }
        if node.child1 != null as *HirNode {
            NBE_EmitExpr(nbe, node.child1);
            NBE_Append(nbe, " ");
        }
        // Emit operator only if we have a left operand
        if node.child1 != null as *HirNode {
            NBE_Append(nbe, NimBackend_OpToNim(node.intValue));
            NBE_Append(nbe, " ");
        }
        NBE_EmitExpr(nbe, node.child2);
        return;
    }

    // Unary
    if kind == hUnary {
        // Skip address-of operator (&) — Nim uses automatic dereference
        if node.intValue == tkAmp {
            NBE_EmitExpr(nbe, node.child1);
            return;
        }
        NBE_Append(nbe, NimBackend_OpToNim(node.intValue));
        NBE_Append(nbe, "(");
        NBE_EmitExpr(nbe, node.child1);
        NBE_Append(nbe, ")");
        return;
    }

    // Call
    if kind == hCall {
        var fname: String = node.strValue;
        // Translate StringBuilder calls to Nim native operations
        if String_Eq(fname, "StringBuilder_Append") {
            // sb = sb & text (string concatenation)
            NBE_EmitExpr(nbe, node.child1);
            NBE_Append(nbe, " = ");
            NBE_EmitExpr(nbe, node.child1);
            NBE_Append(nbe, " & ");
            NBE_EmitExpr(nbe, node.child2);
            return;
        }
        if String_Eq(fname, "StringBuilder_NewCap") {
            // Just empty string
            NBE_Append(nbe, "\"\"");
            return;
        }
        if String_Eq(fname, "StringBuilder_Build") {
            // Just return the string field
            NBE_EmitExpr(nbe, node.child1);
            return;
        }
        NBE_Append(nbe, NimBackend_EscapeIdent(fname));
        NBE_Append(nbe, "(");
        if node.child1 != null as *HirNode {
            NBE_EmitExpr(nbe, node.child1);
        }
        if node.child2 != null as *HirNode {
            NBE_Append(nbe, ", ");
            NBE_EmitExpr(nbe, node.child2);
        }
        NBE_Append(nbe, ")");
        return;
    }

    // Field access: obj.field → obj.field (Nim auto-dereferences)
    if kind == hFieldPtr || kind == hArrowField {
        NBE_EmitExpr(nbe, node.child1);
        NBE_Append(nbe, ".");
        NBE_Append(nbe, node.strValue);
        return;
    }

    // Index: arr[idx]
    if kind == hIndexPtr {
        NBE_EmitExpr(nbe, node.child1);
        NBE_Append(nbe, "[");
        NBE_EmitExpr(nbe, node.child2);
        NBE_Append(nbe, "]");
        return;
    }

    // Cast: expr as Type
    if kind == hCast {
        // Special case: null as Type → appropriate default
        if node.child1 != null as *HirNode && node.child1.kind == hLit && node.child1.intValue == tkNull {
            var typeName: String = node.typeName;
            if !String_Eq(typeName, "") {
                var converted: String = NimBackend_ConvertTypeName(typeName);
                if String_Eq(converted, "string") {
                    NBE_Append(nbe, "\"\"");
                    return;
                }
                if String_Eq(converted, "pointer") {
                    NBE_Append(nbe, "nil");
                    return;
                }
            }
            // For pointer types (ending in * or "ptr "), emit nil
            var tlen: int = bux_strlen(typeName) as int;
            if tlen > 1 {
                var lastCh: char8 = typeName[tlen - 1];
                if lastCh == 42 as char8 { // '*'
                    NBE_Append(nbe, "nil");
                    return;
                }
            }
        }
        NBE_Append(nbe, "cast[");
        if !String_Eq(node.typeName, "") {
            NBE_Append(nbe, NimBackend_ConvertTypeName(node.typeName));
        } else {
            NBE_Append(nbe, NimBackend_TypeToNim(node.typeKind));
        }
        NBE_Append(nbe, "](");
        NBE_EmitExpr(nbe, node.child1);
        NBE_Append(nbe, ")");
        return;
    }

    // Is
    if kind == hIs {
        NBE_EmitExpr(nbe, node.child1);
        NBE_Append(nbe, " is ");
        if !String_Eq(node.typeName, "") {
            NBE_Append(nbe, node.typeName);
        }
        return;
    }

    // SizeOf
    if kind == hSizeOf {
        NBE_Append(nbe, "sizeof(");
        if !String_Eq(node.typeName, "") {
            NBE_Append(nbe, node.typeName);
        } else {
            NBE_Append(nbe, "int");
        }
        NBE_Append(nbe, ")");
        return;
    }

    // Alloca: variable declaration (used within Store for full decl)
    if kind == hAlloca {
        if !String_Eq(node.typeName, "") {
            NBE_Append(nbe, NimBackend_ConvertTypeNameParam(node.typeName));
        } else {
            NBE_Append(nbe, NimBackend_TypeToNim(node.typeKind));
        }
        NBE_Append(nbe, " ");
        NBE_Append(nbe, NimBackend_EscapeIdent(node.strValue));
        return;
    }

    // Assign: target = value
    if kind == hAssign {
        NBE_EmitExpr(nbe, node.child1);
        NBE_Append(nbe, " = ");
        NBE_EmitExpr(nbe, node.child2);
        return;
    }

    // Store: assignment or declaration with init
    if kind == hStore {
        if node.child1 != null as *HirNode && node.child1.kind == hAlloca {
            // Declaration: var x: Type = value
            NBE_Append(nbe, "var ");
            NBE_Append(nbe, NimBackend_EscapeIdent(node.child1.strValue));
            if !String_Eq(node.child1.typeName, "") {
                NBE_Append(nbe, ": ");
                NBE_Append(nbe, NimBackend_ConvertTypeNameParam(node.child1.typeName));
            }
            if node.child2 != null as *HirNode {
                NBE_Append(nbe, " = ");
                NBE_EmitExpr(nbe, node.child2);
            }
            return;
        }
        // Plain assignment
        NBE_EmitExpr(nbe, node.child1);
        NBE_Append(nbe, " = ");
        NBE_EmitExpr(nbe, node.child2);
        return;
    }

    // If
    if kind == hIf {
        NBE_Append(nbe, "if ");
        NBE_EmitExpr(nbe, node.child1);
        NBE_AppendLine(nbe, ":");
        if node.child2 != null as *HirNode {
            nbe.indent = nbe.indent + 1;
            if node.child2.kind != hBlock { NBE_EmitIndent(nbe); }
            NBE_EmitExpr(nbe, node.child2);
            nbe.indent = nbe.indent - 1;
        } else {
            nbe.indent = nbe.indent + 1;
            NBE_EmitLine(nbe, "discard");
            nbe.indent = nbe.indent - 1;
        }
        if node.child3 != null as *HirNode {
            NBE_EmitIndent(nbe);  // indent else at same level as if
            NBE_AppendLine(nbe, "else:");
            nbe.indent = nbe.indent + 1;
            if node.child3.kind != hBlock { NBE_EmitIndent(nbe); }
            NBE_EmitExpr(nbe, node.child3);
            nbe.indent = nbe.indent - 1;
        }
        return;
    }

    // While
    if kind == hWhile {
        NBE_Append(nbe, "while ");
        NBE_EmitExpr(nbe, node.child1);
        NBE_AppendLine(nbe, ":");
        if node.child2 != null as *HirNode {
            nbe.indent = nbe.indent + 1;
            if node.child2.kind != hBlock { NBE_EmitIndent(nbe); }
            NBE_EmitExpr(nbe, node.child2);
            nbe.indent = nbe.indent - 1;
        }
        return;
    }

    // Loop (infinite)
    if kind == hLoop {
        NBE_AppendLine(nbe, "while true:");
        if node.child1 != null as *HirNode {
            nbe.indent = nbe.indent + 1;
            if node.child1.kind != hBlock { NBE_EmitIndent(nbe); }
            NBE_EmitExpr(nbe, node.child1);
            nbe.indent = nbe.indent - 1;
        }
        return;
    }

    // Break / Continue
    if kind == hBreak {
        NBE_AppendLine(nbe, "break");
        return;
    }
    if kind == hContinue {
        NBE_AppendLine(nbe, "continue");
        return;
    }

    // Return
    if kind == hReturn {
        NBE_Append(nbe, "return ");
        if node.child1 != null as *HirNode {
            if node.child1.kind == hBinary {
                NBE_Append(nbe, "(");
                NBE_EmitExpr(nbe, node.child1);
                NBE_Append(nbe, ")");
            } else {
                NBE_EmitExpr(nbe, node.child1);
            }
        }
        return;
    }

    // Struct init: TypeName { field: value, ... }
    if kind == hStructInit {
        NBE_Append(nbe, node.strValue);
        NBE_Append(nbe, "(");
        // Emit fields (chained via child3)
        var field: *HirNode = node.child3;
        var first: bool = true;
        while field != null as *HirNode {
            if !first { NBE_Append(nbe, ", "); }
            NBE_Append(nbe, field.strValue);
            NBE_Append(nbe, ": ");
            NBE_EmitExpr(nbe, field.child1);
            first = false;
            field = field.child3;
        }
        NBE_Append(nbe, ")");
        return;
    }

    // Load: dereference — in Nim just emit the pointer expression
    if kind == hLoad {
        NBE_EmitExpr(nbe, node.child1);
        return;
    }

    // Block — sequence of statements
    if kind == hBlock {
        var child: *HirNode = node.child1;
        if child == null as *HirNode {
            NBE_EmitIndent(nbe);
            NBE_AppendLine(nbe, "discard");
            return;
        }
        // First pass: emit statements, merging continuation lines
        while child != null as *HirNode {
            NBE_EmitIndent(nbe);
            // Check if this is a return/binary that has continuation siblings
            if child.kind == hReturn && child.child1 != null as *HirNode {
                // Emit return and open paren before expression
                NBE_Append(nbe, "return (");
                NBE_EmitExpr(nbe, child.child1);
                child = child.child3;
                // Merge all continuation siblings
                while child != null as *HirNode && child.kind == hBinary && child.child1 == null as *HirNode {
                    NBE_Append(nbe, " ");
                    NBE_EmitExpr(nbe, child);
                    child = child.child3;
                }
                NBE_AppendLine(nbe, ")");
            } else {
                // Wrap with discard for non-control-flow statements
                if child.kind == hCall || child.kind == hAssign || child.kind == hStore {
                    NBE_Append(nbe, "discard (");
                    NBE_EmitExpr(nbe, child);
                    NBE_Append(nbe, ")");
                } else {
                    NBE_EmitExpr(nbe, child);
                }
                child = child.child3;
                // Merge continuation siblings for other statement types
                while child != null as *HirNode && child.kind == hBinary && child.child1 == null as *HirNode {
                    NBE_Append(nbe, " ");
                    NBE_EmitExpr(nbe, child);
                    child = child.child3;
                }
                NBE_AppendLine(nbe, "");
            }
        }
        return;
    }

    // Match — simplified
    if kind == hMatch {
        NBE_EmitLine(nbe, "# match (simplified)");
        return;
    }

    // Spawn
    if kind == hSpawn {
        NBE_Append(nbe, "spawn ");
        NBE_Append(nbe, node.strValue);
        NBE_Append(nbe, "(");
        if node.child1 != null as *HirNode {
            NBE_EmitExpr(nbe, node.child1);
        }
        NBE_Append(nbe, ")");
        return;
    }

    // Await
    if kind == hAwait {
        NBE_Append(nbe, "await ");
        NBE_EmitExpr(nbe, node.child1);
        return;
    }

    // Fallback
    NBE_Append(nbe, "nil");
}

// ---------------------------------------------------------------------------
// Function declaration emission
// ---------------------------------------------------------------------------

func NBE_EmitFuncDecl(nbe: *NBEmitter, f: *HirFunc) {
    // Return type
    var retType: String = f.retTypeName;
    if String_Eq(retType, "") || String_Eq(retType, "void") {
        // No return type annotation needed
    } else if String_Eq(retType, "int") {
        NBE_Append(nbe, "int");
    } else if String_Eq(retType, "bool") {
        NBE_Append(nbe, "bool");
    } else if String_Eq(retType, "string") || String_Eq(retType, "String") {
        NBE_Append(nbe, "string");
    } else {
        NBE_Append(nbe, retType);
    }
}

// ---------------------------------------------------------------------------
// Convert type name from Bux to Nim
// ---------------------------------------------------------------------------

func NimBackend_EscapeIdent(name: String) -> String {
    if name == null as String || String_Eq(name, "") { return "x"; }
    // Nim keywords that need escaping
    if String_Eq(name, "block") { return "`block`"; }
    if String_Eq(name, "type") { return "`type`"; }
    if String_Eq(name, "object") { return "`object`"; }
    if String_Eq(name, "proc") { return "`proc`"; }
    if String_Eq(name, "var") { return "`var`"; }
    if String_Eq(name, "let") { return "`let`"; }
    if String_Eq(name, "const") { return "`const`"; }
    if String_Eq(name, "from") { return "`from`"; }
    if String_Eq(name, "import") { return "`import`"; }
    if String_Eq(name, "export") { return "`export`"; }
    if String_Eq(name, "include") { return "`include`"; }
    if String_Eq(name, "iterator") { return "`iterator`"; }
    if String_Eq(name, "method") { return "`method`"; }
    if String_Eq(name, "func") { return "`func`"; }
    if String_Eq(name, "string") { return "`string`"; }
    if String_Eq(name, "int") { return "`int`"; }
    if String_Eq(name, "bool") { return "`bool`"; }
    if String_Eq(name, "float") { return "`float`"; }
    if String_Eq(name, "char") { return "`char`"; }
    if String_Eq(name, "ptr") { return "`ptr`"; }
    if String_Eq(name, "ref") { return "`ref`"; }
    if String_Eq(name, "nil") { return "`nil`"; }
    if String_Eq(name, "true") { return "`true`"; }
    if String_Eq(name, "false") { return "`false`"; }
    if String_Eq(name, "and") { return "`and`"; }
    if String_Eq(name, "or") { return "`or`"; }
    if String_Eq(name, "not") { return "`not`"; }
    if String_Eq(name, "xor") { return "`xor`"; }
    if String_Eq(name, "shl") { return "`shl`"; }
    if String_Eq(name, "shr") { return "`shr`"; }
    if String_Eq(name, "div") { return "`div`"; }
    if String_Eq(name, "mod") { return "`mod`"; }
    if String_Eq(name, "is") { return "`is`"; }
    if String_Eq(name, "of") { return "`of`"; }
    if String_Eq(name, "in") { return "`in`"; }
    if String_Eq(name, "out") { return "`out`"; }
    if String_Eq(name, "static") { return "`static`"; }
    if String_Eq(name, "enum") { return "`enum`"; }
    if String_Eq(name, "tuple") { return "`tuple`"; }
    if String_Eq(name, "concept") { return "`concept`"; }
    if String_Eq(name, "distinct") { return "`distinct`"; }
    if String_Eq(name, "Result") { return "`Result`"; }
    return name;
}

func NimBackend_ConvertTypeNameParam(tn: String) -> String {
    // For function parameters: strip pointer indirection, Nim passes by ref
    if tn == null as String || String_Eq(tn, "") { return "int"; }
    if String_Eq(tn, "String") { return "string"; }
    if String_Eq(tn, "bool") { return "bool"; }
    if String_Eq(tn, "int") { return "int"; }
    if String_Eq(tn, "int64") { return "int64"; }
    if String_Eq(tn, "uint32") { return "uint32"; }
    if String_Eq(tn, "uint") { return "uint"; }
    if String_Eq(tn, "float64") { return "float64"; }
    // Pointer types → ptr types in Nim
    if !String_Eq(tn, "") {
        var tlen: int = bux_strlen(tn) as int;
        if tlen > 1 {
            var lastCh: char8 = tn[tlen - 1];
            if lastCh == 42 as char8 {
                var base: String = String_Slice(tn, 0 as uint, (tlen - 1) as uint);
                if String_Eq(base, "void") { return "pointer"; }
                return String_Concat("ptr ", base);
            }
        }
    }
    return tn;
}

func NimBackend_ConvertTypeName(tn: String) -> String {
    if tn == null as String || String_Eq(tn, "") { return "int"; }
    if String_Eq(tn, "String") { return "string"; }
    if String_Eq(tn, "bool") { return "bool"; }
    if String_Eq(tn, "int") { return "int"; }
    if String_Eq(tn, "int64") { return "int64"; }
    if String_Eq(tn, "uint32") { return "uint32"; }
    if String_Eq(tn, "uint") { return "uint"; }
    if String_Eq(tn, "float64") { return "float64"; }
    // Convert pointer types: "Foo*" → "ptr Foo"
    if !String_Eq(tn, "") {
        var tlen: int = bux_strlen(tn) as int;
        if tlen > 1 {
            var lastCh: char8 = tn[tlen - 1];
            if lastCh == 42 as char8 { // '*'
                // Extract base name (without *)
                var base: String = String_Slice(tn, 0 as uint, (tlen - 1) as uint);
                if String_Eq(base, "void") { return "pointer"; }
                return String_Concat("ptr ", base);
            }
        }
    }
    return tn;
}

// ---------------------------------------------------------------------------
// Generate complete Nim module
// ---------------------------------------------------------------------------

func NimBackend_Generate(mod: *HirModule) -> String {
    let nbe: *NBEmitter = bux_alloc(sizeof(NBEmitter)) as *NBEmitter;
    nbe.sb = StringBuilder_NewCap(8192);
    nbe.indent = 0;

    // Header
    NBE_AppendLine(nbe, "# Generated by Bux Nim Backend");
    NBE_AppendLine(nbe, "");
    NBE_AppendLine(nbe, "import std/strutils");
    NBE_AppendLine(nbe, "import std/tables");
    NBE_AppendLine(nbe, "import std/sequtils");
    NBE_AppendLine(nbe, "import std/os");
    NBE_AppendLine(nbe, "");

    // Type definitions — skip builtins that Nim already has
    NBE_AppendLine(nbe, "# Type aliases");
    NBE_AppendLine(nbe, "type");
    nbe.indent = nbe.indent + 1;
    NBE_EmitLine(nbe, "String* = string");
    NBE_EmitLine(nbe, "char8* = char");
    // StringBuild is just a string in Nim
    NBE_EmitLine(nbe, "StringBuilder* = string");
    // Close type block
    nbe.indent = nbe.indent - 1;
    NBE_AppendLine(nbe, "");

    NBE_AppendLine(nbe, "");

    // Struct definitions
    if mod.structCount > 0 {
        NBE_AppendLine(nbe, "# Struct types");
        NBE_AppendLine(nbe, "type");
        var si: int = 0;
        while si < mod.structCount {
            NBE_Append(nbe, "    ");
            NBE_Append(nbe, NimBackend_EscapeIdent(mod.structs[si].name));
            NBE_AppendLine(nbe, " = object");
            var fi: int = 0;
            while fi < mod.structs[si].fieldCount {
                var fname: String = mod.structs[si].fields[fi].name;
                if !String_Eq(fname, "") {
                    NBE_Append(nbe, "        ");
                    NBE_Append(nbe, NimBackend_EscapeIdent(fname));
                    NBE_Append(nbe, ": ");
                    var ft: String = mod.structs[si].fields[fi].typeName;
                    NBE_Append(nbe, NimBackend_ConvertTypeName(ft));
                    NBE_AppendLine(nbe, "");
                }
                fi = fi + 1;
            }
            si = si + 1;
        }
        NBE_AppendLine(nbe, "");
    }

    // Const definitions
    if mod.constCount > 0 {
        NBE_AppendLine(nbe, "# Constants");
        var ci: int = 0;
        while ci < mod.constCount {
            NBE_Emit(nbe, "const ");
            NBE_Append(nbe, mod.consts[ci].name);
            NBE_Append(nbe, " = ");
            NBE_Append(nbe, String_FromInt(mod.consts[ci].value as int64));
            NBE_AppendLine(nbe, "");
            ci = ci + 1;
        }
        NBE_AppendLine(nbe, "");
    }

    // Extern declarations — skip (Nim doesn't need them)
    // Bux extern functions are C runtime functions that Nim can't directly call.
    // Instead, we use Nim native equivalents (e.g., len() instead of bux_strlen).

    // Function definitions (reverse order: dependencies first)
    var i: int = mod.funcCount;
    while i > 0 {
        i = i - 1;
        let f: *HirFunc = &mod.funcs[i];
        if f.body == null as *HirNode { continue; }

        NBE_Emit(nbe, "proc ");
        NBE_Append(nbe, NimBackend_EscapeIdent(f.name));
        NBE_Append(nbe, "(");

        // Parameters
        var pi: int = 0;
        var pnames: *HirParam = &f.param0;
        while pi < f.paramCount {
            if pi > 0 { NBE_Append(nbe, ", "); }
            NBE_Append(nbe, NimBackend_EscapeIdent(pnames[pi].name));
            NBE_Append(nbe, ": ");
            var pt: String = pnames[pi].typeName;
            NBE_Append(nbe, NimBackend_ConvertTypeNameParam(pt));
            pi = pi + 1;
        }
        NBE_Append(nbe, "): ");
        // Return type
        var rtn: String = NimBackend_ConvertTypeNameParam(f.retTypeName);
        if String_Eq(rtn, "") || String_Eq(rtn, "void") {
            NBE_AppendLine(nbe, "void =");
        } else {
            NBE_Append(nbe, rtn);
            NBE_AppendLine(nbe, " =");
        }

        // Body
        nbe.indent = nbe.indent + 1;
        if f.body != null as *HirNode && f.body.kind != hBlock {
            NBE_EmitIndent(nbe);
        }
        NBE_EmitExpr(nbe, f.body);
        nbe.indent = nbe.indent - 1;
        NBE_AppendLine(nbe, "");
        NBE_AppendLine(nbe, "");
    }

    // Main entry point
    NBE_AppendLine(nbe, "# Entry point");
    NBE_AppendLine(nbe, "when isMainModule:");
    NBE_EmitLine(nbe, "    var args: seq[string] = @[]");
    NBE_EmitLine(nbe, "    var argCount = paramCount()");
    NBE_EmitLine(nbe, "    discard Main()");
    NBE_EmitLine(nbe, "");

    let result: String = NBE_Build(nbe);
    return result;
}

} // module NimBackend
