///| Git object types and common errors

///|
/// SHA-1 object identifier (20 bytes)
pub struct ObjectId {
  bytes : FixedArray[Byte] // 20 bytes
}

///|
pub fn ObjectId::new(bytes : FixedArray[Byte]) -> ObjectId {
  { bytes, }
}

///|
pub fn ObjectId::zero() -> ObjectId {
  { bytes: FixedArray::make(20, b'\x00') }
}

///|
/// Convert ObjectId to Bytes (20 bytes) - zero-copy when possible
pub fn ObjectId::to_bytes(self : ObjectId) -> Bytes {
  Bytes::from_array(self.bytes.iter().collect())
}

///|
/// Create ObjectId from Bytes (must be exactly 20 bytes)
pub fn ObjectId::from_bytes(data : Bytes) -> ObjectId raise GitError {
  if data.length() != 20 {
    raise GitError::InvalidObject("Invalid ObjectId length: \{data.length()}")
  }
  let bytes = FixedArray::make(20, b'\x00')
  for i = 0; i < 20; i = i + 1 {
    bytes[i] = data[i]
  }
  { bytes, }
}

///|
/// Create ObjectId from Bytes at offset (for parsing packed data)
pub fn ObjectId::from_bytes_at(data : Bytes, offset : Int) -> ObjectId {
  let bytes = FixedArray::make(20, b'\x00')
  for i = 0; i < 20; i = i + 1 {
    bytes[i] = data[offset + i]
  }
  { bytes, }
}

///|
pub fn ObjectId::from_hex(hex : String) -> ObjectId raise GitError {
  if hex.length() != 40 {
    raise GitError::InvalidObject("Invalid hex length: \{hex.length()}")
  }
  let bytes = FixedArray::make(20, b'\x00')
  let chars = hex.to_array()
  for i = 0; i < 20; i = i + 1 {
    let hi = hex_char_to_int(chars[i * 2])
    let lo = hex_char_to_int(chars[i * 2 + 1])
    bytes[i] = ((hi << 4) | lo).to_byte()
  }
  { bytes, }
}

///|
fn hex_char_to_int(c : Char) -> Int raise GitError {
  if c >= '0' && c <= '9' {
    c.to_int() - '0'.to_int()
  } else if c >= 'a' && c <= 'f' {
    c.to_int() - 'a'.to_int() + 10
  } else if c >= 'A' && c <= 'F' {
    c.to_int() - 'A'.to_int() + 10
  } else {
    raise GitError::InvalidObject("Invalid hex char: \{c}")
  }
}

///|
pub fn ObjectId::to_hex(self : ObjectId) -> String {
  let result = StringBuilder::new()
  for b in self.bytes {
    let hi = (b.to_int() >> 4) & 0x0f
    let lo = b.to_int() & 0x0f
    result.write_char(int_to_hex_char(hi))
    result.write_char(int_to_hex_char(lo))
  }
  result.to_string()
}

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

///|
pub impl Show for ObjectId with output(self, logger) {
  logger.write_string(self.to_hex())
}

///|
pub impl Eq for ObjectId with equal(self, other) {
  for i = 0; i < 20; i = i + 1 {
    if self.bytes[i] != other.bytes[i] {
      return false
    }
  }
  true
}

///|
/// Hash implementation for using ObjectId as Map key
pub impl Hash for ObjectId with hash_combine(self, hasher) {
  // Use first 8 bytes as hash (sufficient for distribution)
  for i = 0; i < 8; i = i + 1 {
    self.bytes[i].hash_combine(hasher)
  }
}

///|
/// Git object types
pub(all) enum ObjectType {
  Blob
  Tree
  Commit
  Tag
} derive(Eq)

///|
pub fn ObjectType::to_string(self : ObjectType) -> String {
  match self {
    Blob => "blob"
    Tree => "tree"
    Commit => "commit"
    Tag => "tag"
  }
}

///|
pub fn ObjectType::to_packfile_type(self : ObjectType) -> Int {
  match self {
    Commit => 1
    Tree => 2
    Blob => 3
    Tag => 4
  }
}

///|
pub impl Show for ObjectType with output(self, logger) {
  logger.write_string(self.to_string())
}

///|
/// Git operation errors
pub(all) suberror GitError {
  InvalidObject(String)
  HashMismatch(String, String)
  PackfileError(String)
  ProtocolError(String)
  IoError(String)
} derive(Show, Eq)

///|
/// Tree entry in a Git tree object
pub struct TreeEntry {
  mode : String // "100644", "040000", etc.
  name : String
  id : ObjectId
}

///|
pub fn TreeEntry::new(mode : String, name : String, id : ObjectId) -> TreeEntry {
  { mode, name, id }
}

///|
/// Git commit object
pub struct Commit {
  tree : ObjectId
  parents : Array[ObjectId]
  author : String
  author_time : Int64
  author_tz : String
  committer : String
  commit_time : Int64
  committer_tz : String
  message : String
}

///|
pub fn Commit::new(
  tree : ObjectId,
  parents : Array[ObjectId],
  author : String,
  author_time : Int64,
  author_tz : String,
  committer : String,
  commit_time : Int64,
  committer_tz : String,
  message : String,
) -> Commit {
  {
    tree,
    parents,
    author,
    author_time,
    author_tz,
    committer,
    commit_time,
    committer_tz,
    message,
  }
}