///|
/// Write subdir-clone metadata to .git/info/attributes
pub fn write_subdir_attributes(
  fs : &@bit.FileSystem,
  git_dir : String,
  remote_url : String,
  subdir_path : String,
  base_commit : @bit.ObjectId,
  local_commit? : @bit.ObjectId? = None,
) -> Unit {
  let info_dir = git_dir + "/info"
  fs.mkdir_p(info_dir) catch {
    _ => ()
  }
  let content = StringBuilder()
  content.write_string("# subdir-clone metadata\n")
  content.write_string("* subdir-remote=")
  content.write_string(remote_url)
  content.write_string("\n")
  content.write_string("* subdir-path=")
  content.write_string(subdir_path)
  content.write_string("\n")
  content.write_string("* subdir-base=")
  content.write_string(base_commit.to_hex())
  content.write_string("\n")
  // subdir-local is the local commit whose tree matches subdir-path at
  // subdir-base: the sync point push starts from and rebase builds onto.
  match local_commit {
    Some(id) => {
      content.write_string("* subdir-local=")
      content.write_string(id.to_hex())
      content.write_string("\n")
    }
    None => ()
  }
  let attr_path = info_dir + "/attributes"
  fs.write_file(attr_path, string_to_bytes(content.to_string())) catch {
    _ => ()
  }
}

///|
/// Read subdir-clone metadata from .git/info/attributes
pub fn read_subdir_attributes(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
) -> (String, String, String)? {
  let attr_path = git_dir + "/info/attributes"
  let content = fs.read_file(attr_path) catch { _ => return None }
  let text = bytes_to_string(content)
  let mut remote = ""
  let mut path = ""
  let mut base = ""
  for line in text.split("\n") {
    let l = line.to_owned()
    if l.has_prefix("* subdir-remote=") {
      remote = String::unsafe_substring(l, start=16, end=l.length())
    } else if l.has_prefix("* subdir-path=") {
      path = String::unsafe_substring(l, start=14, end=l.length())
    } else if l.has_prefix("* subdir-base=") {
      base = String::unsafe_substring(l, start=14, end=l.length())
    }
  }
  if remote.length() > 0 && path.length() > 0 {
    Some((remote, path, base))
  } else {
    None
  }
}

///|
/// Update subdir-base in .git/info/attributes
pub fn update_subdir_base(
  wfs : &@bit.FileSystem,
  rfs : &@bit.RepoFileSystem,
  git_dir : String,
  new_base : String,
) -> Unit {
  match read_subdir_attributes(rfs, git_dir) {
    Some((remote, path, _)) =>
      write_subdir_attributes(
        wfs,
        git_dir,
        remote,
        path,
        @bit.ObjectId::from_hex(new_base) catch {
          _ => return
        },
        local_commit=read_subdir_local(rfs, git_dir),
      )
    None => ()
  }
}

///|
/// Read subdir-local (the local commit synced with subdir-base), if recorded.
/// Clones made before it was introduced do not have one.
pub fn read_subdir_local(
  rfs : &@bit.RepoFileSystem,
  git_dir : String,
) -> @bit.ObjectId? {
  let content = rfs.read_file(git_dir + "/info/attributes") catch {
    _ => return None
  }
  for line in bytes_to_string(content).split("\n") {
    let l = line.to_owned()
    if l.has_prefix("* subdir-local=") {
      let hex = String::unsafe_substring(l, start=15, end=l.length())
      return Some(@bit.ObjectId::from_hex(hex)) catch { _ => None }
    }
  }
  None
}

///|
/// Record that local commit `local_commit` corresponds to upstream commit
/// `base`.
pub fn update_subdir_sync_point(
  wfs : &@bit.FileSystem,
  rfs : &@bit.RepoFileSystem,
  git_dir : String,
  base : @bit.ObjectId,
  local_commit : @bit.ObjectId,
) -> Unit {
  match read_subdir_attributes(rfs, git_dir) {
    Some((remote, path, _)) =>
      write_subdir_attributes(
        wfs,
        git_dir,
        remote,
        path,
        base,
        local_commit=Some(local_commit),
      )
    None => ()
  }
}

///|
/// Rewrite a raw commit object onto a new tree and parents, keeping its
/// author, committer, encoding and message. Signature headers are dropped
/// because they no longer match the rewritten content.
pub fn rewrite_subdir_commit(
  raw : Bytes,
  tree : @bit.ObjectId,
  parents : Array[@bit.ObjectId],
) -> Bytes {
  let out : Array[Byte] = []
  let push_str = (text : String) => {
    for b in string_to_bytes(text) {
      out.push(b)
    }
  }
  push_str("tree " + tree.to_hex() + "\n")
  for parent in parents {
    push_str("parent " + parent.to_hex() + "\n")
  }
  let n = raw.length()
  let mut i = 0
  let mut skipping = false
  while i < n {
    let mut j = i
    while j < n && raw[j] != b'\n' {
      j += 1
    }
    if j == i {
      // Blank line: the header ends and the message follows verbatim.
      for k in i.. Bool {
  let p = string_to_bytes(prefix)
  if line.length() < p.length() {
    return false
  }
  for k in 0.. Bool {
  read_subdir_attributes(fs, git_dir) is Some(_)
}

///|
/// Get effective remote URL - prefers subdir remote over config
pub fn get_effective_remote_url(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
) -> String? {
  // First check subdir-clone attributes
  match read_subdir_attributes(fs, git_dir) {
    Some((remote_url, _, _)) => Some(remote_url)
    None => read_remote_url_from_config(fs, git_dir)
  }
}

///|
/// Get subdir info for display (remote, path, base)
pub fn get_subdir_info(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
) -> (String, String, String)? {
  read_subdir_attributes(fs, git_dir)
}

///|
/// Get the subdir tree from a commit (for subdir-clone operations)
pub fn get_subdir_tree_from_commit(
  db : ObjectDb,
  fs : &@bit.RepoFileSystem,
  commit_id : @bit.ObjectId,
  subdir_path : String,
) -> @bit.ObjectId? raise @bit.GitError {
  let obj = db.get(fs, commit_id)
  guard obj is Some(o) else { return None }
  let info = @bit.parse_commit(o.data)
  find_subtree(db, fs, info.tree, subdir_path)
}

///|
/// Resolve a ref to commit ID, handling both local and remote refs
pub fn resolve_ref_for_subdir(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  refspec : String,
) -> @bit.ObjectId? raise @bit.GitError {
  // Try direct rev_parse first
  match rev_parse(fs, git_dir, refspec) {
    Some(id) => Some(id)
    None => {
      // Try as remote ref (e.g., origin/main -> refs/remotes/origin/main)
      let remote_path = git_dir + "/refs/remotes/" + refspec
      let bytes = fs.read_file(remote_path) catch { _ => return None }
      let hex = bytes_to_string(bytes).trim(chars=" \n\t").to_owned()
      let id = @bit.ObjectId::from_hex(hex) catch { _ => return None }
      Some(id)
    }
  }
}

///|
fn find_subtree(
  db : ObjectDb,
  rfs : &@bit.RepoFileSystem,
  tree_id : @bit.ObjectId,
  path : String,
) -> @bit.ObjectId? raise @bit.GitError {
  let parts : Array[String] = []
  for p in path.split("/") {
    let s = p.to_owned()
    if s.length() > 0 {
      parts.push(s)
    }
  }
  let mut current = tree_id
  for part in parts {
    let obj = db.get(rfs, current)
    guard obj is Some(tree_obj) else { return None }
    let entries = @bit.parse_tree(tree_obj.data)
    let mut found = false
    for entry in entries {
      if entry.name == part {
        if entry.mode != "40000" && entry.mode != "040000" {
          return None
        }
        current = entry.id
        found = true
        break
      }
    }
    if !found {
      return None
    }
  }
  Some(current)
}

///|
pub fn collect_tree_blobs(
  db : ObjectDb,
  rfs : &@bit.RepoFileSystem,
  tree_id : @bit.ObjectId,
) -> Array[@bit.ObjectId] raise @bit.GitError {
  let blobs : Array[@bit.ObjectId] = []
  let obj = db.get(rfs, tree_id)
  guard obj is Some(tree_obj) else { return blobs }
  let entries = @bit.parse_tree(tree_obj.data)
  for entry in entries {
    if entry.mode == "40000" || entry.mode == "040000" {
      let sub_blobs = collect_tree_blobs(db, rfs, entry.id)
      for b in sub_blobs {
        blobs.push(b)
      }
    } else {
      blobs.push(entry.id)
    }
  }
  blobs
}

///|
pub fn write_tree_to_worktree(
  db : ObjectDb,
  rfs : &@bit.RepoFileSystem,
  wfs : &@bit.FileSystem,
  tree_id : @bit.ObjectId,
  dir : String,
) -> Unit raise @bit.GitError {
  let obj = db.get(rfs, tree_id)
  guard obj is Some(tree_obj) else { return }
  let entries = @bit.parse_tree(tree_obj.data)
  for entry in entries {
    let path = dir + "/" + entry.name
    if entry.mode == "40000" || entry.mode == "040000" {
      wfs.mkdir_p(path) catch {
        _ => ()
      }
      write_tree_to_worktree(db, rfs, wfs, entry.id, path)
    } else {
      let blob_obj = db.get(rfs, entry.id)
      guard blob_obj is Some(blob) else { continue }
      wfs.write_file(path, blob.data) catch {
        _ => ()
      }
    }
  }
}

///|
pub fn move_directory(
  rfs : &@bit.RepoFileSystem,
  wfs : &@bit.FileSystem,
  src : String,
  dst : String,
) -> Unit {
  wfs.mkdir_p(dst) catch {
    _ => ()
  }
  let entries = rfs.readdir(src) catch { _ => return }
  for entry in entries {
    let src_path = src + "/" + entry
    let dst_path = dst + "/" + entry
    if rfs.is_dir(src_path) {
      move_directory(rfs, wfs, src_path, dst_path)
    } else {
      let content = rfs.read_file(src_path) catch { _ => continue }
      wfs.write_file(dst_path, content) catch {
        _ => ()
      }
    }
  }
}

///|
pub fn subdir_remove_dir(
  rfs : &@bit.RepoFileSystem,
  wfs : &@bit.FileSystem,
  path : String,
) -> Unit {
  let entries = rfs.readdir(path) catch { _ => return }
  for entry in entries {
    let child_path = path + "/" + entry
    if rfs.is_dir(child_path) {
      subdir_remove_dir(rfs, wfs, child_path)
    } else {
      wfs.remove_file(child_path) catch {
        _ => ()
      }
    }
  }
  wfs.remove_dir(path) catch {
    _ => ()
  }
}

///|
fn string_to_bytes(s : String) -> Bytes {
  let bytes : Array[Byte] = []
  for c in s {
    bytes.push(c.to_int().to_byte())
  }
  Bytes::from_array(bytes)
}

///|
fn bytes_to_string(bytes : Bytes) -> String {
  let result = StringBuilder()
  for i in 0.. String? {
  let config_path = git_dir + "/config"
  if !fs.is_file(config_path) {
    return None
  }
  let config_content = @string_utils.decode_bytes(
    fs.read_file(config_path) catch {
      _ => return None
    },
  )
  let mut in_remote_section = false
  for line_view in config_content.split("\n") {
    let line = @string_utils.trim_string(line_view.to_owned())
    if line.has_prefix("[remote ") {
      in_remote_section = true
      continue
    }
    if line.has_prefix("[") {
      in_remote_section = false
      continue
    }
    if in_remote_section && line.has_prefix("url = ") {
      return Some(String::unsafe_substring(line, start=6, end=line.length()))
    }
  }
  None
}