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

#ifndef CHROME_BROWSER_UI_VIEWS_TRANSLATE_PARTIAL_TRANSLATE_BUBBLE_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_TRANSLATE_PARTIAL_TRANSLATE_BUBBLE_VIEW_H_

#include <memory>
#include <string>

#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/translate/chrome_translate_client.h"
#include "chrome/browser/ui/translate/partial_translate_bubble_model.h"
#include "chrome/browser/ui/translate/source_language_combobox_model.h"
#include "chrome/browser/ui/translate/target_language_combobox_model.h"
#include "chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.h"
#include "components/language/core/common/language_experiments.h"
#include "components/translate/core/common/translate_errors.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/actions/actions.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/menus/simple_menu_model.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/tabbed_pane/tabbed_pane.h"
#include "ui/views/controls/tabbed_pane/tabbed_pane_listener.h"
#include "ui/views/controls/throbber.h"
#include "ui/views/window/frame_view.h"

namespace views {
class Combobox;
class LabelButton;
class View;
}  // namespace views

// The bubble shown when translating a partial text (e.g. selecting a some
// text from the contents).
class PartialTranslateBubbleView : public LocationBarBubbleDelegateView,
                                   public ui::SimpleMenuModel::Delegate,
                                   public views::TabbedPaneListener {
  METADATA_HEADER(PartialTranslateBubbleView, LocationBarBubbleDelegateView)

 public:
  // Item IDs for the option button's menu.
  enum class OptionsMenuItem { kChangeTargetLanguage, kChangeSourceLanguage };

  // Element IDs for ui::ElementTracker
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kIdentifier);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kSourceLanguageTab);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kTargetLanguageTab);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kCloseButton);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kOptionsMenuButton);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kChangeTargetLanguage);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kTargetLanguageCombobox);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kTargetLanguageDoneButton);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kChangeSourceLanguage);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kSourceLanguageCombobox);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kSourceLanguageDoneButton);
  DECLARE_CLASS_ELEMENT_IDENTIFIER_VALUE(kErrorMessage);

  PartialTranslateBubbleView(views::BubbleAnchor anchor,
                             std::unique_ptr<PartialTranslateBubbleModel> model,
                             content::WebContents* web_contents,
                             base::OnceClosure on_closing);

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

  ~PartialTranslateBubbleView() override;

  PartialTranslateBubbleModel* model() { return model_.get(); }

  // LocationBarBubbleDelegateView:
  void Init() override;
  View* GetInitiallyFocusedView() override;
  bool ShouldShowCloseButton() const override;
  bool ShouldShowWindowTitle() const override;
  void WindowClosing() override;
  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override;
  void OnWidgetDestroying(views::Widget* widget) override;

  // ui::SimpleMenuModel::Delegate:
  void ExecuteCommand(int command_id, int event_flags) override;

  // Returns the current view state.
  PartialTranslateBubbleModel::ViewState GetViewState() const;

  // Initialize the bubble in the correct view state when it is shown.
  void SetViewState(PartialTranslateBubbleModel::ViewState view_state,
                    translate::TranslateErrors error_type);

  // Update the source language combobox's selected index to match the current
  // index in the model. These values desynchronize when a request does not
  // specify a source language, such as with initial translations from the menu,
  // or when "Detected Language" is used.
  void MaybeUpdateSourceLanguageCombobox();

  // LocationBarBubbleDelegateView:
  void CloseBubble() override;

 private:
  // IDs used by PartialTranslateBubbleViewTest to simulate button presses.
  enum ButtonID {
    BUTTON_ID_DONE = 1,
    BUTTON_ID_TRY_AGAIN,
    BUTTON_ID_OPTIONS_MENU,
    BUTTON_ID_CLOSE,
    BUTTON_ID_RESET,
    BUTTON_ID_FULL_PAGE_TRANSLATE
  };

  friend class PartialTranslateBubbleViewTest;
  FRIEND_TEST_ALL_PREFIXES(PartialTranslateBubbleViewTest,
                           TargetLanguageTabDoesntTriggerTranslate);
  FRIEND_TEST_ALL_PREFIXES(PartialTranslateBubbleViewTest,
                           TabSelectedAfterTranslation);
  FRIEND_TEST_ALL_PREFIXES(PartialTranslateBubbleViewTest,
                           UpdateLanguageTabsFromResponse);
  FRIEND_TEST_ALL_PREFIXES(PartialTranslateBubbleViewTest,
                           SourceLanguageTabUpdatesViewState);
  FRIEND_TEST_ALL_PREFIXES(PartialTranslateBubbleViewTest,
                           SourceLanguageTabSelectedLogged);
  FRIEND_TEST_ALL_PREFIXES(PartialTranslateBubbleViewTest,
                           TranslateFullPageButton);
  FRIEND_TEST_ALL_PREFIXES(PartialTranslateBubbleViewTest,
                           TranslatedTextIsSelectable);

  // views::TabbedPaneListener:
  void TabSelectedAt(int index) override;

  // Returns the current child view.
  views::View* GetCurrentView() const;

  // Triggers options menu.
  void ShowOptionsMenu(views::Button* source);

  // Handles the event when the user changes an index of a combobox.
  void SourceLanguageChanged();
  void TargetLanguageChanged();

  // Updates the visibilities of child views according to the current view type.
  void UpdateChildVisibilities();

  // Creates the view used before/during/after translate.
  std::unique_ptr<views::View> CreateView();

  // AddTab function requires a view element to be shown below each tab.
  // This function creates an empty view so no extra white space below the tab.
  std::unique_ptr<views::View> CreateEmptyPane();

  // Creates the 'error' view for Button UI.
  std::unique_ptr<views::View> CreateViewError();

  // Creates the 'error' view skeleton UI with no title.
  std::unique_ptr<views::View> CreateViewErrorNoTitle(
      std::unique_ptr<views::Button> button);

  // Creates the 'waiting' view that shows an empty bubble with a throbber.
  std::unique_ptr<views::View> CreateViewWaiting();

  // Creates source language label and combobox for Tab UI advanced view. Caller
  // takes ownership of the returned view.
  std::unique_ptr<views::View> CreateViewAdvancedSource();

  // Creates target language label and combobox for Tab UI advanced view. Caller
  // takes ownership of the returned view.
  std::unique_ptr<views::View> CreateViewAdvancedTarget();

  // Creates the 'advanced' view to show source/target language combobox. Caller
  // takes ownership of the returned view.
  std::unique_ptr<views::View> CreateViewAdvanced(
      std::unique_ptr<views::Combobox> combobox,
      std::unique_ptr<views::Label> language_title_label,
      std::unique_ptr<views::Button> advanced_reset_button,
      std::unique_ptr<views::Button> advanced_done_button);

  // Creates a translate icon for when the bottom branding isn't showing. This
  // should only be used on non-Chrome-branded builds.
  std::unique_ptr<views::ImageView> CreateTranslateIcon();

  // Creates a three dot options menu button.
  std::unique_ptr<views::Button> CreateOptionsMenuButton();

  // Creates a close button.
  std::unique_ptr<views::Button> CreateCloseButton();

  // Sets the window title. The window title still needs to be set, even when it
  // is not shown, for accessibility purposes.
  void SetWindowTitle(PartialTranslateBubbleModel::ViewState view_state);

  // Finds and saves the width of the bubble's largest child view, excluding
  // |translate_view_|. This value is needed to properly resize
  // |partial_text_label_| as the width of the tabbed pane changes with changes
  // in selected languages.
  void ComputeLargestViewStateWidth();

  // Updates the view state. Whenever the view state is updated, the title needs
  // to be updated for accessibility.
  void UpdateViewState(PartialTranslateBubbleModel::ViewState view_state);

  // Switches the view type.
  void SwitchView(PartialTranslateBubbleModel::ViewState view_state);

  // Handles tab switching on when the view type switches.
  void SwitchTabForViewState(PartialTranslateBubbleModel::ViewState view_state);

  // Switches to the error view.
  void SwitchToErrorView(translate::TranslateErrors error_type);

  // Updates the advanced view.
  void UpdateAdvancedView();

  // Actions for button presses shared with accelerators.
  void ShowTranslated();
  void ShowOriginal();
  void ConfirmAdvancedOptions();

  // Returns whether or not the current language selection is different from the
  // initial language selection in an advanced view.
  bool DidLanguageSelectionChange(
      PartialTranslateBubbleModel::ViewState view_state);

  // Handles the reset button in advanced view under Tab UI.
  void ResetLanguage();

  // Updates the body text for the bubble based on the view state (either the
  // source text if we're pre-translate or translating, or the target text if
  // we're done translating).
  void UpdateTextForViewState(
      PartialTranslateBubbleModel::ViewState view_state);

  // Update the names of the source/target language tabs.
  void UpdateLanguageTabNames();

  void UpdateInsets(PartialTranslateBubbleModel::ViewState state);

  // Function bound to the "Translate full page" button.
  void TranslateFullPage();

  // Update the alignment of |partial_text_label_| to match the direction of
  // the locale being used.
  void SetTextAlignmentForLocaleTextDirection(std::string locale);

  // Forces announcement of translation state and conditionally also accounces
  // the translated text.
  void AnnounceForAccessibility(
      PartialTranslateBubbleModel::ViewState view_state);

  static PartialTranslateBubbleView* partial_translate_bubble_view_;

  raw_ptr<views::View, DanglingUntriaged> translate_view_waiting_ = nullptr;
  raw_ptr<views::View, DanglingUntriaged> translate_view_ = nullptr;
  raw_ptr<views::View, DanglingUntriaged> error_view_ = nullptr;
  raw_ptr<views::View, DanglingUntriaged> advanced_view_source_ = nullptr;
  raw_ptr<views::View, DanglingUntriaged> advanced_view_target_ = nullptr;

  raw_ptr<views::Throbber, DanglingUntriaged> throbber_;

  raw_ptr<views::Combobox, DanglingUntriaged> source_language_combobox_ =
      nullptr;
  raw_ptr<views::Combobox, DanglingUntriaged> target_language_combobox_ =
      nullptr;

  raw_ptr<views::TabbedPane, DanglingUntriaged> tabbed_pane_ = nullptr;
  raw_ptr<views::View, DanglingUntriaged> tab_view_top_row_ = nullptr;
  raw_ptr<views::Label, DanglingUntriaged> partial_text_label_ = nullptr;

  raw_ptr<views::LabelButton, DanglingUntriaged> advanced_reset_button_source_ =
      nullptr;
  raw_ptr<views::LabelButton, DanglingUntriaged> advanced_reset_button_target_ =
      nullptr;
  raw_ptr<views::LabelButton, DanglingUntriaged> advanced_done_button_source_ =
      nullptr;
  raw_ptr<views::LabelButton, DanglingUntriaged> advanced_done_button_target_ =
      nullptr;

  // Default source/target language without user interaction.
  size_t previous_source_language_index_;
  size_t previous_target_language_index_;

  // Whether or not user changed target language and triggered translation from
  // the advanced options.
  bool target_language_changed_ = false;

  std::unique_ptr<ui::SimpleMenuModel> options_menu_model_;
  std::unique_ptr<views::MenuRunner> options_menu_runner_;

  std::unique_ptr<PartialTranslateBubbleModel> model_;

  translate::TranslateErrors error_type_;

  std::unique_ptr<WebContentMouseHandler> mouse_handler_;

  std::u16string text_selection_;

  // The width of the largest non-|translate_view_| child view at
  // initialization. The default minimum width is set to 300dp to provide a more
  // consistent experience between different UI languages - for high density
  // languages the width would otherwise be very narrow.
  int largest_view_state_width_ = 300;

  // The threshold for character volume of |partial_text_label_|. If the volume
  // is larger than the threshold, use the preset maximum width allowable for
  // the bubble. Otherwise, resize normally.
  const size_t char_threshold_for_max_width_ = 1300;

  // The max allowable width for the bubble, used when the character volume of
  // |partial_text_label_| exceeds |char_threshold_for_max_width_|. This is
  // necessary to accommodate the size of the label in cases of largest possible
  // character volume. It follows that this is based off of
  // translate::kDesktopPartialTranslateTextSelectionMaxCharacters and should be
  // updated alongside it.
  const int bubble_max_width_ = 550;

  base::OnceClosure on_closing_;

  raw_ptr<content::WebContents> web_contents_;
};

#endif  // CHROME_BROWSER_UI_VIEWS_TRANSLATE_PARTIAL_TRANSLATE_BUBBLE_VIEW_H_
