// Rendering `Target`s as MoonBit source.
//
// Two naming conventions keep generated code from colliding with the user's
// own names: the trait method parameters are `serializer` / `deserializer`
// rather than `s` / `d`, and accumulators are prefixed `f_`. Labelled variant
// payloads are bound with explicit `label=binding` patterns for the same
// reason — a variant field called `deserializer` would otherwise shadow it.

///|
/// The banner every generated file opens with.
const HEADER : String = "// Generated by `moonx Yu-zh/data/derive`. DO NOT EDIT.\n// Re-run the generator after changing the types it was generated from."

///|
fn Target::self_type(self : Target) -> String {
  if self.params.is_empty() {
    self.name
  } else {
    "\{self.name}[\{self.params.join(", ")}]"
  }
}

///|
/// `pub impl[T : @data.Serialize] @data.Serialize for Foo[T]`
fn Target::impl_head(self : Target, trait_ : String) -> String {
  let visibility = if self.public { "pub " } else { "" }
  let bounds = if self.params.is_empty() {
    ""
  } else {
    let each = self.params.map(p => "\{p} : @data.\{trait_}")
    "[\{each.join(", ")}]"
  }
  "\{visibility}impl\{bounds} @data.\{trait_} for \{self.self_type()}"
}

///|
/// Whether the generated `serialize` body reads the value at all.
///
/// A type with nothing left to write — no fields, or none that survive
/// `#data.skip` — hands its parameter to no one, and an unused binding named
/// `self` would warn. Generated code lands in projects that have no way to
/// annotate away a warning they did not write, so the binder is named for what
/// the body does with it.
fn Target::reads_self(self : Target) -> Bool {
  match self.shape {
    UnitStruct => false
    NewtypeStruct | Variant(_) => true
    Record(fields) => !fields.filter(f => !f.skip).is_empty()
    TupleStruct(arity) => arity > 0
  }
}

///|
fn quote(text : String) -> String {
  let out = StringBuilder::new()
  out.write_char('"')
  for c in text {
    match c {
      '"' => out.write_string("\\\"")
      '\\' => out.write_string("\\\\")
      '\n' => out.write_string("\\n")
      _ => out.write_char(c)
    }
  }
  out.write_char('"')
  out.to_string()
}

///|
fn hex_digit(n : Int) -> Char {
  if n < 10 {
    ('0'.to_int() + n).unsafe_to_char()
  } else {
    ('a'.to_int() + n - 10).unsafe_to_char()
  }
}

///|
/// Renders a UTF-8 byte literal without relying on the generated source file's
/// text encoding. Printable ASCII stays readable; everything else uses `\xNN`.
fn byte_quote(text : String) -> String {
  let out = StringBuilder::new()
  out.write_string("b\"")
  for byte in @utf8.encode(text) {
    let n = byte.to_int()
    match n {
      0x22 => out.write_string("\\\"")
      0x5C => out.write_string("\\\\")
      _ =>
        if n >= 0x20 && n <= 0x7E {
          out.write_char(n.unsafe_to_char())
        } else {
          out.write_string("\\x")
          out.write_char(hex_digit(n >> 4))
          out.write_char(hex_digit(n & 0x0F))
        }
    }
  }
  out.write_char('"')
  out.to_string()
}

///|
fn byte_pattern(text : String) -> String {
  let out = StringBuilder::new()
  out.write_char('[')
  for index, byte in @utf8.encode(text) {
    if index > 0 {
      out.write_string(", ")
    }
    let n = byte.to_int()
    out.write_string("0x")
    out.write_char(hex_digit(n >> 4))
    out.write_char(hex_digit(n & 0x0F))
  }
  out.write_char(']')
  out.to_string()
}

///|
fn name_list(fields : Array[Field]) -> String {
  let names = fields.filter(f => !f.skip).map(f => quote(f.name))
  "[\{names.join(", ")}]"
}

///|
fn record_field_names_name(target : Target) -> String {
  "__data_record_fields_\{target.name}"
}

///|
fn variant_field_names_name(target : Target, index : Int) -> String {
  "__data_variant_fields_\{target.name}_\{index}"
}

///|
fn variant_names_name(target : Target) -> String {
  "__data_variants_\{target.name}"
}

///|
fn record_field_identifier_name(target : Target) -> String {
  "DataRecordField\{target.name}"
}

///|
fn variant_field_identifier_name(target : Target, index : Int) -> String {
  "DataVariantField\{target.name}\{index}"
}

///|
fn field_case(field : Field) -> String {
  "Field_\{field.ident}"
}

///|
fn unknown_field_case() -> String {
  "Unknown"
}

///|
/// Emits the generated equivalent of Serde's private `__Field` enum and its
/// visitor. The declaration-order hint makes the common path one comparison;
/// the fallback is an exact `BytesView` pattern match, giving MoonBit's match
/// compiler the complete field set from which to build a decision tree.
fn write_field_identifier(
  out : StringBuilder,
  identifier : String,
  fields : Array[Field],
) -> Unit {
  let kept = fields.filter(field => !field.skip)
  let unknown = unknown_field_case()
  out.write_string("\n///|\n")
  out.write_string("priv enum \{identifier} {\n")
  for field in kept {
    out.write_string("  \{field_case(field)}\n")
  }
  out.write_string("  Unknown\n")
  out.write_string("}\n")
  out.write_string("\n///|\n")
  if kept.is_empty() {
    out.write_string(
      "impl @data.FieldIdentifier for \{identifier} with fn from_index(_index) {\n  \{unknown}\n}\n",
    )
    out.write_string("\n///|\n")
    out.write_string(
      "impl @data.FieldIdentifier for \{identifier} with fn from_bytes(_key, _index_hint) {\n  \{unknown}\n}\n",
    )
    out.write_string("\n///|\n")
    out.write_string(
      "impl @data.FieldIdentifier for \{identifier} with fn from_string(_name) {\n  \{unknown}\n}\n",
    )
  } else {
    out.write_string(
      "impl @data.FieldIdentifier for \{identifier} with fn from_index(index) {\n  match index {\n",
    )
    for index, field in kept {
      out.write_string("    \{index} => \{field_case(field)}\n")
    }
    out.write_string("    _ => \{unknown}\n  }\n}\n")
    out.write_string("\n///|\n")
    out.write_string(
      "impl @data.FieldIdentifier for \{identifier} with fn from_bytes(key, index_hint) {\n  match index_hint {\n",
    )
    for index, field in kept {
      out.write_string(
        "    \{index} if key.equal_to_bytes(\{byte_quote(field.name)}) => \{field_case(field)}\n",
      )
    }
    out.write_string("    _ =>\n      match key {\n")
    for field in kept {
      out.write_string(
        "        \{byte_pattern(field.name)} => \{field_case(field)}\n",
      )
    }
    out.write_string("        _ => \{unknown}\n      }\n  }\n}\n")
    out.write_string("\n///|\n")
    out.write_string(
      "impl @data.FieldIdentifier for \{identifier} with fn from_string(name) {\n  match name {\n",
    )
    for field in kept {
      out.write_string("    \{quote(field.name)} => \{field_case(field)}\n")
    }
    out.write_string("    _ => \{unknown}\n  }\n}\n")
  }
  out.write_string("\n///|\n")
  out.write_string(
    "impl @data.FieldIdentifier for \{identifier} with fn name(self) {\n  match self {\n",
  )
  for field in kept {
    out.write_string("    \{field_case(field)} => Some(\{quote(field.name)})\n")
  }
  out.write_string("    \{unknown} => None\n  }\n}\n")
}

///|
fn write_field_metadata(
  out : StringBuilder,
  binding : String,
  identifier : String,
  fields : Array[Field],
) -> Unit {
  out.write_string("\n///|\n")
  out.write_string(
    "let \{binding} : ReadOnlyArray[String] = \{name_list(fields)}\n",
  )
  write_field_identifier(out, identifier, fields)
}

///|
fn write_all_field_metadata(out : StringBuilder, target : Target) -> Unit {
  match target.shape {
    Record(fields) =>
      write_field_metadata(
        out,
        record_field_names_name(target),
        record_field_identifier_name(target),
        fields,
      )
    Variant(cases) => {
      let names = cases.map(c => quote(c.name))
      out.write_string("\n///|\n")
      out.write_string(
        "let \{variant_names_name(target)} : ReadOnlyArray[String] = [\{names.join(", ")}]\n",
      )
      for index, case_ in cases {
        match case_.payload {
          Struct(fields) =>
            write_field_metadata(
              out,
              variant_field_names_name(target, index),
              variant_field_identifier_name(target, index),
              fields,
            )
          _ => ()
        }
      }
    }
    _ => ()
  }
}

///|
fn variant_info(target : Target, index : Int, case_ : Case) -> String {
  "@data.VariantInfo::{ enum_name: \{quote(target.name)}, index: \{index}, name: \{quote(case_.name)} }"
}

///|
/// Writes the body that reads a set of named fields into `f_*` accumulators.
///
/// Shared by structs and struct-shaped enum variants, which differ only in
/// what they build at the end.
fn write_field_loop(
  out : StringBuilder,
  fields : Array[Field],
  field_identifier : String,
  indent : String,
) -> Unit {
  for field in fields {
    if !field.skip {
      out.write_string("\{indent}let mut f_\{field.ident} = None\n")
    }
  }
  out.write_string(
    "\{indent}while (deserializer.deserialize_field() : \{field_identifier}?) is Some(field) {\n",
  )
  out.write_string("\{indent}  match field {\n")
  for field in fields {
    if field.skip {
      continue
    }
    // An `Option` field takes the read straight, so an absent field and an
    // explicit null both land on `None`. Everything else is wrapped so that
    // absence stays distinguishable and can be reported by name.
    let rhs = if field.optional {
      "deserializer.deserialize_field_value()"
    } else {
      "Some(deserializer.deserialize_field_value())"
    }
    out.write_string(
      "\{indent}    \{field_case(field)} => f_\{field.ident} = \{rhs}\n",
    )
  }
  out.write_string(
    "\{indent}    \{unknown_field_case()} => deserializer.skip_value()\n",
  )
  out.write_string("\{indent}  }\n")
  out.write_string("\{indent}}\n")
}

///|
fn field_value(field : Field, indent : String) -> String {
  if field.skip {
    "Default::default()"
  } else if field.optional {
    "f_\{field.ident}"
  } else {
    "match f_\{field.ident} {\n\{indent}  Some(value) => value\n\{indent}  None => @data.required(None, name=\{quote(field.name)}, path=deserializer.path())\n\{indent}}"
  }
}

///|
/// The `extend` declaration for the method an impl silently promotes.
///
/// A trait impl installs its methods as regular methods on the type as well,
/// so a derived `Serialize` would quietly add `value.serialize(s)` to the
/// public surface of a project that only asked to derive. Declaring it
/// deprecated and hidden keeps that out of the user's interface while leaving
/// the promotion legal for anyone already relying on it: the trait bound is
/// how generated code reaches these, and how everyone else should.
fn write_extend(out : StringBuilder, target : Target, trait_ : String) -> Unit {
  let visibility = if target.public { "pub " } else { "" }
  let method_name = if trait_ == "Serialize" {
    "serialize"
  } else {
    "deserialize"
  }
  out.write_string("\n///|\n")
  out.write_string(
    "#deprecated(\"reach it through the `\{trait_}` bound\", skip_current_package=true)\n",
  )
  out.write_string("#doc(hidden)\n")
  out.write_string(
    "\{visibility}extend \{target.name} with @data.\{trait_}::{\{method_name}}\n",
  )
}

///|
fn write_serialize(out : StringBuilder, target : Target) -> Unit {
  let self_ = if target.reads_self() { "self" } else { "_self" }
  out.write_string("\n///|\n")
  out.write_string(
    "\{target.impl_head("Serialize")} with fn serialize(\{self_}, serializer) {\n",
  )
  match target.shape {
    UnitStruct =>
      out.write_string(
        "  serializer.serialize_unit_struct(\{quote(target.name)})\n",
      )
    NewtypeStruct =>
      out.write_string(
        "  serializer.serialize_newtype_struct(\{quote(target.name)}, self.0)\n",
      )
    Record(fields) => {
      let kept = fields.filter(f => !f.skip)
      out.write_string(
        "  serializer.serialize_struct_begin(\{quote(target.name)}, \{kept.length()})\n",
      )
      for field in kept {
        out.write_string(
          "  serializer.serialize_field(\{quote(field.name)}, self.\{field.ident})\n",
        )
      }
      out.write_string("  serializer.serialize_struct_end()\n")
    }
    TupleStruct(arity) => {
      out.write_string(
        "  serializer.serialize_tuple_struct_begin(\{quote(target.name)}, \{arity})\n",
      )
      for i in 0.. {
      out.write_string("  match self {\n")
      for index, case_ in cases {
        let info = variant_info(target, index, case_)
        match case_.payload {
          Unit =>
            out.write_string(
              "    \{case_.ident} => serializer.serialize_unit_variant(\{info})\n",
            )
          Newtype =>
            out.write_string(
              "    \{case_.ident}(v0) => serializer.serialize_newtype_variant(\{info}, v0)\n",
            )
          Tuple(arity) => {
            let binds = Array::makei(arity, i => "v\{i}")
            out.write_string("    \{case_.ident}(\{binds.join(", ")}) => {\n")
            out.write_string(
              "      serializer.serialize_tuple_variant_begin(\{info}, \{arity})\n",
            )
            for bind in binds {
              out.write_string(
                "      serializer.serialize_tuple_variant_element(\{bind})\n",
              )
            }
            out.write_string("      serializer.serialize_tuple_variant_end()\n")
            out.write_string("    }\n")
          }
          Struct(fields) => {
            let binds = fields.mapi((i, f) => "\{f.ident}=v\{i}")
            out.write_string("    \{case_.ident}(\{binds.join(", ")}) => {\n")
            out.write_string(
              "      serializer.serialize_struct_variant_begin(\{info}, \{fields.length()})\n",
            )
            for i, field in fields {
              out.write_string(
                "      serializer.serialize_struct_variant_field(\{quote(field.name)}, v\{i})\n",
              )
            }
            out.write_string(
              "      serializer.serialize_struct_variant_end()\n",
            )
            out.write_string("    }\n")
          }
        }
      }
      out.write_string("  }\n")
    }
  }
  out.write_string("}\n")
}

///|
fn write_deserialize(out : StringBuilder, target : Target) -> Unit {
  out.write_string("\n///|\n")
  out.write_string(
    "\{target.impl_head("Deserialize")} with fn deserialize(deserializer) {\n",
  )
  match target.shape {
    UnitStruct => {
      out.write_string(
        "  deserializer.deserialize_unit_struct(\{quote(target.name)})\n",
      )
      // Spelled with the type name rather than as a bare `{ }`, which has no
      // fields to infer the type from.
      out.write_string("  \{target.name}::{  }\n")
    }
    NewtypeStruct =>
      out.write_string(
        "  \{target.name}(deserializer.deserialize_newtype_struct(\{quote(target.name)}))\n",
      )
    Record(fields) => {
      let field_names = record_field_names_name(target)
      let field_identifier = record_field_identifier_name(target)
      out.write_string(
        "  deserializer.deserialize_struct_begin(\{quote(target.name)}, \{field_names})\n",
      )
      write_field_loop(out, fields, field_identifier, "  ")
      out.write_string("  {\n")
      for field in fields {
        out.write_string("    \{field.ident}: \{field_value(field, "    ")},\n")
      }
      out.write_string("  }\n")
    }
    TupleStruct(arity) => {
      out.write_string(
        "  deserializer.deserialize_tuple_struct_begin(\{quote(target.name)}, \{arity})\n",
      )
      for i in 0.. "v\{i}")
      out.write_string("  \{target.name}(\{binds.join(", ")})\n")
    }
    Variant(cases) => {
      let variants = variant_names_name(target)
      out.write_string(
        "  let value = match deserializer.deserialize_enum_begin(\{quote(target.name)}, \{variants}) {\n",
      )
      for i, case_ in cases {
        match case_.payload {
          Unit => {
            out.write_string("    \{i} => {\n")
            out.write_string("      deserializer.deserialize_unit_variant()\n")
            out.write_string("      \{case_.ident}\n")
            out.write_string("    }\n")
          }
          Newtype =>
            out.write_string(
              "    \{i} => \{case_.ident}(deserializer.deserialize_newtype_variant())\n",
            )
          Tuple(arity) => {
            out.write_string("    \{i} => {\n")
            out.write_string(
              "      deserializer.deserialize_tuple_begin(\{arity})\n",
            )
            for i in 0.. "v\{i}")
            out.write_string("      \{case_.ident}(\{binds.join(", ")})\n")
            out.write_string("    }\n")
          }
          Struct(fields) => {
            let field_names = variant_field_names_name(target, i)
            let field_identifier = variant_field_identifier_name(target, i)
            out.write_string("    \{i} => {\n")
            out.write_string(
              "      deserializer.deserialize_struct_begin(\{quote(case_.name)}, \{field_names})\n",
            )
            write_field_loop(out, fields, field_identifier, "      ")
            out.write_string("      \{case_.ident}(\n")
            for field in fields {
              out.write_string(
                "        \{field.ident}=\{field_value(field, "        ")},\n",
              )
            }
            out.write_string("      )\n")
            out.write_string("    }\n")
          }
        }
      }
      // Unreachable: `deserialize_enum_begin` is contractually required to
      // raise rather than return an index outside `variants`. The arm exists
      // for exhaustiveness, and reports a format that broke that contract
      // rather than trusting it.
      out.write_string(
        "    index =>\n      raise @data.UnknownVariant(path=deserializer.path(), variant=index.to_string(), expected=\{variants})\n",
      )
      out.write_string("  }\n")
      out.write_string("  deserializer.deserialize_enum_end()\n")
      out.write_string("  value\n")
    }
  }
  out.write_string("}\n")
}

///|
/// Renders every impl for one file's worth of targets, or `None` when nothing
/// in the file was marked.
fn emit(targets : Array[Target]) -> String? {
  let wanted = targets.filter(t => t.serialize || t.deserialize)
  if wanted.is_empty() {
    return None
  }
  let out = StringBuilder::new()
  out.write_string(HEADER)
  out.write_string("\n")
  for target in wanted {
    if target.serialize {
      write_serialize(out, target)
      write_extend(out, target, "Serialize")
    }
    if target.deserialize {
      write_all_field_metadata(out, target)
      write_deserialize(out, target)
      write_extend(out, target, "Deserialize")
    }
  }
  Some(out.to_string())
}