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

#ifndef ASH_QUICK_INSERT_VIEWS_QUICK_INSERT_VIEW_H_
#define ASH_QUICK_INSERT_VIEWS_QUICK_INSERT_VIEW_H_

#include <memory>
#include <optional>
#include <string>
#include <string_view>

#include "ash/ash_export.h"
#include "ash/quick_insert/metrics/quick_insert_performance_metrics.h"
#include "ash/quick_insert/model/quick_insert_search_results_section.h"
#include "ash/quick_insert/quick_insert_category.h"
#include "ash/quick_insert/quick_insert_controller.h"
#include "ash/quick_insert/quick_insert_search_result.h"
#include "ash/quick_insert/views/quick_insert_emoji_bar_view_delegate.h"
#include "ash/quick_insert/views/quick_insert_key_event_handler.h"
#include "ash/quick_insert/views/quick_insert_preview_bubble_controller.h"
#include "ash/quick_insert/views/quick_insert_pseudo_focus_handler.h"
#include "ash/quick_insert/views/quick_insert_search_results_view_delegate.h"
#include "ash/quick_insert/views/quick_insert_submenu_controller.h"
#include "ash/quick_insert/views/quick_insert_zero_state_view_delegate.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/base/emoji/emoji_panel_helper.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/view.h"
#include "ui/views/view_tracker.h"
#include "ui/views/widget/widget_delegate.h"

namespace views {
class Widget;
class FrameView;
}  // namespace views

namespace ash {

enum class QuickInsertCapsLockPosition;
enum class QuickInsertLayoutType;
enum class QuickInsertPositionType;
enum class QuickInsertPseudoFocusDirection;
class QuickInsertEmojiBarView;
class QuickInsertMainContainerView;
class QuickInsertSearchFieldView;
class QuickInsertPageView;
class QuickInsertSearchResultsSection;
class QuickInsertSearchResultsView;
class QuickInsertTraversableItemContainer;
class QuickInsertViewDelegate;
class QuickInsertZeroStateView;

// View for the Quick Insert widget.
class ASH_EXPORT QuickInsertView
    : public views::WidgetDelegateView,
      public QuickInsertZeroStateViewDelegate,
      public QuickInsertSearchResultsViewDelegate,
      public QuickInsertEmojiBarViewDelegate,
      public QuickInsertPseudoFocusHandler,
      public QuickInsertPreviewBubbleController::Observer {
  METADATA_HEADER(QuickInsertView, views::WidgetDelegateView)

 public:
  // `delegate` must remain valid for the lifetime of this class.
  explicit QuickInsertView(QuickInsertViewDelegate* delegate,
                           const gfx::Rect& anchor_bounds,
                           QuickInsertLayoutType layout_type,
                           QuickInsertPositionType position_type,
                           base::TimeTicks trigger_event_timestamp);
  QuickInsertView(const QuickInsertView&) = delete;
  QuickInsertView& operator=(const QuickInsertView&) = delete;
  ~QuickInsertView() override;

  // Time from when a search starts to when the previous set of results are
  // cleared.
  // Slightly longer than the real burn in period to ensure empty results do not
  // flash on the screen before showing burn-in results.
  static constexpr base::TimeDelta kClearResultsTimeout =
      QuickInsertController::kBurnInPeriod + base::Milliseconds(50);

  // views::WidgetDelegateView:
  bool AcceleratorPressed(const ui::Accelerator& accelerator) override;
  std::unique_ptr<views::FrameView> CreateFrameView(
      views::Widget* widget) override;
  void AddedToWidget() override;
  void RemovedFromWidget() override;
  void Layout(PassKey) override;

  // QuickInsertZeroStateViewDelegate:
  void SelectZeroStateCategory(QuickInsertCategory category) override;
  void SelectZeroStateResult(const QuickInsertSearchResult& result) override;
  void GetZeroStateSuggestedResults(SuggestedResultsCallback callback) override;
  void RequestPseudoFocus(views::View* view) override;
  void OnZeroStateViewHeightChanged() override;
  void SetCapsLockDisplayed(bool displayed) override;
  QuickInsertCapsLockPosition GetCapsLockPosition() override;

  // QuickInsertSearchResultsViewDelegate:
  void SelectSearchResult(const QuickInsertSearchResult& result) override;
  void SelectMoreResults(QuickInsertSectionType type) override;
  QuickInsertActionType GetActionForResult(
      const QuickInsertSearchResult& result) override;
  void OnSearchResultsViewHeightChanged() override;

  // QuickInsertEmojiBarViewDelegate:
  void ToggleGifs(bool is_checked) override;
  void ShowEmojiPicker(ui::EmojiPickerCategory category) override;

  // QuickInsertPseudoFocusHandler:
  bool DoPseudoFocusedAction() override;
  bool MovePseudoFocusUp() override;
  bool MovePseudoFocusDown() override;
  bool MovePseudoFocusLeft() override;
  bool MovePseudoFocusRight() override;
  bool AdvancePseudoFocus(QuickInsertPseudoFocusDirection direction) override;

  // QuickInsertPreviewBubbleController::Observer:
  void OnPreviewBubbleVisibilityChanged(bool visible) override;

  // Returns the target bounds for this Quick Insert view. The target bounds try
  // to vertically align `search_field_view_` with `anchor_bounds`.
  // `anchor_bounds` and returned bounds should be in screen coordinates.
  gfx::Rect GetTargetBounds(const gfx::Rect& anchor_bounds,
                            QuickInsertLayoutType layout_type);

  QuickInsertSubmenuController& submenu_controller_for_testing() {
    return submenu_controller_;
  }
  QuickInsertPreviewBubbleController& preview_controller_for_testing() {
    return preview_controller_;
  }

  QuickInsertSearchFieldView& search_field_view_for_testing() {
    return *search_field_view_;
  }
  QuickInsertSearchResultsView& search_results_view_for_testing() {
    return *search_results_view_;
  }
  QuickInsertSearchResultsView& category_results_view_for_testing() {
    return *category_results_view_;
  }
  QuickInsertZeroStateView& zero_state_view_for_testing() {
    return *zero_state_view_;
  }
  QuickInsertEmojiBarView* emoji_bar_view_for_testing() {
    return emoji_bar_view_;
  }

 private:
  // Sets the search text field's query text to the query, focuses it, then
  // updates the active page - starting / ending a search if necessary.
  void UpdateSearchQueryAndActivePage(std::u16string query);

  // Updates the active page based on the search text field's query text, as
  // well as the active category.
  // If the search text field's query text is non-empty, this starts a search
  // and sets the active page to the search view after a delay via
  // `OnClearResultsTimerFired` and `PublishSearchResults`.
  // Otherwise, stops any previous searches and immediately sets the active page
  // to the zero state / category results view, fetching category results if
  // necessary.
  void UpdateActivePage();

  // Displays `results` in the emoji bar.
  void PublishEmojiResults(std::vector<QuickInsertEmojiResult> results);

  // Clears the search results and sets the active page to the search view.
  void OnClearResultsTimerFired();

  // Displays `results` in the search view and sets it as the active page.
  // If `results` is empty and no results were previously published, then a "no
  // results found" view is shown instead of a blank view.
  void PublishSearchResults(
      std::vector<QuickInsertSearchResultsSection> results);

  // Selects a category. This shows the category view and fetches zero-state
  // results for the category, which are returned to `PublishCategoryResults`.
  void SelectCategory(QuickInsertCategory category);

  // Selects a category. This shows the category view and fetches search
  // results for the category based on `query`, which are returned to
  // `PublishSearchResults`.
  void SelectCategoryWithQuery(QuickInsertCategory category,
                               std::u16string_view query);

  // Displays `results` in the category view.
  void PublishCategoryResults(
      QuickInsertCategory category,
      std::vector<QuickInsertSearchResultsSection> results);

  // Adds the main container, which includes the search field and contents
  // pages.
  void AddMainContainerView(QuickInsertLayoutType layout_type);

  // Adds the emoji bar, which contains emoji and other expression results and
  // is shown above the main container.
  void AddEmojiBarView();

  // Sets `page_view` as the active page in `main_container_view_`.
  void SetActivePage(QuickInsertPageView* page_view);

  // Sets emoji bar visibility, or does nothing if the emoji bar is not enabled.
  void SetEmojiBarVisibleIfEnabled(bool visible);

  // Moves pseudo focus between different parts of the QuickInsertView, i.e.
  // between the emoji bar and the main container.
  void AdvanceActiveItemContainer(QuickInsertPseudoFocusDirection direction);

  // Sets `view` as the pseudo focused view, i.e. the view which responds to
  // user actions that trigger `DoPseudoFocusedAction`. If `view` is null,
  // pseudo focus instead moves back to the search field.
  void SetPseudoFocusedView(views::View* view);

  views::View* GetPseudoFocusedView();

  // Removes the currently selected category filter, with the option to clear
  // the search field.
  void ResetSelectedCategory(bool reset_query);

  // Called when the search field back button is pressed.
  void OnSearchBackButtonPressed();

  // Clears the current results in the emoji bar and shows recent and
  // placeholder emojis instead.
  void ResetEmojiBarToZeroState();

  // Returns true if `view` is contained in a submenu of this QuickInsertView.
  bool IsContainedInSubmenu(views::View* view);

  // Called to indicate that the Quick Insert widget bounds need to be be
  // updated (e.g. to re-align the Quick Insert search field after results have
  // changed).
  void SetWidgetBoundsNeedsUpdate();

  // The currently selected category.
  // Should only be set to `std::nullopt` through `OnSearchBackButtonPressed`.
  // Should only be set to a value through `SelectCategory` and
  // `SelectCategoryWithQuery`.
  std::optional<QuickInsertCategory> selected_category_;

  // Whether the GIF toggle is checked (i.e. should only show GIF results).
  bool is_gif_toggle_checked_ = false;

  // The category which `category_results_view_` has results for.
  // Used for caching results if the user did not change their selected
  // category.
  // For example:
  // - When a user starts a filtered search from a category's suggested results,
  //   then clears the search query, the old suggested results are not cleared
  //   as `last_suggested_results_category_ == selected_category_`.
  // - When a user starts a non-filtered search from zero state, then filters
  //   results to a category, then clears the search query, new results will be
  //   fetched as the `last_suggested_results_category_ != selected_category_`.
  std::optional<QuickInsertCategory> last_suggested_results_category_;
  // The whitespace-trimmed query and category when `UpdateActivePage()` was
  // last called.
  // Used for avoid unnecessary searches if `UpdateActivePage()` is called again
  // with the same {query, selected_category, is_gif_toggle_checked}.
  std::u16string last_query_;
  std::optional<QuickInsertCategory> last_selected_category_;
  bool last_is_gif_toggle_checked_ = false;

  QuickInsertKeyEventHandler key_event_handler_;
  QuickInsertSubmenuController submenu_controller_;
  QuickInsertPreviewBubbleController preview_controller_;
  QuickInsertPerformanceMetrics performance_metrics_;
  raw_ptr<QuickInsertViewDelegate> delegate_ = nullptr;

  // The main container contains the search field and contents pages.
  raw_ptr<QuickInsertMainContainerView> main_container_view_ = nullptr;
  raw_ptr<QuickInsertSearchFieldView> search_field_view_ = nullptr;
  raw_ptr<QuickInsertZeroStateView> zero_state_view_ = nullptr;
  raw_ptr<QuickInsertSearchResultsView> category_results_view_ = nullptr;
  raw_ptr<QuickInsertSearchResultsView> search_results_view_ = nullptr;

  raw_ptr<QuickInsertEmojiBarView> emoji_bar_view_ = nullptr;

  // The item container which contains `pseudo_focused_view_` and will respond
  // to keyboard navigation events.
  raw_ptr<QuickInsertTraversableItemContainer> active_item_container_ = nullptr;

  // Tracks the currently pseudo focused view, which responds to user actions
  // that trigger `DoPseudoFocusedAction`.
  views::ViewTracker pseudo_focused_view_tracker_;

  // If true, the Widget bounds should be adjusted on the next layout.
  bool widget_bounds_needs_update_ = true;

  // Clears `search_results_view_`'s old search results when a new search is
  // started - after `kClearResultsTimeout`, or when the first search results
  // come in (whatever is earliest).
  // This timer is running iff the first set of results for the current search
  // have not been published yet.
  base::OneShotTimer clear_results_timer_;

  base::ScopedObservation<QuickInsertPreviewBubbleController,
                          QuickInsertPreviewBubbleController::Observer>
      preview_bubble_observation_{this};

  base::WeakPtrFactory<QuickInsertView> weak_ptr_factory_{this};
};

}  // namespace ash

#endif  // ASH_QUICK_INSERT_VIEWS_QUICK_INSERT_VIEW_H_
