// Copyright 2019 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_WM_OVERVIEW_OVERVIEW_HIGHLIGHT_CONTROLLER_H_
#define ASH_WM_OVERVIEW_OVERVIEW_HIGHLIGHT_CONTROLLER_H_

#include <memory>
#include <vector>

#include "ash/ash_export.h"
#include "base/memory/raw_ptr.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace ash {
class OverviewHighlightableView;
class OverviewItem;
class OverviewSession;
class ScopedA11yOverrideWindowSetter;

// Manages highlighting items while in overview. Responsible for telling
// highlightable items to show or hide their focus ring borders, or when tabbing
// through highlightable items with arrow keys and trackpad swipes. In this
// context, an highlightable item can represent anything focusable in overview
// mode such as a desk textfield, saved desk button and an `OverviewItem`. The
// idea behind the movement strategy is that it should be possible to access any
// highlightable view via keyboard by pressing the tab or arrow keys repeatedly.
// +-------+  +-------+  +-------+
// |   0   |  |   1   |  |   2   |
// +-------+  +-------+  +-------+
// +-------+  +-------+  +-------+
// |   3   |  |   4   |  |   5   |
// +-------+  +-------+  +-------+
// +-------+
// |   6   |
// +-------+
// Example sequences:
//  - Going right to left
//    0, 1, 2, 3, 4, 5, 6
// The highlight is switched to the next window grid (if available) or wrapped
// if it reaches the end of its movement sequence.
class ASH_EXPORT OverviewHighlightController {
 public:
  explicit OverviewHighlightController(OverviewSession* overview_session);
  OverviewHighlightController(const OverviewHighlightController&) = delete;
  OverviewHighlightController& operator=(const OverviewHighlightController&) =
      delete;
  ~OverviewHighlightController();

  OverviewHighlightableView* highlighted_view() { return highlighted_view_; }

  // Moves the focus ring to the next traversable view.
  void MoveHighlight(bool reverse);

  // Called when pressing two save desks buttons to show the saved desk grids
  // and focus on a saved desk name view.
  void UpdateA11yFocusWindow(OverviewHighlightableView* name_view);

  // Moves the focus ring directly to |target_view|. |target_view| must be a
  // traversable view, i.e. one of the views returned by GetTraversableViews().
  // This should be used when a view requests focus directly so the overview
  // highlight can be in-sync with focus. Due to this expected use, it should
  // not normally be necessary to trigger an accessibility event.
  void MoveHighlightToView(OverviewHighlightableView* target_view,
                           bool suppress_accessibility_event = true);

  // Called when a |view| that might be in the focus traversal rotation is about
  // to be deleted.
  // Note: When removing multiple highlightable views in one call, by calling
  // this function repeatedly, make sure to call it in reverse order (i.e. on
  // the views that come later in the highlight order first). This makes sure
  // that traversal continues correctly from where it was left off.
  void OnViewDestroyingOrDisabling(OverviewHighlightableView* view);

  // Sets and gets the visibility of |highlighted_view_|.
  void SetFocusHighlightVisibility(bool visible);
  bool IsFocusHighlightVisible() const;

  // Activates or closes the currently highlighted view (if any) if it supports
  // the activation or closing operations respectively.
  bool MaybeActivateHighlightedView();
  bool MaybeCloseHighlightedView(bool primary_action);

  // Swaps the currently highlighted view with its neighbor views.
  bool MaybeSwapHighlightedView(bool right);

  // Activates highlighted view when exiting overview mode.
  bool MaybeActivateHighlightedViewOnOverviewExit();

  // Tries to get the item that is currently highlighted. Returns null if there
  // is no highlight, or if the highlight is on a desk view.
  OverviewItem* GetHighlightedItem() const;

  // If `highlighted_view_` is not null, remove the highlight. The next tab will
  // start at the beginning of the tab order. This can be used when a lot of
  // views are getting removed or hidden, such as when the desks bar goes from
  // expanded to zero state.
  void ResetHighlightedView();

 private:
  // Returns a vector of views that can be traversed via overview tabbing.
  // Includes desk mini views, the new desk button and overview items.
  std::vector<OverviewHighlightableView*> GetTraversableViews() const;

  // Sets |highlighted_view_| to |view_to_be_highlighted| and updates the
  // highlight visibility for the previous |highlighted_view_|.
  // |suppress_accessibility_event| should be true if |view_to_be_highlighted|
  // will also request focus to avoid double emitting event.
  void UpdateHighlight(OverviewHighlightableView* view_to_be_highlighted,
                       bool suppress_accessibility_event = false);

  // The overview session which owns this object. Guaranteed to be non-null for
  // the lifetime of |this|.
  const raw_ptr<OverviewSession, ExperimentalAsh> overview_session_;

  // If an item that is selected is deleted, store its index, so the next
  // traversal can pick up where it left off.
  absl::optional<int> deleted_index_ = absl::nullopt;

  // The current view that is being highlighted, if any.
  raw_ptr<OverviewHighlightableView, ExperimentalAsh> highlighted_view_ =
      nullptr;

  // Helps to update the current a11y override window. And accessibility
  // features will focus on the window that is being set. Once `this` goes out
  // of scope, the a11y override window is set to nullptr.
  std::unique_ptr<ScopedA11yOverrideWindowSetter> scoped_a11y_overrider_;
};

}  // namespace ash

#endif  // ASH_WM_OVERVIEW_OVERVIEW_HIGHLIGHT_CONTROLLER_H_