///| Layer management (overlayfs-style)

///|
pub fn Fs::add_layer(
  self : Fs,
  backing_fs : &@bit.RepoFileSystem,
  tree_id : @bit.ObjectId,
  name : String,
  commit_id? : @bit.ObjectId? = None,
) -> Unit raise @bit.GitError {
  let db = self.get_db(backing_fs)
  let obj = db.get(backing_fs, tree_id)
  guard obj is Some(o) else {
    raise @bit.GitError::InvalidObject("Tree not found: \{tree_id}")
  }
  if o.obj_type != @bit.ObjectType::Tree {
    raise @bit.GitError::InvalidObject("Not a tree: \{tree_id}")
  }
  self.layers.insert(0, { tree_id, commit_id, name })
  self.cache.clear()
}

///|
pub fn Fs::add_layer_from_commit(
  self : Fs,
  backing_fs : &@bit.RepoFileSystem,
  commit_id : @bit.ObjectId,
  name : String,
) -> Unit raise @bit.GitError {
  let db = self.get_db(backing_fs)
  let obj = db.get(backing_fs, commit_id)
  guard obj is Some(o) else {
    raise @bit.GitError::InvalidObject("Commit not found: \{commit_id}")
  }
  if o.obj_type != @bit.ObjectType::Commit {
    raise @bit.GitError::InvalidObject("Not a commit: \{commit_id}")
  }
  let info = @bit.parse_commit(o.data)
  self.add_layer(backing_fs, info.tree, name, commit_id=Some(commit_id))
}

///|
pub fn Fs::remove_layer(self : Fs, name : String) -> Bool {
  let mut i = 0
  while i < self.layers.length() {
    if self.layers[i].name == name {
      let _ = self.layers.remove(i)
      self.cache.clear()
      return true
    }
    i += 1
  }
  false
}

///|
pub fn Fs::list_layers(self : Fs) -> Array[Layer] {
  self.layers.copy()
}

///|
pub fn Fs::flatten_layers(
  self : Fs,
  backing_fs : &@bit.FileSystem,
  rfs : &@bit.RepoFileSystem,
) -> @bit.ObjectId raise @bit.GitError {
  if self.layers.length() == 0 && !self.working.is_dirty() {
    return self.base_tree
  }
  let root = TreeNode::new_dir()
  let db = self.get_db(rfs)
  if self.base_tree != @bit.ObjectId::zero() {
    load_tree_recursive(db, rfs, self.base_tree, root)
  }
  let mut i = self.layers.length() - 1
  while i >= 0 {
    let layer = self.layers[i]
    merge_tree_into(db, rfs, layer.tree_id, root)
    i -= 1
  }
  for path in self.working.deleted.keys() {
    delete_from_tree(Some(db), rfs, root, path)
  }
  for path, content in self.working.files {
    let blob_id = @lib.write_loose_object(
      backing_fs,
      self.git_dir,
      @bit.ObjectType::Blob,
      content,
    )
    insert_into_tree(Some(db), rfs, root, path, blob_id, 0o100644)
  }
  write_tree_recursive(backing_fs, self.git_dir, root)
}

///|
fn merge_tree_into(
  db : @lib.ObjectDb,
  rfs : &@bit.RepoFileSystem,
  tree_id : @bit.ObjectId,
  target : TreeNode,
) -> Unit raise @bit.GitError {
  let obj = db.get(rfs, tree_id)
  guard obj is Some(o) else {
    raise @bit.GitError::InvalidObject("Tree not found: \{tree_id}")
  }
  if o.obj_type != @bit.ObjectType::Tree {
    raise @bit.GitError::InvalidObject("Not a tree: \{tree_id}")
  }
  let entries = @bit.parse_tree(o.data)
  for entry in entries {
    if is_tree_mode(entry.mode) {
      // Check if target has existing directory to merge into
      if target.children.get(entry.name) is Some(existing) &&
        existing.blob_id is None {
        merge_tree_into(db, rfs, entry.id, existing)
      } else {
        let child = TreeNode::new_dir()
        load_tree_recursive(db, rfs, entry.id, child)
        target.children[entry.name] = child
      }
    } else {
      let mode = parse_octal(entry.mode)
      target.children[entry.name] = TreeNode::new_file(entry.id, mode)
    }
  }
}

///|
pub fn Fs::clear_layers(self : Fs) -> Unit {
  self.layers.clear()
  self.cache.clear()
}

///|
pub fn Fs::has_layer(self : Fs, name : String) -> Bool {
  self.layers.iter().any(layer => layer.name == name)
}