///| Git object creation and hashing

///|
/// Helper to convert Array[Byte] to Bytes
pub fn array_to_bytes(arr : Array[Byte]) -> Bytes {
  Bytes::from_iter(arr.iter())
}

///|
/// Helper to convert String to byte array (ASCII encoding)
fn string_to_byte_array(s : String) -> Array[Byte] {
  let arr : Array[Byte] = []
  for c in s {
    arr.push(c.to_int().to_byte())
  }
  arr
}

///|
fn object_append_bytes(out : Array[Byte], data : Bytes) -> Unit {
  for b in data {
    out.push(b)
  }
}

///|
fn object_append_utf8_string(out : Array[Byte], s : String) -> Unit {
  object_append_bytes(out, @utf8.encode(s))
}

///|
fn object_normalize_encoding_name(value : String) -> String {
  let out = StringBuilder::new()
  for c in value.trim().to_lower() {
    if c == '-' || c == '_' || c == ' ' {
      continue
    }
    out.write_char(c)
  }
  out.to_string()
}

///|
fn object_is_utf8_encoding(encoding : String) -> Bool {
  let normalized = object_normalize_encoding_name(encoding)
  normalized == "" || normalized == "utf8"
}

///|
fn object_is_iso_8859_1_encoding(encoding : String) -> Bool {
  let normalized = object_normalize_encoding_name(encoding)
  normalized == "iso88591" || normalized == "latin1" || normalized == "latin"
}

///|
fn object_commit_message_bytes(
  message : String,
  encoding : String,
) -> Array[Byte] {
  if object_is_iso_8859_1_encoding(encoding) {
    let out : Array[Byte] = []
    for c in message {
      let code = c.to_int()
      if code <= 0xff {
        out.push(code.to_byte())
      } else {
        object_append_bytes(out, @utf8.encode(c.to_string()))
      }
    }
    out
  } else {
    @utf8.encode(message).to_array()
  }
}

///|
fn object_serialize_commit_content_array(commit : Commit) -> Array[Byte] {
  let content : Array[Byte] = []
  object_append_utf8_string(content, "tree ")
  object_append_utf8_string(content, commit.tree.to_hex())
  content.push(b'\n')
  for parent in commit.parents {
    object_append_utf8_string(content, "parent ")
    object_append_utf8_string(content, parent.to_hex())
    content.push(b'\n')
  }
  object_append_utf8_string(content, "author ")
  object_append_utf8_string(content, commit.author)
  object_append_utf8_string(content, " ")
  object_append_utf8_string(content, commit.author_time.to_string())
  object_append_utf8_string(content, " ")
  object_append_utf8_string(content, commit.author_tz)
  content.push(b'\n')
  object_append_utf8_string(content, "committer ")
  object_append_utf8_string(content, commit.committer)
  object_append_utf8_string(content, " ")
  object_append_utf8_string(content, commit.commit_time.to_string())
  object_append_utf8_string(content, " ")
  object_append_utf8_string(content, commit.committer_tz)
  content.push(b'\n')
  if commit.encoding.length() > 0 && !object_is_utf8_encoding(commit.encoding) {
    object_append_utf8_string(content, "encoding ")
    object_append_utf8_string(content, commit.encoding)
    content.push(b'\n')
  }
  content.push(b'\n')
  let msg_bytes = object_commit_message_bytes(commit.message, commit.encoding)
  content.append(msg_bytes)
  // For verbatim mode (commit-tree), store message exactly as given.
  // Otherwise, ensure trailing newline (git commit behavior).
  if !commit.verbatim_message {
    if msg_bytes.length() == 0 || msg_bytes[msg_bytes.length() - 1] != b'\n' {
      content.push(b'\n')
    }
  }
  content
}

///|
/// Create a Git object and return its ID and compressed data
/// Format: {type} {size}\0{content} -> SHA-1 -> zlib compress
pub fn create_object(
  obj_type : ObjectType,
  content : Bytes,
) -> (ObjectId, Bytes) {
  let raw = format_object(obj_type, content)
  let id = sha1(raw)
  let compressed = @zlib.zlib_compress(raw)
  (id, compressed)
}

///|
/// Hash object content (without compression) - optimized with incremental SHA1
pub fn hash_object_content(obj_type : ObjectType, content : Bytes) -> ObjectId {
  hash_object_content_with_algo(Sha1, obj_type, content)
}

///|
/// Format a Git object (without compression)
fn format_object(obj_type : ObjectType, content : Bytes) -> Bytes {
  let header = "\{obj_type.to_string()} \{content.length()}\u0000"
  let result : Array[Byte] = []
  for c in header {
    result.push(c.to_int().to_byte())
  }
  for b in content {
    result.push(b)
  }
  array_to_bytes(result)
}

///|
/// Create a blob object from content
pub fn create_blob(content : Bytes) -> (ObjectId, Bytes) {
  create_object(ObjectType::Blob, content)
}

///|
/// Create a blob from string content
pub fn create_blob_string(content : String) -> (ObjectId, Bytes) {
  content |> string_to_byte_array |> array_to_bytes |> create_blob
}

///|
/// Create a tree object from entries
/// Entries must be sorted by name
pub fn create_tree(entries : Array[TreeEntry]) -> (ObjectId, Bytes) {
  let content : Array[Byte] = []
  for entry in entries {
    // mode (as ASCII)
    for c in entry.mode {
      content.push(c.to_int().to_byte())
    }
    // space
    content.push(b' ')
    // name (as UTF-8 bytes)
    object_append_utf8_string(content, entry.name)
    // null byte
    content.push(b'\x00')
    // 20-byte SHA
    for b in entry.id.bytes {
      content.push(b)
    }
  }
  create_object(ObjectType::Tree, array_to_bytes(content))
}

///|
/// Create a commit object
pub fn create_commit(commit : Commit) -> (ObjectId, Bytes) {
  create_object(ObjectType::Commit, serialize_commit_content(commit))
}

///|
/// Hash a blob without creating the compressed object - optimized with incremental SHA1
pub fn hash_blob(content : Bytes) -> ObjectId {
  hash_object_content(ObjectType::Blob, content)
}

///|
/// Serialize tree entries to raw content bytes (without git object header)
pub fn serialize_tree(entries : Array[TreeEntry]) -> Bytes {
  let content : Array[Byte] = []
  for entry in entries {
    for c in entry.mode {
      content.push(c.to_int().to_byte())
    }
    content.push(b' ')
    object_append_utf8_string(content, entry.name)
    content.push(b'\x00')
    for b in entry.id.bytes {
      content.push(b)
    }
  }
  array_to_bytes(content)
}

///|
/// Serialize commit to raw content bytes (without git object header)
pub fn serialize_commit_content(commit : Commit) -> Bytes {
  array_to_bytes(object_serialize_commit_content_array(commit))
}

///|
/// Hash a blob from string - optimized with incremental SHA1
pub fn hash_blob_string(content : String) -> ObjectId {
  let state = Sha1State::new()
  // Count bytes for header (ASCII only, same as original behavior)
  let byte_len = content.iter().count()
  let header = "blob \{byte_len}\u0000"
  state.update_string(header)
  state.update_string(content)
  state.finish()
}