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

#include "chrome/browser/ui/uma_browsing_activity_observer.h"

#include <algorithm>

#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "chrome/browser/lifetime/termination_notification.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/upgrade_detector/upgrade_detector.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/search_engines/template_url_service.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "components/tabs/public/tab_group.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"
#include "ui/gfx/range/range.h"

namespace {

UMABrowsingActivityObserver* g_uma_browsing_activity_observer_instance =
    nullptr;

}  // namespace

// static
void UMABrowsingActivityObserver::Init() {
  DCHECK(!g_uma_browsing_activity_observer_instance);
  // Must be created before any Browsers are.
  DCHECK(!KeepAliveRegistry::GetInstance()->IsOriginRegistered(
      KeepAliveOrigin::BROWSER));
  g_uma_browsing_activity_observer_instance = new UMABrowsingActivityObserver;
}

UMABrowsingActivityObserver::UMABrowsingActivityObserver() {
  subscription_ = browser_shutdown::AddAppTerminatingCallback(base::BindOnce(
      &UMABrowsingActivityObserver::OnAppTerminating, base::Unretained(this)));
}

UMABrowsingActivityObserver::~UMABrowsingActivityObserver() = default;

void UMABrowsingActivityObserver::OnNavigationEntryCommitted(
    content::WebContents* web_contents,
    const content::LoadCommittedDetails& load_details) const {
  // Track whether the page loaded is a search results page (SRP). Track
  // the non-SRP navigations as well so there is a control.
  base::RecordAction(base::UserMetricsAction("NavEntryCommitted"));

  if (!load_details.is_navigation_to_different_page()) {
    // Don't log for subframes or other trivial types.
    return;
  }

  LogBrowserTabCount();
}

void UMABrowsingActivityObserver::OnAppTerminating() const {
  LogTimeBeforeUpdate();

  DCHECK_EQ(this, g_uma_browsing_activity_observer_instance);
  delete g_uma_browsing_activity_observer_instance;
  g_uma_browsing_activity_observer_instance = nullptr;
}

void UMABrowsingActivityObserver::LogTimeBeforeUpdate() const {
  const base::Time upgrade_detected_time =
      UpgradeDetector::GetInstance()->upgrade_detected_time();
  if (upgrade_detected_time.is_null()) {
    return;
  }
  const base::TimeDelta time_since_upgrade =
      base::Time::Now() - upgrade_detected_time;
  constexpr int kMaxDays = 30;
  base::UmaHistogramExactLinear("UpgradeDetector.DaysBeforeUpgrade",
                                base::TimeDelta(time_since_upgrade).InDays(),
                                kMaxDays);
  base::UmaHistogramCounts1000("UpgradeDetector.HoursBeforeUpgrade",
                               base::TimeDelta(time_since_upgrade).InHours());
}

void UMABrowsingActivityObserver::LogBrowserTabCount() const {
  int tab_count = 0;
  int tab_group_count = 0;
  int collapsed_tab_group_count = 0;
  int customized_tab_group_count = 0;
  int pinned_tab_count = 0;

  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
      [&](BrowserWindowInterface* browser) {
        TabStripModel* const tab_strip_model = browser->GetTabStripModel();

        // Record how many tabs each window has open.
        UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountPerWindow",
                                    tab_strip_model->count(), 1, 200, 50);
        tab_count += tab_strip_model->count();

        for (const tabs::TabInterface* tab : *tab_strip_model) {
          if (tab->IsPinned()) {
            pinned_tab_count++;
          }
        }

        if (tab_strip_model->group_model()) {
          const std::vector<tab_groups::TabGroupId>& groups =
              tab_strip_model->group_model()->ListTabGroups();
          tab_group_count += groups.size();
          for (const tab_groups::TabGroupId& group_id : groups) {
            const TabGroup* const tab_group =
                tab_strip_model->group_model()->GetTabGroup(group_id);
            if (tab_group->IsCustomized() ||
                !tab_group->visual_data()->title().empty()) {
              ++customized_tab_group_count;
            }
            if (tab_group->visual_data()->is_collapsed()) {
              ++collapsed_tab_group_count;
            }
          }
        }

        if (browser->IsActive()) {
          // Record how many tabs the active window has open.
          UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountActiveWindow",
                                      tab_strip_model->count(), 1, 200, 50);
        }

        return true;
      });

  // Record how many tabs total are open (across all windows).
  UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountPerLoad", tab_count, 1, 200, 50);

  // Record how many tab groups (including zero) are open across all windows.
  UMA_HISTOGRAM_COUNTS_100("TabGroups.UserGroupCountPerLoad", tab_group_count);

  UMA_HISTOGRAM_COUNTS_100("TabGroups.UserPinnedTabCountPerLoad",
                           std::min(pinned_tab_count, 100));

  // Record how many tabs are in the current group. Records 0 if the active tab
  // is not in a group.
  BrowserWindowInterface* const current_browser =
      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
  if (current_browser) {
    TabStripModel* const tab_strip_model = current_browser->GetTabStripModel();
    if (tab_strip_model->group_model()) {
      const std::optional<tab_groups::TabGroupId> active_group =
          tab_strip_model->GetTabGroupForTab(tab_strip_model->active_index());
      UMA_HISTOGRAM_COUNTS_100("Tabs.TabCountInGroupPerLoad",
                               active_group.has_value()
                                   ? tab_strip_model->group_model()
                                         ->GetTabGroup(active_group.value())
                                         ->ListTabs()
                                         .length()
                                   : 0);
    }
  }

  // Record how many tab groups with a user-set name or color are open across
  // all windows.
  UMA_HISTOGRAM_COUNTS_100("TabGroups.UserCustomizedGroupCountPerLoad",
                           customized_tab_group_count);

  // Record how many tab groups are collapsed across all windows.
  UMA_HISTOGRAM_COUNTS_100("TabGroups.CollapsedGroupCountPerLoad",
                           collapsed_tab_group_count);
}

UMABrowsingActivityObserver::TabHelper::TabHelper(
    content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents),
      content::WebContentsUserData<TabHelper>(*web_contents) {}

UMABrowsingActivityObserver::TabHelper::~TabHelper() = default;

void UMABrowsingActivityObserver::TabHelper::NavigationEntryCommitted(
    const content::LoadCommittedDetails& load_details) {
  // This is null in unit tests. Crash reports suggest it's possible for it to
  // be null in production. See https://crbug.com/41482621 and
  // https://crbug.com/41496706
  if (!g_uma_browsing_activity_observer_instance) {
    return;
  }

  g_uma_browsing_activity_observer_instance->OnNavigationEntryCommitted(
      web_contents(), load_details);
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(UMABrowsingActivityObserver::TabHelper);
