// 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/page_content_annotations/core/page_content_annotations_features.h"

#include <algorithm>
#include <cstddef>
#include <cstdint>

#include "base/i18n/legacy_language_tag_helpers.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "components/page_content_annotations/core/page_content_annotations_switches.h"
#include "ui/base/l10n/l10n_util.h"

namespace page_content_annotations::features {

namespace {

constexpr auto enabled_by_default_desktop_only =
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
    base::FEATURE_DISABLED_BY_DEFAULT;
#else
    base::FEATURE_ENABLED_BY_DEFAULT;
#endif

constexpr auto enabled_by_default_non_ios =
#if BUILDFLAG(IS_IOS)
    base::FEATURE_DISABLED_BY_DEFAULT;
#else
    base::FEATURE_ENABLED_BY_DEFAULT;
#endif

constexpr auto enabled_by_default_ios_only =
#if BUILDFLAG(IS_IOS)
    base::FEATURE_ENABLED_BY_DEFAULT;
#else
    base::FEATURE_DISABLED_BY_DEFAULT;
#endif

const base::FeatureParam<base::TimeDelta> kAnnotatedPageContentCaptureDelay{
    &kAnnotatedPageContentExtraction, "capture_delay", base::Seconds(3)};

const base::FeatureParam<bool> kAnnotatedPageContentStudyIncludeInnerText{
    &kAnnotatedPageContentExtraction, "include_inner_text", false};

const base::FeatureParam<bool> kAnnotatedPageContentOnCriticalPath{
    &kAnnotatedPageContentExtraction, "on_critical_path", false};

const base::FeatureParam<std::string> kAnnotatedPageContentMode{
    &kAnnotatedPageContentExtraction, "mode", "default"};

const base::FeatureParam<std::string> kPageContentExtractionTriggeringMode{
    &kAnnotatedPageContentExtraction, "triggering_mode", "on_load"};

bool IsSupportedLocale(const std::string& locale,
                       const std::string& supported_locales) {
  if (supported_locales == "*") {
    return true;
  }

  std::vector<std::string> supported = base::SplitString(
      supported_locales, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  // An empty admits any locale.
  if (supported.empty()) {
    return true;
  }

  return std::ranges::contains(supported, locale) ||
         std::ranges::contains(
             supported, base::i18n::GetLanguageSubtagUsingLanguageTag(locale));
}

bool IsSupportedCountry(const std::string& country_code,
                        const std::string& supported_countries) {
  if (supported_countries == "*") {
    return true;
  }

  std::vector<std::string> supported =
      base::SplitString(supported_countries, ",", base::TRIM_WHITESPACE,
                        base::SPLIT_WANT_NONEMPTY);
  // An empty allowlist admits any country.
  if (supported.empty()) {
    return true;
  }

  return std::ranges::any_of(
      supported, [&country_code](const auto& supported_country_code) {
        return base::EqualsCaseInsensitiveASCII(supported_country_code,
                                                country_code);
      });
}

}  // namespace

BASE_FEATURE(kPageContentAnnotationsValidation,
             base::FEATURE_DISABLED_BY_DEFAULT);

BASE_FEATURE(kOptimizationGuideUseContinueOnShutdownForPageContentAnnotations,
             enabled_by_default_non_ios);

BASE_FEATURE(kExtractRelatedSearchesFromPrefetchedZPSResponse,
             enabled_by_default_desktop_only);

BASE_FEATURE(kAnnotatedPageContentExtraction,
             base::FEATURE_DISABLED_BY_DEFAULT);

BASE_FEATURE(kAnnotatedPageContentExtractionOnHideFix,
             base::FEATURE_ENABLED_BY_DEFAULT);

BASE_FEATURE(kPageContentExtractionAllowOnDemandWithoutObservers,
             base::FEATURE_ENABLED_BY_DEFAULT);

BASE_FEATURE(kAnnotatedPageContentNonSalientFiltering,
             base::FEATURE_ENABLED_BY_DEFAULT);

const base::FeatureParam<bool> kAnnotatedPageContentExcludeAdRelatedParam{
    &kAnnotatedPageContentNonSalientFiltering, "exclude_ad_related", true};

BASE_FEATURE(kAnnotatedPageContentPDFTextExtraction,
             base::FEATURE_ENABLED_BY_DEFAULT);

const base::FeatureParam<size_t> kMaxPDFTextExtractionByteSizeParam{
    &kAnnotatedPageContentPDFTextExtraction, "max_text_byte_size",
    1048576};  // 1MB

BASE_FEATURE(kOnDeviceCategoryClassifier, enabled_by_default_desktop_only);

BASE_FEATURE(kPageContentCache, enabled_by_default_ios_only);

const base::FeatureParam<int> kPageContentCacheMaxCacheAgeInDays{
    &kPageContentCache, "max_cache_age_in_days", 7};

const base::FeatureParam<int> kPageContentCacheMaxTabs{
    &kPageContentCache, "max_cache_tabs_count", 50};

const base::FeatureParam<bool> kPageContentCacheEnableScreenshot{
    &kPageContentCache, "enable_screenshot", false};

const base::FeatureParam<bool> kPageContentCacheUseUserEngagement{
    &kPageContentCache, "page_content_cache_use_user_engagement", false};

BASE_FEATURE(kPageSettledMonitor, base::FEATURE_ENABLED_BY_DEFAULT);

const base::FeatureParam<base::TimeDelta> kPageStabilityTimeout{
    &kPageSettledMonitor, "page-stability-timeout", base::Seconds(4)};

const base::FeatureParam<base::TimeDelta> kPageStabilityMinWait{
    &kPageSettledMonitor, "page-stability-min-wait", base::Seconds(1)};

const base::FeatureParam<base::TimeDelta> kPaintStabilityInitialPaintTimeout{
    &kPageSettledMonitor, "paint-stability-initial-paint-timeout",
    base::Seconds(1)};

const base::FeatureParam<base::TimeDelta> kPaintStabilitySubsequentPaintTimeout{
    &kPageSettledMonitor, "paint-stability-subsequent-paint-timeout",
    base::Seconds(1)};

const base::FeatureParam<base::TimeDelta> kObservationDelayTimeout{
    &kPageSettledMonitor, "observation-delay-timeout", base::Seconds(10)};

const base::FeatureParam<base::TimeDelta> kObservationDelayLcp{
    &kPageSettledMonitor, "observation-delay-lcp", base::Seconds(1)};

BASE_FEATURE(kPageContentExtractionUsingPageSettledMonitor,
             base::FEATURE_ENABLED_BY_DEFAULT);

const base::FeatureParam<base::TimeDelta> kPageSettledCaptureDelay{
    &kPageContentExtractionUsingPageSettledMonitor, "capture_delay",
    base::TimeDelta()};

BASE_FEATURE(kPageSettledMonitorExcludeAdFrameLoading,
             base::FEATURE_DISABLED_BY_DEFAULT);

BASE_FEATURE(kPageSettledMonitorSkipAwaitVisualStateForHiddenTabs,
             base::FEATURE_ENABLED_BY_DEFAULT);

base::TimeDelta PCAServiceWaitForTitleDelayDuration() {
  return base::Milliseconds(5000);
}

bool ShouldEnablePageContentAnnotations() {
  // Remote page metadata is permanently enabled without a feature flag, so
  // the service should always be enabled.
  return true;
}

size_t MaxContentAnnotationRequestsCached() {
  return 50;
}

bool ShouldExtractRelatedSearches() {
  return true;
}

bool ShouldExecutePageVisibilityModelOnPageContent(const std::string& locale) {
#if defined(ARCH_CPU_ARMEL)
  return false;
#else
  return IsSupportedLocale(locale, "ar,en,es,fa,fr,hi,id,pl,pt,tr,vi");
#endif
}

bool ShouldExecuteOnDeviceCategoryClassifierOnPageContent(
    const std::string& locale,
    const std::string& country_code) {
  // If the feature is overridden (e.g. via server-side config or command-line),
  // use that state.
  auto* feature_list = base::FeatureList::GetInstance();
  if (feature_list &&
      feature_list->IsFeatureOverridden(kOnDeviceCategoryClassifier.name)) {
    // Important: If a server-side config applies to this client (i.e. after
    // accounting for its filters), but the client gets assigned to the default
    // group, they will still take this code path and receive the state
    // specified via BASE_FEATURE() above.
    return base::FeatureList::IsEnabled(kOnDeviceCategoryClassifier);
  }
  return base::FeatureList::IsEnabled(kOnDeviceCategoryClassifier) &&
         IsSupportedLocale(locale, "en") &&
         IsSupportedCountryForFeature(country_code, kOnDeviceCategoryClassifier,
                                      "US");
}

int NumBitsForRAPPORMetrics() {
  // The number of bits must be at least 1.
  return std::max(
      1, GetFieldTrialParamByFeatureAsInt(kPageContentAnnotationsValidation,
                                          "num_bits_for_rappor_metrics", 4));
}

double NoiseProbabilityForRAPPORMetrics() {
  // The noise probability must be between 0 and 1.
  return std::max(0.0, std::min(1.0, GetFieldTrialParamByFeatureAsDouble(
                                         kPageContentAnnotationsValidation,
                                         "noise_prob_for_rappor_metrics", .5)));
}

size_t AnnotateVisitBatchSize() {
  // When new visits are synced, the service gets visit notifications in a loop.
  // The service drops new visits during processing a batch. Often only the
  // `kDefaultBatchSize` entries are annotated when new visits are synced. Set
  // the limit to 5 since up to 5 URLs are shown on tab resume module.
  constexpr int kDefaultBatchSize = 5;
  return kDefaultBatchSize;
}

base::TimeDelta PageContentAnnotationValidationStartupDelay() {
  return switches::PageContentAnnotationsValidationStartupDelay().value_or(
      base::Seconds(std::max(
          1, GetFieldTrialParamByFeatureAsInt(kPageContentAnnotationsValidation,
                                              "startup_delay", 30))));
}

size_t PageContentAnnotationsValidationBatchSize() {
  return switches::PageContentAnnotationsValidationBatchSize().value_or(
      std::max(1, GetFieldTrialParamByFeatureAsInt(
                      kPageContentAnnotationsValidation, "batch_size", 25)));
}

base::TimeDelta PageContentAnnotationBatchSizeTimeoutDuration() {
  return base::Seconds(1);
}

size_t MaxVisitAnnotationCacheSize() {
  return 50;
}

size_t MaxRelatedSearchesCacheSize() {
  return GetFieldTrialParamByFeatureAsInt(
      kExtractRelatedSearchesFromPrefetchedZPSResponse,
      "max_related_searches_cache_size", 10);
}

bool IsAnnotatedPageContentOnCriticalPath() {
  return kAnnotatedPageContentOnCriticalPath.Get();
}

base::TimeDelta GetAnnotatedPageContentCaptureDelay() {
  return kAnnotatedPageContentCaptureDelay.Get();
}

bool ShouldAnnotatedPageContentStudyIncludeInnerText() {
  return kAnnotatedPageContentStudyIncludeInnerText.Get();
}

std::string AnnotatedPageContentMode() {
  return kAnnotatedPageContentMode.Get();
}

uint32_t MaxPDFTextExtractionByteSize() {
  size_t limit = kMaxPDFTextExtractionByteSizeParam.Get();
  return base::IsValueInRangeForNumericType<uint32_t>(limit)
             ? static_cast<uint32_t>(limit)
             : static_cast<uint32_t>(
                   kMaxPDFTextExtractionByteSizeParam.default_value);
}

bool ShouldAnnotatedPageContentExcludeAdRelated() {
  return base::FeatureList::IsEnabled(
             kAnnotatedPageContentNonSalientFiltering) &&
         kAnnotatedPageContentExcludeAdRelatedParam.Get();
}

PageContentExtractionTriggeringMode GetPageContentExtractionTriggeringMode() {
  std::string mode_str = kPageContentExtractionTriggeringMode.Get();
  if (mode_str == "on_hidden") {
    return PageContentExtractionTriggeringMode::kOnHidden;
  }
  if (mode_str == "on_load_and_hidden") {
    return PageContentExtractionTriggeringMode::kOnLoadAndHidden;
  }
  return PageContentExtractionTriggeringMode::kOnLoad;
}

base::TimeDelta GetPageSettledCaptureDelay() {
  return kPageSettledCaptureDelay.Get();
}

bool IsSupportedCountryForFeature(const std::string& country_code,
                                  const base::Feature& feature,
                                  const std::string& default_value) {
  if (!base::FeatureList::IsEnabled(feature)) {
    return false;
  }

  std::string value =
      base::GetFieldTrialParamValueByFeature(feature, "supported_countries");
  if (value.empty()) {
    // The default list of supported countries for optimization guide features.
    value = default_value;
  }

  return IsSupportedCountry(country_code, value);
}

}  // namespace page_content_annotations::features
