///|
fn codegen_identifier(source : StringView) -> String {
  let chars : Array[Char] = []
  let mut separator = true
  for char in source.to_upper().iter() {
    let allowed = (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9')
    if allowed {
      chars.push(char)
      separator = false
    } else if !separator && !chars.is_empty() {
      chars.push('_')
      separator = true
    }
  }
  if chars.length() > 0 && chars[chars.length() - 1] == '_' {
    let _ = chars.pop()
  }
  if chars.is_empty() {
    "UNNAMED"
  } else {
    String::from_array(chars)
  }
}

///|
fn message_codegen_prefix(message : Message) -> String {
  "DBC_\{codegen_identifier(message.name)}_\{message.id}"
}

///|
fn signal_codegen_prefix(
  message : Message,
  signal : Signal,
  index : Int,
) -> String {
  "\{message_codegen_prefix(message)}_\{codegen_identifier(signal.name)}_\{index}"
}

///|
fn append_signal_constants(
  lines : Array[String],
  message : Message,
  signal : Signal,
  index : Int,
) -> Unit {
  let prefix = signal_codegen_prefix(message, signal, index)
  lines.push("/// Signal \{signal.name} in message \{message.name}.")
  lines.push("pub const \{prefix}_START_BIT : Int = \{signal.start_bit}")
  lines.push("pub const \{prefix}_BIT_LENGTH : Int = \{signal.bit_length}")
  lines.push(
    "pub const \{prefix}_IS_INTEL : Bool = \{signal.byte_order == Intel}",
  )
  lines.push(
    "pub const \{prefix}_IS_SIGNED : Bool = \{signal.value_kind == Signed}",
  )
  match signal.value_type {
    Integer => ()
    Float32 => lines.push("pub const \{prefix}_IS_FLOAT32 : Bool = true")
    Float64 => lines.push("pub const \{prefix}_IS_FLOAT64 : Bool = true")
  }
  lines.push("pub const \{prefix}_FACTOR : Double = \{signal.factor}")
  lines.push("pub const \{prefix}_OFFSET : Double = \{signal.offset}")
  lines.push("pub const \{prefix}_MINIMUM : Double = \{signal.minimum}")
  lines.push("pub const \{prefix}_MAXIMUM : Double = \{signal.maximum}")
  match signal.multiplex {
    Plain => ()
    Multiplexer =>
      lines.push("pub const \{prefix}_IS_MULTIPLEXER : Bool = true")
    Multiplexed(value) =>
      lines.push("pub const \{prefix}_MULTIPLEX_VALUE : Int = \{value}")
    MultiplexedRanges(_, ranges) =>
      for range_index, range in ranges {
        lines.push(
          "pub const \{prefix}_MULTIPLEX_RANGE_\{range_index}_START : Int = \{range.start}",
        )
        lines.push(
          "pub const \{prefix}_MULTIPLEX_RANGE_\{range_index}_END : Int = \{range.end}",
        )
      }
  }
  lines.push("")
}

///|
fn append_message_constants(lines : Array[String], message : Message) -> Unit {
  let prefix = message_codegen_prefix(message)
  let identity = if message.is_extended_frame() {
    "extended \{message.frame_id()}; DBC id \{message.id}"
  } else {
    message.id.to_string()
  }
  lines.push("/// Message \{message.name} (\{identity}).")
  lines.push("pub const \{prefix}_ID : UInt = \{message.frame_id()}U")
  lines.push(
    "pub const \{prefix}_IS_EXTENDED_FRAME : Bool = \{message.is_extended_frame()}",
  )
  lines.push("pub const \{prefix}_PAYLOAD_SIZE : Int = \{message.payload_size}")
  lines.push("")
  for index, signal in message.signals {
    append_signal_constants(lines, message, signal, index)
  }
}

///|
fn append_value_constants(lines : Array[String], table : ValueTable) -> Unit {
  for index, entry in table.entries {
    let prefix = "DBC_VALUE_\{table.message_id}_\{codegen_identifier(table.signal_name)}_\{codegen_identifier(entry.label)}_\{index}"
    lines.push(
      "/// Value \{entry.label} for signal \{table.signal_name} in message \{table.message_id}.",
    )
    lines.push("pub const \{prefix} : Int64 = \{entry.value}L")
    lines.push("")
  }
}

///|
/// Generate standalone MoonBit constants for message identifiers, payload
/// sizes, signal layouts, scaling parameters, multiplexing, and value tables.
/// Names are normalized and include stable numeric suffixes to avoid clashes.
pub fn Database::generate_moonbit_constants(self : Database) -> String {
  let lines = ["// MoonDBC static CAN metadata.", ""]
  for message in self.messages {
    append_message_constants(lines, message)
  }
  for table in self.value_tables {
    append_value_constants(lines, table)
  }
  if lines.length() > 1 && lines[lines.length() - 1] == "" {
    let _ = lines.pop()
  }
  lines.join("\n")
}

///|
fn codec_byte_order(order : ByteOrder) -> String {
  match order {
    Intel => "@moondbc.Intel"
    Motorola => "@moondbc.Motorola"
  }
}

///|
fn codec_value_kind(kind : ValueKind) -> String {
  match kind {
    Unsigned => "@moondbc.Unsigned"
    Signed => "@moondbc.Signed"
  }
}

///|
fn codec_value_type(value_type : SignalValueType) -> String {
  match value_type {
    Integer => "@moondbc.Integer"
    Float32 => "@moondbc.Float32"
    Float64 => "@moondbc.Float64"
  }
}

///|
fn append_signal_codec(
  lines : Array[String],
  message : Message,
  signal : Signal,
  index : Int,
) -> Unit {
  let name = signal_codegen_prefix(message, signal, index).to_lower()
  lines.push("///|")
  lines.push("/// Signal \{index} of DBC message \{message.id}.")
  lines.push("fn \{name}_signal() -> @moondbc.Signal {")
  lines.push("  {")
  lines.push("    name: \"\",")
  lines.push("    start_bit: \{signal.start_bit},")
  lines.push("    bit_length: \{signal.bit_length},")
  lines.push("    byte_order: \{codec_byte_order(signal.byte_order)},")
  lines.push("    value_kind: \{codec_value_kind(signal.value_kind)},")
  lines.push("    value_type: \{codec_value_type(signal.value_type)},")
  lines.push("    factor: \{signal.factor},")
  lines.push("    offset: \{signal.offset},")
  lines.push("    minimum: \{signal.minimum},")
  lines.push("    maximum: \{signal.maximum},")
  lines.push("    unit: \"\",")
  lines.push("    receivers: [],")
  lines.push("    multiplex: @moondbc.Plain,")
  lines.push("  }")
  lines.push("}")
  lines.push("")
  lines.push("///|")
  lines.push("pub fn decode_\{name}(")
  lines.push("  payload : BytesView,")
  lines.push(") -> Result[Double, @moondbc.DecodeError] {")
  lines.push("  \{name}_signal().decode(payload)")
  lines.push("}")
  lines.push("")
  lines.push("///|")
  lines.push("pub fn encode_\{name}(")
  lines.push("  payload : BytesView,")
  lines.push("  physical : Double,")
  lines.push(") -> Result[Bytes, @moondbc.EncodeError] {")
  lines.push("  \{name}_signal().encode(payload, physical)")
  lines.push("}")
  lines.push("")
}

///|
/// Generate named, signal-level encode and decode functions that do not parse
/// DBC text at runtime. The destination package must import this library as
/// `@moondbc`. Multiplex branch selection remains the caller's responsibility;
/// use `Message::decode_frame` and `Message::encode_frame` for whole frames.
pub fn Database::generate_moonbit_signal_codecs(
  self : Database,
) -> Result[String, String] {
  if !self.validate().is_empty() {
    return Err("cannot generate codecs from an invalid DBC model")
  }
  let lines = [
    "// MoonDBC signal codecs. Import cn-cheems/moondbc as @moondbc.", "",
  ]
  for message in self.messages {
    for index, signal in message.signals {
      append_signal_codec(lines, message, signal, index)
    }
  }
  if lines.length() > 1 && lines[lines.length() - 1] == "" {
    let _ = lines.pop()
  }
  Ok(lines.join("\n"))
}