## LIR → C Backend
## Emits clean, well-structured C code from LIR instructions.
## Since LIR is already linear and low-level, C emission is straightforward.

import std/[strutils, strformat, tables, sequtils]
import lir, hir, types, token

type
  LirCBackend* = object
    output*: string
    indent*: int
    tempTypes*: Table[string, string]  ## Track C types of temp variables

proc initLirCBackend*(): LirCBackend =
  result = LirCBackend(
    indent: 0,
    tempTypes: initTable[string, string](),
  )

proc emit(be: var LirCBackend, s: string) =
  be.output.add(s)

proc emitIndent(be: var LirCBackend) =
  for i in 0 ..< be.indent:
    be.output.add("    ")

proc emitLine(be: var LirCBackend, s: string) =
  be.emitIndent()
  be.output.add(s)
  be.output.add("\n")

proc valToC(be: var LirCBackend, v: LirValue): string =
  ## Convert a LirValue to its C representation.
  case v.kind
  of lvkVoid: ""
  of lvkTemp: v.strVal
  of lvkVar: v.strVal
  of lvkInt: $v.intVal
  of lvkFloat: $v.floatVal
  of lvkString: v.strVal
  of lvkGlobal: v.strVal
  of lvkLabel: v.strVal
  of lvkField: v.strVal
  of lvkType: v.strVal

proc typeFromValue(be: var LirCBackend, v: LirValue): string =
  ## Infer a C type for a value. Temps are tracked; named vars use lookup.
  case v.kind
  of lvkTemp:
    if be.tempTypes.hasKey(v.strVal):
      return be.tempTypes[v.strVal]
    return "int"  # Default
  of lvkString: return "const char*"
  of lvkInt: return "int"
  of lvkFloat: return "double"
  else: return ""

proc setTempType(be: var LirCBackend, temp: string, cType: string) =
  be.tempTypes[temp] = cType

proc cParamDecl(cType, name: string): string =
  ## Emit a C parameter declaration, handling function-pointer syntax.
  if cType.contains("(*)"):
    return cType.replace("(*)", "(*" & name & ")")
  else:
    return cType & " " & name

# ── Per-instruction emission ──

proc emitInstr(be: var LirCBackend, instr: LirInstr) =
  template v(x: LirValue): string = valToC(be, x)
  case instr.kind

  # ── Data movement ──
  of lirMov:
    be.emitLine(&"{v(instr.dst)} = {v(instr.src)};")

  of lirLoad:
    # dst = *(base + offset)  or  dst = base->src2 (if src2 is a field name)
    if instr.src2.kind == lvkField:
      be.emitLine(&"{v(instr.dst)} = {v(instr.src)}.{v(instr.src2)};")
    elif instr.src2.kind == lvkInt and instr.src2.intVal == 0:
      be.emitLine(&"{v(instr.dst)} = *{v(instr.src)};")
    elif instr.src2.kind == lvkTemp or instr.src2.kind == lvkVar:
      be.emitLine(&"{v(instr.dst)} = {v(instr.src)}[{v(instr.src2)}];")
    else:
      be.emitLine(&"{v(instr.dst)} = {v(instr.src)}[{v(instr.src2)}];")

  of lirStore:
    # *(base + offset) = src
    if instr.src2.kind == lvkField:
      be.emitLine(&"{v(instr.src2)}.{v(instr.dst)} = {v(instr.src)};")
    elif instr.dst.kind == lvkInt and instr.dst.intVal == 0:
      be.emitLine(&"*{v(instr.src2)} = {v(instr.src)};")
    elif instr.src2.kind == lvkTemp or instr.src2.kind == lvkVar:
      be.emitLine(&"{v(instr.src2)}[{v(instr.dst)}] = {v(instr.src)};")
    else:
      be.emitLine(&"*({v(instr.src2)} + {v(instr.dst)}) = {v(instr.src)};")

  of lirLoadGlobal:
    be.emitLine(&"{v(instr.dst)} = {v(instr.src)};")

  # ── Arithmetic ──
  of lirAdd, lirSub, lirMul, lirDiv, lirMod,
     lirAnd, lirOr, lirXor, lirShl, lirShr:
    let op = case instr.kind
      of lirAdd: "+"
      of lirSub: "-"
      of lirMul: "*"
      of lirDiv: "/"
      of lirMod: "%"
      of lirAnd: "&"
      of lirOr: "|"
      of lirXor: "^"
      of lirShl: "<<"
      of lirShr: ">>"
      else: "?"
    be.emitLine(&"{v(instr.dst)} = {v(instr.src)} {op} {v(instr.src2)};")

  of lirNeg:
    be.emitLine(&"{v(instr.dst)} = -{v(instr.src)};")
  of lirNot:
    be.emitLine(&"{v(instr.dst)} = !{v(instr.src)};")
  of lirBNot:
    be.emitLine(&"{v(instr.dst)} = ~{v(instr.src)};")

  # ── Comparison ──
  of lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe:
    let op = case instr.kind
      of lirCmpEq: "=="
      of lirCmpNe: "!="
      of lirCmpLt: "<"
      of lirCmpLe: "<="
      of lirCmpGt: ">"
      of lirCmpGe: ">="
      else: "=="
    be.emitLine(&"{v(instr.dst)} = ({v(instr.src)} {op} {v(instr.src2)});")

  # ── Control flow ──
  of lirLabel:
    be.emitLine(&"{v(instr.src)}:;")  # C requires statement after label
    # Add a null statement to avoid "label at end of compound statement" warnings
    # Handled by the next instruction naturally

  of lirJmp:
    be.emitLine(&"goto {v(instr.src)};")

  of lirJz:
    be.emitLine(&"if (!{v(instr.src2)}) goto {v(instr.src)};")

  of lirJnz:
    be.emitLine(&"if ({v(instr.src2)}) goto {v(instr.src)};")

  # ── Calls ──
  of lirCall:
    var argsStr = ""
    for i, arg in instr.extra:
      if i > 0: argsStr.add(", ")
      argsStr.add(v(arg))
    be.emitLine(&"{v(instr.dst)} = {v(instr.src)}({argsStr});")

  of lirCallVoid:
    var argsStr = ""
    for i, arg in instr.extra:
      if i > 0: argsStr.add(", ")
      argsStr.add(v(arg))
    be.emitLine(&"{v(instr.src)}({argsStr});")

  of lirCallIndirect:
    var argsStr = ""
    for i, arg in instr.extra:
      if i > 0: argsStr.add(", ")
      argsStr.add(v(arg))
    if instr.dst.kind != lvkVoid:
      be.emitLine(&"{v(instr.dst)} = ({v(instr.src)})({argsStr});")
    else:
      be.emitLine(&"({v(instr.src)})({argsStr});")

  # ── Return ──
  of lirRet:
    if instr.src.kind != lvkVoid:
      be.emitLine(&"return {v(instr.src)};")
    else:
      be.emitLine("return;")

  # ── Alloca ──
  of lirAlloca:
    var ct = v(instr.src)
    if instr.dst.strVal.len > 0 and be.tempTypes.hasKey(instr.dst.strVal):
      let inferred = be.tempTypes[instr.dst.strVal]
      if inferred != "" and inferred != ct:
        ct = inferred
    be.emitLine(cParamDecl(ct, v(instr.dst)) & ";")

  # ── Pointers ──
  of lirAddrOf:
    be.emitLine(&"{v(instr.dst)} = &{v(instr.src)};")

  of lirFieldPtr:
    be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}.{v(instr.src2)});")

  of lirArrowFieldPtr:
    be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}->{v(instr.src2)});")

  of lirIndexPtr:
    be.emitLine(&"{v(instr.dst)} = &({v(instr.src)}[{v(instr.src2)}]);")

  of lirPtrAdd:
    be.emitLine(&"{v(instr.dst)} = ({v(instr.src)} + {v(instr.src2)});")

  # ── Cast ──
  of lirCast:
    be.emitLine(&"{v(instr.dst)} = ({v(instr.src2)}){v(instr.src)};")

  # ── StructInit ──
  of lirStructInit:
    let structType = v(instr.extra[0])
    var fieldPairs = ""
    var i = 1
    while i < instr.extra.len:
      let fieldName = v(instr.extra[i])     # e.g. "width"
      let fieldVal = v(instr.extra[i + 1])  # e.g. "10"
      if i > 1: fieldPairs.add(", ")
      fieldPairs.add(&".{fieldName} = {fieldVal}")
      i += 2
    be.emitLine(&"{v(instr.dst)} = ({structType}){{{fieldPairs}}};")

  # ── SliceInit ──
  of lirSliceInit:
    let elemType = v(instr.extra[0])
    be.emitLine(&"{v(instr.dst)} = (Slice_{elemType}){{.data = ({elemType}*){v(instr.src)}, .len = {v(instr.src2)}}};")

  # ── Select (ternary) ──
  of lirSelect:
    let elseVal = if instr.extra.len > 0: v(instr.extra[0]) else: "0"
    be.emitLine(&"{v(instr.dst)} = ({v(instr.src)}) ? {v(instr.src2)} : {elseVal};")

  # ── Raw C ──
  of lirRawC:
    let code = v(instr.src)
    if code.len > 0:
      be.emitLine(code)

  # ── Comment ──
  of lirComment:
    let text = v(instr.src)
    be.emitLine(&"/* {text} */")

# ── Function emission ──

proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, string], funcPtrTypes: Table[string, string]) =
  var paramsStr = ""
  for i, p in f.params:
    if i > 0: paramsStr.add(", ")
    paramsStr.add(cParamDecl(p.cType, p.name))
  if f.params.len == 0:
    paramsStr = "void"

  be.emitLine(&"{f.retType} {f.name}({paramsStr}) {{")
  be.indent += 1

  # ── Pass 1: collect types from allocas, params, and instructions ──
  var varTypes = initTable[string, string]()
  var tempsSet: seq[string] = @[]
  for p in f.params:
    varTypes[p.name] = p.cType
    be.tempTypes[p.name] = p.cType
  for instr in f.instrs:
    if instr.kind == lirAlloca and instr.dst.kind == lvkVar and instr.src.kind == lvkType:
      varTypes[instr.dst.strVal] = instr.src.strVal
      be.tempTypes[instr.dst.strVal] = instr.src.strVal
      if instr.dst.strVal notin tempsSet:
        tempsSet.add(instr.dst.strVal)

  # ── Pass 2: iterative type inference for temps ──
  var changed = true
  while changed:
    changed = false
    for instr in f.instrs:
      if instr.dst.kind != lvkTemp or instr.dst.strVal.len == 0:
        continue
      let name = instr.dst.strVal
      let oldType = if be.tempTypes.hasKey(name): be.tempTypes[name] else: ""
      var newType = oldType

      case instr.kind
      of lirStructInit:
        if instr.extra.len > 0 and instr.extra[0].kind == lvkType:
          newType = instr.extra[0].strVal
      of lirSliceInit:
        if instr.extra.len > 0 and instr.extra[0].kind == lvkType:
          newType = "Slice_" & instr.extra[0].strVal
      of lirCast:
        if instr.src2.kind == lvkType:
          newType = instr.src2.strVal
      of lirCall:
        if instr.src.kind == lvkGlobal and funcRetTypes.hasKey(instr.src.strVal):
          newType = funcRetTypes[instr.src.strVal]
      of lirCallIndirect:
        # Conservative; try to infer from dst usage in later passes
        discard
      of lirMov:
        if instr.src.kind == lvkTemp and be.tempTypes.hasKey(instr.src.strVal):
          newType = be.tempTypes[instr.src.strVal]
        elif instr.src.kind == lvkVar and varTypes.hasKey(instr.src.strVal):
          newType = varTypes[instr.src.strVal]
      of lirLoad, lirLoadGlobal:
        # Try to deduce pointee type from pointer vars/temps
        if instr.src.kind == lvkVar and varTypes.hasKey(instr.src.strVal):
          let srcType = varTypes[instr.src.strVal]
          if srcType.endsWith("*"):
            newType = srcType[0 ..< srcType.len - 1]
          elif srcType.startsWith("Slice_"):
            newType = srcType[6 ..< srcType.len]
        elif instr.src.kind == lvkTemp and be.tempTypes.hasKey(instr.src.strVal):
          let srcType = be.tempTypes[instr.src.strVal]
          if srcType.endsWith("*"):
            newType = srcType[0 ..< srcType.len - 1]
          elif srcType.startsWith("Slice_"):
            newType = srcType[6 ..< srcType.len]
      of lirSelect:
        if instr.src2.kind == lvkTemp and be.tempTypes.hasKey(instr.src2.strVal):
          newType = be.tempTypes[instr.src2.strVal]
        elif instr.extra.len > 0 and instr.extra[0].kind == lvkTemp and be.tempTypes.hasKey(instr.extra[0].strVal):
          newType = be.tempTypes[instr.extra[0].strVal]
        elif instr.src2.kind == lvkVar and varTypes.hasKey(instr.src2.strVal):
          newType = varTypes[instr.src2.strVal]
      of lirAddrOf:
        if funcPtrTypes.hasKey(instr.src.strVal):
          newType = funcPtrTypes[instr.src.strVal]
        else:
          newType = "void*";
      of lirFieldPtr, lirArrowFieldPtr, lirIndexPtr, lirPtrAdd:
        newType = "void*"
      of lirAdd, lirSub, lirMul, lirDiv, lirMod, lirNeg,
         lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe,
         lirAnd, lirOr, lirXor, lirShl, lirShr, lirNot, lirBNot:
        newType = "int"
      else:
        discard

      if newType != "" and newType != oldType:
        be.tempTypes[name] = newType
        changed = true

  # ── Pass 3: declare temps that were inferred ──
  var declared: seq[string] = @[]
  for instr in f.instrs:
    if instr.kind == lirAlloca and instr.dst.strVal.len > 0 and instr.dst.strVal notin declared:
      declared.add(instr.dst.strVal)
      continue
    if instr.dst.kind == lvkTemp and instr.dst.strVal.len > 0 and instr.dst.strVal notin declared:
      if be.tempTypes.hasKey(instr.dst.strVal):
        let ct = be.tempTypes[instr.dst.strVal]
        if ct != "":
          declared.add(instr.dst.strVal)
          be.emitLine(cParamDecl(ct, instr.dst.strVal) & ";")

  # ── Pass 4: emit instructions ──
  for instr in f.instrs:
    be.emitInstr(instr)

  be.indent -= 1
  be.emitLine("}")
  be.emitLine("")

# ── Struct/Enum emission (from HIR module) ──

proc typeToCStr(typ: Type): string =
  ## Duplicate from lir_lower for self-containedness
  if typ == nil: return "int"
  case typ.kind
  of tkVoid: return "void"
  of tkBool, tkBool8, tkBool16, tkBool32: return "bool"
  of tkChar8: return "char"
  of tkChar16: return "char16_t"
  of tkChar32: return "char32_t"
  of tkStr: return "const char*"
  of tkInt8: return "int8_t"
  of tkInt16: return "int16_t"
  of tkInt32: return "int32_t"
  of tkInt64: return "int64_t"
  of tkInt: return "int"
  of tkUInt8: return "uint8_t"
  of tkUInt16: return "uint16_t"
  of tkUInt32: return "uint32_t"
  of tkUInt64: return "uint64_t"
  of tkUInt: return "unsigned int"
  of tkFloat32: return "float"
  of tkFloat64: return "double"
  of tkPointer, tkRef, tkMutRef:
    if typ.inner.len > 0:
      return typeToCStr(typ.inner[0]) & "*"
    return "void*"
  of tkDynRef:
    return typ.name & "_FatPtr"
  of tkSlice:
    let elem = if typ.inner.len > 0: typeToCStr(typ.inner[0]) else: "void"
    return "Slice_" & elem.replace(" ", "_").replace("*", "Ptr")
  of tkNamed:
    case typ.name
    of "String", "str": return "const char*"
    of "int": return "int"
    of "int8": return "int8_t"
    of "int16": return "int16_t"
    of "int32": return "int32_t"
    of "int64": return "int64_t"
    of "uint": return "unsigned int"
    of "uint8": return "uint8_t"
    of "uint16": return "uint16_t"
    of "uint32": return "uint32_t"
    of "uint64": return "uint64_t"
    of "float32": return "float"
    of "float64": return "double"
    of "bool": return "bool"
    else: return typ.name
  of tkFunc:
    if typ.inner.len == 0: return "void (*)(void)"
    let params = typ.inner[0..^2].mapIt(typeToCStr(it)).join(", ")
    let ret = typeToCStr(typ.inner[^1])
    return ret & " (*)(" & params & ")"
  else: return "int"

proc emitStructDef(be: var LirCBackend, name: string, fields: seq[tuple[name: string, typ: Type]]) =
  be.emitLine(&"typedef struct {name} {{")
  be.indent += 1
  for f in fields:
    be.emitLine(&"{typeToCStr(f.typ)} {f.name};")
  be.indent -= 1
  be.emitLine(&"}} {name};")
  be.emitLine("")

proc emitEnumDef(be: var LirCBackend, name: string, variants: seq[HirEnumVariant]) =
  var hasData = false
  for v in variants:
    if v.fields.len > 0 or v.namedFields.len > 0:
      hasData = true
      break

  if not hasData:
    # Simple enum
    be.emitLine(&"typedef enum {{")
    be.indent += 1
    for i, v in variants:
      if i < variants.len - 1:
        be.emitLine(&"{name}_{v.name},")
      else:
        be.emitLine(&"{name}_{v.name}")
    be.indent -= 1
    be.emitLine(&"}} {name};")
    be.emitLine("")
  else:
    # Tagged union
    be.emitLine(&"typedef enum {{")
    be.indent += 1
    for i, v in variants:
      if i < variants.len - 1:
        be.emitLine(&"{name}_{v.name},")
      else:
        be.emitLine(&"{name}_{v.name}")
    be.indent -= 1
    be.emitLine(&"}} {name}_Tag;")
    be.emitLine("")

    be.emitLine(&"typedef union {{")
    be.indent += 1
    for v in variants:
      if v.fields.len > 0:
        for i, f in v.fields:
          be.emitLine(&"{typeToCStr(f)} {v.name}_{i};")
      elif v.namedFields.len > 0:
        be.emitLine(&"struct {{")
        be.indent += 1
        for nf in v.namedFields:
          be.emitLine(&"{typeToCStr(nf.typ)} {nf.name};")
        be.indent -= 1
        be.emitLine(&"}} {v.name};")
    be.indent -= 1
    be.emitLine(&"}} {name}_Data;")
    be.emitLine("")

    be.emitLine(&"typedef struct {{")
    be.indent += 1
    be.emitLine(&"{name}_Tag tag;")
    be.emitLine(&"{name}_Data data;")
    be.indent -= 1
    be.emitLine(&"}} {name};")
    be.emitLine("")

# ── Module emission ──

proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): string =
  ## Emit full C source from LIR builder + HIR module metadata.
  be.output = ""

  # Build function return type lookup table
  var funcRetTypes = initTable[string, string]()
  for f in module.funcs:
    funcRetTypes[f.name] = typeToCStr(f.retType)
  for f in module.externFuncs:
    funcRetTypes[f.name] = typeToCStr(f.retType)

  # Build function-pointer type lookup table (for address-of)
  var funcPtrTypes = initTable[string, string]()
  for f in module.funcs:
    let params = f.params.mapIt(typeToCStr(it.typ)).join(", ")
    let ret = typeToCStr(f.retType)
    funcPtrTypes[f.name] = ret & " (*)(" & params & ")"
  for f in module.externFuncs:
    let params = f.params.mapIt(typeToCStr(it.typ)).join(", ")
    let ret = typeToCStr(f.retType)
    funcPtrTypes[f.name] = ret & " (*)(" & params & ")"

  # Header
  be.emitLine("/* Generated by Bux Compiler (LIR backend) */")
  be.emitLine("#include <stdio.h>")
  be.emitLine("#include <stdlib.h>")
  be.emitLine("#include <stdint.h>")
  be.emitLine("#include <stdbool.h>")
  be.emitLine("#include <string.h>")
  be.emitLine("")

  # Forward struct declarations
  for s in module.structs:
    be.emitLine(&"typedef struct {s.name} {s.name};")
  if module.structs.len > 0:
    be.emitLine("")
  
  # Forward trait object declarations
  for iface in module.interfaces:
    if not iface.hasAssocTypes:
      be.emitLine(&"typedef struct {iface.name}_FatPtr {iface.name}_FatPtr;")
  if module.interfaces.len > 0:
    be.emitLine("")

  # Extern declarations
  if module.externFuncs.len > 0:
    be.emitLine("/* Extern function declarations */")
    for ef in module.externFuncs:
      let rt = typeToCStr(ef.retType)
      var params: seq[string] = @[]
      for p in ef.params:
        params.add(cParamDecl(typeToCStr(p.typ), p.name))
      if params.len == 0: params.add("void")
      be.emitLine(&"extern {rt} {ef.name}({params.join(\", \")});")
    be.emitLine("")

  # Constants as #define
  if module.consts.len > 0:
    be.emitLine("/* Constants */")
    for c in module.consts:
      if c.value != nil and c.value.kind == hLit:
        case c.value.litToken.kind
        of tkIntLiteral: be.emitLine(&"#define {c.name} {c.value.litToken.text}")
        of tkStringLiteral: be.emitLine(&"#define {c.name} \"{c.value.litToken.text}\"")
        of tkBoolLiteral: be.emitLine(&"#define {c.name} {c.value.litToken.text}")
        else: discard
    be.emitLine("")

  # Enum definitions
  for e in module.enums:
    be.emitEnumDef(e.name, e.variants)
  if module.enums.len > 0:
    be.emitLine("")

  # Struct definitions
  for s in module.structs:
    be.emitStructDef(s.name, s.fields)

  # Slice types (collect from functions/structs)
  # Simple: scan function params/returns for slice types
  var sliceTypes: seq[tuple[name: string, elem: string]] = @[]
  for f in module.funcs:
    for p in f.params:
      let ct = typeToCStr(p.typ)
      if ct.startsWith("Slice_"):
        let elem = ct[6 .. ^1]
        if not sliceTypes.anyIt(it.name == ct):
          sliceTypes.add((ct, elem))
  if sliceTypes.len > 0:
    for st in sliceTypes:
      be.emitLine(&"typedef struct {{ {st.elem}* data; size_t len; }} {st.name};")
    be.emitLine("")

  # Forward function declarations
  for f in module.funcs:
    let rt = typeToCStr(f.retType)
    var params: seq[string] = @[]
    for p in f.params:
      params.add(cParamDecl(typeToCStr(p.typ), p.name))
    if params.len == 0: params.add("void")
    be.emitLine(&"{rt} {f.name}({params.join(\", \")});")
  be.emitLine("")

  # VTable and fat pointer structs
  for iface in module.interfaces:
    if iface.hasAssocTypes: continue
    let iname = iface.name
    be.emitLine(&"typedef struct {iname}_VTable {{")
    be.indent += 1
    for m in iface.methods:
      var paramCTypes: seq[string] = @["void* self"]
      for i in 1 ..< m.params.len:
        paramCTypes.add(cParamDecl(typeToCStr(m.params[i]), "param"))
      let rt = typeToCStr(m.ret)
      be.emitLine(&"{rt} (*{m.name})({paramCTypes.join(\", \")});")
    be.indent -= 1
    be.emitLine(&"}} {iname}_VTable;")
    be.emitLine(&"typedef struct {iname}_FatPtr {{")
    be.indent += 1
    be.emitLine("void* data;")
    be.emitLine(&"{iname}_VTable* vtable;")
    be.indent -= 1
    be.emitLine(&"}} {iname}_FatPtr;")
    be.emitLine("")

  # VTable instances
  for vt in module.vtables:
    if vt.hasAssocTypes: continue
    let varName = vt.concreteType & "_" & vt.interfaceName & "_VTable"
    be.emitLine(&"{vt.interfaceName}_VTable {varName} = {{")
    be.indent += 1
    for m in vt.methodNames:
      be.emitLine(&".{m} = (void*){vt.concreteType}_{m},")
    be.indent -= 1
    be.emitLine("};")
    be.emitLine("")

  # Emit env structs for closures with captures
  for f in module.funcs:
    if f.captureNames.len > 0 and f.envStructName != "":
      be.emitLine(&"struct {f.envStructName} {{")
      be.indent += 1
      for i in 0 ..< f.captureNames.len:
        let capName = f.captureNames[i]
        let capType = if i < f.captureTypes.len: typeToCStr(f.captureTypes[i]) else: "int"
        be.emitLine(&"{capType} {capName};")
      be.indent -= 1
      be.emitLine("};")
      be.emitLine(&"static struct {f.envStructName} {f.envInstanceName};")
      be.emitLine("")

  # Emit all LIR functions
  for f in builder.funcs:
    be.emitFunc(f, funcRetTypes, funcPtrTypes)

  # C main wrapper
  var hasMain = false
  for f in module.funcs:
    if f.name == "Main":
      hasMain = true
      break
  if hasMain:
    be.emitLine("/* C entry point wrapper */")
    be.emitLine("extern int g_argc;")
    be.emitLine("extern char** g_argv;")
    be.emitLine("int main(int argc, char** argv) {")
    be.emitLine("    g_argc = argc;")
    be.emitLine("    g_argv = argv;")
    be.emitLine("    return Main();")
    be.emitLine("}")

  return be.output
