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

#ifndef COMPONENTS_WEBUI_FLAGS_FLAGS_STATE_H_
#define COMPONENTS_WEBUI_FLAGS_FLAGS_STATE_H_

#include <stddef.h>

#include <map>
#include <set>
#include <string>
#include <string_view>
#include <vector>

#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_span.h"
#include "base/values.h"

#if BUILDFLAG(IS_ANDROID)
#include "components/cached_flags/android/jni_delegate.h"
#endif

namespace flags_ui {

// Internal functionality exposed for tests.
namespace internal {
// The trial group selected when feature variation parameters are registered via
// FlagsState::RegisterFeatureVariationParameters().
extern const char kTrialGroupAboutFlags[];
}  // namespace internal

struct FeatureEntry;
class FlagsStorage;

// Enumeration of flag filters. These values don't persist and can be
// renumbered.
enum {
  kOsMac = 1 << 0,
  kOsWin = 1 << 1,
  kOsLinux = 1 << 2,
  kOsCrOS = 1 << 3,
  kOsAndroid = 1 << 4,
  kOsCrOSOwnerOnly = 1 << 5,
  kOsIos = 1 << 6,
  kOsFuchsia = 1 << 7,
  kOsWebView = 1 << 8,

  kDeprecated = 1 << 9,

  // Flags marked with this are internal to the flags system. Never set this on
  // a manually-added flag.
  kFlagInfrastructure = 1 << 10,
};

// A flag controlling the behavior of the |ConvertFlagsToSwitches| function -
// whether it should add the sentinel switches around flags.
enum SentinelsMode { kNoSentinels, kAddSentinels };

// Differentiate between generic flags available on a per session base and flags
// that influence the whole machine and can be said by the admin only. This flag
// is relevant for ChromeOS for now only and dictates whether entries marked
// with the |kOsCrOSOwnerOnly| label should be enabled in the UI or not.
enum FlagAccess { kGeneralAccessFlagsOnly, kOwnerAccessToFlags };

// Stores and encapsulates the little state that about:flags has.
class FlagsState {
 public:
  // This delegate is used for embedders to configure the behavior of
  // FlagsState. The delegate is optional.
  class Delegate {
   public:
    // Returns whether |entry| should be excluded from the sets of
    // switches/features generated by ConvertFlagsToSwitches().
    virtual bool ShouldExcludeFlag(const FlagsStorage* state,
                                   const FeatureEntry& entry);

   protected:
    Delegate();
    virtual ~Delegate();

    Delegate(const Delegate&) = delete;
    Delegate& operator=(const Delegate&) = delete;
  };

  // The delegate may be nullptr.
  FlagsState(base::span<const FeatureEntry> feature_entries,
             Delegate* delegate);

  FlagsState(const FlagsState&) = delete;
  FlagsState& operator=(const FlagsState&) = delete;

  ~FlagsState();

  // Reads the state from |flags_storage| and adds the command line flags
  // belonging to the active feature entries to |command_line|. Features are
  // appended via |enable_features_flag_name| and |disable_features_flag_name|.
  void ConvertFlagsToSwitches(FlagsStorage* flags_storage,
                              base::CommandLine* command_line,
                              SentinelsMode sentinels,
                              const char* enable_features_flag_name,
                              const char* disable_features_flag_name);

  // Returns the FeatureEntry named |internal_name|. Returns null if no entry is
  // matched.
  const FeatureEntry* FindFeatureEntryByName(
      const std::string& internal_name) const;

  // Gets sanitized entries from |flags_storage|, filtering out any entries that
  // don't exist in |feature_entries_|, and updates |flags_storage|.
  void GetSanitizedEnabledFlags(FlagsStorage* flags_storage,
                                std::set<std::string>* result) const;

  // Reads the state from |flags_storage| and fills |switches| with the set of
  // switches corresponding to enabled entries and |features| with the set of
  // strings corresponding to enabled/disabled base::Feature states. Feature
  // names are suffixed with ":enabled" or ":disabled" depending on their state.
  // Also fills |variation_ids| with variation IDs to force based on
  // flags_storage, in the format of VariationsIdsProvider::ForceVariationIds().
  void GetSwitchesAndFeaturesFromFlags(
      FlagsStorage* flags_storage,
      std::set<std::string>* switches,
      std::set<std::string>* features,
      std::set<std::string>* variation_ids) const;

  bool IsRestartNeededToCommitChanges();
  void SetFeatureEntryEnabled(FlagsStorage* flags_storage,
                              const std::string& internal_name,
                              bool enable);

  // Sets |value| as the command line switch for feature given by
  // |internal_name|. |value| contains a list of origins (serialized form of
  // url::Origin()) separated by whitespace and/or comma. Invalid values in this
  // list are ignored.
  void SetOriginListFlag(const std::string& internal_name,
                         const std::string& value,
                         FlagsStorage* flags_storage);

  // Sets |value| as the parameter for the feature given by |internal_name|.
  // |value| contains an arbitrary string.
  void SetStringFlag(const std::string& internal_name,
                     const std::string& value,
                     FlagsStorage* flags_storage);

  // This method removes command line switches that were set by flags state.
  // |switch_list| is an input and an output.
  void RemoveFlagsSwitches(base::CommandLine::SwitchMap* switch_list);

  void ResetAllFlags(FlagsStorage* flags_storage);
  void Reset();

  // Registers variations parameter values selected for features in about:flags.
  // The selected flags are retrieved from |flags_storage|, the registered
  // variation parameters are connected to their corresponding features in
  // |feature_list|. Returns the (possibly empty) comma separated list of
  // additional variation ids to register in the MetricsService that come from
  // variations selected using chrome://flags.
  std::vector<std::string> RegisterAllFeatureVariationParameters(
      FlagsStorage* flags_storage,
      base::FeatureList* feature_list);

  // A static version of above RegisterAllFeatureVariationParameters(), which
  // finds the enabled feature entries from |enabled_entries| from
  // |feature_entries|.
  // |enabled_entries| is a set of string whose format is
  // feature_entry_internal_name@index_of_enabled_variation, refer to
  // FeatureEntry::NameForOption.
  static std::vector<std::string> RegisterEnabledFeatureVariationParameters(
      const base::span<const FeatureEntry>& feature_entries,
      const std::set<std::string>& enabled_entries,
      const std::string& trial_group,
      base::FeatureList* feature_list);

  // Gets the list of feature entries. Entries that are available for the
  // current platform are appended to |supported_entries|; all other entries are
  // appended to |unsupported_entries|.
  //
  // |skip_feature_entry| is called once for each feature in |feature_entries_|,
  // and entry data for a feature is only included in the output data if the
  // callback returns |false| for the entry.
  void GetFlagFeatureEntries(
      FlagsStorage* flags_storage,
      FlagAccess access,
      base::ListValue& supported_entries,
      base::ListValue& unsupported_entries,
      base::RepeatingCallback<bool(const FeatureEntry&)> skip_feature_entry);

  // Returns the value for the current platform. This is one of the values
  // defined by the OS enum above.
  // This is exposed only for testing.
  static unsigned short GetCurrentPlatform();

 private:
  // Keeps track of affected switches for each FeatureEntry, based on which
  // choice is selected for it.
  struct SwitchEntry;

  // Adds mapping to |name_to_switch_map| to set the given switch name/value.
  void AddSwitchMapping(
      const std::string& key,
      const std::string& switch_name,
      const std::string& switch_value,
      std::map<std::string, SwitchEntry>* name_to_switch_map) const;

  // Adds mapping to |name_to_switch_map| to toggle base::Feature |feature_name|
  // to state |feature_state|, along with the given |variation_id|, in the
  // format of VariationsIdsProvider::ForceVariationIds().
  void AddFeatureMapping(
      const std::string& key,
      const std::string& feature_name,
      bool feature_state,
      const std::string& variation_id,
      std::map<std::string, SwitchEntry>* name_to_switch_map) const;

  // Updates the switches in |command_line| by applying the modifications
  // specified in |name_to_switch_map| for each entry in |enabled_entries|.
  // |enable_features_flag_name| and |disable_features_flag_name| are switches
  // used by the embedder to enable/disable features respectively if supported.
  void AddSwitchesToCommandLine(
      const std::set<std::string>& enabled_entries,
      const std::map<std::string, SwitchEntry>& name_to_switch_map,
      SentinelsMode sentinels,
      base::CommandLine* command_line,
      const char* enable_features_flag_name,
      const char* disable_features_flag_name);

  // Updates |command_line| by merging the value of the --enable-features= or
  // --disable-features= list (per the |switch_name| param) with corresponding
  // entries in |feature_switches| that have value |feature_state|. Keeps track
  // of the changes by updating |appended_switches|.
  void MergeFeatureCommandLineSwitch(
      const std::map<std::string, bool>& feature_switches,
      const char* switch_name,
      bool feature_state,
      base::CommandLine* command_line);

  // Sanitizes |enabled_entries| to only contain entries that are defined in the
  // |feature_entries_| and whose |supported_platforms| matches |platform_mask|.
  // Pass -1 to |platform_mask| to not do platform filtering.
  std::set<std::string> SanitizeList(
      const FlagsStorage* storage,
      const std::set<std::string>& enabled_entries,
      int platform_mask) const;

  // Variant of GetSanitizedEnabledFlags that also removes any flags that aren't
  // enabled on the current platform.
  void GetSanitizedEnabledFlagsForCurrentPlatform(
      FlagsStorage* flags_storage,
      std::set<std::string>* result) const;

  // Generates a flags to switches mapping based on the set of enabled flags
  // from |flags_storage|. On output, |enabled_entries| will contain the
  // internal names of enabled flags and |name_to_switch_map| will contain
  // information on how they map to command-line flags or features.
  // When |enabled_entries| is empty |name_to_switch_map| won't be filled.
  void GenerateFlagsToSwitchesMapping(
      FlagsStorage* flags_storage,
      const base::CommandLine& command_line,
      std::set<std::string>* enabled_entries,
      std::map<std::string, SwitchEntry>* name_to_switch_map) const;

  // Returns whether there is a FeatureEntry named by |name| in
  // |feature_entries_| that:
  // a) Is supported on this |platform_mask|, and
  // b) Is not excluded by |exclude_predicate_|, if it is set (i.e. for which
  //    |exclude_predicate_| returns false).
  bool IsSupportedFeature(const FlagsStorage* storage,
                          std::string_view name,
                          int platform_mask) const;

  // Stores the flags in both FlagsStorage and SharedPreferences.
  // Call FlagsStorage.SetFlags() to store the flags in FlagsStorage.
  // Make the appropriate JNI calls to store the flags in SharedPreferences.
  void SetFlags(FlagsStorage* flags_storage,
                const std::set<std::string>& enabled_flags,
                const std::set<std::string>& prev_enabled_flags) const;

  const base::raw_span<const FeatureEntry> feature_entries_;

  bool needs_restart_;
  // Switches that were set by flags state. Used to remove them from the command
  // line when resetting it.
  std::set<std::string, std::less<>> modified_flag_switches_;

  // Map from switch name to a set of string, that keeps track which strings
  // were appended to existing (list value) switches.
  std::map<std::string, std::set<std::string>> appended_switches_;

  // Delegate used for embedders to control display and application of flags.
  // May be null.
  raw_ptr<Delegate> delegate_;

#if BUILDFLAG(IS_ANDROID)
  // Delegate used by internal code to make JNI calls.
  std::unique_ptr<cached_flags::JniDelegate> jni_delegate_;

 public:
  void SetJniDelegateForTesting(
      std::unique_ptr<cached_flags::JniDelegate> delegate) {
    jni_delegate_ = std::move(delegate);
  }
#endif
};

}  // namespace flags_ui

#endif  // COMPONENTS_WEBUI_FLAGS_FLAGS_STATE_H_
