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

#include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h"

#include <algorithm>
#include <memory>

#include "ash/frame/frame_view_ash.h"
#include "ash/game_dashboard/game_dashboard_controller.h"
#include "ash/game_dashboard/game_dashboard_utils.h"
#include "ash/public/cpp/arc_game_controls_flag.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/style/style_util.h"
#include "ash/wm/window_state.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "chrome/browser/ash/arc/input_overlay/actions/action.h"
#include "chrome/browser/ash/arc/input_overlay/arc_input_overlay_metrics.h"
#include "chrome/browser/ash/arc/input_overlay/touch_injector.h"
#include "chrome/browser/ash/arc/input_overlay/ui/action_highlight.h"
#include "chrome/browser/ash/arc/input_overlay/ui/action_view.h"
#include "chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h"
#include "chrome/browser/ash/arc/input_overlay/ui/button_options_menu.h"
#include "chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.h"
#include "chrome/browser/ash/arc/input_overlay/ui/editing_list.h"
#include "chrome/browser/ash/arc/input_overlay/ui/input_mapping_view.h"
#include "chrome/browser/ash/arc/input_overlay/ui/rich_nudge.h"
#include "chrome/browser/ash/arc/input_overlay/ui/target_view.h"
#include "chrome/browser/ash/arc/input_overlay/ui/ui_utils.h"
#include "chrome/browser/ash/arc/input_overlay/util.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/exo/shell_surface_base.h"
#include "components/exo/shell_surface_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/wm/core/transient_window_manager.h"
#include "ui/wm/core/window_util.h"

namespace arc::input_overlay {

namespace {

using ash::game_dashboard_utils::GetNextWidgetToFocus;
using ash::game_dashboard_utils::UpdateAccessibilityTree;

constexpr char kButtonOptionsMenu[] = "GameControlsButtonOptionsMenu";
constexpr char kEditingList[] = "GameControlsEditingList";
constexpr char kInputMapping[] = "GameControlsInputMapping";
constexpr char kActionHighlight[] = "ActionHighlight";

std::unique_ptr<views::Widget> CreateTransientWidget(
    aura::Window* parent_window,
    const std::string& widget_name,
    bool accept_events) {
  views::Widget::InitParams params(
      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  params.parent = parent_window;
  params.name = widget_name;
  params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
  params.activatable = views::Widget::InitParams::Activatable::kYes;
  params.accept_events = accept_events;

  auto widget = std::make_unique<views::Widget>();
  widget->Init(std::move(params));

  auto* widget_window = widget->GetNativeWindow();
  DCHECK_EQ(parent_window, wm::GetTransientParent(widget_window));
  wm::TransientWindowManager::GetOrCreate(widget_window)
      ->set_parent_controls_visibility(false);
  widget->SetVisibilityAnimationTransition(views::Widget::ANIMATE_NONE);
  return widget;
}

}  // namespace

DisplayOverlayController::DisplayOverlayController(
    TouchInjector* touch_injector)
    : touch_injector_(touch_injector) {
  touch_injector_->set_display_overlay_controller(this);

  auto* window = touch_injector_->window();
  window->AddObserver(this);

  UpdateDisplayMode();
  ash::Shell::Get()->AddPreTargetHandler(this);
}

DisplayOverlayController::~DisplayOverlayController() {
  touch_injector_->set_display_overlay_controller(nullptr);

  touch_injector_->window()->RemoveObserver(this);
  RemoveAllWidgets();

  ash::Shell::Get()->RemovePreTargetHandler(this);
}

void DisplayOverlayController::UpdateDisplayMode() {
  ash::ArcGameControlsFlag flags =
      touch_injector_->window()->GetProperty(ash::kArcGameControlsFlagsKey);
  SetDisplayMode(IsFlagSet(flags, ash::ArcGameControlsFlag::kEnabled)
                     ? (IsFlagSet(flags, ash::ArcGameControlsFlag::kEdit)
                            ? DisplayMode::kEdit
                            : DisplayMode::kView)
                     : DisplayMode::kNone);
}

void DisplayOverlayController::ChangeActionType(Action* reference_action,
                                                ActionType type) {
  touch_injector_->ChangeActionType(reference_action, type);
}

void DisplayOverlayController::SetDisplayMode(DisplayMode mode) {
  if (display_mode_ == mode) {
    return;
  }

  switch (mode) {
    case DisplayMode::kNone:
      RemoveAllWidgets();
      break;

    case DisplayMode::kView: {
      DCHECK(!rich_nudge_widget_);
      DCHECK(!target_widget_);
      DCHECK(!button_options_widget_);
      RemoveActionHighlightWidget();
      RemoveDeleteEditShortcutWidget();
      RemoveEditingListWidget();
      if (GetActiveActionsSize() == 0u) {
        // If there is no active action in `kView` mode, it doesn't create
        // `input_mapping_widget_` to save resources. When switching from
        // `kEdit` mode, destroy `input_mapping_widget_` for no active actions.
        RemoveInputMappingWidget();
      } else {
        AddInputMappingWidget();
        if (auto* input_mapping = GetInputMapping()) {
          input_mapping->SetDisplayMode(mode);
        }
        SetInputMappingVisible(
            /*visible=*/touch_injector_->input_mapping_visible());

        // In the view mode, make sure the input mapping is displayed under game
        // dashboard UIs.
        StackInputMappingAtBottomForViewMode();

        auto* input_mapping_window = input_mapping_widget_->GetNativeWindow();
        input_mapping_window->SetEventTargetingPolicy(
            aura::EventTargetingPolicy::kNone);
      }
    } break;

    case DisplayMode::kEdit: {
      if (GetActiveActionsSize() == 0u) {
        // Because`input_mapping_widget_` was not created in `kView` mode,
        // create `input_mapping_widget_` in `kEdit` mode for adding new
        // actions.
        AddInputMappingWidget();
      }

      // No matter if the mapping hint is hidden, `input_mapping_widget_` needs
      // to show up in `kEdit` mode.
      SetInputMappingVisible(/*visible=*/true);

      UpdateAccessibilityTree(GetTraversableWidgets());

      if (auto* input_mapping = GetInputMapping()) {
        input_mapping->SetDisplayMode(mode);
      }
      auto* input_mapping_window = input_mapping_widget_->GetNativeWindow();
      input_mapping_window->SetEventTargetingPolicy(
          aura::EventTargetingPolicy::kTargetAndDescendants);
      AddEditingListWidget();
    } break;

    default:
      break;
  }

  display_mode_ = mode;
}

void DisplayOverlayController::OnInputBindingChange(
    Action* action,
    std::unique_ptr<InputElement> input_element) {
  touch_injector_->OnInputBindingChange(action, std::move(input_element));
}

void DisplayOverlayController::SaveToProtoFile() {
  touch_injector_->OnSaveProtoFile();
}

void DisplayOverlayController::OnCustomizeSave() {
  touch_injector_->OnBindingSave();
  SetDisplayMode(DisplayMode::kView);
}

const std::string& DisplayOverlayController::GetPackageName() const {
  return touch_injector_->package_name();
}

void DisplayOverlayController::OnApplyMenuState() {
  if (display_mode_ != DisplayMode::kView) {
    return;
  }

  SetInputMappingVisible(
      /*visible=*/GetTouchInjectorEnable() && GetInputMappingViewVisible(),
      /*store_visible_state=*/true);
}

InputOverlayWindowStateType DisplayOverlayController::GetWindowStateType()
    const {
  DCHECK(touch_injector_);
  auto* window = touch_injector_->window();
  DCHECK(window);
  auto* state = ash::WindowState::Get(window);
  InputOverlayWindowStateType type = InputOverlayWindowStateType::kInvalid;
  if (state) {
    if (state->IsNormalStateType()) {
      type = InputOverlayWindowStateType::kNormal;
    } else if (state->IsMaximized()) {
      type = InputOverlayWindowStateType::kMaximized;
    } else if (state->IsFullscreen()) {
      type = InputOverlayWindowStateType::kFullscreen;
    } else if (state->IsSnapped()) {
      type = InputOverlayWindowStateType::kSnapped;
    }
  }
  return type;
}

void DisplayOverlayController::AddNewAction(ActionType action_type,
                                            const gfx::Point& target_pos) {
  // Keep adding new action before exiting button placement mode because
  // `target_pos` is from button placement mode.
  touch_injector_->AddNewAction(action_type, target_pos);
  ExitButtonPlaceMode(/*is_action_added=*/true);
}

void DisplayOverlayController::RemoveAction(Action* action) {
  touch_injector_->RemoveAction(action);
}

void DisplayOverlayController::RemoveActionNewState(Action* action) {
  if (!action->is_new()) {
    return;
  }
  touch_injector_->RemoveActionNewState(action);
}

size_t DisplayOverlayController::GetActiveActionsSize() const {
  return touch_injector_->GetActiveActionsSize();
}

bool DisplayOverlayController::HasSingleUserAddedAction() const {
  return touch_injector_->HasSingleUserAddedAction();
}

bool DisplayOverlayController::IsActiveAction(Action* action) const {
  const auto& actions = touch_injector_->actions();
  if (actions.empty()) {
    return false;
  }

  auto it = std::find_if(
      actions.begin(), actions.end(),
      [&](const std::unique_ptr<Action>& p) { return action == p.get(); });
  return it != actions.end() && !(it->get()->IsDeleted());
}

MappingSource DisplayOverlayController::GetMappingSource() const {
  const auto& actions = touch_injector_->actions();
  if (actions.empty()) {
    return MappingSource::kEmpty;
  }

  // Check if there is any default action.
  auto default_it = std::find_if(
      actions.begin(), actions.end(),
      [&](const std::unique_ptr<Action>& p) { return p->IsDefaultAction(); });

  // Check if there is any user added action.
  auto user_added_it = std::find_if(
      actions.begin(), actions.end(),
      [&](const std::unique_ptr<Action>& p) { return !p->IsDefaultAction(); });

  return default_it != actions.end() && user_added_it != actions.end()
             ? MappingSource::kDefaultAndUserAdded
             : (default_it != actions.end() ? MappingSource::kDefault
                                            : MappingSource::kUserAdded);
}

void DisplayOverlayController::AddTouchInjectorObserver(
    TouchInjectorObserver* observer) {
  touch_injector_->AddObserver(observer);
}

void DisplayOverlayController::RemoveTouchInjectorObserver(
    TouchInjectorObserver* observer) {
  touch_injector_->RemoveObserver(observer);
}

void DisplayOverlayController::AddButtonOptionsMenuWidget(Action* action) {
  if (button_options_widget_) {
    auto* menu = GetButtonOptionsMenu();
    if (menu && menu->action() == action) {
      return;
    }
    RemoveButtonOptionsMenuWidget();
  }

  button_options_widget_ =
      CreateTransientWidget(input_mapping_widget_->GetNativeWindow(),
                            /*widget_name=*/kButtonOptionsMenu,
                            /*accept_events=*/true);
  button_options_widget_->SetContentsView(
      std::make_unique<ButtonOptionsMenu>(this, action));
  UpdateButtonOptionsMenuWidgetBounds();
  button_options_widget_->widget_delegate()->SetAccessibleTitle(
      l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_BUTTON_OPTIONS_A11Y_LABEL));

  // Always hide editing list when button options menu shows up.
  SetEditingListVisibility(/*visible=*/false);

  button_options_widget_->Show();
  UpdateAccessibilityTree(GetTraversableWidgets());
}

void DisplayOverlayController::RemoveButtonOptionsMenuWidget() {
  if (!button_options_widget_) {
    return;
  }
  // Check if related action is already deleted.
  if (const auto* menu = GetButtonOptionsMenu()) {
    if (auto* menu_action = menu->action(); IsActiveAction(menu_action)) {
      RemoveActionNewState(menu_action);
    }
  }

  button_options_widget_->Close();
  button_options_widget_.reset();
  UpdateAccessibilityTree(GetTraversableWidgets());
}

void DisplayOverlayController::SetButtonOptionsMenuWidgetVisibility(
    bool is_visible) {
  if (!button_options_widget_) {
    return;
  }

  if (is_visible) {
    if (GetButtonOptionsMenu()) {
      UpdateButtonOptionsMenuWidgetBounds();
    }
    button_options_widget_->Show();
  } else {
    button_options_widget_->Hide();
  }
  UpdateAccessibilityTree(GetTraversableWidgets());
}

void DisplayOverlayController::AddDeleteEditShortcutWidget(
    ActionViewListItem* anchor_view) {
  if (!delete_edit_shortcut_widget_) {
    delete_edit_shortcut_widget_ =
        base::WrapUnique(views::BubbleDialogDelegateView::CreateBubble(
            std::make_unique<DeleteEditShortcut>(this, anchor_view)));
  }

  if (auto* shortcut = GetDeleteEditShortcut();
      shortcut->GetAnchorView() != anchor_view) {
    shortcut->UpdateAnchorView(anchor_view);
  }

  delete_edit_shortcut_widget_->Show();
  UpdateAccessibilityTree(GetTraversableWidgets());
}

void DisplayOverlayController::RemoveDeleteEditShortcutWidget() {
  if (delete_edit_shortcut_widget_) {
    delete_edit_shortcut_widget_->Close();
    delete_edit_shortcut_widget_.reset();
    UpdateAccessibilityTree(GetTraversableWidgets());
  }
}

void DisplayOverlayController::AddActionHighlightWidget(Action* action) {
  auto* anchor_view = action->action_view();
  if (!action_highlight_widget_) {
    action_highlight_widget_ = CreateTransientWidget(
        touch_injector_->window(), /*widget_name=*/kActionHighlight,
        /*accept_events=*/false);
    action_highlight_widget_->SetContentsView(
        std::make_unique<ActionHighlight>(this, anchor_view));
  }

  if (auto* highlight = views::AsViewClass<ActionHighlight>(
          action_highlight_widget_->GetContentsView())) {
    highlight->UpdateAnchorView(anchor_view);

    // Show `action_highlight_widget_` if it is hidden.
    if (!action_highlight_widget_->IsVisible()) {
      action_highlight_widget_->ShowInactive();
      input_mapping_widget_->StackAboveWidget(action_highlight_widget_.get());
    }
  }
}

void DisplayOverlayController::RemoveActionHighlightWidget() {
  if (action_highlight_widget_) {
    action_highlight_widget_->Close();
    action_highlight_widget_.reset();
  }
}

void DisplayOverlayController::HideActionHighlightWidget() {
  if (action_highlight_widget_) {
    action_highlight_widget_->Hide();
  }
}

void DisplayOverlayController::HideActionHighlightWidgetForAction(
    Action* action) {
  if (!action_highlight_widget_) {
    return;
  }

  if (auto* highlight = views::AsViewClass<ActionHighlight>(
          action_highlight_widget_->GetContentsView());
      highlight && highlight->anchor_view() == action->action_view()) {
    action_highlight_widget_->Hide();
  }
}

void DisplayOverlayController::UpdateWidgetBoundsInRootWindow(
    views::Widget* widget,
    const gfx::Rect& bounds_in_root_window) {
  auto root_bounds =
      touch_injector_->window()->GetRootWindow()->GetBoundsInScreen();
  auto bounds_in_screen = bounds_in_root_window;
  bounds_in_screen.Offset(root_bounds.OffsetFromOrigin());
  widget->SetBounds(bounds_in_screen);
}

ActionViewListItem* DisplayOverlayController::GetEditingListItemForAction(
    Action* action) {
  if (auto* editing_list = GetEditingList()) {
    return editing_list->GetListItemForAction(action);
  }
  return nullptr;
}

void DisplayOverlayController::OnMouseEvent(ui::MouseEvent* event) {
  if (display_mode_ == DisplayMode::kView ||
      event->type() != ui::EventType::kMousePressed) {
    return;
  }

  ProcessPressedEvent(*event);
}

void DisplayOverlayController::OnTouchEvent(ui::TouchEvent* event) {
  if (display_mode_ == DisplayMode::kView ||
      event->type() != ui::EventType::kTouchPressed) {
    return;
  }
  ProcessPressedEvent(*event);
}

void DisplayOverlayController::OnKeyEvent(ui::KeyEvent* event) {
  if (display_mode_ == DisplayMode::kEdit) {
    MaybeChangeFocusWidget(*event);
  }
}

void DisplayOverlayController::OnWindowBoundsChanged(
    aura::Window* window,
    const gfx::Rect& old_bounds,
    const gfx::Rect& new_bounds,
    ui::PropertyChangeReason reason) {
  DCHECK_EQ(window, touch_injector_->window());
  // Disregard the bounds from animation and only care final window bounds.
  if (reason == ui::PropertyChangeReason::FROM_ANIMATION) {
    return;
  }

  UpdateForBoundsChanged();
}

void DisplayOverlayController::OnWindowPropertyChanged(aura::Window* window,
                                                       const void* key,
                                                       intptr_t old) {
  DCHECK_EQ(window, touch_injector_->window());
  if (key == chromeos::kImmersiveIsActive) {
    bool is_immersive = window->GetProperty(chromeos::kImmersiveIsActive);
    // This is to catch the corner case that when an app is launched as
    // fullscreen/immersive mode, so it only cares when the window turns into
    // immersive mode from non-immersive mode.
    if (!is_immersive || is_immersive == static_cast<bool>(old)) {
      return;
    }

    UpdateForBoundsChanged();
  }

  if (key == ash::kArcGameControlsFlagsKey) {
    ash::ArcGameControlsFlag old_flags =
        static_cast<ash::ArcGameControlsFlag>(old);
    ash::ArcGameControlsFlag flags =
        window->GetProperty(ash::kArcGameControlsFlagsKey);
    if (flags != old_flags) {
      UpdateDisplayMode();

      SetTouchInjectorEnable(
          IsFlagSet(flags, ash::ArcGameControlsFlag::kEnabled));

      // `input_mapping_widget_` is always visible in edit mode.
      SetInputMappingVisible(/*visible=*/
                             IsFlagSet(flags, ash::ArcGameControlsFlag::kEdit)
                                 ? true
                                 : IsFlagSet(flags,
                                             ash::ArcGameControlsFlag::kHint),
                             /*store_visible_state=*/true);

      // Save the menu states upon menu closing.
      if (IsFlagChanged(flags, old_flags, ash::ArcGameControlsFlag::kMenu) &&
          !IsFlagSet(flags, ash::ArcGameControlsFlag::kMenu)) {
        touch_injector_->OnInputMenuViewRemoved();
      }

      UpdateEventRewriteCapability();

      // Record metrics.
      const auto mapping_source = GetMappingSource();
      if (IsFlagChanged(flags, old_flags, ash::ArcGameControlsFlag::kEnabled)) {
        RecordToggleWithMappingSource(
            GetPackageName(),
            /*is_feature=*/true,
            /*is_on=*/IsFlagSet(flags, ash::ArcGameControlsFlag::kEnabled),
            mapping_source);
      }
      if (IsFlagChanged(flags, old_flags, ash::ArcGameControlsFlag::kHint)) {
        RecordToggleWithMappingSource(
            GetPackageName(),
            /*is_feature=*/false,
            /*is_on=*/IsFlagSet(flags, ash::ArcGameControlsFlag::kHint),
            mapping_source);
      }
    }
  }
}

void DisplayOverlayController::SetInputMappingVisible(
    bool visible,
    bool store_visible_state) {
  // There is no `input_mapping_widget_` if there is no active action or the
  // feature is disabled.
  if (input_mapping_widget_ && input_mapping_widget_->IsVisible() != visible) {
    if (visible) {
      input_mapping_widget_->ShowInactive();
    } else {
      input_mapping_widget_->Hide();
    }
  }

  if (store_visible_state) {
    CHECK(touch_injector_);
    touch_injector_->store_input_mapping_visible(visible);
  }
}

bool DisplayOverlayController::GetInputMappingViewVisible() const {
  DCHECK(touch_injector_);
  if (!touch_injector_) {
    return false;
  }
  return touch_injector_->input_mapping_visible();
}

void DisplayOverlayController::SetTouchInjectorEnable(bool enable) {
  DCHECK(touch_injector_);
  if (!touch_injector_) {
    return;
  }
  touch_injector_->store_touch_injector_enable(enable);
}

bool DisplayOverlayController::GetTouchInjectorEnable() {
  DCHECK(touch_injector_);
  if (!touch_injector_) {
    return false;
  }
  return touch_injector_->touch_injector_enable();
}

void DisplayOverlayController::ProcessPressedEvent(
    const ui::LocatedEvent& event) {
  if (auto* delete_edit_view = GetDeleteEditShortcut()) {
    if (const auto bounds = delete_edit_view->GetBoundsInScreen();
        !bounds.Contains(event.target()->GetScreenLocation(event))) {
      RemoveDeleteEditShortcutWidget();
    }
  }
}

void DisplayOverlayController::EnsureTaskWindowToFrontForViewMode(
    views::Widget* overlay_widget) {
  DCHECK(overlay_widget);
  DCHECK(overlay_widget->GetNativeWindow());
  DCHECK_EQ(overlay_widget->GetNativeWindow()->event_targeting_policy(),
            aura::EventTargetingPolicy::kNone);

  auto* shell_surface_base =
      exo::GetShellSurfaceBaseForWindow(touch_injector_->window());
  DCHECK(shell_surface_base);
  auto* host_window = shell_surface_base->host_window();
  DCHECK(host_window);
  if (const auto& children = host_window->children(); children.size() > 0u) {
    // First child is the root ExoSurface window. Focus on the root surface
    // window can bring the task window to the front of the task stack.
    if (!children[0]->HasFocus()) {
      children[0]->Focus();
    }
  } else {
    host_window->Focus();
  }
}

void DisplayOverlayController::UpdateForBoundsChanged() {
  auto content_bounds = CalculateWindowContentBounds(touch_injector_->window());
  if (content_bounds == touch_injector_->content_bounds_f()) {
    return;
  }
  touch_injector_->UpdateForOverlayBoundsChanged(content_bounds);

  UpdateInputMappingWidgetBounds();
  UpdateEditingListWidgetBounds();
  UpdateTargetWidgetBounds();
  UpdateButtonOptionsMenuWidgetBounds();
}

void DisplayOverlayController::RemoveAllWidgets() {
  RemoveRichNudge();
  RemoveDeleteEditShortcutWidget();
  RemoveTargetWidget();
  RemoveButtonOptionsMenuWidget();
  RemoveEditingListWidget();
  RemoveActionHighlightWidget();
  RemoveInputMappingWidget();
}

void DisplayOverlayController::AddInputMappingWidget() {
  if (input_mapping_widget_) {
    return;
  }

  input_mapping_widget_ = CreateTransientWidget(touch_injector_->window(),
                                                /*widget_name=*/kInputMapping,
                                                /*accept_events=*/false);
  input_mapping_widget_->SetContentsView(
      std::make_unique<InputMappingView>(this));
  UpdateInputMappingWidgetBounds();
}

void DisplayOverlayController::RemoveInputMappingWidget() {
  if (input_mapping_widget_) {
    input_mapping_widget_->Close();
    input_mapping_widget_.reset();
  }
}

InputMappingView* DisplayOverlayController::GetInputMapping() {
  if (!input_mapping_widget_) {
    return nullptr;
  }

  return views::AsViewClass<InputMappingView>(
      input_mapping_widget_->GetContentsView());
}

void DisplayOverlayController::StackInputMappingAtBottomForViewMode() {
  if (!input_mapping_widget_ || display_mode_ != DisplayMode::kView) {
    return;
  }

  input_mapping_widget_->Deactivate();

  // ash::GameDashboardController::Get() is empty for the
  // unit test.
  if (auto* gd_controller = ash::GameDashboardController::Get()) {
    gd_controller->MaybeStackAboveWidget(touch_injector_->window(),
                                         input_mapping_widget_.get());
  }
}

void DisplayOverlayController::AddEditingListWidget() {
  if (editing_list_widget_) {
    return;
  }
  editing_list_widget_ = CreateTransientWidget(
      input_mapping_widget_->GetNativeWindow(), /*widget_name=*/kEditingList,
      /*accept_events=*/true);
  editing_list_widget_->SetContentsView(std::make_unique<EditingList>(this));

  // Avoid active conflict with the game dashboard main menu.
  editing_list_widget_->ShowInactive();
  UpdateEditingListWidgetBounds();
  editing_list_widget_->widget_delegate()->SetAccessibleTitle(
      l10n_util::GetStringUTF16(IDS_INPUT_OVERLAY_EDITING_LIST_A11Y_LABEL));
  UpdateAccessibilityTree(GetTraversableWidgets());
}

void DisplayOverlayController::RemoveEditingListWidget() {
  if (editing_list_widget_) {
    editing_list_widget_->Close();
    editing_list_widget_.reset();
    UpdateAccessibilityTree(GetTraversableWidgets());
    UpdateFlagAndProperty(touch_injector_->window(),
                          ash::ArcGameControlsFlag::kEdit,
                          /*turn_on=*/false);
    UpdateEventRewriteCapability();
  }
}

void DisplayOverlayController::SetEditingListVisibility(bool visible) {
  if (!editing_list_widget_ || editing_list_widget_->IsVisible() == visible) {
    return;
  }

  if (visible) {
    editing_list_widget_->Show();
  } else {
    editing_list_widget_->Hide();
  }
  UpdateAccessibilityTree(GetTraversableWidgets());
}

EditingList* DisplayOverlayController::GetEditingList() {
  if (!editing_list_widget_) {
    return nullptr;
  }

  return views::AsViewClass<EditingList>(
      editing_list_widget_->GetContentsView());
}

ButtonOptionsMenu* DisplayOverlayController::GetButtonOptionsMenu() {
  if (!button_options_widget_) {
    return nullptr;
  }

  return views::AsViewClass<ButtonOptionsMenu>(
      button_options_widget_->GetContentsView());
}

void DisplayOverlayController::EnterButtonPlaceMode(ActionType action_type) {
  RemoveDeleteEditShortcutWidget();
  SetEditingListVisibility(/*visible=*/false);
  AddTargetWidget(action_type);
  AddRichNudge();
}

void DisplayOverlayController::ExitButtonPlaceMode(bool is_action_added) {
  RemoveRichNudge();
  RemoveTargetWidget();
  if (!is_action_added) {
    SetEditingListVisibility(/*visible=*/true);
  }
}

void DisplayOverlayController::UpdateButtonPlacementNudgeAnchorRect() {
  auto* target_view = GetTargetView();
  if (!target_view) {
    return;
  }

  auto target_circle_bounds = target_view->GetTargetCircleBounds();
  views::View::ConvertRectToScreen(target_view, &target_circle_bounds);
  auto* rich_nudge = GetRichNudge();
  if (rich_nudge && rich_nudge_widget_->GetWindowBoundsInScreen().Intersects(
                        target_circle_bounds)) {
    rich_nudge->FlipPosition();
  }
}

void DisplayOverlayController::AddTargetWidget(ActionType action_type) {
  DCHECK(!target_widget_);

  target_widget_ = CreateTransientWidget(touch_injector_->window(),
                                         /*widget_name=*/kInputMapping,
                                         /*accept_events=*/true);
  target_widget_->SetContentsView(
      std::make_unique<TargetView>(this, action_type));
  target_widget_->ShowInactive();
}

void DisplayOverlayController::RemoveTargetWidget() {
  if (target_widget_) {
    target_widget_->Close();
    target_widget_.reset();
  }
}

TargetView* DisplayOverlayController::GetTargetView() const {
  return target_widget_
             ? views::AsViewClass<TargetView>(target_widget_->GetContentsView())
             : nullptr;
}

void DisplayOverlayController::AddRichNudge() {
  if (GetTargetView()) {
    rich_nudge_widget_ =
        base::WrapUnique(views::BubbleDialogDelegateView::CreateBubble(
            std::make_unique<RichNudge>(target_widget_->GetNativeWindow())));
    rich_nudge_widget_->ShowInactive();
  }
}

void DisplayOverlayController::RemoveRichNudge() {
  if (rich_nudge_widget_) {
    rich_nudge_widget_->Close();
    rich_nudge_widget_.reset();
  }
}

RichNudge* DisplayOverlayController::GetRichNudge() const {
  return rich_nudge_widget_ ? views::AsViewClass<RichNudge>(
                                  rich_nudge_widget_->widget_delegate()
                                      ->AsBubbleDialogDelegate()
                                      ->GetContentsView())
                            : nullptr;
}

DeleteEditShortcut* DisplayOverlayController::GetDeleteEditShortcut() const {
  return delete_edit_shortcut_widget_
             ? views::AsViewClass<DeleteEditShortcut>(
                   delete_edit_shortcut_widget_->widget_delegate()
                       ->AsBubbleDialogDelegate()
                       ->GetContentsView())
             : nullptr;
}

void DisplayOverlayController::UpdateButtonOptionsMenuWidgetBounds() {
  // There is no `button_options_widget_` in view mode.
  if (!button_options_widget_) {
    return;
  }

  if (auto* menu = GetButtonOptionsMenu()) {
    menu->UpdateWidget();
  }
}

void DisplayOverlayController::UpdateInputMappingWidgetBounds() {
  // There is no `input_mapping_widget_` if there is no active action or gio is
  // disabled.
  if (!input_mapping_widget_) {
    return;
  }

  UpdateWidgetBoundsInRootWindow(input_mapping_widget_.get(),
                                 touch_injector_->content_bounds());
  StackInputMappingAtBottomForViewMode();
}

void DisplayOverlayController::UpdateEditingListWidgetBounds() {
  // There is no `editing_list_widget_` in view mode.
  if (!editing_list_widget_) {
    return;
  }

  if (auto* editing_list = GetEditingList()) {
    editing_list->UpdateWidget();
  }
}

void DisplayOverlayController::UpdateTargetWidgetBounds() {
  if (!target_widget_) {
    return;
  }

  if (auto* target_view = GetTargetView()) {
    target_view->UpdateWidgetBounds();
  }
}

void DisplayOverlayController::UpdateEventRewriteCapability() {
  ash::ArcGameControlsFlag flags =
      touch_injector_->window()->GetProperty(ash::kArcGameControlsFlagsKey);

  touch_injector_->set_can_rewrite_event(
      IsFlagSet(flags, ash::ArcGameControlsFlag::kEnabled) &&
      !IsFlagSet(flags, ash::ArcGameControlsFlag::kEmpty) &&
      !IsFlagSet(flags, ash::ArcGameControlsFlag::kMenu) &&
      !IsFlagSet(flags, ash::ArcGameControlsFlag::kEdit));
}

std::vector<views::Widget*> DisplayOverlayController::GetTraversableWidgets()
    const {
  std::vector<views::Widget*> widget_list;
  if (input_mapping_widget_ && input_mapping_widget_->IsVisible()) {
    widget_list.emplace_back(input_mapping_widget_.get());
  }
  if (editing_list_widget_ && editing_list_widget_->IsVisible()) {
    widget_list.emplace_back(editing_list_widget_.get());
  }
  if (button_options_widget_ && button_options_widget_->IsVisible()) {
    widget_list.emplace_back(button_options_widget_.get());
  }
  if (delete_edit_shortcut_widget_ &&
      delete_edit_shortcut_widget_->IsVisible()) {
    widget_list.emplace_back(delete_edit_shortcut_widget_.get());
  }
  return widget_list;
}

void DisplayOverlayController::MaybeChangeFocusWidget(ui::KeyEvent& event) {
  // Only tab pressed is checked because the focus change is triggered by the
  // tab pressed event. No need to change widget focus again on tab key
  // released event.
  if (event.type() == ui::EventType::kKeyReleased ||
      !views::FocusManager::IsTabTraversalKeyEvent(event)) {
    return;
  }

  const bool reverse = event.IsShiftDown();
  auto* target_widget = views::Widget::GetWidgetForNativeWindow(
      static_cast<aura::Window*>(event.target()));
  auto* focus_manager = target_widget->GetFocusManager();

  // Once there is next focusable view (dont_loop==true), it means the current
  // focus is not the first or the last focusable view, so it doesn't need to
  // change focus to the next widget.
  if (focus_manager->GetNextFocusableView(
          /*starting_view=*/focus_manager->GetFocusedView(),
          /*starting_widget=*/target_widget, /*reverse=*/reverse,
          /*dont_loop=*/true)) {
    return;
  }

  // Change focus to the next widget.
  if (auto* next_widget = GetNextWidgetToFocus(GetTraversableWidgets(),
                                               target_widget, reverse)) {
    next_widget->GetFocusManager()->AdvanceFocus(reverse);
    // Change the event target.
    ui::Event::DispatcherApi(&event).set_target(next_widget->GetNativeWindow());
  }
}

}  // namespace arc::input_overlay
