// 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 "chrome/browser/web_applications/sampling_metrics_provider.h"

#include "base/metrics/histogram_macros.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_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/web_applications/app_browser_controller.h"
#include "chrome/browser/web_applications/daily_metrics_helper.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/web_app_filter.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_tab_helper.h"
#include "chrome/browser/web_applications/web_app_ui_manager.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "components/services/app_service/public/cpp/preferred_apps_list.h"
#include "components/tabs/public/tab_interface.h"
#include "components/webapps/browser/banners/app_banner_manager.h"
#include "components/webapps/browser/banners/installable_web_app_check_result.h"
#include "components/webapps/browser/banners/web_app_banner_data.h"
#include "components/webapps/browser/installable/installable_metrics.h"

namespace web_app {

namespace {

// Decreasing this number will improve accuracy at the expense of more frequent
// client-side work.
constexpr int kTimerIntervalInSeconds = 5 * 60;

using IdSet = std::set<webapps::AppId>;

// Emits UKM metrics for the given tab. The tab may be for an app window or a
// normal browser window.
void EmitUkmMetricsForTab(tabs::TabInterface* tab) {
  BrowserWindowInterface* browser = tab->GetBrowserWindowInterface();
  CHECK(browser);
  Profile* profile = browser->GetProfile();
  auto* web_app_helper =
      web_app::WebAppTabHelper::FromWebContents(tab->GetContents());
  CHECK(web_app_helper);
  std::optional<webapps::AppId> app_id = web_app_helper->app_id();
  CHECK(app_id);

  auto* provider = web_app::WebAppProvider::GetForWebApps(profile);
  auto& registrar = provider->registrar_unsafe();
  DailyInteraction interaction;

  interaction.start_url = registrar.GetAppStartUrl(*app_id);
  interaction.installed =
      registrar.AppMatches(*app_id, WebAppFilter::InstalledInChrome());
  auto install_source =
      provider->registrar_unsafe().GetLatestAppInstallSource(*app_id);
  if (install_source) {
    interaction.install_source = static_cast<int>(*install_source);
  }
  interaction.effective_display_mode = static_cast<int>(
      provider->registrar_unsafe().GetAppEffectiveDisplayMode(*app_id));

#if BUILDFLAG(IS_CHROMEOS)
  if (apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
    auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile);
    interaction.captures_links =
        proxy->PreferredAppsList().IsPreferredAppForSupportedLinks(*app_id);
  }
#else
  interaction.captures_links = registrar.CapturesLinksInScope(*app_id);
#endif
  interaction.promotable =
      registrar.AppMatches(*app_id, WebAppFilter::IsCraftedApp());

  if (tab->IsActivated() && browser->IsActive()) {
    interaction.foreground_duration = base::Seconds(kTimerIntervalInSeconds);
  } else {
    interaction.background_duration = base::Seconds(kTimerIntervalInSeconds);
  }
  // Note that with this new sampling approach we are no longer tracking the
  // concept of session, and thus don't fill in num_sessions.
  FlushOldRecordsAndUpdate(interaction, profile);
}

// Checks whether metrics should be emitted. If so, updates `emitted_ids` and
// emits metrics.
void MaybeEmitUkmMetricsForTab(tabs::TabInterface* tab, IdSet& emitted_ids) {
  CHECK(tab->GetContents());
  auto* web_app_helper =
      web_app::WebAppTabHelper::FromWebContents(tab->GetContents());
  if (!web_app_helper) {
    return;
  }
  std::optional<webapps::AppId> app_id = web_app_helper->app_id();

  // A tab in an app window doesn't necessarily have to be in-scope of that
  // app. In can be out of scope, or simply not have it's navigation committed
  // yet.
  if (!app_id) {
    return;
  }

  // We only emit UKM metrics a single time for a given AppId.
  if (emitted_ids.contains(*app_id)) {
    return;
  }

  auto* provider = web_app::WebAppProvider::GetForWebApps(
      tab->GetBrowserWindowInterface()->GetProfile());
  auto& registrar = provider->registrar_unsafe();
  std::optional<mojom::UserDisplayMode> user_display_mode =
      registrar.GetAppUserDisplayMode(*app_id);
  CHECK(user_display_mode);
  switch (*user_display_mode) {
    case mojom::UserDisplayMode::kBrowser:
      // Emit metrics only if the app is configured to run in a tab
      // window, and is running in a tab.
      if (tab->GetBrowserWindowInterface()->GetType() ==
          BrowserWindowInterface::Type::TYPE_NORMAL) {
        emitted_ids.insert(*app_id);
        EmitUkmMetricsForTab(tab);
      }
      break;
    case mojom::UserDisplayMode::kStandalone:
      // Emit metrics only if the app is configured to run in a standalone
      // window, and is running in a standalone window.
      if (AppBrowserController::IsWebApp(tab->GetBrowserWindowInterface())) {
        emitted_ids.insert(*app_id);
        EmitUkmMetricsForTab(tab);
      }
      break;
    case mojom::UserDisplayMode::kTabbed:
      // We don't track metrics for tabbed standalone PWAs.
      break;
  }
}

// Checks whether metrics should be emitted. If so, updates `emitted_ids` and
// emits metrics.
void MaybeEmitUkmMetricsForPromotable(
    tabs::TabInterface* tab,
    const webapps::WebAppBannerData& banner_data,
    IdSet& emitted_ids) {
  const GURL& url = banner_data.manifest().start_url;
  if (emitted_ids.contains(url.spec())) {
    return;
  }

  emitted_ids.insert(url.spec());
  DailyInteraction interaction;
  interaction.start_url = url;
  interaction.installed = false;
  if (!banner_data.manifest().display_override.empty()) {
    interaction.effective_display_mode =
        static_cast<int>(banner_data.manifest().display_override[0].display());
  } else {
    interaction.effective_display_mode =
        static_cast<int>(banner_data.manifest().display);
  }
  interaction.promotable = true;
  FlushOldRecordsAndUpdate(interaction,
                           tab->GetBrowserWindowInterface()->GetProfile());
}

}  // namespace

SamplingMetricsProvider::SamplingMetricsProvider() {
  timer_.Start(FROM_HERE, base::Seconds(kTimerIntervalInSeconds),
               base::BindRepeating(&SamplingMetricsProvider::EmitMetrics));
}

SamplingMetricsProvider::~SamplingMetricsProvider() = default;

void SamplingMetricsProvider::EmitMetrics() {
  // Total number of PWAs, including backgrounded, separated by tabbed vs
  // standalone.
  int standalone_pwas_count = 0;
  int tabbed_pwas_count = 0;

  // Whether the foreground window has a PWA as its foreground tab, separated by
  // tabbed vs standalone.
  bool standalone_pwas_in_active_use = false;
  bool tabbed_pwas_in_active_use = false;

  // Number of tabbed PWAs, split by most common configurations.
  int tabbed_pwas_user_display_mode_browser_count = 0;
  int tabbed_pwas_user_display_mode_browser_installed_by_user_count = 0;

  int tabbed_pwas_display_mode_standalone_count = 0;
  int tabbed_pwas_display_mode_standalone_installed_by_user_count = 0;

  IdSet emitted_ukm_ids;
  bool is_migration_suggested_in_menu = false;
  bool is_migration_dialog_showing = false;

  for (BrowserWindowInterface* browser : GetAllBrowserWindowInterfaces()) {
    if (!AreWebAppsEnabled(browser->GetProfile())) {
      continue;
    }

    auto* provider = WebAppProvider::GetForWebApps(browser->GetProfile());
    CHECK(provider);

    // If this is a standalone app window.
    if (AppBrowserController::IsWebApp(browser)) {
      // A browser may be being closed due to empty tabs. See
      // https://crbug.com/378020140.
      tabs::TabInterface* active_tab = browser->GetActiveTabInterface();
      if (!active_tab) {
        continue;
      }

      if (provider->ui_manager().IsAppMigrationSuggested(browser)) {
        is_migration_suggested_in_menu = true;
      }

      if (provider->ui_manager().IsAppMigrationDialogShowing(browser)) {
        is_migration_dialog_showing = true;
      }

      ++standalone_pwas_count;

      // TODO(https://crbug.com/358404364): This function does not work on macOS
      // for app windows.
      if (browser->IsActive()) {
        standalone_pwas_in_active_use = true;
      }

      MaybeEmitUkmMetricsForTab(active_tab, emitted_ukm_ids);
    }

    // If this is a PWA-tab in a normal browser window.
    if (browser->GetType() == BrowserWindowInterface::Type::TYPE_NORMAL) {
      auto& registrar = provider->registrar_unsafe();

      for (tabs::TabInterface* tab : browser->GetAllTabInterfaces()) {
        auto* web_app_helper =
            web_app::WebAppTabHelper::FromWebContents(tab->GetContents());
        std::optional<webapps::AppId> app_id = web_app_helper->app_id();
        if (app_id) {
          ++tabbed_pwas_count;
          if (tab->IsActivated() && browser->IsActive()) {
            tabbed_pwas_in_active_use = true;
          }

          std::optional<mojom::UserDisplayMode> user_display_mode =
              registrar.GetAppUserDisplayMode(*app_id);
          bool installed_by_user =
              registrar.AppMatches(*app_id, WebAppFilter::InstalledByUser());
          if (user_display_mode == mojom::UserDisplayMode::kBrowser) {
            ++tabbed_pwas_user_display_mode_browser_count;
            if (installed_by_user) {
              ++tabbed_pwas_user_display_mode_browser_installed_by_user_count;
            }
          }

          if (user_display_mode == mojom::UserDisplayMode::kStandalone) {
            ++tabbed_pwas_display_mode_standalone_count;
            if (installed_by_user) {
              ++tabbed_pwas_display_mode_standalone_installed_by_user_count;
            }
          }
          MaybeEmitUkmMetricsForTab(tab, emitted_ukm_ids);
        } else {
          // If the tab does not have an app id, it might be promotable.
          auto* app_banner_manager =
              webapps::AppBannerManager::FromWebContents(tab->GetContents());
          if (app_banner_manager) {
            std::optional<webapps::WebAppBannerData> banner_data =
                app_banner_manager->GetCurrentWebAppBannerData();
            webapps::InstallableWebAppCheckResult installable =
                app_banner_manager->GetInstallableWebAppCheckResult();

            if (banner_data &&
                installable ==
                    webapps::InstallableWebAppCheckResult::kYes_Promotable) {
              MaybeEmitUkmMetricsForPromotable(tab, *banner_data,
                                               emitted_ukm_ids);
            }
          }
        }
      }
    }
  }

  int pwas_count = standalone_pwas_count + tabbed_pwas_count;
  bool pwas_in_active_use =
      standalone_pwas_in_active_use || tabbed_pwas_in_active_use;
  UMA_HISTOGRAM_COUNTS_100("WebApp.Engagement2.Count", pwas_count);
  UMA_HISTOGRAM_BOOLEAN("WebApp.Engagement2.Active", pwas_in_active_use);

  UMA_HISTOGRAM_COUNTS_100("WebApp.Engagement2.Standalone.Count",
                           standalone_pwas_count);
  UMA_HISTOGRAM_COUNTS_100("WebApp.Engagement2.Tabbed.Count",
                           tabbed_pwas_count);
  UMA_HISTOGRAM_BOOLEAN("WebApp.Engagement2.Standalone.Active",
                        standalone_pwas_in_active_use);
  UMA_HISTOGRAM_BOOLEAN("WebApp.Engagement2.Tabbed.Active",
                        tabbed_pwas_in_active_use);

  UMA_HISTOGRAM_COUNTS_100(
      "WebApp.Engagement2.Tabbed.UserDisplayModeBrowser.Count",
      tabbed_pwas_user_display_mode_browser_count);
  UMA_HISTOGRAM_COUNTS_100(
      "WebApp.Engagement2.Tabbed.UserDisplayModeBrowserInstalledByUser.Count",
      tabbed_pwas_user_display_mode_browser_installed_by_user_count);
  UMA_HISTOGRAM_COUNTS_100(
      "WebApp.Engagement2.Tabbed.UserDisplayModeStandalone.Count",
      tabbed_pwas_display_mode_standalone_count);
  UMA_HISTOGRAM_COUNTS_100(
      "WebApp.Engagement2.Tabbed.UserDisplayModeStandaloneInstalledByUser."
      "Count",
      tabbed_pwas_display_mode_standalone_installed_by_user_count);

  UMA_HISTOGRAM_BOOLEAN("WebApp.Engagement2.Standalone.MigrationSuggested",
                        is_migration_suggested_in_menu);
  UMA_HISTOGRAM_BOOLEAN("WebApp.Engagement2.Standalone.MigrationDialogShowing",
                        is_migration_dialog_showing);
}

}  // namespace web_app
