///|
struct OsObjectStore {
  git_dir : String
  // Cached lazily-loaded ObjectDb. Reused across get/has calls so pack
  // indexes are parsed at most once per store instance.
  mut cached_db : @bitlib.ObjectDb?
}

///|
/// Return a cached ObjectDb, building it lazily on first use.
///
/// Rebuilding the ObjectDb on every `get`/`has` re-parsed each pack `.idx`
/// from scratch, and parsing an index hex-encodes every object id it
/// contains. Once `bit issue` blobs were packed by gc, every object read
/// re-hashed the entire pack index, making hub operations extremely slow.
/// Caching the db lets `LazyPackIndex` keep its parsed index between reads.
fn OsObjectStore::object_db(
  self : OsObjectStore,
  fs : OsFs,
) -> @bitlib.ObjectDb raise @bitcore.GitError {
  match self.cached_db {
    Some(db) => db
    None => {
      let db = @bitlib.ObjectDb::load_lazy(fs, self.git_dir)
      self.cached_db = Some(db)
      db
    }
  }
}

///|
impl @bitlib.ObjectStore for OsObjectStore with fn get(self, id) {
  let fs = OsFs::new()
  let db = self.object_db(fs)
  db.get(fs, id)
}

///|
impl @bitlib.ObjectStore for OsObjectStore with fn put(self, obj_type, content) {
  let fs = OsFs::new()
  let (id, compressed) = @bitcore.create_object(obj_type, content)
  let hex = id.to_hex()
  let dir = self.git_dir +
    "/objects/" +
    String::unsafe_substring(hex, start=0, end=2)
  let path = dir + "/" + String::unsafe_substring(hex, start=2, end=40)
  fs.mkdir_p(dir)
  fs.write_file(path, compressed)
  id
}

///|
impl @bitlib.ObjectStore for OsObjectStore with fn has(self, id) {
  let fs = OsFs::new()
  let db = self.object_db(fs)
  let obj = db.get(fs, id)
  obj is Some(_)
}

///|
struct OsRefStore {
  git_dir : String
}

///|
impl @bitlib.RefStore for OsRefStore with fn resolve(self, ref_name) {
  let fs = OsFs::new()
  @bitlib.resolve_ref(fs, self.git_dir, ref_name)
}

///|
impl @bitlib.RefStore for OsRefStore with fn update(self, ref_name, id) {
  let fs = OsFs::new()
  match id {
    Some(commit_id) => {
      let ref_path = self.git_dir + "/" + ref_name
      let dir = os_parent_path(ref_path)
      fs.mkdir_p(dir)
      fs.write_string(ref_path, commit_id.to_hex() + "\n")
    }
    None => ()
  }
}

///|
impl @bitlib.RefStore for OsRefStore with fn list(self, _prefix) {
  ignore(self)
  []
}

///|
fn os_parent_path(path : String) -> String {
  match path.rev_find("/") {
    None => "."
    Some(0) => "."
    Some(idx) => String::unsafe_substring(path, start=0, end=idx)
  }
}

///|
struct OsClock {
  time : Int64
}

///|
impl @bitlib.Clock for OsClock with fn now(self) {
  self.time
}

///|
priv enum HubRepoBackendKind {
  Bit
  Git
}

///|
priv trait HubRepoBackend {
  fn resolve_git_dir(Self, String) -> String
  fn current_branch_name(Self, String) -> String?
}

///|
priv struct BitHubRepoBackend {}

///|
priv struct GitHubRepoBackend {}

///|
fn parse_hub_repo_backend_kind(value : String) -> HubRepoBackendKind? {
  match trim_string(value).to_lower() {
    "bit" => Some(HubRepoBackendKind::Bit)
    "git" | "libgit2" => Some(HubRepoBackendKind::Git)
    _ => None
  }
}

///|
fn can_use_hub_git_backend() -> Bool {
  hub_libgit2_is_loaded() == 1 || hub_libgit2_init() >= 0
}

///|
fn resolve_hub_repo_backend_kind() -> HubRepoBackendKind {
  match @env.get_env_var("BIT_HUB_BACKEND") {
    Some(raw) =>
      match parse_hub_repo_backend_kind(raw) {
        Some(HubRepoBackendKind::Bit) => HubRepoBackendKind::Bit
        Some(HubRepoBackendKind::Git) =>
          if can_use_hub_git_backend() {
            HubRepoBackendKind::Git
          } else {
            HubRepoBackendKind::Bit
          }
        None =>
          if can_use_hub_git_backend() {
            HubRepoBackendKind::Git
          } else {
            HubRepoBackendKind::Bit
          }
      }
    None =>
      if can_use_hub_git_backend() {
        HubRepoBackendKind::Git
      } else {
        HubRepoBackendKind::Bit
      }
  }
}

///|
fn trim_trailing_path_separator(path : String) -> String {
  let mut end = path.length()
  while end > 1 {
    if path.code_unit_at(end - 1).to_int() != 47 {
      break
    }
    end -= 1
  }
  if end == path.length() {
    path
  } else {
    String::unsafe_substring(path, start=0, end~)
  }
}

///|
fn resolve_hub_common_git_dir(git_dir : String) -> String {
  let fs = OsFs::new()
  let resolved_git_dir = normalize_path(trim_trailing_path_separator(git_dir))
  let commondir_path = resolved_git_dir + "/commondir"
  if fs.is_file(commondir_path) {
    let raw = @utf8.decode_lossy(
      (fs.read_file(commondir_path) catch { _ => Default::default() })[:],
    )
    let rel = raw.trim(chars=" \t\r\n").to_owned()
    if rel.length() > 0 {
      if rel.has_prefix("/") {
        return normalize_path(rel)
      }
      return normalize_path(resolved_git_dir + "/" + rel)
    }
  }
  resolved_git_dir
}

///|
fn resolve_hub_git_dir_with_bit_backend(root : String) -> String {
  let fs = OsFs::new()
  let git_path = root + "/.git"
  let git_dir = if fs.is_file(git_path) {
    @bitlib.resolve_gitdir(fs, git_path)
  } else {
    git_path
  }
  resolve_hub_common_git_dir(git_dir)
}

///|
fn resolve_hub_git_dir_with_git_backend(root : String) -> String {
  if !can_use_hub_git_backend() {
    return resolve_hub_git_dir_with_bit_backend(root)
  }
  match hub_git_repository_path(root) {
    Some(git_dir) => resolve_hub_common_git_dir(git_dir)
    None => resolve_hub_git_dir_with_bit_backend(root)
  }
}

///|
impl HubRepoBackend for BitHubRepoBackend with fn resolve_git_dir(_self, root) {
  resolve_hub_git_dir_with_bit_backend(root)
}

///|
impl HubRepoBackend for BitHubRepoBackend with fn current_branch_name(
  _self,
  root,
) {
  let git_dir = resolve_hub_git_dir_with_bit_backend(root)
  detect_current_branch_name(git_dir)
}

///|
impl HubRepoBackend for GitHubRepoBackend with fn resolve_git_dir(_self, root) {
  resolve_hub_git_dir_with_git_backend(root)
}

///|
impl HubRepoBackend for GitHubRepoBackend with fn current_branch_name(
  _self,
  root,
) {
  if !can_use_hub_git_backend() {
    let fallback = BitHubRepoBackend::{  }
    return HubRepoBackend::current_branch_name(fallback, root)
  }
  match hub_git_head_ref_name(root) {
    Some(name) => parse_branch_ref_name(name)
    None => {
      let fallback = BitHubRepoBackend::{  }
      HubRepoBackend::current_branch_name(fallback, root)
    }
  }
}

///|
/// Resolve git_dir for hub operations, handling worktrees.
/// In a worktree, .git is a file pointing to .git/worktrees/.
/// Hub data (refs/notes) lives in the shared common git dir.
fn resolve_hub_git_dir(root : String) -> String {
  match resolve_hub_repo_backend_kind() {
    HubRepoBackendKind::Bit => {
      let backend = BitHubRepoBackend::{  }
      HubRepoBackend::resolve_git_dir(backend, root)
    }
    HubRepoBackendKind::Git => {
      let backend = GitHubRepoBackend::{  }
      HubRepoBackend::resolve_git_dir(backend, root)
    }
  }
}

///|
fn detect_hub_current_branch_name(root : String) -> String? {
  match resolve_hub_repo_backend_kind() {
    HubRepoBackendKind::Bit => {
      let backend = BitHubRepoBackend::{  }
      HubRepoBackend::current_branch_name(backend, root)
    }
    HubRepoBackendKind::Git => {
      let backend = GitHubRepoBackend::{  }
      HubRepoBackend::current_branch_name(backend, root)
    }
  }
}

///|
fn make_hub_stores(
  git_dir : String,
) -> (&@bitlib.ObjectStore, &@bitlib.RefStore, &@bitlib.Clock) {
  let objects : OsObjectStore = { git_dir, cached_db: None }
  let refs : OsRefStore = { git_dir, }
  let clock : OsClock = { time: get_commit_timestamp() }
  (objects, refs, clock)
}

///|
/// Load hub store, auto-initializing if needed.
fn load_hub_store(
  objects : &@bitlib.ObjectStore,
  refs : &@bitlib.RefStore,
) -> @hub.Hub raise @bitcore.GitError {
  @hub.Hub::init(
    objects,
    refs,
    signing_key=get_hub_signing_key(),
    require_signed=get_hub_require_signed(),
  )
}

///|
fn ensure_hub_initialized(git_dir : String) -> Unit raise @bitcore.GitError {
  let fs = OsFs::new()
  let (objects, refs, _clock) = make_hub_stores(git_dir)
  let _ = load_hub_store(objects, refs)
  ensure_hub_merge_policy_file(fs, git_dir)
}

///|
async fn handle_hub_init(args : Array[String]) -> Unit raise Error {
  for arg in args {
    if arg.has_prefix("-") {
      warn_unimplemented_arg("init", arg)
    }
  }
  let root = get_work_root()
  let git_dir = resolve_hub_git_dir(root)
  let fs = OsFs::new()
  let (objects, refs, _clock) = make_hub_stores(git_dir)
  let _ = load_hub_store(objects, refs)
  ensure_hub_merge_policy_file(fs, git_dir)
  print_line("Initialized hub metadata")
}