///| Ref resolution helpers (rev-parse/show-ref)

///|
pub fn rev_parse(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  spec : String,
) -> @bit.ObjectId? raise @bit.GitError {
  if spec.has_prefix(":/") && spec.length() > 2 {
    let needle = substring_revparse(spec, 2, spec.length())
    return resolve_commit_message_search_all_refs(fs, git_dir, needle)
  }
  if spec.has_suffix("}") && spec.find("@{") is Some(_) {
    match resolve_reflog_numeric_selector(fs, git_dir, spec) {
      Some(id) => return Some(id)
      None => ()
    }
  }
  match parse_commit_message_search_spec(spec) {
    Some((base_spec, needle)) =>
      return resolve_commit_message_search(fs, git_dir, base_spec, needle)
    None => ()
  }
  match spec.find(":") {
    Some(idx) =>
      if idx > 0 && idx + 1 <= spec.length() {
        let rev_part = substring_revparse(spec, 0, idx)
        let path_part = substring_revparse(spec, idx + 1, spec.length())
        let base = rev_parse(fs, git_dir, rev_part)
        guard base is Some(base_id) else { return None }
        return resolve_path_in_commit(fs, git_dir, base_id, path_part)
      }
    None => ()
  }
  match parse_peel_commit_spec(spec) {
    Some(base_spec) => {
      let base = rev_parse(fs, git_dir, base_spec)
      guard base is Some(base_id) else { return None }
      return peel_object_to_commit(fs, git_dir, base_id)
    }
    None => ()
  }
  match parse_peel_tree_spec(spec) {
    Some(base_spec) => {
      let base = rev_parse(fs, git_dir, base_spec)
      guard base is Some(base_id) else { return None }
      return peel_object_to_tree(fs, git_dir, base_id)
    }
    None => ()
  }
  match parse_peel_tag_spec(spec) {
    Some(base_spec) => {
      let base = rev_parse(fs, git_dir, base_spec)
      guard base is Some(base_id) else { return None }
      return peel_object_to_tag(fs, git_dir, base_id)
    }
    None => ()
  }
  // Parse suffix (^, ^^, ~n, ^n)
  let (base_spec, suffix) = parse_rev_suffix(spec)
  // Resolve base reference
  let base_id = match resolve_base_ref(fs, git_dir, base_spec) {
    Some(id) => id
    None => return None
  }
  match suffix {
    None => Some(base_id)
    Some(parsed_suffix) => apply_rev_suffix(fs, git_dir, base_id, parsed_suffix)
  }
}

///|
fn parse_commit_message_search_spec(spec : String) -> (String, String)? {
  let marker = "^{/"
  match spec.find(marker) {
    Some(idx) if idx > 0 &&
      spec.has_suffix("}") &&
      idx + marker.length() <= spec.length() - 1 =>
      Some(
        (
          substring_revparse(spec, 0, idx),
          substring_revparse(spec, idx + marker.length(), spec.length() - 1),
        ),
      )
    _ => None
  }
}

///|
fn resolve_commit_message_search(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  base_spec : String,
  needle : String,
) -> @bit.ObjectId? raise @bit.GitError {
  if needle.length() == 0 {
    return None
  }
  let base = rev_parse(fs, git_dir, base_spec)
  guard base is Some(base_id) else { return None }
  find_reachable_commit_by_message_from_many(fs, git_dir, [base_id], needle)
}

///|
fn resolve_commit_message_search_all_refs(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  needle : String,
) -> @bit.ObjectId? raise @bit.GitError {
  if needle.length() == 0 {
    return None
  }
  let starts : Array[@bit.ObjectId] = []
  let seen_start : Map[String, Bool] = Map([])
  let refs = show_ref(fs, git_dir)
  for item in refs {
    let (_, id) = item
    let hex = id.to_hex()
    if !seen_start.contains(hex) {
      seen_start[hex] = true
      starts.push(id)
    }
  }
  match resolve_head_commit(fs, git_dir) {
    Some(id) => {
      let hex = id.to_hex()
      if !seen_start.contains(hex) {
        seen_start[hex] = true
        starts.push(id)
      }
    }
    None => ()
  }
  if starts.length() == 0 {
    return None
  }
  find_reachable_commit_by_message_from_many(fs, git_dir, starts, needle)
}

///|
fn find_reachable_commit_by_message_from_many(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  starts : Array[@bit.ObjectId],
  needle : String,
) -> @bit.ObjectId? raise @bit.GitError {
  let db = ObjectDb::load(fs, git_dir)
  let queue : Array[@bit.ObjectId] = []
  for start in starts {
    queue.push(start)
  }
  let seen : Map[String, Bool] = Map([])
  let mut best_id : @bit.ObjectId? = None
  let mut best_time = -1L
  while queue.length() > 0 {
    let current = queue.unsafe_pop()
    let hex = current.to_hex()
    if seen.contains(hex) {
      continue
    }
    seen[hex] = true
    match db.get(fs, current) {
      Some(obj) =>
        if obj.obj_type == @bit.ObjectType::Commit {
          if commit_message_contains(obj.data, needle) {
            let ts = parse_commit_committer_timestamp(obj.data)
            if best_id is None || ts > best_time {
              best_id = Some(current)
              best_time = ts
            }
          }
          let info = @bit.parse_commit(obj.data)
          for parent in info.parents {
            if !seen.contains(parent.to_hex()) {
              queue.push(parent)
            }
          }
        }
      None => ()
    }
  }
  best_id
}

///|
fn commit_message_contains(data : Bytes, needle : String) -> Bool {
  let text = @utf8.decode_lossy(data[:])
  match text.find("\n\n") {
    Some(idx) =>
      substring_revparse(text, idx + 2, text.length()).find(needle) is Some(_)
    None => false
  }
}

///|
fn parse_commit_committer_timestamp(data : Bytes) -> Int64 {
  let text = @utf8.decode_lossy(data[:])
  for line_view in text.split("\n") {
    let line = line_view.to_owned()
    if line.length() == 0 {
      break
    }
    if line.has_prefix("committer ") {
      let rest = substring_revparse(line, 10, line.length())
      return parse_identity_timestamp(rest)
    }
  }
  0L
}

///|
fn parse_identity_timestamp(identity : String) -> Int64 {
  let last_space = identity.rev_find(" ")
  guard last_space is Some(tz_idx) else { return 0L }
  let before_tz = substring_revparse(identity, 0, tz_idx)
  let second_last = before_tz.rev_find(" ")
  guard second_last is Some(ts_idx) else { return 0L }
  let ts = substring_revparse(before_tz, ts_idx + 1, before_tz.length())
  parse_revparse_int64(ts)
}

///|
fn parse_revparse_int64(s : String) -> Int64 {
  let mut value = 0L
  for c in s {
    if c < '0' || c > '9' {
      return value
    }
    let digit = c.to_int() - '0'.to_int()
    value = value * 10L + digit.to_int64()
  }
  value
}

///|
fn parse_peel_commit_spec(spec : String) -> String? {
  let suffix = "^{commit}"
  if spec.has_suffix(suffix) && spec.length() > suffix.length() {
    return Some(substring_revparse(spec, 0, spec.length() - suffix.length()))
  }
  None
}

///|
fn parse_peel_tree_spec(spec : String) -> String? {
  let suffix = "^{tree}"
  if spec.has_suffix(suffix) && spec.length() > suffix.length() {
    return Some(substring_revparse(spec, 0, spec.length() - suffix.length()))
  }
  None
}

///|
fn peel_object_to_commit(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  id : @bit.ObjectId,
) -> @bit.ObjectId? raise @bit.GitError {
  let db = ObjectDb::load(fs, git_dir)
  let mut current = id
  for _ in 0..<8 {
    let obj = db.get(fs, current)
    guard obj is Some(o) else { return None }
    match o.obj_type {
      @bit.ObjectType::Commit => return Some(current)
      @bit.ObjectType::Tag =>
        match parse_tag_object_target(o.data) {
          Some(next) => current = next
          None => return None
        }
      _ => return None
    }
  }
  None
}

///|
fn peel_object_to_tree(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  id : @bit.ObjectId,
) -> @bit.ObjectId? raise @bit.GitError {
  let db = ObjectDb::load(fs, git_dir)
  let mut current = id
  for _ in 0..<8 {
    let obj = db.get(fs, current)
    guard obj is Some(o) else { return None }
    match o.obj_type {
      @bit.ObjectType::Tree => return Some(current)
      @bit.ObjectType::Commit => return Some(@bit.parse_commit(o.data).tree)
      @bit.ObjectType::Tag =>
        match parse_tag_object_target(o.data) {
          Some(next) => current = next
          None => return None
        }
      _ => return None
    }
  }
  None
}

///|
fn parse_peel_tag_spec(spec : String) -> String? {
  let suffix = "^{tag}"
  if spec.has_suffix(suffix) && spec.length() > suffix.length() {
    return Some(substring_revparse(spec, 0, spec.length() - suffix.length()))
  }
  None
}

///|
fn peel_object_to_tag(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  id : @bit.ObjectId,
) -> @bit.ObjectId? raise @bit.GitError {
  let db = ObjectDb::load(fs, git_dir)
  let obj = db.get(fs, id)
  guard obj is Some(o) else { return None }
  if o.obj_type == @bit.ObjectType::Tag {
    return Some(id)
  }
  None
}

///|
fn parse_tag_object_target(data : Bytes) -> @bit.ObjectId? {
  let text = @utf8.decode_lossy(data[:])
  for line_view in text.split("\n") {
    let line = line_view.to_owned()
    if line.length() == 0 {
      break
    }
    if line.has_prefix("object ") {
      let hex = substring_revparse(line, 7, line.length())
      let id = @bit.ObjectId::from_hex(hex) catch { _ => return None }
      return Some(id)
    }
  }
  None
}

///|
/// Parse revision suffix like ^, ^^, ~n
fn parse_rev_suffix(spec : String) -> (String, String?) {
  let chars = spec.to_array()
  let len = chars.length()
  if len == 0 {
    return (spec, None)
  }
  // Find where suffix starts - must start with ^ or ~
  let mut suffix_start = -1
  for i = 0; i < len; i = i + 1 {
    let c = chars[i]
    if c == '^' || c == '~' {
      suffix_start = i
      break
    }
  }
  if suffix_start < 0 {
    return (spec, None)
  }
  // Validate suffix - only ^, ~, and digits after the first ^ or ~
  for i = suffix_start; i < len; i = i + 1 {
    let c = chars[i]
    if !(c == '^' || c == '~' || (c >= '0' && c <= '9')) {
      // Invalid suffix character - no suffix
      return (spec, None)
    }
  }
  let base = substring_revparse(spec, 0, suffix_start)
  let suffix = substring_revparse(spec, suffix_start, len)
  (base, Some(suffix))
}

///|
/// Apply revision suffix operators.
fn apply_rev_suffix(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  start : @bit.ObjectId,
  suffix : String,
) -> @bit.ObjectId? raise @bit.GitError {
  let db = ObjectDb::load(fs, git_dir)
  let chars = suffix.to_array()
  let mut current = start
  let mut i = 0
  while i < chars.length() {
    let op = chars[i]
    if op == '^' {
      i += 1
      let mut parent_index = 0
      let mut has_digits = false
      while i < chars.length() && chars[i] >= '0' && chars[i] <= '9' {
        has_digits = true
        parent_index = parent_index * 10 + (chars[i].to_int() - '0'.to_int())
        i += 1
      }
      if has_digits {
        // `^0` peels to the commit itself.
        if parent_index == 0 {
          continue
        }
      } else {
        parent_index = 1
      }
      let next = commit_nth_parent(db, fs, git_dir, current, parent_index)
      guard next is Some(parent_id) else { return None }
      current = parent_id
      continue
    }
    if op == '~' {
      i += 1
      let mut steps = 0
      let mut has_digits = false
      while i < chars.length() && chars[i] >= '0' && chars[i] <= '9' {
        has_digits = true
        steps = steps * 10 + (chars[i].to_int() - '0'.to_int())
        i += 1
      }
      if !has_digits {
        steps = 1
      }
      for _ in 0.. @bit.ObjectId? raise @bit.GitError {
  if nth_parent <= 0 {
    return Some(commit_id)
  }
  let obj = db.get(fs, commit_id)
  guard obj is Some(o) else { return None }
  if o.obj_type != @bit.ObjectType::Commit {
    return None
  }
  let info = @bit.parse_commit(o.data)
  let parents = match revparse_read_graft_parents(fs, git_dir, commit_id) {
    Some(grafted) => grafted
    None => info.parents
  }
  if nth_parent > parents.length() {
    return None
  }
  Some(parents[nth_parent - 1])
}

///|
/// Read `.git/info/grafts` and return the grafted parent list for `commit_id`
/// if a graft entry exists for it. Each line is ` ...`.
/// Returns None when there is no graft entry (or the file is missing).
fn revparse_read_graft_parents(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  commit_id : @bit.ObjectId,
) -> Array[@bit.ObjectId]? {
  let graft_path = join_path(git_dir, "info/grafts")
  if !fs.is_file(graft_path) {
    return None
  }
  let raw = fs.read_file(graft_path) catch { _ => return None }
  let content = @utf8.decode_lossy(raw[:])
  let commit_hex = commit_id.to_hex()
  for line_view in content.split("\n") {
    let line = @string_utils.trim_string(line_view.to_owned())
    if line.length() == 0 || line.has_prefix("#") {
      continue
    }
    let tokens : Array[String] = []
    for token_view in line.split(" ") {
      let token = @string_utils.trim_string(token_view.to_owned())
      if token.length() > 0 {
        tokens.push(token)
      }
    }
    if tokens.length() == 0 || tokens[0] != commit_hex {
      continue
    }
    let parents : Array[@bit.ObjectId] = []
    for i in 1.. continue
        }
        parents.push(parent_id)
      }
    }
    return Some(parents)
  }
  None
}

///|
fn substring_revparse(s : String, start : Int, end : Int) -> String {
  let chars = s.to_array()
  let result = StringBuilder::new()
  for i = start; i < end && i < chars.length(); i = i + 1 {
    result.write_char(chars[i])
  }
  result.to_string()
}

///|
fn resolve_abbrev_ref_id(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  spec : String,
) -> @bit.ObjectId? raise @bit.GitError {
  if spec.length() < 4 || spec.length() >= 40 || !@bithash.is_hex_string(spec) {
    return None
  }
  let needle = @bithash.lower_hex_string(spec)
  let refs = show_ref(fs, git_dir)
  let db = ObjectDb::load(fs, git_dir)
  let queue : Array[@bit.ObjectId] = []
  let seen : Map[String, Bool] = Map([])
  for item in refs {
    let (_, id) = item
    queue.push(id)
  }
  match resolve_head_commit(fs, git_dir) {
    Some(id) => queue.push(id)
    None => ()
  }
  let mut matched : @bit.ObjectId? = None
  while queue.length() > 0 {
    let id = match queue.pop() {
      Some(oid) => oid
      None => break
    }
    let hex = id.to_hex()
    if seen.contains(hex) {
      continue
    }
    seen[hex] = true
    if hex.has_prefix(needle) {
      match matched {
        None => matched = Some(id)
        Some(prev) => if prev != id { return None }
      }
    }
    match db.get(fs, id) {
      Some(obj) =>
        if obj.obj_type == @bit.ObjectType::Commit {
          let info = @bit.parse_commit(obj.data)
          for parent in info.parents {
            queue.push(parent)
          }
        }
      None => ()
    }
  }
  matched
}

///|
/// Resolve base reference (without suffix)
fn resolve_base_ref(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  spec : String,
) -> @bit.ObjectId? raise @bit.GitError {
  if spec == "HEAD" || spec == "" || spec == "@" {
    return resolve_head_commit(fs, git_dir)
  }
  if resolve_ref(fs, git_dir, spec) is Some(id) {
    return Some(id)
  }
  if spec.has_prefix("refs/") {
    return resolve_ref(fs, git_dir, spec)
  }
  if spec.has_prefix("heads/") ||
    spec.has_prefix("tags/") ||
    spec.has_prefix("remotes/") {
    return resolve_ref(fs, git_dir, "refs/" + spec)
  }
  if (spec.length() == 40 || spec.length() == 64) &&
    @bithash.is_hex_string(spec) {
    match @bit.ObjectId::from_hex(spec) {
      id => return Some(id)
    }
  }
  if resolve_abbrev_ref_id(fs, git_dir, spec) is Some(id) {
    return Some(id)
  }
  if resolve_ref(fs, git_dir, "refs/heads/" + spec) is Some(id) {
    return Some(id)
  }
  if resolve_ref(fs, git_dir, "refs/remotes/" + spec) is Some(id) {
    return Some(id)
  }
  if resolve_ref(fs, git_dir, "refs/tags/" + spec) is Some(id) {
    return Some(id)
  }
  None
}

///|
/// Resolve path within a commit/tree
fn resolve_path_in_commit(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  base_id : @bit.ObjectId,
  path : String,
) -> @bit.ObjectId? raise @bit.GitError {
  let db = ObjectDb::load(fs, git_dir)
  let obj = db.get(fs, base_id)
  match obj {
    None => return None
    Some(o) => {
      let tree_id = match o.obj_type {
        @bit.ObjectType::Commit => @bit.parse_commit(o.data).tree
        @bit.ObjectType::Tree => base_id
        _ => return None
      }
      // Empty path means "return the tree itself" (e.g., HEAD:)
      if path.length() == 0 {
        return Some(tree_id)
      }
      let entry = find_tree_entry(db, fs, tree_id, path)
      match entry {
        Some(e) => Some(e.id)
        None => None
      }
    }
  }
}

///|
/// List all refs (loose + packed).
pub fn show_ref(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
) -> Array[(String, @bit.ObjectId)] raise @bit.GitError {
  let out : Map[String, @bit.ObjectId] = Map([])
  // Collect from reftable if present
  if fs.is_dir(join_path(git_dir, "reftable")) {
    let reftable_refs = @reftable.collect_reftable_refs(fs, git_dir)
    for pair in reftable_refs {
      let (name, id) = pair
      out[name] = id
    }
  }
  let refs_dir = join_path(git_dir, "refs")
  if fs.is_dir(refs_dir) {
    ref_collect_loose(fs, git_dir, refs_dir, "refs", out)
  }
  let packed = join_path(git_dir, "packed-refs")
  if fs.is_file(packed) {
    ref_collect_packed(fs, packed, out)
  }
  let result : Array[(String, @bit.ObjectId)] = Array::new(
    capacity=out.length(),
  )
  for name, id in out {
    result.push((name, id))
  }
  result.sort_by((a, b) => String::compare(a.0, b.0))
  result
}

///|
/// Format like `git show-ref` output.
pub fn show_ref_text(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
) -> Array[String] raise @bit.GitError {
  let refs = show_ref(fs, git_dir)
  let lines : Array[String] = []
  for item in refs {
    let (name, id) = item
    lines.push("\{id.to_hex()} \{name}")
  }
  lines
}

///|
fn ref_collect_loose(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  dir : String,
  prefix : String,
  out : Map[String, @bit.ObjectId],
) -> Unit raise @bit.GitError {
  let entries = fs.readdir(dir)
  for name in entries {
    let path = join_path(dir, name)
    let rel = if prefix == "" { name } else { prefix + "/" + name }
    if fs.is_dir(path) {
      ref_collect_loose(fs, git_dir, path, rel, out)
    } else if fs.is_file(path) {
      let line = ref_read_line(fs, path)
      if line.has_prefix("ref: ") {
        let target = String::unsafe_substring(line, start=5, end=line.length())
        let resolved = resolve_ref(fs, git_dir, target) catch { _ => None }
        match resolved {
          Some(id) => out[rel] = id
          None => ()
        }
      } else {
        out[rel] = @bit.ObjectId::from_hex(line)
      }
    }
  }
}

///|
fn ref_collect_packed(
  fs : &@bit.RepoFileSystem,
  packed_path : String,
  out : Map[String, @bit.ObjectId],
) -> Unit raise @bit.GitError {
  let text = @utf8.decode_lossy(fs.read_file(packed_path)[:])
  for line_view in text.split("\n") {
    let line = ref_trim_line(line_view.to_owned())
    if line.length() == 0 {
      continue
    }
    if line.has_prefix("#") || line.has_prefix("^") {
      continue
    }
    let space = line.find(" ")
    match space {
      None => continue
      Some(idx) => {
        if idx + 1 >= line.length() {
          continue
        }
        let id_hex = String::unsafe_substring(line, start=0, end=idx)
        let name = String::unsafe_substring(
          line,
          start=idx + 1,
          end=line.length(),
        )
        if !out.contains(name) {
          out[name] = @bit.ObjectId::from_hex(id_hex)
        }
      }
    }
  }
}

///|
fn ref_read_line(
  fs : &@bit.RepoFileSystem,
  path : String,
) -> String raise @bit.GitError {
  let text = @utf8.decode_lossy(fs.read_file(path)[:])
  for line_view in text.split("\n") {
    let line = ref_trim_line(line_view.to_owned())
    if line.length() > 0 {
      return line
    }
  }
  raise @bit.GitError::InvalidObject("Empty ref: \{path}")
}

///|
fn ref_trim_line(line : String) -> String {
  let mut s = line
  if s.has_suffix("\r") {
    s = String::unsafe_substring(s, start=0, end=s.length() - 1)
  }
  s
}

// ─── Pure parsing helpers (extracted from src/cmd/bit/rev_parse.mbt) ───

///|
/// Parse `--short=N` value into an integer.
pub fn parse_rev_parse_short_length(value : String) -> Int? {
  if value.length() == 0 {
    return None
  }
  let mut result = 0
  for c in value {
    if c < '0' || c > '9' {
      return None
    }
    result = result * 10 + (c.to_int() - '0'.to_int())
  }
  Some(result)
}

///|
/// Resolve a reflog numeric selector like `main@{2}` or `@{0}` (`@{2}` counts
/// backward from the ref's current position: `@{0}` is where it points now,
/// `@{1}` is its previous position, and so on). Returns None for anything
/// that isn't this exact numeric form (e.g. `@{upstream}`, `@{2.days.ago}`),
/// leaving those to their own resolution paths.
fn resolve_reflog_numeric_selector(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  spec : String,
) -> @bit.ObjectId? raise @bit.GitError {
  guard parse_reflog_selector(spec) is Some((raw_refname, index)) else {
    return None
  }
  let refname = if raw_refname.length() == 0 {
    match read_head_ref(fs, git_dir) {
      HeadRef::Branch(name) => "refs/heads/" + name
      HeadRef::Detached(_) => return None
    }
  } else {
    raw_refname
  }
  let entries = read_reflog(fs, git_dir, refname)
  let len = entries.length()
  if index == 0 && len == 0 {
    return resolve_ref(fs, git_dir, refname)
  }
  if index < len {
    return Some(entries[len - 1 - index].new_id)
  }
  if index == len && len > 0 {
    let old_id = entries[0].old_id
    if old_id != @bit.ObjectId::zero() {
      return Some(old_id)
    }
  }
  None
}

///|
/// Parse a reflog numeric selector like `main@{2}` or `@{0}`.
/// Returns (refname, index) where refname is canonicalised.
pub fn parse_reflog_selector(spec : String) -> (String, Int)? {
  guard spec.has_suffix("}") else { return None }
  match spec.find("@{") {
    Some(idx) => {
      let number_start = idx + 2
      guard number_start < spec.length() - 1 else { return None }
      let number_text = String::unsafe_substring(
        spec,
        start=number_start,
        end=spec.length() - 1,
      )
      let mut number = 0
      for c in number_text {
        if c < '0' || c > '9' {
          return None
        }
        number = number * 10 + (c.to_int() - '0'.to_int())
      }
      let raw_ref = String::unsafe_substring(spec, start=0, end=idx)
      let refname = if raw_ref == "HEAD" {
        "HEAD"
      } else if raw_ref.length() == 0 {
        ""
      } else if raw_ref.has_prefix("refs/") {
        raw_ref
      } else {
        "refs/heads/" + raw_ref
      }
      Some((refname, number))
    }
    None => None
  }
}

///|
/// Parse a reflog date selector like `main@{May 25 2005}` or `HEAD@{2005-05-26}`.
/// Returns (refname, date_string) when the content is non-numeric.
pub fn parse_reflog_date_selector(spec : String) -> (String, String)? {
  guard spec.has_suffix("}") else { return None }
  match spec.find("@{") {
    Some(idx) => {
      let content_start = idx + 2
      guard content_start < spec.length() - 1 else { return None }
      let content = String::unsafe_substring(
        spec,
        start=content_start,
        end=spec.length() - 1,
      )
      let mut all_digits = true
      for c in content {
        if c < '0' || c > '9' {
          all_digits = false
          break
        }
      }
      if all_digits {
        return None
      }
      let raw_ref = String::unsafe_substring(spec, start=0, end=idx)
      let refname = if raw_ref == "HEAD" {
        "HEAD"
      } else if raw_ref.length() == 0 {
        ""
      } else if raw_ref.has_prefix("refs/") {
        raw_ref
      } else {
        "refs/heads/" + raw_ref
      }
      Some((refname, content))
    }
    None => None
  }
}

///|
/// Parse `^@`, `^!`, `^-N` parent suffixes.
/// Returns (base_spec, suffix_type) where suffix_type is "@", "!", "-N", etc.
pub fn rev_parse_parse_parent_suffix(spec : String) -> (String, String)? {
  if spec.has_suffix("^@") && spec.length() > 2 {
    let base = String::unsafe_substring(spec, start=0, end=spec.length() - 2)
    return Some((base, "@"))
  }
  if spec.has_suffix("^!") && spec.length() > 2 {
    let base = String::unsafe_substring(spec, start=0, end=spec.length() - 2)
    return Some((base, "!"))
  }
  let chars = spec.to_array()
  let len = chars.length()
  for i = len - 1; i >= 1; i = i - 1 {
    if chars[i - 1] == '^' && chars[i] == '-' {
      let base = String::unsafe_substring(spec, start=0, end=i - 1)
      if base.length() == 0 {
        return None
      }
      let rest = String::unsafe_substring(spec, start=i + 1, end=len)
      if rest.length() == 0 {
        return Some((base, "-1"))
      }
      let mut all_digits = true
      for c in rest {
        if c < '0' || c > '9' {
          all_digits = false
          break
        }
      }
      if all_digits {
        return Some((base, "-" + rest))
      }
      return Some((base, "-invalid:" + rest))
    }
  }
  None
}

///|
/// Normalize a glob pattern by expanding shorthand prefixes.
/// `"heads/*"` -> `"refs/heads/*"`, `"refs/heads/*"` unchanged.
pub fn rev_parse_normalize_glob_pattern(pattern : String) -> String {
  if pattern.has_prefix("refs/") {
    pattern
  } else if pattern.has_prefix("heads/") {
    "refs/" + pattern
  } else if pattern.has_prefix("tags/") {
    "refs/" + pattern
  } else if pattern.has_prefix("remotes/") {
    "refs/" + pattern
  } else {
    "refs/" + pattern
  }
}

///|
/// Glob match for ref patterns. Supports `*` (any chars) and `?` (single char).
/// Non-glob patterns match as exact or path prefix (with `/` boundary).
pub fn rev_parse_glob_match(text : String, pattern : String) -> Bool {
  let has_star = pattern.find("*") is Some(_)
  let has_question = pattern.find("?") is Some(_)
  if !has_star && !has_question {
    text == pattern ||
    (
      text.has_prefix(pattern) &&
      pattern.length() < text.length() &&
      (pattern[pattern.length() - 1] == '/' || text[pattern.length()] == '/')
    )
  } else {
    let t = text.to_array()
    let p = pattern.to_array()
    rev_parse_glob_match_inner(t, 0, p, 0)
  }
}

///|
fn rev_parse_glob_match_inner(
  t : Array[Char],
  ti : Int,
  p : Array[Char],
  pi : Int,
) -> Bool {
  let mut ti = ti
  let mut pi = pi
  while pi < p.length() {
    if p[pi] == '*' {
      pi += 1
      for k = ti; k <= t.length(); k = k + 1 {
        if rev_parse_glob_match_inner(t, k, p, pi) {
          return true
        }
      }
      return false
    } else if p[pi] == '?' {
      if ti >= t.length() {
        return false
      }
      ti += 1
      pi += 1
    } else {
      if ti >= t.length() || t[ti] != p[pi] {
        return false
      }
      ti += 1
      pi += 1
    }
  }
  ti == t.length()
}

///|
/// Parse `@{-N}` prefix. Returns (N, rest_of_spec).
pub fn parse_at_minus_prefix(spec : String) -> (Int, String)? {
  guard spec.has_prefix("@{-") else { return None }
  let chars = spec.to_array()
  let mut i = 3
  let mut n = 0
  let mut found_digit = false
  while i < chars.length() && chars[i] != '}' {
    if chars[i] < '0' || chars[i] > '9' {
      return None
    }
    n = n * 10 + (chars[i].to_int() - '0'.to_int())
    found_digit = true
    i += 1
  }
  guard found_digit && i < chars.length() && chars[i] == '}' else {
    return None
  }
  let rest = String::unsafe_substring(spec, start=i + 1, end=spec.length())
  Some((n, rest))
}

///|
/// Shorten a hex OID to the requested length (minimum 4).
pub fn shorten_oid_hex(hex : String, requested : Int) -> String {
  let mut length = requested
  if length < 4 {
    length = 4
  }
  if length > hex.length() {
    length = hex.length()
  }
  String::unsafe_substring(hex, start=0, end=length)
}

///|
/// Shell-escape a value with single quotes.
pub fn shell_single_quote(value : String) -> String {
  let out = StringBuilder::new()
  out.write_char('\'')
  for c in value {
    if c == '\'' {
      out.write_char('\'')
      out.write_char('\\')
      out.write_char('\'')
      out.write_char('\'')
    } else {
      out.write_char(c)
    }
  }
  out.write_char('\'')
  out.to_string()
}

///|
/// Check whether cwd is inside the git directory.
pub fn rev_parse_is_inside_git_dir(
  cwd_abs : String,
  abs_git_dir : String,
) -> Bool {
  if cwd_abs == abs_git_dir {
    return true
  }
  let prefix = if abs_git_dir.has_suffix("/") {
    abs_git_dir
  } else {
    abs_git_dir + "/"
  }
  cwd_abs.has_prefix(prefix)
}

///|
/// Compute a relative path from `cwd_abs` to `abs_target`.
pub fn rev_parse_make_relative(abs_target : String, cwd_abs : String) -> String {
  if abs_target == cwd_abs {
    return "./"
  }
  let cwd_prefix = if cwd_abs.has_suffix("/") { cwd_abs } else { cwd_abs + "/" }
  if abs_target.has_prefix(cwd_prefix) {
    return String::unsafe_substring(
      abs_target,
      start=cwd_prefix.length(),
      end=abs_target.length(),
    )
  }
  let mut common_len = 0
  let target_chars = abs_target.to_array()
  let cwd_chars = cwd_abs.to_array()
  let min_len = if target_chars.length() < cwd_chars.length() {
    target_chars.length()
  } else {
    cwd_chars.length()
  }
  for i in 0.. 0 {
    common_len = 1
  }
  let remaining_cwd = String::unsafe_substring(
    cwd_abs,
    start=common_len,
    end=cwd_abs.length(),
  )
  let remaining_target = String::unsafe_substring(
    abs_target,
    start=common_len,
    end=abs_target.length(),
  )
  let depth = remaining_cwd.iter().filter(fn(c) { c == '/' }).count() + 1
  let parts : Array[String] = []
  for _ in 0.. String {
  let parts = path.split("/")
  let result : Array[String] = []
  for part in parts {
    let s = part.to_owned()
    if s == "." {
      continue
    } else if s == ".." {
      if result.length() > 0 {
        let _ = result.pop()
      }
    } else {
      result.push(s)
    }
  }
  result.join("/")
}

///|
/// Apply `--prefix` to `revision:path` specs with relative paths.
pub fn rev_parse_apply_prefix_to_colon_path(
  spec : String,
  prefix : String,
) -> String {
  guard spec.find(":") is Some(colon_idx) else { return spec }
  guard colon_idx > 0 else { return spec }
  let path_part = String::unsafe_substring(
    spec,
    start=colon_idx + 1,
    end=spec.length(),
  )
  if !path_part.has_prefix("./") && !path_part.has_prefix("../") {
    return spec
  }
  let rev_part = String::unsafe_substring(spec, start=0, end=colon_idx)
  let combined = prefix + path_part
  let normalized = rev_parse_normalize_path(combined)
  rev_part + ":" + normalized
}

///|
/// Parse index pathspec like `:0:path` or `:path`.
pub fn parse_index_pathspec(spec : String) -> (Int, String?) {
  if !spec.has_prefix(":") || spec.length() <= 1 {
    return (0, None)
  }
  let rest = String::unsafe_substring(spec, start=1, end=spec.length())
  let mut stage = 0
  let mut path = rest
  if rest.length() >= 3 && rest[0] >= '0' && rest[0] <= '3' && rest[1] == ':' {
    stage = rest[0].to_int() - '0'.to_int()
    path = String::unsafe_substring(rest, start=2, end=rest.length())
  }
  if path.length() == 0 {
    (stage, None)
  } else {
    (stage, Some(path))
  }
}

///|
/// Strip leading `./` and trailing `/` from an index pathspec path.
pub fn normalize_index_pathspec_path(path : String) -> String {
  let mut out = path
  while out.has_prefix("./") {
    out = String::unsafe_substring(out, start=2, end=out.length())
  }
  while out.length() > 1 && out.has_suffix("/") {
    out = String::unsafe_substring(out, start=0, end=out.length() - 1)
  }
  out
}

///|
/// Extract the `object` header hex from tag text.
pub fn rev_parse_extract_tag_target_name(tag_text : String) -> String? {
  for line_view in tag_text.split("\n") {
    let line = line_view.to_owned()
    if line.has_prefix("object ") {
      return Some(
        rev_parse_trim_string(
          String::unsafe_substring(line, start=7, end=line.length()),
        ),
      )
    }
  }
  None
}

///|
fn rev_parse_trim_string(s : String) -> String {
  let mut start = 0
  let mut end = s.length()
  while start < end &&
        (
          s[start] == ' ' ||
          s[start] == '\t' ||
          s[start] == '\n' ||
          s[start] == '\r'
        ) {
    start += 1
  }
  while end > start &&
        (
          s[end - 1] == ' ' ||
          s[end - 1] == '\t' ||
          s[end - 1] == '\n' ||
          s[end - 1] == '\r'
        ) {
    end -= 1
  }
  String::unsafe_substring(s, start~, end~)
}

///|
/// Check whether a git path should use the common directory.
pub fn rev_parse_git_path_uses_common_dir(requested_path : String) -> Bool {
  requested_path == "objects" ||
  requested_path.has_prefix("objects/") ||
  requested_path == "refs" ||
  requested_path.has_prefix("refs/") ||
  requested_path == "packed-refs" ||
  requested_path == "config" ||
  requested_path == "hooks" ||
  requested_path.has_prefix("hooks/") ||
  requested_path == "info" ||
  requested_path.has_prefix("info/") ||
  requested_path == "logs/refs" ||
  requested_path.has_prefix("logs/refs/")
}

///|
/// Parse a date string to a Unix timestamp.
pub fn parse_date_to_timestamp(date_str : String) -> Int64 {
  if date_str.find("T") is Some(_) {
    return parse_iso8601_timestamp(date_str)
  }
  let mut is_numeric = true
  for c in date_str {
    if c < '0' || c > '9' {
      is_numeric = false
      break
    }
  }
  if is_numeric && date_str.length() > 0 {
    let mut result = 0L
    for c in date_str {
      result = result * 10L + (c.to_int() - '0'.to_int()).to_int64()
    }
    return result
  }
  0L
}

///|
/// Parse ISO 8601 timestamp (YYYY-MM-DDTHH:MM:SS).
pub fn parse_iso8601_timestamp(date_str : String) -> Int64 {
  let s = date_str.to_array()
  if s.length() < 19 {
    return 0L
  }
  let year = @date_parse.parse_date_digits(s, 0, 4)
  let month = @date_parse.parse_date_digits(s, 5, 7)
  let day = @date_parse.parse_date_digits(s, 8, 10)
  let hour = @date_parse.parse_date_digits(s, 11, 13)
  let minute = @date_parse.parse_date_digits(s, 14, 16)
  let second = @date_parse.parse_date_digits(s, 17, 19)
  let days = @date_parse.days_since_epoch(year, month, day)
  days.to_int64() * 86400L +
  hour.to_int64() * 3600L +
  minute.to_int64() * 60L +
  second.to_int64()
}

///|
/// Parse a boolean config value (true/yes/1/false/no/0).
pub fn rev_parse_parse_config_bool(raw : String) -> Bool? {
  let value = config_strip_quotes(raw).to_lower()
  if value == "true" || value == "yes" || value == "1" {
    Some(true)
  } else if value == "false" || value == "no" || value == "0" {
    Some(false)
  } else {
    None
  }
}

///|
/// Split a string into lines (parseopt helper).
pub fn parseopt_split_lines(s : String) -> Array[String] {
  let lines : Array[String] = []
  let buf = StringBuilder::new()
  for c in s {
    if c == '\n' {
      lines.push(buf.to_string())
      buf.reset()
    } else {
      buf.write_char(c)
    }
  }
  let last = buf.to_string()
  if last.length() > 0 {
    lines.push(last)
  }
  lines
}

///|
/// Find the first space or tab in a string.
pub fn parseopt_find_space(s : String) -> Int? {
  for i = 0; i < s.length(); i = i + 1 {
    let c = s[i]
    if c == ' ' || c == '\t' {
      return Some(i)
    }
  }
  None
}

///|
/// Skip leading spaces and tabs.
pub fn parseopt_skipspaces(s : String) -> String {
  let mut i = 0
  while i < s.length() {
    let c = s[i]
    if c != ' ' && c != '\t' {
      break
    }
    i = i + 1
  }
  String::unsafe_substring(s, start=i, end=s.length())
}

///|
/// Find the index of the first flag character (`*`, `=`, `?`, `!`).
pub fn parseopt_find_flags(s : String) -> Int {
  for i = 0; i < s.length(); i = i + 1 {
    let c = s[i]
    if c == '*' || c == '=' || c == '?' || c == '!' {
      return i
    }
  }
  s.length()
}

///|
/// Find the index of a specific character in a string.
pub fn parseopt_find_char(s : String, ch : Char) -> Int? {
  for i = 0; i < s.length(); i = i + 1 {
    if s[i].to_int() == ch.to_int() {
      return Some(i)
    }
  }
  None
}