// 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.

#import "ios/chrome/browser/browsing_data/model/tabs_counter.h"

#import "base/functional/bind.h"
#import "components/browsing_data/core/pref_names.h"
#import "ios/chrome/browser/sessions/model/session_restoration_service.h"
#import "ios/chrome/browser/sessions/model/session_restoration_service_tmpl.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/browser_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "net/base/completion_repeating_callback.h"

TabsCounter::TabsCounter(BrowserList* browser_list,
                         SessionRestorationService* service)
    : browser_list_(browser_list), service_(service), weak_ptr_factory_(this) {
  CHECK(browser_list);
  CHECK(service);
}

TabsCounter::~TabsCounter() = default;

const char* TabsCounter::GetPrefName() const {
  return browsing_data::prefs::kCloseTabs;
}

void TabsCounter::Count() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Cancel existing requests and make sure the counters are reset.
  weak_ptr_factory_.InvalidateWeakPtrs();
  ResetCounts();

  std::set<Browser*> all_regular_browsers = browser_list_->BrowsersOfType(
      BrowserList::BrowserType::kRegularAndInactive);

  // Report result early if browser list is empty.
  if (all_regular_browsers.empty()) {
    ReportTabsResult();
    return;
  }

  for (Browser* browser : all_regular_browsers) {
    pending_tasks_count_++;
    service_->LoadDataFromStorage(
        browser,
        base::BindRepeating(
            &tabs_closure_util::GetLastCommittedTimestampFromStorage),
        base::BindOnce(&TabsCounter::OnLoadDataFromStorageResult,
                       weak_ptr_factory_.GetWeakPtr(), browser->AsWeakPtr()));
  }
}

void TabsCounter::OnLoadDataFromStorageResult(
    base::WeakPtr<Browser> weak_browser,
    tabs_closure_util::WebStateIDToTime result) {
  // Ensure that all callbacks are on the same thread, so that we do not need
  // a mutex for `total_tab_count_`, `total_window_count_` and
  // `pending_tasks_count_`.
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  Browser* browser = weak_browser.get();
  if (!browser) {
    return;
  }

  std::set<web::WebStateID> tabs_to_close = tabs_closure_util::GetTabsToClose(
      browser->GetWebStateList(), GetPeriodStart(), GetPeriodEnd(), result);
  int tab_count = tabs_to_close.size();
  if (tab_count > 0) {
    total_tab_count_ += tab_count;

    // Inactive tabs live in a separate Browser from their active counterparts.
    // This avoids overcounting the number of windows that have tabs within the
    // timeframe (e.g. active and inactive browsers with tabs within the range
    // in the same window).
    if (!active_browsers_.contains(browser->GetActiveBrowser())) {
      total_window_count_++;
      active_browsers_.insert(browser->GetActiveBrowser());
    }

    // The cached tabs' information should include the timestamp for all tabs
    // within the time range including pinned tabs. Tabs can be unpined without
    // being realized, and as such we should make sure we still have their
    // information cached.
    cached_tabs_info_.merge(tabs_closure_util::GetTabsInfoForCache(
        result, GetPeriodStart(), GetPeriodEnd()));
  }

  // Check if all tasks have returned. If not, return early.
  CHECK_GT(pending_tasks_count_, 0);
  if (--pending_tasks_count_ > 0) {
    return;
  }

  // If all tasks have returned, then report the result and reset counts.
  ReportTabsResult();
}

void TabsCounter::ReportTabsResult() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_GE(total_tab_count_, 0);
  CHECK_GE(total_window_count_, 0);

  auto tabs_result = std::make_unique<TabsCounter::TabsResult>(
      this, total_tab_count_, total_window_count_, cached_tabs_info_);
  ReportResult(std::move(tabs_result));
  ResetCounts();
}

void TabsCounter::ResetCounts() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  total_tab_count_ = 0;
  total_window_count_ = 0;
  pending_tasks_count_ = 0;
  active_browsers_.clear();
  cached_tabs_info_.clear();
}

// TabsCounter::TabsResult -----------------------------------------

TabsCounter::TabsResult::TabsResult(
    const TabsCounter* source,
    ResultInt tab_count,
    ResultInt window_count,
    tabs_closure_util::WebStateIDToTime cached_tabs_info)
    : FinishedResult(source, tab_count),
      window_count_(window_count),
      cached_tabs_info_(cached_tabs_info) {}

TabsCounter::TabsResult::~TabsResult() = default;
