///|
/// 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,
) -> Unit {
  let info_dir = git_dir + "/info"
  fs.mkdir_p(info_dir) catch {
    _ => ()
  }
  let content = StringBuilder::new()
  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")
  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
        },
      )
    None => ()
  }
}

///|
/// Check if current directory is a subdir-clone
pub fn is_subdir_clone(fs : &@bit.RepoFileSystem, git_dir : String) -> 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) catch { err => raise err }
  find_subtree(db, fs, info.tree, subdir_path) catch {
    err => raise err
  }
}

///|
/// 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) catch { err => raise err }
    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) catch { err => raise err }
  for entry in entries {
    if entry.mode == "40000" || entry.mode == "040000" {
      let sub_blobs = collect_tree_blobs(db, rfs, entry.id) catch {
        err => raise err
      }
      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) catch { err => raise err }
  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) catch {
        err => raise err
      }
    } 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::new()
  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
}