///| Promisor remote support for partial clones (pure)

///|
/// Read promisor remote URL from .git/objects/info/promisor
pub fn read_promisor_remote(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
) -> String? {
  let promisor_path = promisor_join_path(git_dir, "objects/info/promisor")
  if !fs.is_file(promisor_path) {
    return None
  }
  let content = fs.read_file(promisor_path) catch { _ => return None }
  let remote = @utf8.decode_lossy(content[:]).trim().to_owned()
  if remote.length() > 0 {
    Some(remote)
  } else {
    None
  }
}

///|
/// Check if this is a partial clone repository
pub fn is_partial_clone(fs : &@bit.RepoFileSystem, git_dir : String) -> Bool {
  read_promisor_remote(fs, git_dir) is Some(_)
}

///|
/// Promisor-aware object database that can fetch missing objects.
/// The fetch_fn callback is injectable for platform independence.
/// When fetch_fn is None, only local operations are available.
pub struct PromisorDb {
  db : ObjectDb
  git_dir : String
  promisor_remote : String?
  fetched : Map[String, Bool]
  fetch_fn : (async (String, Array[@bit.ObjectId]) -> Result[
    Array[@bit.PackObject],
    @bit.GitError,
  ] noraise)?
}

///|
pub fn PromisorDb::new(
  fs : &@bit.RepoFileSystem,
  git_dir : String,
  lazy_load? : Bool = true,
  fetch_fn~ : (async (String, Array[@bit.ObjectId]) -> Result[
    Array[@bit.PackObject],
    @bit.GitError,
  ] noraise)?,
) -> PromisorDb raise @bit.GitError {
  let db = if lazy_load {
    ObjectDb::load_lazy(fs, git_dir)
  } else {
    ObjectDb::load(fs, git_dir)
  }
  let promisor_remote = read_promisor_remote(fs, git_dir)
  { db, git_dir, promisor_remote, fetched: Map([]), fetch_fn }
}

///|
/// Get object synchronously (local only)
pub fn PromisorDb::get(
  self : PromisorDb,
  fs : &@bit.RepoFileSystem,
  id : @bit.ObjectId,
) -> @bit.PackObject? raise @bit.GitError {
  self.db.get(fs, id)
}

///|
/// Check if an object exists locally
pub fn PromisorDb::has_local(
  self : PromisorDb,
  fs : &@bit.RepoFileSystem,
  id : @bit.ObjectId,
) -> Bool raise @bit.GitError {
  match self.db.get(fs, id) {
    Some(_) => true
    None => false
  }
}

///|
/// Check if this is a partial clone with promisor remote
pub fn PromisorDb::has_promisor(self : PromisorDb) -> Bool {
  self.promisor_remote is Some(_)
}

///|
/// Get the promisor remote URL if available
pub fn PromisorDb::get_promisor_remote(self : PromisorDb) -> String? {
  self.promisor_remote
}

///|
/// Fetch missing objects from the promisor remote (async).
/// Requires fetch_fn to be set; returns empty if no fetcher available.
pub async fn PromisorDb::fetch_missing(
  self : PromisorDb,
  write_fs : &@bit.FileSystem,
  ids : Array[@bit.ObjectId],
) -> Array[@bit.PackObject] raise @bit.GitError {
  guard self.promisor_remote is Some(remote) else { return [] }
  guard self.fetch_fn is Some(fetch_fn) else { return [] }
  // Filter out already fetched
  let to_fetch : Array[@bit.ObjectId] = []
  for id in ids {
    let hex = id.to_hex()
    if !self.fetched.contains(hex) {
      to_fetch.push(id)
      self.fetched.set(hex, true)
    }
  }
  if to_fetch.length() == 0 {
    return []
  }
  // Fetch from remote via injected callback
  let result = fetch_fn(remote, to_fetch)
  let objects = match result {
    Ok(objs) => objs
    Err(e) => raise e
  }
  // Write to local storage
  for obj in objects {
    let _ = write_loose_object(write_fs, self.git_dir, obj.obj_type, obj.data)
  }
  objects
}

///|
/// Fetch a single missing object (async)
pub async fn PromisorDb::fetch_one(
  self : PromisorDb,
  write_fs : &@bit.FileSystem,
  id : @bit.ObjectId,
) -> @bit.PackObject? raise @bit.GitError {
  let objects = self.fetch_missing(write_fs, [id])
  for obj in objects {
    if obj.id == id {
      return Some(obj)
    }
  }
  None
}

///|
/// Get object, fetching from remote if necessary (async version)
pub async fn PromisorDb::get_or_fetch(
  self : PromisorDb,
  read_fs : &@bit.RepoFileSystem,
  write_fs : &@bit.FileSystem,
  id : @bit.ObjectId,
) -> @bit.PackObject? raise @bit.GitError {
  // First try local
  let result = self.db.get(read_fs, id)
  if result is Some(_) {
    return result
  }
  // If not found and we have a promisor remote, fetch it
  if self.promisor_remote is None {
    return None
  }
  let hex = id.to_hex()
  // Check if already tried to fetch
  if self.fetched.contains(hex) {
    return None
  }
  // Fetch and return
  self.fetch_one(write_fs, id)
}

///|
fn promisor_join_path(root : String, path : String) -> String {
  if root.has_suffix("/") {
    root + path
  } else {
    root + "/" + path
  }
}