///|
fn main {
  // Skip the first two args (node path and script path)
  let all_args = @env.args()
  println(all_args)
  guard all_args is [_, input, output, ..] else {
    println("Error: expecting input and output file.")
    return
  }
  let (impls, diag) = try! @parser.parse_file(input)
  guard diag is [] else {
    println("Error: input file \{input} contains syntax error.")
    return
  }
  let buf = StringBuilder::new()
  buf.write_string("// generated file, do not edit\n")
  impls.each(fn(x) { process(x, buf) })
  println("auto_derive: \{input} -> \{output}")
  try! @fs.write_string_to_file(output, buf.to_string())
}

///|
/// Check if an attribute is a `#debug.derive` attribute
fn is_debug_derive_attr(attr : @attribute.Attribute) -> Bool {
  attr.raw == "#debug.derive"
}

///|
/// Check if a TypeDecl has the `#debug.derive` attribute
fn has_debug_derive(decl : @syntax.TypeDecl) -> Bool {
  decl.attrs.any(is_debug_derive_attr)
}

///|
/// Process a top-level item, if it's a TypeDef with #debug.derive, generate Debug impl
fn process(top : @syntax.Impl, buf : StringBuilder) -> Unit {
  match top {
    TopTypeDef(decl) =>
      if has_debug_derive(decl) {
        generate_debug_impl(decl, buf)
      }
    _ => ()
  }
}

///|
/// Generate visibility prefix based on TypeDecl visibility
/// pub(all) or pub types should generate "pub impl", otherwise "impl"
fn generate_visibility_prefix(decl : @syntax.TypeDecl) -> String {
  match decl.type_vis {
    Pub(_) => "pub "
    Default | Priv(_) => ""
  }
}

///|
/// Generate Debug trait implementation code for a TypeDecl
fn generate_debug_impl(decl : @syntax.TypeDecl, buf : StringBuilder) -> Unit {
  let type_name = decl.tycon
  let type_params = generate_type_params(decl)
  let type_constraints = generate_type_constraints(decl)
  let body = generate_debug_body(type_name, decl.components)
  let full_type = if type_params.is_empty() {
    type_name
  } else {
    "\{type_name}[\{type_params}]"
  }
  // Generate visibility prefix based on type visibility
  let vis_prefix = generate_visibility_prefix(decl)
  // Generate the impl block
  let impl_header = if type_constraints.is_empty() {
    "\{vis_prefix}impl @debug.Debug for \{full_type} with debug(self)"
  } else {
    "\{vis_prefix}impl[\{type_constraints}] @debug.Debug for \{full_type} with debug(self)"
  }
  buf.write_string("///|\n\{impl_header} {\n\{body}\n}\n\n")
}

///|
/// Generate type parameters string (e.g., "T, U")
fn generate_type_params(decl : @syntax.TypeDecl) -> String {
  decl.params
  .map(fn(p) {
    match p.name {
      Some(name) => name
      None => "_"
    }
  })
  .to_array()
  .join(", ")
}

///|
/// Generate type constraints string (e.g., "T : @debug.Debug, U : @debug.Debug")
fn generate_type_constraints(decl : @syntax.TypeDecl) -> String {
  decl.params
  .filter_map(fn(p) {
    match p.name {
      Some(name) => Some("\{name} : @debug.Debug")
      None => None
    }
  })
  .to_array()
  .join(", ")
}

///|
/// Generate the debug method body based on TypeDesc
fn generate_debug_body(type_name : String, desc : @syntax.TypeDesc) -> String {
  match desc {
    Abstract | Extern | Alias(_) =>
      // For abstract, extern, and alias types, just return the type name as literal
      "  @repr.Repr::literal(\"\{type_name}\")"
    Newtype(inner_type) => generate_newtype_body(type_name, inner_type)
    Variant(constrs) => generate_variant_body(type_name, constrs)
    Record(fields) => generate_record_body(type_name, fields)
    TupleStruct(types) => generate_tuple_struct_body(type_name, types)
    Error(exception_decl) => generate_error_body(type_name, exception_decl)
  }
}

///|
/// Generate debug body for Newtype
fn generate_newtype_body(type_name : String, _inner : @syntax.Type) -> String {
  // Newtype: struct TypeName(InnerType)
  // Pattern: TypeName(inner) => Repr::ctor("TypeName", [(None, debug(inner))])
  let buf = StringBuilder::new()
  buf.write_string("  match self {\n")
  buf.write_string(
    "    \{type_name}(inner) => @repr.Repr::ctor(\"\{type_name}\", [(None, @debug.debug(inner))])\n",
  )
  buf.write_string("  }")
  buf.to_string()
}

///|
/// Generate debug body for Variant (enum)
fn generate_variant_body(
  _type_name : String,
  constrs : @list.List[@syntax.ConstrDecl],
) -> String {
  let buf = StringBuilder::new()
  buf.write_string("  match self {\n")
  constrs.each(fn(constr) {
    let constr_name = constr.name.name
    match constr.args {
      None =>
        // Enum variant with no arguments: Ctor => Repr::ctor("Ctor", [])
        buf.write_string(
          "    \{constr_name} => @repr.Repr::ctor(\"\{constr_name}\", [])\n",
        )
      Some(args) => {
        let arg_list = args.to_array()
        if arg_list.is_empty() {
          buf.write_string(
            "    \{constr_name} => @repr.Repr::ctor(\"\{constr_name}\", [])\n",
          )
        } else {
          // Generate pattern and repr construction
          let (pattern, repr_args) = generate_constr_pattern_and_args(arg_list)
          buf.write_string(
            "    \{constr_name}(\{pattern}) => @repr.Repr::ctor(\"\{constr_name}\", [\{repr_args}])\n",
          )
        }
      }
    }
  })
  buf.write_string("  }")
  buf.to_string()
}

///|
/// Generate pattern matching and Repr args for constructor arguments
fn generate_constr_pattern_and_args(
  args : Array[@syntax.ConstrParam],
) -> (String, String) {
  let patterns : Array[String] = []
  let repr_args : Array[String] = []
  for i, arg in args {
    match arg.label {
      Some(label) => {
        // Labeled argument: label~ pattern
        let var_name = label.name
        patterns.push("\{var_name}~")
        repr_args.push("(Some(\"\{var_name}\"), @debug.debug(\{var_name}))")
      }
      None => {
        // Positional argument: x0, x1, ...
        let var_name = "x\{i}"
        patterns.push(var_name)
        repr_args.push("(None, @debug.debug(\{var_name}))")
      }
    }
  }
  (patterns.join(", "), repr_args.join(", "))
}

///|
/// Generate debug body for Record (struct with named fields)
fn generate_record_body(
  _type_name : String,
  fields : @list.List[@syntax.FieldDecl],
) -> String {
  let buf = StringBuilder::new()
  buf.write_string("  let fields : Map[String, @repr.Repr] = {}\n")
  fields.each(fn(field) {
    let field_name = field.name.label
    buf.write_string(
      "  fields[\"\{field_name}\"] = @debug.debug(self.\{field_name})\n",
    )
  })
  buf.write_string("  @repr.Repr::record(fields)")
  buf.to_string()
}

///|
/// Generate debug body for TupleStruct
fn generate_tuple_struct_body(
  type_name : String,
  types : @list.List[@syntax.Type],
) -> String {
  let buf = StringBuilder::new()
  let type_count = types.length()
  if type_count == 0 {
    buf.write_string("  @repr.Repr::ctor(\"\{type_name}\", [])")
  } else {
    // Generate pattern: TypeName(x0, x1, ...)
    let vars : Array[String] = []
    let repr_args : Array[String] = []
    for i = 0; i < type_count; i = i + 1 {
      let var_name = "x\{i}"
      vars.push(var_name)
      repr_args.push("(None, @debug.debug(\{var_name}))")
    }
    let pattern = vars.join(", ")
    let args_str = repr_args.join(", ")
    buf.write_string("  match self {\n")
    buf.write_string(
      "    \{type_name}(\{pattern}) => @repr.Repr::ctor(\"\{type_name}\", [\{args_str}])\n",
    )
    buf.write_string("  }")
  }
  buf.to_string()
}

///|
/// Generate debug body for Error (suberror types)
fn generate_error_body(
  type_name : String,
  exception_decl : @syntax.ExceptionDecl,
) -> String {
  match exception_decl {
    NoPayload =>
      // suberror MyError - no payload
      "  @repr.Repr::ctor(\"\{type_name}\", [])"
    SinglePayload(_) => {
      // suberror MyError Type - single payload
      let buf = StringBuilder::new()
      buf.write_string("  match self {\n")
      buf.write_string(
        "    \{type_name}(x0) => @repr.Repr::ctor(\"\{type_name}\", [(None, @debug.debug(x0))])\n",
      )
      buf.write_string("  }")
      buf.to_string()
    }
    EnumPayload(constrs) =>
      // suberror MyError { Variant1; Variant2(Int) } - enum-like payload
      generate_variant_body(type_name, constrs)
  }
}