import std/[strformat, strutils]
import hir, types, token

type
  CBackend* = object
    output*: string
    indent*: int
    varCounter*: int
    declaredVars*: seq[string]
    sliceTypeDefs*: seq[tuple[name: string, elem: string]]  ## Generated Slice_<T> typedefs
    deferStack*: seq[seq[string]]  ## Function-level deferred statements (LIFO)

proc cEscape(s: string): string =
  ## Escape a string for use as a C string literal.
  result = ""
  for c in s:
    case c
    of '\\': result.add("\\\\")
    of '"': result.add("\\\"")
    of '\n': result.add("\\n")
    of '\r': result.add("\\r")
    of '\t': result.add("\\t")
    of '\0': result.add("\\0")
    else: result.add(c)

proc initCBackend*(): CBackend =
  result.output = ""
  result.indent = 0
  result.varCounter = 0
  result.declaredVars = @[]

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

proc emitLine(be: var CBackend, s: string) =
  for i in 0..<be.indent:
    be.output.add("    ")
  be.output.add(s)
  be.output.add("\n")

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

proc freshVar(be: var CBackend): string =
  inc be.varCounter
  result = &"__tmp_{be.varCounter}"

# ---------------------------------------------------------------------------
# Defer helpers
# ---------------------------------------------------------------------------
proc pushDeferScope(be: var CBackend) =
  be.deferStack.add(@[])

proc popDeferScope(be: var CBackend) =
  if be.deferStack.len > 0:
    be.deferStack.setLen(be.deferStack.len - 1)

proc emitCurrentDefers(be: var CBackend) =
  ## Emit all deferred statements in current scope, LIFO order
  if be.deferStack.len == 0: return
  let idx = be.deferStack.len - 1
  for i in countdown(be.deferStack[idx].len - 1, 0):
    be.emitLine(be.deferStack[idx][i])

proc clearCurrentDefers(be: var CBackend) =
  if be.deferStack.len > 0:
    let idx = be.deferStack.len - 1
    be.deferStack[idx].setLen(0)

# Type conversion: Bux Type → C type string
proc typeToC*(be: var CBackend, typ: Type): string =
  if typ == nil: return "void"
  case typ.kind
  of tkVoid: return "void"
  of tkBool: return "bool"
  of tkBool8: return "bool"
  of tkBool16: return "bool"
  of 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 typeToC(be, typ.inner[0]) & "*"
    return "void*"
  of tkDynRef:
    return typ.name & "_FatPtr"
  of tkSlice:
    let elemName = if typ.inner.len > 0: typeToC(be, typ.inner[0]) else: "void"
    let sliceName = "Slice_" & elemName.replace(" ", "_").replace("*", "Ptr")
    var alreadyDefined = false
    for d in be.sliceTypeDefs:
      if d.name == sliceName:
        alreadyDefined = true
        break
    if not alreadyDefined:
      be.sliceTypeDefs.add((name: sliceName, elem: elemName))
    return sliceName
  of tkNamed:
    # Map common Bux type names to C types
    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 tkTuple:
    if typ.inner.len == 0:
      return "Tuple_Empty"
    var parts: seq[string] = @[]
    for e in typ.inner:
      var p = typeToC(be, e)
      p = p.replace("const char*", "cstr").replace("unsigned int", "uint")
      p = p.replace(" ", "_").replace("*", "Ptr").replace("(", "").replace(")", "").replace(",", "_")
      parts.add(p)
    let tname = "Tuple_" & parts.join("_")
    # Ensure typedef is collected alongside slices
    var already = false
    for d in be.sliceTypeDefs:
      if d.name == tname:
        already = true
        break
    if not already:
      # Reuse sliceTypeDefs as a generic "extra typedef" bag: elem holds field list markup
      be.sliceTypeDefs.add((name: tname, elem: "/*tuple*/"))
    return tname
  of tkFunc:
    if typ.inner.len == 0: return "void (*)(void)"
    let params = typ.inner[0..^2].mapIt(typeToC(be, it)).join(", ")
    let ret = typeToC(be, typ.inner[^1])
    return ret & " (*)(" & params & ")"
  else:
    when defined(release):
      return "void*"
    else:
      stderr.writeLine("warning: C backend: unknown type kind " & $typ.kind & ", using void*")
      return "void*"

proc operatorToC(op: TokenKind): string =
  case op
  of tkPlus: return "+"
  of tkMinus: return "-"
  of tkStar: return "*"
  of tkSlash: return "/"
  of tkPercent: return "%"
  of tkAmp: return "&"
  of tkPipe: return "|"
  of tkCaret: return "^"
  of tkShl: return "<<"
  of tkShr: return ">>"
  of tkAmpAmp: return "&&"
  of tkPipePipe: return "||"
  of tkEq: return "=="
  of tkNe: return "!="
  of tkLt: return "<"
  of tkLe: return "<="
  of tkGt: return ">"
  of tkGe: return ">="
  of tkBang: return "!"
  of tkTilde: return "~"
  of tkPlusPlus: return "++"
  of tkMinusMinus: return "--"
  of tkAssign: return "="
  of tkPlusAssign: return "+="
  of tkMinusAssign: return "-="
  of tkStarAssign: return "*="
  of tkSlashAssign: return "/="
  of tkPercentAssign: return "%="
  of tkAmpAssign: return "&="
  of tkPipeAssign: return "|="
  of tkCaretAssign: return "^="
  of tkShlAssign: return "<<="
  of tkShrAssign: return ">>="
  else: return "?"

# Forward declaration
proc emitExpr(be: var CBackend, node: HirNode): string
proc emitStmt(be: var CBackend, node: HirNode)

proc emitExpr(be: var CBackend, node: HirNode): string =
  if node == nil: return "0"
  case node.kind
  of hLit:
    case node.litToken.kind
    of tkBoolLiteral:
      if node.litToken.text == "true": return "true"
      else: return "false"
    of tkStringLiteral:
      var text = node.litToken.text
      # Backtick raw string: strip backticks, escape content for C
      if text.len >= 2 and text[0] == '`' and text[text.len-1] == '`':
        text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
      # If text has no surrounding quotes, it's from constFoldConstDecl (already unescaped)
      elif text.len >= 2 and text[0] == '"' and text[text.len-1] == '"':
        # Strip c8" c16" c32" prefixes — in C they are just regular string literals
        if text.startsWith("c32\""):
          text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
        elif text.startsWith("c16\""):
          text = "\"" & cEscape(text[4 ..< text.len-1]) & "\""
        elif text.startsWith("c8\""):
          text = "\"" & cEscape(text[3 ..< text.len-1]) & "\""
        else:
          text = "\"" & cEscape(text[1 ..< text.len-1]) & "\""
      elif text.len >= 2 and text[0] == '"':
        # Partial quote — escape anyway
        text = "\"" & cEscape(text[1 ..< text.len]) & "\""
      else:
        # No quotes — from constFoldConstDecl, needs wrapping and escaping
        text = "\"" & cEscape(text) & "\""
      return text
    of tkNull:
      return "NULL"
    else:
      return node.litToken.text

  of hVar:
    return node.varName

  of hSelf:
    return "self"

  of hUnary:
    let operand = be.emitExpr(node.unaryOperand)
    let op = operatorToC(node.unaryOp)
    if node.unaryOp == tkStar:
      return &"(*{operand})"
    elif node.unaryOp == tkAmp:
      return &"(&{operand})"
    else:
      return &"({op}{operand})"

  of hBinary:
    let left = be.emitExpr(node.binaryLeft)
    let right = be.emitExpr(node.binaryRight)
    let op = operatorToC(node.binaryOp)
    return &"({left} {op} {right})"

  of hCall:
    var args: seq[string] = @[]
    for arg in node.callArgs:
      args.add(be.emitExpr(arg))
    let argsStr = args.join(", ")
    return &"{node.callCallee}({argsStr})"

  of hCallIndirect:
    let callee = be.emitExpr(node.callIndirectCallee)
    var args: seq[string] = @[]
    for arg in node.callIndirectArgs:
      args.add(be.emitExpr(arg))
    let argsStr = args.join(", ")
    return &"({callee})({argsStr})"

  of hLoad:
    # Optimize: load(field_ptr(base, field)) → base.field (avoids & on temporaries)
    if node.loadPtr != nil and node.loadPtr.kind == hFieldPtr:
      let base = be.emitExpr(node.loadPtr.fieldPtrBase)
      return &"({base}.{node.loadPtr.fieldName})"
    # Optimize: load(arrow_field(base, field)) → base->field
    if node.loadPtr != nil and node.loadPtr.kind == hArrowField:
      let base = be.emitExpr(node.loadPtr.arrowFieldBase)
      return &"({base}->{node.loadPtr.arrowFieldName})"
    # Optimize: load(index_ptr(base, idx)) → base[idx]
    if node.loadPtr != nil and node.loadPtr.kind == hIndexPtr:
      let base = be.emitExpr(node.loadPtr.indexPtrBase)
      let idx = be.emitExpr(node.loadPtr.indexPtrIndex)
      return &"({base}[{idx}])"
    let ptrExpr = be.emitExpr(node.loadPtr)
    return &"(*{ptrExpr})"

  of hFieldPtr:
    let base = be.emitExpr(node.fieldPtrBase)
    return &"(&({base}.{node.fieldName}))"

  of hFieldAccess:
    let base = be.emitExpr(node.fieldAccessBase)
    return &"({base}.{node.fieldAccessName})"

  of hArrowField:
    let base = be.emitExpr(node.arrowFieldBase)
    return &"(&({base}->{node.arrowFieldName}))"

  of hIndexPtr:
    let base = be.emitExpr(node.indexPtrBase)
    let idx = be.emitExpr(node.indexPtrIndex)
    return &"(&({base}[{idx}]))"

  of hStructInit:
    # C99 compound literal: (StructName){.field1 = val1, .field2 = val2}
    var fields: seq[string] = @[]
    for f in node.structInitFields:
      let val = be.emitExpr(f.value)
      fields.add(&".{f.name} = {val}")
    let fieldsStr = fields.join(", ")
    return &"(({node.structInitName}){{{fieldsStr}}})"

  of hSliceInit:
    let sliceName = typeToC(be, node.typ)
    var elems: seq[string] = @[]
    for e in node.sliceInitElements:
      elems.add(be.emitExpr(e))
    let elemsStr = elems.join(", ")
    let elemType = if node.typ.inner.len > 0: typeToC(be, node.typ.inner[0]) else: "void"
    return &"({sliceName}){{.data = ({elemType}[]){{{elemsStr}}}, .len = {node.sliceInitLen}}}"
  of hSliceIndex:
    let base = be.emitExpr(node.sliceIndexBase)
    let idx = be.emitExpr(node.sliceIndexIndex)
    if node.sliceIndexBoundsCheck:
      return &"(bux_bounds_check((size_t)({idx}), ({base}).len), ({base}).data[{idx}])"
    else:
      return &"({base}).data[{idx}]"

  of hTupleInit:
    let typeName = typeToC(be, node.typ)
    var fields: seq[string] = @[]
    for i, e in node.tupleInitElements:
      fields.add(&"._{i} = {be.emitExpr(e)}")
    return &"(({typeName}){{{fields.join(\", \")}}})"

  of hCast:
    let operand = be.emitExpr(node.castOperand)
    let typ = typeToC(be, node.castType)
    return &"(({typ}){operand})"

  of hIs:
    return "true"  # TODO: proper type checking

  of hSizeOf:
    let typ = typeToC(be, node.sizeOfType)
    return &"sizeof({typ})"

  of hSpawn:
    if node.spawnAsync:
      # Async coroutine spawn
      return &"bux_async_spawn({node.spawnCallee})"
    else:
      # OS thread spawn
      var argsStr = "NULL"
      if node.spawnArgs.len > 0:
        argsStr = &"(void*){be.emitExpr(node.spawnArgs[0])}"
      return &"bux_task_spawn((void* (*)(void*)){node.spawnCallee}, {argsStr})"

  of hDynRef:
    let data = be.emitExpr(node.dynRefData)
    let iface = node.dynRefInterface
    let concrete = node.dynRefConcreteType
    return &"({iface}_FatPtr){{.data = {data}, .vtable = &{concrete}_{iface}_VTable}}"

  of hDynCall:
    let receiver = be.emitExpr(node.dynCallReceiver)
    let methodName = node.dynCallMethod
    var args: seq[string] = @[]
    args.add(&"{receiver}.data")
    for i in 1 ..< node.dynCallArgs.len:
      args.add(be.emitExpr(node.dynCallArgs[i]))
    let argsStr = args.join(", ")
    return &"({receiver}.vtable->{methodName}({argsStr}))"

  of hIf:
    # Ternary expression
    let cond = be.emitExpr(node.ifCond)
    let thenE = be.emitExpr(node.ifThen)
    let elseE = be.emitExpr(node.ifElse)
    return &"({cond} ? {thenE} : {elseE})"

  of hAssign:
    let target = be.emitExpr(node.assignTarget)
    let value = be.emitExpr(node.assignValue)
    let op = operatorToC(node.assignOp)
    return &"({target} {op} {value})"

  of hBlock:
    # For block expressions, just emit the last expression
    if node.blockExpr != nil:
      return be.emitExpr(node.blockExpr)
    elif node.blockStmts.len > 0:
      return be.emitExpr(node.blockStmts[^1])
    return "0"

  of hMatch:
    # Match should be desugared in hir_lower to if-else. Fallback: 0.
    return "0"

  else:
    return "0"

proc emitStmt(be: var CBackend, node: HirNode) =
  if node == nil: return
  case node.kind
  of hDefer:
    let expr = be.emitExpr(node.deferBody)
    if be.deferStack.len > 0:
      let idx = be.deferStack.len - 1
      be.deferStack[idx].add(expr & ";")

  of hReturn:
    be.emitCurrentDefers()
    if node.returnValue != nil:
      let val = be.emitExpr(node.returnValue)
      be.emitLine(&"return {val};")
    else:
      be.emitLine("return;")

  of hIf:
    let cond = be.emitExpr(node.ifCond)
    be.emitLine(&"if ({cond}) {{")
    inc be.indent
    be.emitStmt(node.ifThen)
    dec be.indent
    if node.ifElse != nil:
      be.emitLine("} else {")
      inc be.indent
      be.emitStmt(node.ifElse)
      dec be.indent
    be.emitLine("}")

  of hWhile:
    let cond = be.emitExpr(node.whileCond)
    be.emitLine(&"while ({cond}) {{")
    inc be.indent
    be.emitStmt(node.whileBody)
    dec be.indent
    be.emitLine("}")

  of hLoop:
    be.emitLine("while (1) {")
    inc be.indent
    be.emitStmt(node.loopBody)
    dec be.indent
    be.emitLine("}")

  of hBreak:
    be.emitCurrentDefers()
    be.emitLine("break;")

  of hContinue:
    be.emitCurrentDefers()
    be.emitLine("continue;")

  of hEmit:
    be.emitLine(node.emitCode)

  of hBlock:
    if node.isScope:
      be.emitLine("{")
      inc be.indent
    for stmt in node.blockStmts:
      be.emitStmt(stmt)
    if node.blockExpr != nil:
      let val = be.emitExpr(node.blockExpr)
      be.emitLine(&"{val};")
    if node.isScope:
      dec be.indent
      be.emitLine("}")

  of hAlloca:
    let typ = typeToC(be, node.allocaType)
    be.emitLine(&"{typ} {node.allocaName};")

  of hStore:
    let ptrExpr = be.emitExpr(node.storePtr)
    let val = be.emitExpr(node.storeValue)
    be.emitLine(&"{ptrExpr} = {val};")

  of hAssign:
    let target = be.emitExpr(node.assignTarget)
    let value = be.emitExpr(node.assignValue)
    let op = operatorToC(node.assignOp)
    be.emitLine(&"{target} {op} {value};")

  of hCall:
    let expr = be.emitExpr(node)
    be.emitLine(&"{expr};")

  of hCallIndirect:
    let expr = be.emitExpr(node)
    be.emitLine(&"{expr};")

  else:
    # Expression statement
    let expr = be.emitExpr(node)
    be.emitLine(&"{expr};")

proc emitFunc*(be: var CBackend, hfunc: HirFunc) =
  # Emit env struct and global instance for closures with captures
  if hfunc.captureNames.len > 0 and hfunc.envStructName != "":
    be.emitLine(&"struct {hfunc.envStructName} {{")
    inc be.indent
    for i in 0 ..< hfunc.captureNames.len:
      let capName = hfunc.captureNames[i]
      let capType = if i < hfunc.captureTypes.len: typeToC(be, hfunc.captureTypes[i]) else: "int"
      be.emitLine(&"{capType} {capName};")
    dec be.indent
    be.emitLine("};")
    be.emitLine(&"static struct {hfunc.envStructName} {hfunc.envInstanceName};")
    be.emitLine("")
  let retType = typeToC(be, hfunc.retType)
  var params: seq[string] = @[]
  for p in hfunc.params:
    params.add(&"{typeToC(be, p.typ)} {p.name}")
  if params.len == 0:
    params.add("void")
  let paramsStr = params.join(", ")
  be.emitLine(&"{retType} {hfunc.name}({paramsStr}) {{")
  inc be.indent
  be.pushDeferScope()
  if hfunc.body != nil:
    if hfunc.body.kind == hBlock:
      for stmt in hfunc.body.blockStmts:
        be.emitStmt(stmt)
      if hfunc.body.blockExpr != nil and hfunc.retType.kind != tkVoid:
        be.emitCurrentDefers()
        let val = be.emitExpr(hfunc.body.blockExpr)
        be.emitLine(&"return {val};")
      else:
        be.emitCurrentDefers()
    else:
      be.emitStmt(hfunc.body)
      be.emitCurrentDefers()
  else:
    be.emitCurrentDefers()
  be.popDeferScope()
  dec be.indent
  be.emitLine("}")
  be.emitLine("")

proc emitStruct*(be: var CBackend, name: string, fields: seq[tuple[name: string, typ: Type]]) =
  be.emitLine(&"typedef struct {name} {{")
  inc be.indent
  for f in fields:
    let typ = typeToC(be, f.typ)
    be.emitLine(&"{typ} {f.name};")
  dec be.indent
  be.emitLine(&"}} {name};")
  be.emitLine("")

proc emitEnum*(be: var CBackend, name: string, variants: seq[HirEnumVariant]) =
  # Check if this is a simple enum (no data) or algebraic enum (with data)
  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 - generate as before
    be.emitLine(&"typedef enum {{")
    inc be.indent
    for i, v in variants:
      if i < variants.len - 1:
        be.emitLine(&"{name}_{v.name},")
      else:
        be.emitLine(&"{name}_{v.name}")
    dec be.indent
    be.emitLine(&"}} {name};")
    be.emitLine("")
  else:
    # Algebraic enum - generate tagged union
    # 1. Generate tag enum
    be.emitLine(&"typedef enum {{")
    inc be.indent
    for i, v in variants:
      if i < variants.len - 1:
        be.emitLine(&"{name}_{v.name},")
      else:
        be.emitLine(&"{name}_{v.name}")
    dec be.indent
    be.emitLine(&"}} {name}_Tag;")
    be.emitLine("")
    
    # 2. Generate union for data
    be.emitLine(&"typedef union {{")
    inc be.indent
    for v in variants:
      if v.fields.len == 1:
        # Single positional field — flat union member (compat: data.Variant_0)
        let typ = typeToC(be, v.fields[0])
        be.emitLine(&"{typ} {v.name}_0;")
      elif v.fields.len > 1:
        # Multi positional — named nested typedef Enum_Variant_Payload
        let nestedName = name & "_" & v.name & "_Payload"
        be.emitLine(&"{nestedName} {v.name};")
      elif v.namedFields.len > 0:
        let nestedName = name & "_" & v.name & "_Payload"
        be.emitLine(&"{nestedName} {v.name};")
    dec be.indent
    be.emitLine(&"}} {name}_Data;")
    be.emitLine("")
    
    # 3. Generate main struct with tag + union
    be.emitLine(&"typedef struct {{")
    inc be.indent
    be.emitLine(&"{name}_Tag tag;")
    be.emitLine(&"{name}_Data data;")
    dec be.indent
    be.emitLine(&"}} {name};")
    be.emitLine("")

proc emitExternDecl*(be: var CBackend, efunc: HirFunc) =
  let retType = typeToC(be, efunc.retType)
  var params: seq[string] = @[]
  for p in efunc.params:
    params.add(&"{typeToC(be, p.typ)} {p.name}")
  if params.len == 0:
    params.add("void")
  let paramsStr = params.join(", ")
  be.emitLine(&"extern {retType} {efunc.name}({paramsStr});")

proc collectSliceTypes(module: HirModule): seq[tuple[name: string, elem: string]] =
  ## Pre-pass: collect all slice types used in the module.
  var dummyBe = initCBackend()
  for f in module.funcs:
    discard typeToC(dummyBe, f.retType)
    for p in f.params:
      discard typeToC(dummyBe, p.typ)
  for ef in module.externFuncs:
    discard typeToC(dummyBe, ef.retType)
    for p in ef.params:
      discard typeToC(dummyBe, p.typ)
  for s in module.structs:
    for f in s.fields:
      discard typeToC(dummyBe, f.typ)
  for c in module.consts:
    if c.value != nil:
      discard typeToC(dummyBe, c.value.typ)
  return dummyBe.sliceTypeDefs

proc emitModule*(be: var CBackend, module: HirModule): string =
  # Header
  be.emitLine("/* Generated by Bux Compiler */")
  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("")

  # Pre-collect slice types so we can emit forward declarations early
  let sliceTypes = collectSliceTypes(module)

  # Forward declarations
  for s in module.structs:
    be.emitLine(&"typedef struct {s.name} {s.name};")
  if module.structs.len > 0:
    be.emitLine("")
  # Forward declarations for trait object fat pointers
  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 function declarations
  if module.externFuncs.len > 0:
    be.emitLine("/* Extern function declarations */")
    for ef in module.externFuncs:
      be.emitExternDecl(ef)
    be.emitLine("")

  # Const declarations as #define
  if module.consts.len > 0:
    be.emitLine("/* Constants */")
    for c in module.consts:
      let val = c.value
      if val != nil and val.kind == hLit:
        let tok = val.litToken
        case tok.kind
        of tkIntLiteral:
          be.emitLine(&"#define {c.name} {tok.text}")
        of tkStringLiteral:
          be.emitLine(&"#define {c.name} \"{cEscape(tok.text)}\"")
        of tkBoolLiteral:
          be.emitLine(&"#define {c.name} {tok.text}")
        else:
          be.emitLine(&"/* const {c.name} (unsupported literal kind) */")
      else:
        be.emitLine(&"/* const {c.name} (complex expression) */")
    be.emitLine("")

  # Forward declarations for all structs
  for s in module.structs:
    be.emitLine(&"typedef struct {s.name} {s.name};")
  if module.structs.len > 0:
    be.emitLine("")

  # Nested multi-field enum payloads (Enum_Variant_Payload) must be fully
  # defined before the algebraic enum union that embeds them by value.
  var payloadStructNames: seq[string] = @[]
  for e in module.enums:
    for v in e.variants:
      if v.fields.len > 1 or v.namedFields.len > 0:
        payloadStructNames.add(e.name & "_" & v.name & "_Payload")
  for s in module.structs:
    if s.name in payloadStructNames:
      be.emitStruct(s.name, s.fields)

  # Enum definitions (after payload structs; before user structs that may use them)
  for e in module.enums:
    be.emitEnum(e.name, e.variants)
  if module.enums.len > 0:
    be.emitLine("")

  # Remaining struct definitions (skip payloads already emitted)
  for s in module.structs:
    if s.name notin payloadStructNames:
      be.emitStruct(s.name, s.fields)

  # Slice fat-pointer typedefs
  if sliceTypes.len > 0:
    be.emitLine("/* Slice types */")
    for st in sliceTypes:
      be.emitLine(&"typedef struct {{ {st.elem}* data; size_t len; }} {st.name};")
    be.emitLine("")

  # Forward declarations for all functions
  for f in module.funcs:
    let retType = typeToC(be, f.retType)
    var params: seq[string] = @[]
    for p in f.params:
      params.add(typeToC(be, p.typ) & " " & p.name)
    if params.len == 0:
      params.add("void")
    be.emitLine(retType & " " & f.name & "(" & params.join(", ") & ");")
  be.emitLine("")

  # Trait object vtable and fat pointer struct definitions
  for iface in module.interfaces:
    if iface.hasAssocTypes:
      continue  # Skip vtables for interfaces with associated types (not yet supported)
    let ifaceName = iface.name
    # VTable struct
    be.emitLine(&"typedef struct {ifaceName}_VTable {{")
    inc be.indent
    for m in iface.methods:
      var paramCtypes: seq[string] = @[]
      for i, p in m.params:
        if i == 0:
          paramCtypes.add("void* self")  # First param is always self (erased)
        else:
          paramCtypes.add(typeToC(be, p) & " param")
      if paramCtypes.len == 0:
        paramCtypes.add("void")
      let ret = typeToC(be, m.ret)
      let paramsStr = paramCtypes.join(", ")
      be.emitLine(&"{ret} (*{m.name})({paramsStr});")
    dec be.indent
    be.emitLine(&"}} {ifaceName}_VTable;")
    # Fat pointer struct
    be.emitLine(&"typedef struct {ifaceName}_FatPtr {{")
    inc be.indent
    be.emitLine("void* data;")
    be.emitLine(&"{ifaceName}_VTable* vtable;")
    dec be.indent
    be.emitLine(&"}} {ifaceName}_FatPtr;")
    be.emitLine("")

  # VTable instances
  for vt in module.vtables:
    if vt.hasAssocTypes:
      continue  # Skip vtables for interfaces with associated types
    let varName = vt.concreteType & "_" & vt.interfaceName & "_VTable"
    be.emitLine(&"{vt.interfaceName}_VTable {varName} = {{")
    inc be.indent
    for m in vt.methodNames:
      be.emitLine(&".{m} = (void*){vt.concreteType}_{m},")
    dec be.indent
    be.emitLine("};")
    be.emitLine("")

  # Function definitions
  var hasMain = false
  for f in module.funcs:
    be.emitFunc(f)
    if f.name == "Main":
      hasMain = true

  # Generate C main wrapper if Bux Main exists
  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("}")
    be.emitLine("")

  return be.output
