///|
/// Write indentation (tabs)
fn print_indent(buf : StringBuilder, indent : Int) -> Unit {
  for _ in 0.. String {
  match id {
    Ident(name~) => name
    Dot(pkg~, id~) => "@" + pkg + "." + id
  }
}

///|
/// Get operator precedence (higher = binds tighter)
fn get_precedence(op : String) -> Int {
  // MoonBit operator precedence (based on common conventions)
  match op {
    "||" => 1
    "&&" => 2
    "==" | "!=" | "<" | ">" | "<=" | ">=" => 3
    "|" => 4
    "^" => 5
    "&" => 6
    "<<" | ">>" => 7
    "+" | "-" => 8
    "*" | "/" | "%" => 9
    _ => 10 // custom operators, highest precedence
  }
}

///|
/// Check if expression needs parentheses in a binary context
fn needs_parens(expr : @syntax.Expr, parent_prec : Int) -> Bool {
  match expr {
    Infix(op~, ..) =>
      get_precedence(long_ident_to_string(op.name)) < parent_prec
    _ => false
  }
}

///|
/// Print expression with optional parentheses based on precedence
fn print_expr_with_prec(
  buf : StringBuilder,
  expr : @syntax.Expr,
  parent_prec : Int,
  indent : Int,
) -> Unit {
  if needs_parens(expr, parent_prec) {
    buf.write_char('(')
    print_expr(buf, expr, indent~)
    buf.write_char(')')
  } else {
    print_expr(buf, expr, indent~)
  }
}

///|
/// Print an expression.
pub fn print_expr(
  buf : StringBuilder,
  expr : @syntax.Expr,
  indent? : Int = 0,
) -> Unit {
  match expr {
    // Literals
    Constant(c~, ..) => print_constant(buf, c)
    Unit(..) => buf.write_string("()")

    // Identifiers
    Ident(id~, ..) => print_var(buf, id)
    Constr(constr~, ..) => print_constructor(buf, constr)

    // Collections
    Array(exprs~, ..) => {
      buf.write_char('[')
      let mut first = true
      for e in exprs {
        if !first {
          buf.write_string(", ")
        }
        first = false
        print_expr(buf, e)
      }
      buf.write_char(']')
    }
    ArraySpread(elems~, ..) => {
      buf.write_char('[')
      let mut first = true
      for e in elems {
        if !first {
          buf.write_string(", ")
        }
        first = false
        match e {
          Regular(expr) => print_expr(buf, expr)
          Spread(expr~, ..) => {
            buf.write_string("..")
            print_expr(buf, expr)
          }
        }
      }
      buf.write_char(']')
    }
    Tuple(exprs~, ..) => {
      buf.write_char('(')
      let mut first = true
      for e in exprs {
        if !first {
          buf.write_string(", ")
        }
        first = false
        print_expr(buf, e)
      }
      buf.write_char(')')
    }

    // Operators
    Apply(func~, args~, attr~, ..) => {
      print_expr(buf, func)
      buf.write_char('(')
      print_arguments(buf, args)
      buf.write_char(')')
      match attr {
        NoAttr => ()
        Exclamation => buf.write_char('!')
        Question => buf.write_char('?')
      }
    }
    Infix(op~, lhs~, rhs~, ..) => {
      let prec = get_precedence(long_ident_to_string(op.name))
      print_expr_with_prec(buf, lhs, prec, indent)
      buf.write_char(' ')
      print_var(buf, op)
      buf.write_char(' ')
      print_expr_with_prec(buf, rhs, prec + 1, indent) // right associative needs +1
    }
    Unary(op~, expr~, ..) => {
      print_var(buf, op)
      print_expr(buf, expr)
    }
    Pipe(lhs~, rhs~, ..) => {
      print_expr(buf, lhs)
      buf.write_string(" |> ")
      print_expr(buf, rhs)
    }

    // Array operations
    ArrayGet(array~, index~, ..) => {
      print_expr(buf, array)
      buf.write_char('[')
      print_expr(buf, index)
      buf.write_char(']')
    }
    ArrayGetSlice(array~, start_index~, end_index~, ..) => {
      print_expr(buf, array)
      buf.write_char('[')
      match start_index {
        Some(e) => print_expr(buf, e)
        None => ()
      }
      buf.write_char(':')
      match end_index {
        Some(e) => print_expr(buf, e)
        None => ()
      }
      buf.write_char(']')
    }
    ArraySet(array~, index~, value~, ..) => {
      print_expr(buf, array)
      buf.write_char('[')
      print_expr(buf, index)
      buf.write_string("] = ")
      print_expr(buf, value)
    }
    ArrayAugmentedSet(op~, array~, index~, value~, ..) => {
      print_expr(buf, array)
      buf.write_char('[')
      print_expr(buf, index)
      buf.write_string("] ")
      print_var(buf, op)
      buf.write_string("= ")
      print_expr(buf, value)
    }

    // Control flow
    If(cond~, ifso~, ifnot~, ..) => {
      buf.write_string("if ")
      print_expr(buf, cond)
      buf.write_string(" {\n")
      print_expr(buf, ifso)
      buf.write_string("\n}")
      match ifnot {
        Some(e) => {
          buf.write_string(" else {\n")
          print_expr(buf, e)
          buf.write_string("\n}")
        }
        None => ()
      }
    }
    Match(expr~, cases~, ..) => {
      buf.write_string("match ")
      print_expr(buf, expr, indent~)
      buf.write_string(" {\n")
      for c in cases {
        print_indent(buf, indent + 1)
        print_pattern(buf, c.pattern)
        match c.guard_ {
          Some(g) => {
            buf.write_string(" if ")
            print_expr(buf, g, indent~)
          }
          None => ()
        }
        buf.write_string(" => ")
        print_expr(buf, c.body, indent=indent + 1)
        buf.write_char('\n')
      }
      print_indent(buf, indent)
      buf.write_char('}')
    }
    While(loop_cond~, loop_body~, while_else~, label~, ..) => {
      match label {
        Some(l) => {
          buf.write_string(l.name)
          buf.write_string("~ ")
        }
        None => ()
      }
      buf.write_string("while ")
      print_expr(buf, loop_cond, indent~)
      buf.write_string(" {\n")
      print_indent(buf, indent + 1)
      print_expr(buf, loop_body, indent=indent + 1)
      buf.write_char('\n')
      print_indent(buf, indent)
      buf.write_char('}')
      match while_else {
        Some(e) => {
          buf.write_string(" else {\n")
          print_indent(buf, indent + 1)
          print_expr(buf, e, indent=indent + 1)
          buf.write_char('\n')
          print_indent(buf, indent)
          buf.write_char('}')
        }
        None => ()
      }
    }
    For(binders~, condition~, continue_block~, body~, for_else~, label~, ..) => {
      match label {
        Some(l) => {
          buf.write_string(l.name)
          buf.write_string("~ ")
        }
        None => ()
      }
      buf.write_string("for ")
      let mut first = true
      for b in binders {
        if !first {
          buf.write_string(", ")
        }
        first = false
        buf.write_string(b.0.name)
        buf.write_string(" = ")
        print_expr(buf, b.1, indent~)
      }
      match condition {
        Some(c) => {
          buf.write_string("; ")
          print_expr(buf, c, indent~)
        }
        None => ()
      }
      if !continue_block.is_empty() {
        buf.write_string("; ")
        let mut first_c = true
        for b in continue_block {
          if !first_c {
            buf.write_string(", ")
          }
          first_c = false
          buf.write_string(b.0.name)
          buf.write_string(" = ")
          print_expr(buf, b.1, indent~)
        }
      }
      buf.write_string(" {\n")
      print_indent(buf, indent + 1)
      print_expr(buf, body, indent=indent + 1)
      buf.write_char('\n')
      print_indent(buf, indent)
      buf.write_char('}')
      match for_else {
        Some(e) => {
          buf.write_string(" else {\n")
          print_indent(buf, indent + 1)
          print_expr(buf, e, indent=indent + 1)
          buf.write_char('\n')
          print_indent(buf, indent)
          buf.write_char('}')
        }
        None => ()
      }
    }
    ForEach(binders~, expr~, body~, else_block~, label~, ..) => {
      match label {
        Some(l) => {
          buf.write_string(l.name)
          buf.write_string("~ ")
        }
        None => ()
      }
      buf.write_string("for ")
      let mut first = true
      for b in binders {
        if !first {
          buf.write_string(", ")
        }
        first = false
        match b {
          Some(binder) => buf.write_string(binder.name)
          None => buf.write_char('_')
        }
      }
      buf.write_string(" in ")
      print_expr(buf, expr, indent~)
      buf.write_string(" {\n")
      print_indent(buf, indent + 1)
      print_expr(buf, body, indent=indent + 1)
      buf.write_char('\n')
      print_indent(buf, indent)
      buf.write_char('}')
      match else_block {
        Some(e) => {
          buf.write_string(" else {\n")
          print_indent(buf, indent + 1)
          print_expr(buf, e, indent=indent + 1)
          buf.write_char('\n')
          print_indent(buf, indent)
          buf.write_char('}')
        }
        None => ()
      }
    }
    Loop(arg~, body~, label~, ..) => {
      match label {
        Some(l) => {
          buf.write_string(l.name)
          buf.write_string("~ ")
        }
        None => ()
      }
      buf.write_string("loop ")
      print_expr(buf, arg, indent~)
      buf.write_string(" {\n")
      for c in body {
        print_indent(buf, indent + 1)
        print_pattern(buf, c.pattern)
        match c.guard_ {
          Some(g) => {
            buf.write_string(" if ")
            print_expr(buf, g, indent~)
          }
          None => ()
        }
        buf.write_string(" => ")
        print_expr(buf, c.body, indent=indent + 1)
        buf.write_char('\n')
      }
      print_indent(buf, indent)
      buf.write_char('}')
    }

    // Functions
    Function(func~, ..) =>
      match func.kind {
        Lambda => {
          buf.write_string("fn(")
          print_parameters(buf, func.parameters)
          buf.write_char(')')
          match func.return_type {
            Some(t) => {
              buf.write_string(" -> ")
              print_type(buf, t)
            }
            None => ()
          }
          buf.write_string(" {\n")
          print_indent(buf, indent + 1)
          print_expr(buf, func.body, indent=indent + 1)
          buf.write_char('\n')
          print_indent(buf, indent)
          buf.write_char('}')
        }
        Arrow => {
          buf.write_char('(')
          print_parameters(buf, func.parameters)
          buf.write_string(") => ")
          print_expr(buf, func.body, indent~)
        }
      }

    // Let bindings
    Let(pattern~, expr~, body~, ..) => {
      buf.write_string("let ")
      print_pattern(buf, pattern)
      buf.write_string(" = ")
      print_expr(buf, expr, indent~)
      buf.write_char('\n')
      print_indent(buf, indent)
      print_expr(buf, body, indent~)
    }
    LetMut(binder~, ty~, expr~, body~, ..) => {
      buf.write_string("let mut ")
      buf.write_string(binder.name)
      match ty {
        Some(t) => {
          buf.write_string(" : ")
          print_type(buf, t)
        }
        None => ()
      }
      buf.write_string(" = ")
      print_expr(buf, expr)
      buf.write_char('\n')
      print_expr(buf, body)
    }
    LetFn(name~, func~, body~, ..) => {
      buf.write_string("fn ")
      buf.write_string(name.name)
      buf.write_char('(')
      print_parameters(buf, func.parameters)
      buf.write_char(')')
      match func.return_type {
        Some(t) => {
          buf.write_string(" -> ")
          print_type(buf, t)
        }
        None => ()
      }
      buf.write_string(" {\n")
      print_expr(buf, func.body)
      buf.write_string("\n}\n")
      print_expr(buf, body)
    }
    LetRec(bindings~, body~, ..) => {
      for b in bindings {
        buf.write_string("fn ")
        buf.write_string(b.0.name)
        buf.write_char('(')
        print_parameters(buf, b.1.parameters)
        buf.write_char(')')
        match b.1.return_type {
          Some(t) => {
            buf.write_string(" -> ")
            print_type(buf, t)
          }
          None => ()
        }
        buf.write_string(" {\n")
        print_expr(buf, b.1.body)
        buf.write_string("\n}\n")
      }
      print_expr(buf, body)
    }
    LetAnd(bindings~, body~, ..) => {
      let mut first = true
      for b in bindings {
        if first {
          buf.write_string("let ")
          first = false
        } else {
          buf.write_string("and ")
        }
        buf.write_string(b.0.name)
        match b.1 {
          Some(t) => {
            buf.write_string(" : ")
            print_type(buf, t)
          }
          None => ()
        }
        buf.write_string(" = fn(")
        print_parameters(buf, b.2.parameters)
        buf.write_string(") {\n")
        print_expr(buf, b.2.body)
        buf.write_string("\n}\n")
      }
      print_expr(buf, body)
    }

    // Sequences
    Sequence(exprs~, last_expr~, ..) => {
      for e in exprs {
        print_expr(buf, e)
        buf.write_char('\n')
      }
      print_expr(buf, last_expr)
    }

    // Records
    Record(type_name~, fields~, ..) => {
      match type_name {
        Some(tn) => {
          print_type_name(buf, tn)
          buf.write_string("::")
        }
        None => ()
      }
      buf.write_string("{ ")
      let mut first = true
      for f in fields {
        if !first {
          buf.write_string(", ")
        }
        first = false
        if f.is_pun {
          buf.write_string(f.label.name)
        } else {
          buf.write_string(f.label.name)
          buf.write_string(": ")
          print_expr(buf, f.expr)
        }
      }
      buf.write_string(" }")
    }
    RecordUpdate(type_name~, record~, fields~, ..) => {
      match type_name {
        Some(tn) => {
          print_type_name(buf, tn)
          buf.write_string("::")
        }
        None => ()
      }
      buf.write_string("{ ..")
      print_expr(buf, record)
      for f in fields {
        buf.write_string(", ")
        if f.is_pun {
          buf.write_string(f.label.name)
        } else {
          buf.write_string(f.label.name)
          buf.write_string(": ")
          print_expr(buf, f.expr)
        }
      }
      buf.write_string(" }")
    }
    Field(record~, accessor~, ..) => {
      print_expr(buf, record)
      buf.write_char('.')
      match accessor {
        Label(label) => buf.write_string(label.name)
        Index(tuple_index~, ..) => buf.write_string(tuple_index.to_string())
        Newtype(..) => buf.write_char('_')
      }
    }
    Method(type_name~, method_name~, ..) => {
      print_type_name(buf, type_name)
      buf.write_string("::")
      buf.write_string(method_name.name)
    }
    DotApply(self~, method_name~, args~, return_self~, attr~, ..) => {
      print_expr(buf, self)
      buf.write_char('.')
      buf.write_string(method_name.name)
      buf.write_char('(')
      print_arguments(buf, args)
      buf.write_char(')')
      match attr {
        NoAttr => ()
        Exclamation => buf.write_char('!')
        Question => buf.write_char('?')
      }
      if return_self {
        buf.write_char('.')
      }
    }

    // Mutation and assignment
    Mutate(record~, accessor~, field~, augmented_by~, ..) => {
      print_expr(buf, record)
      buf.write_char('.')
      match accessor {
        Label(label) => buf.write_string(label.name)
        Index(tuple_index~, ..) => buf.write_string(tuple_index.to_string())
        Newtype(..) => buf.write_char('_')
      }
      match augmented_by {
        Some(op) => {
          buf.write_char(' ')
          print_var(buf, op)
          buf.write_string("= ")
        }
        None => buf.write_string(" = ")
      }
      print_expr(buf, field)
    }
    Assign(var_~, expr~, augmented_by~, ..) => {
      print_var(buf, var_)
      match augmented_by {
        Some(op) => {
          buf.write_char(' ')
          print_var(buf, op)
          buf.write_string("= ")
        }
        None => buf.write_string(" = ")
      }
      print_expr(buf, expr)
    }

    // Type constraints
    Constraint(expr~, ty~, ..) => {
      buf.write_char('(')
      print_expr(buf, expr)
      buf.write_string(" : ")
      print_type(buf, ty)
      buf.write_char(')')
    }
    As(expr~, trait_~, ..) => {
      print_expr(buf, expr)
      buf.write_string(" as ")
      print_type_name(buf, trait_)
    }
    Is(expr~, pat~, ..) => {
      print_expr(buf, expr)
      buf.write_string(" is ")
      print_pattern(buf, pat)
    }

    // Control flow keywords
    Return(return_value~, ..) => {
      buf.write_string("return")
      match return_value {
        Some(e) => {
          buf.write_char(' ')
          print_expr(buf, e)
        }
        None => ()
      }
    }
    Raise(err_value~, ..) => {
      buf.write_string("raise ")
      print_expr(buf, err_value)
    }
    Break(arg~, label~, ..) => {
      buf.write_string("break")
      match label {
        Some(l) => {
          buf.write_char(' ')
          buf.write_string(l.name)
        }
        None => ()
      }
      match arg {
        Some(e) => {
          buf.write_char(' ')
          print_expr(buf, e)
        }
        None => ()
      }
    }
    Continue(args~, label~, ..) => {
      buf.write_string("continue")
      match label {
        Some(l) => {
          buf.write_char(' ')
          buf.write_string(l.name)
        }
        None => ()
      }
      if !args.is_empty() {
        buf.write_char(' ')
        let mut first = true
        for e in args {
          if !first {
            buf.write_string(", ")
          }
          first = false
          print_expr(buf, e)
        }
      }
    }

    // Error handling
    Try(body~, catch_~, try_else~, has_try~, ..) => {
      if has_try {
        buf.write_string("try {\n")
        print_expr(buf, body)
        buf.write_string("\n}")
      } else {
        print_expr(buf, body)
      }
      if !catch_.is_empty() {
        buf.write_string(" catch {\n")
        for c in catch_ {
          buf.write_string("  ")
          print_pattern(buf, c.pattern)
          buf.write_string(" => ")
          print_expr(buf, c.body)
          buf.write_char('\n')
        }
        buf.write_char('}')
      }
      match try_else {
        Some(cases) => {
          buf.write_string(" else {\n")
          for c in cases {
            buf.write_string("  ")
            print_pattern(buf, c.pattern)
            buf.write_string(" => ")
            print_expr(buf, c.body)
            buf.write_char('\n')
          }
          buf.write_char('}')
        }
        None => ()
      }
    }
    TryOperator(body~, kind~, ..) => {
      match kind {
        Question => buf.write_string("try? ")
        Exclamation => buf.write_string("try! ")
      }
      print_expr(buf, body)
    }

    // Guard
    Guard(cond~, otherwise~, body~, ..) => {
      buf.write_string("guard ")
      print_expr(buf, cond)
      match otherwise {
        Some(e) => {
          buf.write_string(" else {\n")
          print_expr(buf, e)
          buf.write_string("\n}")
        }
        None => ()
      }
      buf.write_char('\n')
      print_expr(buf, body)
    }

    // Defer
    Defer(expr~, body~, ..) => {
      buf.write_string("defer ")
      print_expr(buf, expr)
      buf.write_char('\n')
      print_expr(buf, body)
    }

    // Hole
    Hole(kind~, ..) =>
      match kind {
        Synthesized => buf.write_char('_')
        Incomplete => buf.write_string("...")
        Todo => buf.write_string("todo")
      }

    // Map
    Map(elems~, ..) => {
      buf.write_string("{ ")
      let mut first = true
      for e in elems {
        if !first {
          buf.write_string(", ")
        }
        first = false
        print_constant(buf, e.key)
        buf.write_string(": ")
        print_expr(buf, e.expr)
      }
      buf.write_string(" }")
    }

    // Group
    Group(expr~, group~, ..) =>
      match group {
        Paren => {
          buf.write_char('(')
          print_expr(buf, expr)
          buf.write_char(')')
        }
        Brace => {
          buf.write_string("{\n")
          print_expr(buf, expr)
          buf.write_string("\n}")
        }
      }

    // String interpolation
    Interp(elems~, ..) => {
      buf.write_char('"')
      for e in elems {
        match e {
          Literal(repr~, ..) => buf.write_string(repr)
          Expr(expr~, ..) => {
            buf.write_string("\\{")
            print_expr(buf, expr)
            buf.write_char('}')
          }
          Source(_) => ()
        }
      }
      buf.write_char('"')
    }
    MultilineString(elems~, ..) =>
      for e in elems {
        match e {
          String(s) => {
            buf.write_string("#| ")
            buf.write_string(s)
            buf.write_char('\n')
          }
          Interp(interp_elems) => {
            buf.write_string("#| ")
            for ie in interp_elems {
              match ie {
                Literal(repr~, ..) => buf.write_string(repr)
                Expr(expr~, ..) => {
                  buf.write_string("\\{")
                  print_expr(buf, expr)
                  buf.write_char('}')
                }
                Source(_) => ()
              }
            }
            buf.write_char('\n')
          }
        }
      }

    // LexMatch
    LexMatch(strategy~, expr~, cases~, ..) => {
      buf.write_string("lexmatch ")
      match strategy {
        Some(s) => {
          buf.write_string("using ")
          buf.write_string(s.name)
          buf.write_char(' ')
        }
        None => ()
      }
      print_expr(buf, expr)
      buf.write_string(" {\n")
      for c in cases {
        buf.write_string("  ")
        print_lex_top_patterns(buf, c.pat)
        buf.write_string(" => ")
        print_expr(buf, c.body)
        buf.write_char('\n')
      }
      buf.write_char('}')
    }
    IsLexMatch(expr~, strategy~, pat~, ..) => {
      print_expr(buf, expr)
      buf.write_string(" lexmatch")
      match strategy {
        Some(s) => {
          buf.write_char('?')
          buf.write_char(' ')
          buf.write_string(s.name)
        }
        None => buf.write_char('?')
      }
      buf.write_char(' ')
      print_lex_top_patterns(buf, pat)
    }
    // StaticAssert is compiler-generated, just print the body
    StaticAssert(body~, ..) => print_expr(buf, body)
  }
}

///|
fn print_var(buf : StringBuilder, v : @syntax.Var) -> Unit {
  print_long_ident(buf, v.name)
}

///|
fn print_arguments(
  buf : StringBuilder,
  args : @list.List[@syntax.Argument],
) -> Unit {
  let mut first = true
  for arg in args {
    if !first {
      buf.write_string(", ")
    }
    first = false
    match arg.kind {
      Positional => ()
      Labelled(label) => {
        buf.write_string(label.name)
        buf.write_string("=")
      }
      LabelledPun(label) => {
        buf.write_string(label.name)
        buf.write_char('~')
        continue // skip value printing for pun
      }
      LabelledOption(label~, ..) => {
        buf.write_string(label.name)
        buf.write_string("?=")
      }
      LabelledOptionPun(label~, ..) => {
        buf.write_string(label.name)
        buf.write_char('?')
        continue // skip value printing for pun
      }
    }
    print_expr(buf, arg.value)
  }
}

///|
fn print_lex_top_patterns(
  buf : StringBuilder,
  pats : @list.List[@syntax.LexTopPattern],
) -> Unit {
  let mut first = true
  for p in pats {
    if !first {
      buf.write_char(' ')
    }
    first = false
    print_lex_top_pattern(buf, p)
  }
}

///|
fn print_lex_top_pattern(
  buf : StringBuilder,
  pat : @syntax.LexTopPattern,
) -> Unit {
  match pat {
    Pattern(lex_pat) => print_lex_pattern(buf, lex_pat)
    Binder(b) => buf.write_string(b.name)
    Wildcard(..) => buf.write_char('_')
  }
}

///|
fn print_lex_pattern(buf : StringBuilder, pat : @syntax.LexPattern) -> Unit {
  match pat {
    Regex(lit~, ..) => {
      buf.write_char('/')
      buf.write_string(lit)
      buf.write_char('/')
    }
    RegexInterp(elems~, ..) => {
      buf.write_char('/')
      for e in elems {
        match e {
          Literal(repr~, ..) => buf.write_string(repr)
          Expr(expr~, ..) => {
            buf.write_string("\\{")
            print_expr(buf, expr)
            buf.write_char('}')
          }
          Source(_) => ()
        }
      }
      buf.write_char('/')
    }
    Alias(pat~, binder~, ..) => {
      print_lex_pattern(buf, pat)
      buf.write_string(" as ")
      buf.write_string(binder.name)
    }
    Sequence(pats~, ..) => {
      let mut first = true
      for p in pats {
        if !first {
          buf.write_char(' ')
        }
        first = false
        print_lex_pattern(buf, p)
      }
    }
  }
}