// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/tabs/public/tab_strip_collection.h"

#include <memory>
#include <optional>
#include <set>

#include "base/containers/adapters.h"
#include "base/memory/ptr_util.h"
#include "components/split_tabs/split_tab_id.h"
#include "components/split_tabs/split_tab_visual_data.h"
#include "components/tabs/public/pinned_tab_collection.h"
#include "components/tabs/public/split_tab_collection.h"
#include "components/tabs/public/tab_collection.h"
#include "components/tabs/public/tab_collection_observer.h"
#include "components/tabs/public/tab_collection_storage.h"
#include "components/tabs/public/tab_collection_types.h"
#include "components/tabs/public/tab_group_tab_collection.h"
#include "components/tabs/public/tab_interface.h"
#include "components/tabs/public/unpinned_tab_collection.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"

namespace {
tabs::TabCollection* GetCommonAncestor(tabs::TabCollection* collection_a,
                                       tabs::TabCollection* collection_b) {
  if (collection_a == collection_b) {
    return collection_a;
  }

  // Use a set to store all ancestors of the first collection.
  absl::flat_hash_set<tabs::TabCollection*> ancestors;

  // 1. Trace the path from collection_a to the root and store it.
  tabs::TabCollection* current_a = collection_a;
  while (current_a) {
    ancestors.insert(current_a);
    current_a = current_a->GetParentCollection();
  }

  // 2. Trace the path from collection_b up and check for a match.
  tabs::TabCollection* current_b = collection_b;
  while (current_b) {
    if (ancestors.contains(current_b)) {
      return current_b;
    }
    current_b = current_b->GetParentCollection();
  }

  return nullptr;
}

}  // namespace

namespace tabs {

TabStripCollection::TabStripCollection(bool send_notifications_immediately)
    : TabCollection(
          TabCollection::Type::TABSTRIP,
          {TabCollection::Type::PINNED, TabCollection::Type::UNPINNED},
          /*supports_tabs=*/false,
          send_notifications_immediately) {
  pinned_collection_ =
      AddCollection(std::make_unique<PinnedTabCollection>(), 0);
  unpinned_collection_ =
      AddCollection(std::make_unique<UnpinnedTabCollection>(), 1);
}

TabStripCollection::~TabStripCollection() = default;

size_t TabStripCollection::IndexOfFirstNonPinnedTab() const {
  return pinned_collection_->TabCountRecursive();
}

void TabStripCollection::AddTabRecursive(
    ScopedTab tab,
    size_t index,
    std::optional<tab_groups::TabGroupId> new_group_id,
    bool new_pinned_state) {
  CHECK(tab);
  // `index` can be equal to the tab count as at this point the tab has not yet
  // been added.
  CHECK(index >= 0 && index <= TabCountRecursive());

  if (new_group_id.has_value()) {
    CHECK(GetTabGroupCollection(new_group_id.value()));
  }

  TabCollection::Position insertion_details =
      GetInsertionDetails(index, new_pinned_state, new_group_id);

  AddTabImpl(std::move(tab), insertion_details);
}

void TabStripCollection::MoveTabRecursive(
    size_t initial_index,
    size_t final_index,
    std::optional<tab_groups::TabGroupId> new_group_id,
    bool new_pinned_state) {
  CHECK(initial_index >= 0 && final_index >= 0);

  TabInterface* tab = GetTabAtIndexRecursive(initial_index);
  const std::optional<tab_groups::TabGroupId> old_group = tab->GetGroup();

  const std::vector<int> tab_indices = {static_cast<int>(initial_index)};

  TabCollection::TypeEnumSet retain_collection_types;

  if (old_group.has_value() && old_group == new_group_id) {
    TabGroupTabCollection* old_group_collection =
        GetTabGroupCollection(old_group.value());

    // If we are moving the only tab in a group then treat this as a group move.
    if (old_group_collection->TabCountRecursive() == 1) {
      retain_collection_types = {tabs::TabCollection::Type::SPLIT,
                                 tabs::TabCollection::Type::GROUP};
      new_group_id = std::nullopt;
    }
  }

  MoveTabsRecursive({tab_indices}, final_index, new_group_id, new_pinned_state,
                    retain_collection_types);
}

void TabStripCollection::MoveTabsRecursive(
    const std::vector<int>& tab_indices,
    size_t destination_index,
    std::optional<tab_groups::TabGroupId> new_group_id,
    bool new_pinned_state,
    const TabCollection::TypeEnumSet retain_collection_types) {
  CHECK(destination_index >= 0);
  ChildrenPtrs tab_or_collections =
      GetTabsAndCollectionsForMove(tab_indices, retain_collection_types);

  // If new group collection is created, we can find the first position of the
  // tab as though it was moving to the unpinned container and then update the
  // positions.
  TabCollection::Position move_position = GetMovePosition(
      tab_or_collections, destination_index, new_group_id, new_pinned_state);
  if (new_group_id.has_value() &&
      !GetTabGroupCollection(new_group_id.value())) {
    TabCollection::Position group_insertion_details = move_position;

    AddTabCollectionImpl(PopDetachedGroupCollection(new_group_id.value()),
                         group_insertion_details);
    move_position = {GetTabGroupCollection(new_group_id.value())->GetHandle(),
                     0};
  }

  for (auto& tab_or_collection : tab_or_collections) {
    TabCollection* src_parent_collection = nullptr;
    if (std::holds_alternative<DanglingUntriagedTabInterface>(
            tab_or_collection)) {
      TabInterface* tab_ptr =
          std::get<DanglingUntriagedTabInterface>(tab_or_collection);
      src_parent_collection = tab_ptr->GetParentCollection(GetPassKey());
      MoveTabImpl(tab_ptr, move_position);
    } else {
      TabCollection* collection_ptr =
          std::get<DanglingUntriagedTabCollection>(tab_or_collection);
      src_parent_collection = collection_ptr->GetParentCollection();
      MoveCollectionImpl(collection_ptr, move_position);
    }

    // Remove previous collection if needed. This also notifies the
    // collection is removed.
    if (src_parent_collection != unpinned_collection_ &&
        src_parent_collection != pinned_collection_ &&
        src_parent_collection->TabCountRecursive() == 0) {
      RemoveTabCollectionImpl(src_parent_collection);
    }

    // Update the move position to right after where the previous node is.
    move_position = GetNodePosition(tab_or_collection);
    move_position.index += 1;
  }
}

TabCollection::Position TabStripCollection::GetMovePosition(
    ChildrenPtrs tab_or_collections,
    size_t destination_index,
    std::optional<tab_groups::TabGroupId> new_group_id,
    bool new_pinned_state) {
  // Calculate destination collection.
  TabCollection* dst_collection = nullptr;
  if (new_pinned_state) {
    dst_collection = pinned_collection_;
  } else if (new_group_id.has_value() &&
             group_mapping_.contains(new_group_id.value())) {
    dst_collection = GetTabGroupCollection(new_group_id.value());
  } else {
    dst_collection = unpinned_collection_;
  }

  // Create a set for tab or collection.
  std::set<tabs::TabInterface*> tabs_moved;
  std::set<tabs::TabCollection*> collections_moved;
  for (const auto& tab_or_collection : tab_or_collections) {
    if (std::holds_alternative<DanglingUntriagedTabInterface>(
            tab_or_collection)) {
      tabs_moved.insert(
          std::get<DanglingUntriagedTabInterface>(tab_or_collection));
    } else {
      TabCollection* collection_ptr =
          std::get<DanglingUntriagedTabCollection>(tab_or_collection);
      collections_moved.insert(collection_ptr);
    }
  }

  // Find the move position. curr_index is updated by reference.
  size_t curr_insertion_index = 0;
  std::optional<TabCollection::Position> move_position =
      FindMovePositionRecursive(destination_index, dst_collection,
                                curr_insertion_index, tabs_moved,
                                collections_moved);

  return move_position.value();
}

ChildrenPtrs TabStripCollection::GetTabsAndCollectionsForMove(
    const std::vector<int>& tab_indices,
    const TabCollection::TypeEnumSet retain_collection_types) {
  absl::flat_hash_set<const TabInterface*> selected_tabs;
  selected_tabs.reserve(tab_indices.size());
  for (int index : tab_indices) {
    selected_tabs.insert(GetTabAtIndexRecursive(index));
  }

  // Contains set of all the collections fully covered by `tab_indices`. This
  // does not include `pinned_collection_` or `unpinned_collection_` as they
  // cannot be moved.
  absl::flat_hash_set<const TabCollection*> selected_collections;

  if (retain_collection_types.Has(TabCollection::Type::GROUP)) {
    for (const auto& [group_id_, group_collection] : group_mapping_) {
      bool fully_selected = true;
      for (const TabInterface* tab : *group_collection) {
        if (!selected_tabs.contains(tab)) {
          fully_selected = false;
          break;
        }
      }

      if (fully_selected) {
        selected_collections.insert(group_collection);
      }
    }
  }

  if (retain_collection_types.Has(TabCollection::Type::SPLIT)) {
    for (const auto& [split_id, split_collection] : split_mapping_) {
      bool fully_selected = true;
      for (const TabInterface* tab : *split_collection) {
        if (!selected_tabs.contains(tab)) {
          fully_selected = false;
          break;
        }
      }

      if (fully_selected) {
        selected_collections.insert(split_collection);
      }
    }
  }

  ChildrenPtrs move_datas;

  // Iterates through `tab_indices`. If the tab is a direct child of
  // `pinned_collection_` or `unpinned_collection_` return it directly as it
  // needs to be removed directly. Otherwise potentially return the biggest
  // subcollection that contains the tab to be removed.
  int array_index = 0;
  while (array_index < static_cast<int>(tab_indices.size())) {
    TabInterface* tab = GetTabAtIndexRecursive(tab_indices[array_index]);
    TabCollection* collection = tab->GetParentCollection(GetPassKey());
    if (collection == pinned_collection_ ||
        collection == unpinned_collection_) {
      TabInterface* move_data =
          GetTabAtIndexRecursive(tab_indices[array_index]);
      move_datas.push_back(move_data);
      array_index++;
      continue;
    } else {
      TabCollection* candidate_subtree_collection = collection;
      TabCollection* subtree_to_remove = nullptr;

      // Finds the largest subcollection containing `tab`. `subtree_to_remove`
      // is the current valid subcollection to remove while
      // `candidate_subtree_collection` is the next potential subcollection to
      // remove.
      while ((candidate_subtree_collection != pinned_collection_ &&
              candidate_subtree_collection != unpinned_collection_) &&
             selected_collections.contains(candidate_subtree_collection)) {
        subtree_to_remove = candidate_subtree_collection;
        candidate_subtree_collection =
            candidate_subtree_collection->GetParentCollection();
      }

      if (subtree_to_remove) {
        move_datas.push_back(subtree_to_remove);
        array_index += subtree_to_remove->TabCountRecursive();
      } else {
        TabInterface* move_data =
            GetTabAtIndexRecursive(tab_indices[array_index]);
        move_datas.push_back(move_data);
        array_index++;
      }
    }
  }

  return move_datas;
}

ScopedTab TabStripCollection::RemoveTabAtIndexRecursive(size_t index) {
  TabInterface* tab_to_be_removed = GetTabAtIndexRecursive(index);
  TabCollection* parent_collection =
      tab_to_be_removed->GetParentCollection(GetPassKey());
  CHECK(parent_collection);

  // If it is the only tab in the group remove the collection.
  if (parent_collection != unpinned_collection_ &&
      parent_collection != pinned_collection_ &&
      parent_collection->TabCountRecursive() == 1) {
    RemoveTabCollectionImpl(parent_collection);
    return parent_collection->MaybeRemoveTab(tab_to_be_removed);
  } else {
    return RemoveTabImpl(tab_to_be_removed);
  }
}

ScopedTab TabStripCollection::MaybeRemoveTab(TabInterface* tab) {
  CHECK(tab);
  return nullptr;
}

std::unique_ptr<TabCollection> TabStripCollection::MaybeRemoveCollection(
    TabCollection* collection) {
  CHECK(collection);
  return nullptr;
}

void TabStripCollection::InsertTabCollectionAt(
    std::unique_ptr<TabCollection> collection,
    int index,
    bool pinned,
    std::optional<tab_groups::TabGroupId> parent_group) {
  TabCollection::Position insertion_details =
      GetInsertionDetails(index, pinned, parent_group);

  CHECK(insertion_details.parent_handle.Get());
  AddTabCollectionImpl(std::move(collection), insertion_details);
}

std::unique_ptr<TabCollection> TabStripCollection::RemoveTabCollection(
    TabCollection* collection) {
  // Removed collection can be nullptr in the case of group. Return from
  // detached collection instead in that case.
  std::unique_ptr<TabCollection> removed_collection =
      RemoveTabCollectionImpl(collection);

  if (collection->type() == TabCollection::Type::GROUP) {
    return PopDetachedGroupCollection(
        static_cast<TabGroupTabCollection*>(collection)->GetTabGroupId());
  } else {
    CHECK(removed_collection);
    return removed_collection;
  }
}

void TabStripCollection::CreateTabGroup(
    std::unique_ptr<tabs::TabGroupTabCollection> tab_group_collection) {
  CHECK(tab_group_collection);
  detached_group_collections_.push_back(std::move(tab_group_collection));
}

TabGroupTabCollection* TabStripCollection::GetTabGroupCollection(
    tab_groups::TabGroupId group_id) {
  if (auto it = group_mapping_.find(group_id); it != group_mapping_.end()) {
    return it->second;
  }
  return nullptr;
}

std::vector<tab_groups::TabGroupId> TabStripCollection::GetAllTabGroupIds()
    const {
  std::vector<tab_groups::TabGroupId> group_ids;
  group_ids.reserve(group_mapping_.size());
  for (const auto& pair : group_mapping_) {
    group_ids.push_back(pair.first);
  }

  return group_ids;
}

void TabStripCollection::CloseDetachedTabGroup(
    const tab_groups::TabGroupId& group_id) {
  PopDetachedGroupCollection(group_id).reset();
}

TabGroupTabCollection* TabStripCollection::GetDetachedTabGroup(
    const tab_groups::TabGroupId& group_id) {
  auto it = std::find_if(
      detached_group_collections_.begin(), detached_group_collections_.end(),
      [group_id](const std::unique_ptr<TabGroupTabCollection>& collection) {
        return collection->GetTabGroupId() == group_id;
      });
  if (it == detached_group_collections_.end()) {
    return nullptr;
  }
  return it->get();
}

SplitTabCollection* TabStripCollection::GetSplitTabCollection(
    split_tabs::SplitTabId split_id) {
  if (auto it = split_mapping_.find(split_id); it != split_mapping_.end()) {
    return it->second;
  }
  return nullptr;
}

void TabStripCollection::CreateSplit(
    split_tabs::SplitTabId split_id,
    const std::vector<TabInterface*>& tabs,
    split_tabs::SplitTabVisualData visual_data) {
  CHECK(tabs.size() >= 2);
  TabCollection* parent_collection = tabs[0]->GetParentCollection(GetPassKey());
  CHECK(std::all_of(
      tabs.begin(), tabs.end(), [this, parent_collection](auto tab) {
        return parent_collection == tab->GetParentCollection(GetPassKey());
      }));

  size_t dst_index = parent_collection->GetIndexOfTab(tabs[0]).value();

  // Create a new split.
  std::unique_ptr<SplitTabCollection> split =
      std::make_unique<SplitTabCollection>(split_id, visual_data);
  SplitTabCollection* split_ptr = split.get();
  TabCollection::Position insertion_details = {parent_collection->GetHandle(),
                                               dst_index};
  AddTabCollectionImpl(std::move(split), insertion_details);

  // Move tabs to the split.
  size_t insertion_index = 0;
  for (TabInterface* tab : tabs) {
    TabCollection::Position tab_move_details = {split_ptr->GetHandle(),
                                                insertion_index};
    MoveTabImpl(tab, tab_move_details);
    insertion_index += 1;
  }
}

void TabStripCollection::ReverseSplit(split_tabs::SplitTabId split_id) {
  SplitTabCollection* split = GetSplitTabCollection(split_id);
  // MoveTabImpl requires the position's index to be prior to the tab being
  // moved, so use the final index of the split + 1.
  TabCollection::Position positon = {split->GetHandle(),
                                     split->TabCountRecursive()};
  MoveTabImpl(split->GetTabAtIndexRecursive(0), positon);
}

void TabStripCollection::Unsplit(split_tabs::SplitTabId split_id) {
  SplitTabCollection* split = GetSplitTabCollection(split_id);
  if (!split) {
    return;
  }

  CHECK(split_mapping_.contains(split_id));
  TabCollection* parent_collection = split->GetParentCollection();
  size_t dst_index = parent_collection->GetIndexOfCollection(split).value();
  std::vector<TabInterface*> tabs = split->GetTabsRecursive();

  // Move tabs to the parent collection.
  size_t move_index = dst_index;
  for (TabInterface* tab : tabs) {
    TabCollection::Position tab_move_details = {parent_collection->GetHandle(),
                                                move_index};
    MoveTabImpl(tab, tab_move_details);
    move_index += 1;
  }

  RemoveTabCollectionImpl(split);
}

std::set<split_tabs::SplitTabId> TabStripCollection::ListSplits() const {
  std::set<split_tabs::SplitTabId> splits;
  for (const auto& [split_id, _] : split_mapping_) {
    splits.insert(split_id);
  }
  return splits;
}

void TabStripCollection::ValidateData() const {
  for (const auto& [_, group] : group_mapping_) {
    CHECK_GT(group->ChildCount(), 0UL);
  }
  for (const auto& [_, split] : split_mapping_) {
    CHECK_GE(split->ChildCount(), 2UL);
    for (auto child : split->GetTabsRecursive()) {
      CHECK(split->GetSplitTabId() == child->GetSplit().value());
    }
  }
}

std::optional<const tab_groups::TabGroupId> TabStripCollection::FindGroupIdFor(
    const tabs::TabCollection::Handle& collection_handle,
    base::PassKey<TabStripModel>) const {
  for (auto& pair : group_mapping_) {
    if (pair.second->GetHandle() == collection_handle) {
      return pair.first;
    }
  }

  return std::nullopt;
}

std::unique_ptr<tabs::TabGroupTabCollection>
TabStripCollection::PopDetachedGroupCollection(
    const tab_groups::TabGroupId& group_id) {
  auto it = std::find_if(
      detached_group_collections_.begin(), detached_group_collections_.end(),
      [group_id](
          const std::unique_ptr<tabs::TabGroupTabCollection>& collection) {
        return collection->GetTabGroupId() == group_id;
      });
  CHECK(it != detached_group_collections_.end());

  std::unique_ptr<tabs::TabGroupTabCollection> group_collection =
      std::move(*it);
  detached_group_collections_.erase(it);

  return group_collection;
}

void TabStripCollection::AddCollectionMapping(TabCollection* root_collection) {
  if (root_collection->type() == TabCollection::Type::GROUP) {
    TabGroupTabCollection* group_collection =
        static_cast<TabGroupTabCollection*>(root_collection);
    group_mapping_.insert(
        {group_collection->GetTabGroupId(), group_collection});

    for (const tabs::TabInterface* tab : *group_collection) {
      if (tab->IsSplit()) {
        const split_tabs::SplitTabId split_id = tab->GetSplit().value();
        if (!split_mapping_.contains(split_id)) {
          split_mapping_.insert(
              {split_id, static_cast<SplitTabCollection*>(
                             tab->GetParentCollection(GetPassKey()))});
        }
      }
    }
  } else if (root_collection->type() == TabCollection::Type::SPLIT) {
    SplitTabCollection* split_collection =
        static_cast<SplitTabCollection*>(root_collection);
    split_mapping_.insert(
        {split_collection->GetSplitTabId(), split_collection});
  }
}

void TabStripCollection::RemoveCollectionMapping(
    TabCollection* root_collection) {
  if (root_collection->type() == TabCollection::Type::GROUP) {
    TabGroupTabCollection* group_collection =
        static_cast<TabGroupTabCollection*>(root_collection);
    CHECK(group_mapping_.erase(group_collection->GetTabGroupId()));

    for (const tabs::TabInterface* tab : *group_collection) {
      if (tab->IsSplit()) {
        split_mapping_.erase(tab->GetSplit().value());
      }
    }

  } else if (root_collection->type() == TabCollection::Type::SPLIT) {
    SplitTabCollection* split_collection =
        static_cast<SplitTabCollection*>(root_collection);
    CHECK(split_mapping_.erase(split_collection->GetSplitTabId()));
  }
}

void TabStripCollection::AddTabImpl(ScopedTab tab,
                                    const TabCollection::Position& position) {
  auto [tab_collection_handle, insert_index] = position;
  TabCollection* tab_collection_ptr = tab_collection_handle.Get();

  TabInterface* tab_ptr =
      tab_collection_ptr->AddTab(std::move(tab), insert_index);

  TabCollectionNodes handles_added;
  handles_added.push_back(tab_ptr->GetHandle());

  tab_collection_ptr->NotifyOnChildrenAdded(GetPassKey(), handles_added,
                                            position, nullptr, false);
}

void TabStripCollection::AddTabCollectionImpl(
    std::unique_ptr<TabCollection> collection,
    const TabCollection::Position& position) {
  TabCollection* collection_ptr = collection.get();
  AddCollectionMapping(collection_ptr);

  auto [tab_collection_handle, insert_index] = position;
  TabCollection* parent_collection_ptr = tab_collection_handle.Get();

  parent_collection_ptr->AddCollection(std::move(collection), insert_index);

  TabCollectionNodes handles_added;
  handles_added.push_back(collection_ptr->GetHandle());

  // Signal whether the collection is an empty collection or a detached
  // collection.
  parent_collection_ptr->NotifyOnChildrenAdded(
      GetPassKey(), handles_added, position, nullptr,
      collection_ptr->TabCountRecursive() > 0);
}

ScopedTab TabStripCollection::RemoveTabImpl(TabInterface* tab) {
  CHECK(tab);

  TabCollection* parent_collection = tab->GetParentCollection(GetPassKey());
  TabCollection::Position position = {
      parent_collection->GetHandle(),
      parent_collection->GetIndexOfTab(tab).value()};

  ScopedTab removed_tab = parent_collection->MaybeRemoveTab(tab);

  CHECK(removed_tab);

  parent_collection->NotifyOnChildrenRemoved(
      GetPassKey(), position,
      std::vector{std::variant<tabs::TabCollectionHandle, tabs::TabHandle>{
          removed_tab->GetHandle()}},
      nullptr);

  return removed_tab;
}

std::unique_ptr<TabCollection> TabStripCollection::RemoveTabCollectionImpl(
    TabCollection* collection) {
  TabCollectionHandle collection_handle = collection->GetHandle();
  TabCollection* parent_collection = collection->GetParentCollection();
  TabCollection::Position position = {
      parent_collection->GetHandle(),
      parent_collection->GetIndexOfCollection(collection).value()};

  RemoveCollectionMapping(collection);
  std::unique_ptr<TabCollection> removed_collection =
      parent_collection->MaybeRemoveCollection(collection);

  // In the case of group return null and store it in
  // `detached_group_collections_` instead.
  if (removed_collection->type() == TabCollection::Type::GROUP) {
    detached_group_collections_.push_back(base::WrapUnique(
        static_cast<TabGroupTabCollection*>(removed_collection.release())));
  }

  parent_collection->NotifyOnChildrenRemoved(
      GetPassKey(), position, NodeHandles{collection_handle}, nullptr);
  return removed_collection;
}

void TabStripCollection::MoveTabImpl(TabInterface* tab_ptr,
                                     TabCollection::Position& position) {
  TabCollection* src_parent_collection =
      tab_ptr->GetParentCollection(GetPassKey());
  TabCollection::Position src_details{
      src_parent_collection->GetHandle(),
      src_parent_collection->GetIndexOfTab(tab_ptr).value()};

  TabCollectionNodes handles;
  handles.push_back(tab_ptr->GetHandle());

  TabCollection* dst_parent_collection = position.parent_handle.Get();

  // If the tab and the position are tied to the same direct parent then we need
  // might need to update the position index.
  if (src_parent_collection == dst_parent_collection &&
      src_parent_collection->GetIndexOfTab(tab_ptr).value() < position.index) {
    position.index -= 1;
  }

  ScopedTab removed_tab = src_parent_collection->MaybeRemoveTab(tab_ptr);

  dst_parent_collection->AddTab(std::move(removed_tab), position.index);

  // Notify removes,add and moves based on the common ancestor.
  TabCollection* common_ancestor =
      GetCommonAncestor(src_parent_collection, dst_parent_collection);

  src_parent_collection->NotifyOnChildrenRemoved(GetPassKey(), src_details,
                                                 handles, common_ancestor);

  dst_parent_collection->NotifyOnChildrenAdded(GetPassKey(), handles, position,
                                               common_ancestor, false);

  common_ancestor->NotifyOnChildMoved(GetPassKey(), handles[0], src_details,
                                      position, nullptr);
}

void TabStripCollection::MoveCollectionImpl(TabCollection* collection_ptr,
                                            TabCollection::Position& position) {
  TabCollection* src_parent_collection = collection_ptr->GetParentCollection();
  TabCollection::Position src_details{
      src_parent_collection->GetHandle(),
      src_parent_collection->GetIndexOfCollection(collection_ptr).value()};

  TabCollectionNodes handles;
  handles.push_back(collection_ptr->GetHandle());

  TabCollection* dst_parent_collection = position.parent_handle.Get();

  // If the collection and the position are tied to the same direct parent then
  // we need might need to update the position index.
  if (src_parent_collection == dst_parent_collection &&
      src_parent_collection->GetIndexOfCollection(collection_ptr).value() <
          position.index) {
    position.index -= 1;
  }
  std::unique_ptr<TabCollection> removed_collection =
      src_parent_collection->MaybeRemoveCollection(collection_ptr);

  dst_parent_collection->AddCollection(std::move(removed_collection),
                                       position.index);

  // Notify removes,add and moves based on the common ancestor.
  TabCollection* common_ancestor =
      GetCommonAncestor(src_parent_collection, dst_parent_collection);

  src_parent_collection->NotifyOnChildrenRemoved(GetPassKey(), src_details,
                                                 handles, common_ancestor);

  dst_parent_collection->NotifyOnChildrenAdded(
      GetPassKey(), handles, position, common_ancestor,
      (collection_ptr->TabCountRecursive() > 0) ? true : false);

  common_ancestor->NotifyOnChildMoved(GetPassKey(), handles[0], src_details,
                                      position, nullptr);
}

TabCollection::Position TabStripCollection::GetInsertionDetails(
    int index,
    bool pinned,
    std::optional<tab_groups::TabGroupId> group) {
  size_t direct_dst_index;
  TabCollection* insert_collection = nullptr;

  if (pinned) {
    direct_dst_index = pinned_collection_->ToDirectIndex(index);
    insert_collection = pinned_collection_;
  } else if (group.has_value()) {
    TabGroupTabCollection* group_collection =
        GetTabGroupCollection(group.value());
    CHECK(group_collection);

    if (group_collection->TabCountRecursive() == 0) {
      // Group has been created but not yet populated.
      direct_dst_index = 0;
    } else {
      size_t offset =
          GetIndexOfTabRecursive(group_collection->GetTabAtIndexRecursive(0))
              .value();

      direct_dst_index = group_collection->ToDirectIndex(index - offset);
    }
    insert_collection = group_collection;
  } else {
    size_t offset = pinned_collection_->TabCountRecursive();
    direct_dst_index = unpinned_collection_->ToDirectIndex(index - offset);
    insert_collection = unpinned_collection_;
  }

  return TabCollection::Position(insert_collection->GetHandle(),
                                 direct_dst_index);
}

TabCollection::Position TabStripCollection::GetNodePosition(
    ChildPtr tab_or_collection) {
  TabCollection::Position node_position;
  if (std::holds_alternative<DanglingUntriagedTabInterface>(
          tab_or_collection)) {
    TabInterface* tab_ptr =
        std::get<DanglingUntriagedTabInterface>(tab_or_collection);
    TabCollection* src_parent_collection =
        tab_ptr->GetParentCollection(GetPassKey());
    return TabCollection::Position{
        src_parent_collection->GetHandle(),
        src_parent_collection->GetIndexOfTab(tab_ptr).value()};
  } else {
    TabCollection* collection_ptr =
        std::get<DanglingUntriagedTabCollection>(tab_or_collection);
    TabCollection* src_parent_collection =
        collection_ptr->GetParentCollection();
    return TabCollection::Position{
        src_parent_collection->GetHandle(),
        src_parent_collection->GetIndexOfCollection(collection_ptr).value()};
  }
}

}  // namespace tabs
