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

#ifndef UI_BASE_MODELS_DIALOG_MODEL_FIELD_H_
#define UI_BASE_MODELS_DIALOG_MODEL_FIELD_H_

#include <string>

#include "base/component_export.h"
#include "base/containers/flat_set.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/types/pass_key.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/models/image_model.h"

namespace ui {

class DialogModel;
class DialogModelButton;
class DialogModelParagraph;
class DialogModelCheckbox;
class DialogModelCombobox;
class DialogModelCustomField;
class DialogModelHost;
class DialogModelMenuItem;
class DialogModelTextfield;
class Event;

// TODO(pbos): Move this to separate header.
// DialogModelLabel is an exception to below classes. This is not a
// DialogModelField but rather represents a text label and styling. This is used
// with DialogModelParagraph and DialogModelCheckbox for instance and has
// support for styling text replacements and showing a link.
class COMPONENT_EXPORT(UI_BASE) DialogModelLabel {
 public:
  // TODO(pbos): Move this definition (maybe as a ui::LinkCallback) so it can
  // be reused with views::Link.
  using Callback = base::RepeatingCallback<void(const Event& event)>;

  class COMPONENT_EXPORT(UI_BASE) TextReplacement {
   public:
    TextReplacement(const TextReplacement&);
    ~TextReplacement();

    const std::u16string& text() const { return text_; }
    bool is_emphasized() const { return is_emphasized_; }
    const absl::optional<Callback>& callback() const { return callback_; }
    const absl::optional<std::u16string>& accessible_name() const {
      return accessible_name_;
    }

   private:
    friend class DialogModelLabel;

    // Used for regular and emphasized text.
    explicit TextReplacement(std::u16string text, bool is_emphasized = false);
    // Used for links.
    TextReplacement(int message_id,
                    Callback closure,
                    std::u16string accessible_name = std::u16string());

    const std::u16string text_;
    const bool is_emphasized_;
    const absl::optional<Callback> callback_;
    const absl::optional<std::u16string> accessible_name_;
  };

  explicit DialogModelLabel(int message_id);
  explicit DialogModelLabel(std::u16string fixed_string);

  DialogModelLabel(const DialogModelLabel&);
  DialogModelLabel& operator=(const DialogModelLabel&) = delete;
  ~DialogModelLabel();

  static DialogModelLabel CreateWithReplacement(int message_id,
                                                TextReplacement replacement);
  static DialogModelLabel CreateWithReplacements(
      int message_id,
      std::vector<TextReplacement> replacements);

  // Builder methods for TextReplacements.
  static TextReplacement CreateLink(
      int message_id,
      base::RepeatingClosure closure,
      std::u16string accessible_name = std::u16string());
  static TextReplacement CreateLink(
      int message_id,
      Callback callback,
      std::u16string accessible_name = std::u16string());
  static TextReplacement CreatePlainText(std::u16string text);
  static TextReplacement CreateEmphasizedText(std::u16string text);

  // Gets the string. Not for use with replacements, in which case the caller
  // must use replacements() and message_id() to construct the final label. This
  // is required to style the final label appropriately and support replacement
  // callbacks. The caller is responsible for checking replacements().empty()
  // before calling this.
  const std::u16string& GetString(base::PassKey<DialogModelHost>) const;

  DialogModelLabel& set_is_secondary() {
    is_secondary_ = true;
    return *this;
  }

  DialogModelLabel& set_allow_character_break() {
    allow_character_break_ = true;
    return *this;
  }

  int message_id(base::PassKey<DialogModelHost>) const { return message_id_; }
  const std::vector<TextReplacement>& replacements(
      base::PassKey<DialogModelHost>) const {
    return replacements_;
  }

  bool is_secondary(base::PassKey<DialogModelHost>) const {
    return is_secondary_;
  }
  bool allow_character_break(base::PassKey<DialogModelHost>) const {
    return allow_character_break_;
  }

 private:
  explicit DialogModelLabel(int message_id,
                            std::vector<TextReplacement> replacements);

  const int message_id_;
  const std::u16string string_;

  // Set of replacements that will be added to `message_id_`.
  const std::vector<TextReplacement> replacements_;

  bool is_secondary_ = false;
  bool allow_character_break_ = false;
};

// These "field" classes represent entries in a DialogModel. They are owned
// by the model and either created through the model or DialogModel::Builder.
// These entries can be referred to by setting the field's ElementIdentifier in
// construction parameters (::Params::SetId()). They can then
// later be acquired through DialogModel::GetFieldByUniqueId() methods.
// These fields own the data corresponding to their field. For instance, the
// text of a textfield in a model is read using DialogModelTextfield::text() and
// stays in sync with the visible dialog (through DialogModelHosts).
class COMPONENT_EXPORT(UI_BASE) DialogModelField {
 public:
  enum Type {
    kButton,
    kParagraph,
    kCheckbox,
    kCombobox,
    kCustom,
    kMenuItem,
    kSeparator,
    kTextfield
  };

  DialogModelField(const DialogModelField&) = delete;
  DialogModelField& operator=(const DialogModelField&) = delete;
  virtual ~DialogModelField();

  // Methods with base::PassKey<DialogModelHost> are only intended to be called
  // by the DialogModelHost implementation.
  Type type(base::PassKey<DialogModelHost>) const { return type_; }
  const base::flat_set<Accelerator>& accelerators(
      base::PassKey<DialogModelHost>) const {
    return accelerators_;
  }
  ElementIdentifier id(base::PassKey<DialogModelHost>) const { return id_; }
  DialogModelButton* AsButton(base::PassKey<DialogModelHost>);
  DialogModelParagraph* AsParagraph(base::PassKey<DialogModelHost>);
  DialogModelCheckbox* AsCheckbox(base::PassKey<DialogModelHost>);
  DialogModelCombobox* AsCombobox(base::PassKey<DialogModelHost>);
  DialogModelMenuItem* AsMenuItem(base::PassKey<DialogModelHost>);
  const DialogModelMenuItem* AsMenuItem(base::PassKey<DialogModelHost>) const;
  DialogModelTextfield* AsTextfield(base::PassKey<DialogModelHost>);
  DialogModelCustomField* AsCustomField(base::PassKey<DialogModelHost>);

 protected:
  // Children of this class need to be constructed through DialogModel to help
  // enforce that they're added to the model.
  DialogModelField(base::PassKey<DialogModel>,
                   DialogModel* model,
                   Type type,
                   ElementIdentifier id,
                   base::flat_set<Accelerator> accelerators);

  DialogModelButton* AsButton();
  DialogModelParagraph* AsParagraph();
  DialogModelCheckbox* AsCheckbox();
  DialogModelCombobox* AsCombobox();
  const DialogModelMenuItem* AsMenuItem() const;
  DialogModelTextfield* AsTextfield();
  DialogModelCustomField* AsCustomField();

 private:
  friend class DialogModel;
  FRIEND_TEST_ALL_PREFIXES(DialogModelButtonTest, UsesParamsUniqueId);

  const raw_ptr<DialogModel> model_;
  const Type type_;
  const ElementIdentifier id_;

  const base::flat_set<Accelerator> accelerators_;
};

// Field class representing a dialog button.
class COMPONENT_EXPORT(UI_BASE) DialogModelButton : public DialogModelField {
 public:
  class COMPONENT_EXPORT(UI_BASE) Params {
   public:
    Params();
    Params(const Params&) = delete;
    Params& operator=(const Params&) = delete;
    ~Params();

    Params& SetId(ElementIdentifier id);
    Params& SetLabel(std::u16string label);

    Params& AddAccelerator(Accelerator accelerator);

   private:
    friend class DialogModel;
    friend class DialogModelButton;

    ElementIdentifier id_;
    std::u16string label_;
    base::flat_set<Accelerator> accelerators_;
  };

  // Note that this is constructed through a DialogModel which adds it to model
  // fields.
  DialogModelButton(base::PassKey<DialogModel> pass_key,
                    DialogModel* model,
                    base::RepeatingCallback<void(const Event&)> callback,
                    const Params& params);
  DialogModelButton(const DialogModelButton&) = delete;
  DialogModelButton& operator=(const DialogModelButton&) = delete;
  ~DialogModelButton() override;

  // Methods with base::PassKey<DialogModelHost> are only intended to be called
  // by the DialogModelHost implementation.
  const std::u16string& label(base::PassKey<DialogModelHost>) const {
    return label_;
  }
  void OnPressed(base::PassKey<DialogModelHost>, const Event& event);

 private:
  friend class DialogModel;

  const std::u16string label_;
  // The button callback gets called when the button is activated. Whether
  // that happens on key-press, release, etc. is implementation (and platform)
  // dependent.
  base::RepeatingCallback<void(const Event&)> callback_;
};

// Field class representing a paragraph.
class COMPONENT_EXPORT(UI_BASE) DialogModelParagraph : public DialogModelField {
 public:
  // Note that this is constructed through a DialogModel which adds it to model
  // fields.
  DialogModelParagraph(base::PassKey<DialogModel> pass_key,
                       DialogModel* model,
                       const DialogModelLabel& label,
                       std::u16string header,
                       ElementIdentifier id);
  DialogModelParagraph(const DialogModelParagraph&) = delete;
  DialogModelParagraph& operator=(const DialogModelParagraph&) = delete;
  ~DialogModelParagraph() override;

  const DialogModelLabel& label(base::PassKey<DialogModelHost>) const {
    return label_;
  }

  const std::u16string header(base::PassKey<DialogModelHost>) const {
    return header_;
  }

 private:
  const DialogModelLabel label_;
  const std::u16string header_;
};

// Field class representing a checkbox with descriptive text.
class COMPONENT_EXPORT(UI_BASE) DialogModelCheckbox : public DialogModelField {
 public:
  class COMPONENT_EXPORT(UI_BASE) Params {
   public:
    Params() = default;
    Params(const Params&) = delete;
    Params& operator=(const Params&) = delete;
    ~Params() = default;

    Params& SetIsChecked(bool is_checked) {
      is_checked_ = is_checked;
      return *this;
    }

   private:
    friend class DialogModelCheckbox;

    bool is_checked_ = false;
  };

  // Note that this is constructed through a DialogModel which adds it to model
  // fields.
  DialogModelCheckbox(base::PassKey<DialogModel> pass_key,
                      DialogModel* model,
                      ElementIdentifier id,
                      const DialogModelLabel& label,
                      const Params& params);
  DialogModelCheckbox(const DialogModelCheckbox&) = delete;
  DialogModelCheckbox& operator=(const DialogModelCheckbox&) = delete;
  ~DialogModelCheckbox() override;

  bool is_checked() const { return is_checked_; }

  void OnChecked(base::PassKey<DialogModelHost>, bool is_checked);
  const DialogModelLabel& label(base::PassKey<DialogModelHost>) const {
    return label_;
  }

 private:
  const DialogModelLabel label_;
  bool is_checked_;
};

// Field class representing a combobox and corresponding label to describe the
// combobox:
//
//     <label>   [combobox]
// Ex: Folder    [My Bookmarks]
class COMPONENT_EXPORT(UI_BASE) DialogModelCombobox : public DialogModelField {
 public:
  class COMPONENT_EXPORT(UI_BASE) Params {
   public:
    Params();
    Params(const Params&) = delete;
    Params& operator=(const Params&) = delete;
    ~Params();

    Params& AddAccelerator(Accelerator accelerator);

    Params& SetAccessibleName(std::u16string accessible_name) {
      accessible_name_ = std::move(accessible_name);
      return *this;
    }

    // The combobox callback is invoked when an item has been selected. This
    // nominally happens when selecting an item in the combobox menu. The
    // selection notably does not change by hovering different items in the
    // combobox menu or navigating it with up/down keys as long as the menu is
    // open.
    Params& SetCallback(base::RepeatingClosure callback);

   private:
    friend class DialogModelCombobox;

    ElementIdentifier id_;
    std::u16string accessible_name_;
    base::RepeatingClosure callback_;
    base::flat_set<Accelerator> accelerators_;
  };

  // Note that this is constructed through a DialogModel which adds it to model
  // fields.
  DialogModelCombobox(base::PassKey<DialogModel> pass_key,
                      DialogModel* model,
                      ElementIdentifier id,
                      std::u16string label,
                      std::unique_ptr<ui::ComboboxModel> combobox_model,
                      const Params& params);
  DialogModelCombobox(const DialogModelCombobox&) = delete;
  DialogModelCombobox& operator=(const DialogModelCombobox&) = delete;
  ~DialogModelCombobox() override;

  size_t selected_index() const { return selected_index_; }
  ui::ComboboxModel* combobox_model() { return combobox_model_.get(); }

  // Methods with base::PassKey<DialogModelHost> are only intended to be called
  // by the DialogModelHost implementation.
  const std::u16string& label(base::PassKey<DialogModelHost>) const {
    return label_;
  }
  const std::u16string& accessible_name(base::PassKey<DialogModelHost>) const {
    return accessible_name_;
  }
  void OnSelectedIndexChanged(base::PassKey<DialogModelHost>,
                              size_t selected_index);
  void OnPerformAction(base::PassKey<DialogModelHost>);

 private:
  friend class DialogModel;

  const std::u16string label_;
  const std::u16string accessible_name_;
  size_t selected_index_;
  std::unique_ptr<ui::ComboboxModel> combobox_model_;
  base::RepeatingClosure callback_;
};

// Field class representing a menu item:
//
//     <icon> <label>
// Ex: [icon] Open URL
class COMPONENT_EXPORT(UI_BASE) DialogModelMenuItem : public DialogModelField {
 public:
  class COMPONENT_EXPORT(UI_BASE) Params {
   public:
    Params();
    Params(const Params&) = delete;
    Params& operator=(const Params&) = delete;
    ~Params();

    Params& SetIsEnabled(bool is_enabled);
    Params& SetId(ElementIdentifier id);

   private:
    friend class DialogModelMenuItem;

    bool is_enabled_ = true;
    ElementIdentifier id_;
  };

  // Note that this is constructed through a DialogModel which adds it to model
  // fields.
  DialogModelMenuItem(base::PassKey<DialogModel> pass_key,
                      DialogModel* model,
                      ImageModel icon,
                      std::u16string label,
                      base::RepeatingCallback<void(int)> callback,
                      const Params& params);
  DialogModelMenuItem(const DialogModelMenuItem&) = delete;
  DialogModelMenuItem& operator=(const DialogModelMenuItem&) = delete;
  ~DialogModelMenuItem() override;

  // Methods with base::PassKey<DialogModelHost> are only intended to be called
  // by the DialogModelHost implementation.
  const ImageModel& icon(base::PassKey<DialogModelHost>) const { return icon_; }
  const std::u16string& label(base::PassKey<DialogModelHost>) const {
    return label_;
  }
  bool is_enabled(base::PassKey<DialogModelHost>) const { return is_enabled_; }
  void OnActivated(base::PassKey<DialogModelHost>, int event_flags);

 private:
  const ImageModel icon_;
  const std::u16string label_;
  const bool is_enabled_;
  base::RepeatingCallback<void(int)> callback_;
};

// Field class representing a separator.
class COMPONENT_EXPORT(UI_BASE) DialogModelSeparator : public DialogModelField {
 public:
  // Note that this is constructed through a DialogModel which adds it to model
  // fields.
  DialogModelSeparator(base::PassKey<DialogModel> pass_key, DialogModel* model);
  DialogModelSeparator(const DialogModelSeparator&) = delete;
  DialogModelSeparator& operator=(const DialogModelSeparator&) = delete;
  ~DialogModelSeparator() override;
};

// Field class representing a textfield and corresponding label to describe the
// textfield:
//
//     <label>   [textfield]
// Ex: Name      [My email]
class COMPONENT_EXPORT(UI_BASE) DialogModelTextfield : public DialogModelField {
 public:
  class COMPONENT_EXPORT(UI_BASE) Params {
   public:
    Params();
    Params(const Params&) = delete;
    Params& operator=(const Params&) = delete;
    ~Params();

    Params& AddAccelerator(Accelerator accelerator);

    Params& SetAccessibleName(std::u16string accessible_name) {
      accessible_name_ = std::move(accessible_name);
      return *this;
    }

   private:
    friend class DialogModelTextfield;

    ElementIdentifier id_;
    std::u16string accessible_name_;
    base::flat_set<Accelerator> accelerators_;
  };

  // Note that this is constructed through a DialogModel which adds it to model
  // fields.
  DialogModelTextfield(base::PassKey<DialogModel> pass_key,
                       DialogModel* model,
                       ElementIdentifier id,
                       std::u16string label,
                       std::u16string text,
                       const Params& params);
  DialogModelTextfield(const DialogModelTextfield&) = delete;
  DialogModelTextfield& operator=(const DialogModelTextfield&) = delete;
  ~DialogModelTextfield() override;

  const std::u16string& text() const { return text_; }

  // Methods with base::PassKey<DialogModelHost> are only intended to be called
  // by the DialogModelHost implementation.
  const std::u16string& label(base::PassKey<DialogModelHost>) const {
    return label_;
  }
  const std::u16string& accessible_name(base::PassKey<DialogModelHost>) const {
    return accessible_name_;
  }
  void OnTextChanged(base::PassKey<DialogModelHost>, std::u16string text);

 private:
  friend class DialogModel;

  const std::u16string label_;
  const std::u16string accessible_name_;
  std::u16string text_;
};

// Field base class representing a "custom" field. Used for instance to inject
// custom Views into dialogs that use DialogModel.
class COMPONENT_EXPORT(UI_BASE) DialogModelCustomField
    : public DialogModelField {
 public:
  // Base class for fields held by DialogModelField. Calling code is responsible
  // for providing the subclass expected by the DialogModelHost used.
  class COMPONENT_EXPORT(UI_BASE) Field {
   public:
    virtual ~Field();
  };

  // Note that this is constructed through a DialogModel which adds it to model
  // fields.
  DialogModelCustomField(base::PassKey<DialogModel> pass_key,
                         DialogModel* model,
                         ElementIdentifier id,
                         std::unique_ptr<DialogModelCustomField::Field> field);
  DialogModelCustomField(const DialogModelCustomField&) = delete;
  DialogModelCustomField& operator=(const DialogModelCustomField&) = delete;
  ~DialogModelCustomField() override;

  // Methods with base::PassKey<DialogModelHost> are only intended to be called
  // by the DialogModelHost implementation.
  DialogModelCustomField::Field* field(base::PassKey<DialogModelHost>) {
    return field_.get();
  }

 private:
  friend class DialogModel;

  std::unique_ptr<DialogModelCustomField::Field> field_;
};

}  // namespace ui

#endif  // UI_BASE_MODELS_DIALOG_MODEL_FIELD_H_