// 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.

#include "ash/quick_insert/views/quick_insert_view.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "ash/ash_element_identifiers.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/quick_insert/metrics/quick_insert_performance_metrics.h"
#include "ash/quick_insert/metrics/quick_insert_session_metrics.h"
#include "ash/quick_insert/model/quick_insert_action_type.h"
#include "ash/quick_insert/model/quick_insert_caps_lock_position.h"
#include "ash/quick_insert/model/quick_insert_mode_type.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_search_result.h"
#include "ash/quick_insert/resources/grit/quick_insert_resources.h"
#include "ash/quick_insert/views/quick_insert_emoji_bar_view.h"
#include "ash/quick_insert/views/quick_insert_item_with_submenu_view.h"
#include "ash/quick_insert/views/quick_insert_key_event_handler.h"
#include "ash/quick_insert/views/quick_insert_main_container_view.h"
#include "ash/quick_insert/views/quick_insert_page_view.h"
#include "ash/quick_insert/views/quick_insert_positioning.h"
#include "ash/quick_insert/views/quick_insert_pseudo_focus.h"
#include "ash/quick_insert/views/quick_insert_search_bar_textfield.h"
#include "ash/quick_insert/views/quick_insert_search_field_view.h"
#include "ash/quick_insert/views/quick_insert_search_results_view.h"
#include "ash/quick_insert/views/quick_insert_search_results_view_delegate.h"
#include "ash/quick_insert/views/quick_insert_strings.h"
#include "ash/quick_insert/views/quick_insert_style.h"
#include "ash/quick_insert/views/quick_insert_submenu_controller.h"
#include "ash/quick_insert/views/quick_insert_submenu_view.h"
#include "ash/quick_insert/views/quick_insert_traversable_item_container.h"
#include "ash/quick_insert/views/quick_insert_view_delegate.h"
#include "ash/quick_insert/views/quick_insert_zero_state_view.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/branding_buildflags.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/window.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/emoji/emoji_panel_helper.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_types.h"
#include "ui/display/screen.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_observer.h"
#include "ui/views/view_tracker.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/frame_view.h"

#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "chromeos/ash/resources/internal/strings/grit/ash_internal_strings.h"
#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)

namespace ash {
namespace {

constexpr int kVerticalPaddingBetweenQuickInsertContainers = 8;

// Padding to separate the Quick Insert window from the screen edge.
constexpr gfx::Insets kPaddingFromScreenEdge(16);

std::unique_ptr<views::BubbleBorder> CreateBorder() {
  auto border = std::make_unique<views::BubbleBorder>(
      views::BubbleBorder::NONE, views::BubbleBorder::NO_SHADOW);
  border->set_rounded_corners(
      gfx::RoundedCornersF(kQuickInsertContainerBorderRadius));
  border->SetColor(SK_ColorTRANSPARENT);
  return border;
}

// Gets the preferred Quick Insert view bounds in screen coordinates. We try to
// place the Quick Insert view close to `anchor_bounds`, while taking into
// account `layout_type`, `quick_insert_view_size` and available space on the
// screen. `quick_insert_view_search_field_vertical_offset` is the vertical
// offset from the top of the Quick Insert view to the center of the search
// field, which we use to try to vertically align the search field with the
// center of the anchor bounds. `anchor_bounds` and returned bounds should be in
// screen coordinates.
gfx::Rect GetQuickInsertViewBoundsWithoutSelectedText(
    const gfx::Rect& anchor_bounds,
    QuickInsertLayoutType layout_type,
    const gfx::Size& quick_insert_view_size,
    int quick_insert_view_search_field_vertical_offset) {
  gfx::Rect screen_work_area =
      display::Screen::Get()->GetDisplayMatching(anchor_bounds).work_area();
  screen_work_area.Inset(kPaddingFromScreenEdge);
  gfx::Rect quick_insert_view_bounds(quick_insert_view_size);
  if (anchor_bounds.right() + quick_insert_view_size.width() <=
      screen_work_area.right()) {
    // If there is space, place Quick Insert to the right of the anchor,
    // vertically aligning the center of the Quick Insert search field with the
    // center of the anchor.
    quick_insert_view_bounds.set_origin(anchor_bounds.right_center());
    quick_insert_view_bounds.Offset(
        0, -quick_insert_view_search_field_vertical_offset);
  } else {
    switch (layout_type) {
      case QuickInsertLayoutType::kMainResultsBelowSearchField:
        // Try to place Quick Insert at the right edge of the screen, below
        // the anchor.
        quick_insert_view_bounds.set_origin(
            {screen_work_area.right() - quick_insert_view_size.width(),
             anchor_bounds.bottom()});
        break;
      case QuickInsertLayoutType::kMainResultsAboveSearchField:
        // Try to place Quick Insert at the right edge of the screen, above
        // the anchor.
        quick_insert_view_bounds.set_origin(
            {screen_work_area.right() - quick_insert_view_size.width(),
             anchor_bounds.y() - quick_insert_view_size.height()});
        break;
    }
  }

  // Adjust if necessary to keep the whole Quick Insert view onscreen. Note that
  // the non client area of Quick Insert, e.g. the shadows, are allowed to
  // be offscreen.
  quick_insert_view_bounds.AdjustToFit(screen_work_area);
  return quick_insert_view_bounds;
}

// Gets the preferred Quick Insert view bounds in the case that there is
// selected text. We try to left align the Quick Insert view above or below
// `anchor_bounds`, while taking into account `layout_type`,
// `quick_insert_view_size` and available space on the screen. `anchor_bounds`
// and returned bounds should be in screen coordinates.
gfx::Rect GetQuickInsertViewBoundsWithSelectedText(
    const gfx::Rect& anchor_bounds,
    QuickInsertLayoutType layout_type,
    const gfx::Size& quick_insert_view_size) {
  gfx::Rect screen_work_area =
      display::Screen::Get()->GetDisplayMatching(anchor_bounds).work_area();
  screen_work_area.Inset(kPaddingFromScreenEdge);
  gfx::Rect quick_insert_view_bounds(quick_insert_view_size);
  switch (layout_type) {
    case QuickInsertLayoutType::kMainResultsBelowSearchField:
      // Left aligned below the anchor.
      quick_insert_view_bounds.set_origin(
          gfx::Point(anchor_bounds.x(), anchor_bounds.bottom()));
      break;
    case QuickInsertLayoutType::kMainResultsAboveSearchField:
      // Left aligned above the anchor.
      quick_insert_view_bounds.set_origin(
          gfx::Point(anchor_bounds.x(),
                     anchor_bounds.y() - quick_insert_view_size.height()));
      break;
  }

  // Adjust if necessary to keep the whole Quick Insert view onscreen.
  quick_insert_view_bounds.AdjustToFit(screen_work_area);
  return quick_insert_view_bounds;
}

QuickInsertCategory GetCategoryForMoreResults(QuickInsertSectionType type) {
  switch (type) {
    case QuickInsertSectionType::kNone:
    case QuickInsertSectionType::kContentEditor:
    case QuickInsertSectionType::kExamples:
    case QuickInsertSectionType::kFeaturedGifs:
    case QuickInsertSectionType::kSearchedGifs:
      NOTREACHED();
    case QuickInsertSectionType::kClipboard:
      return QuickInsertCategory::kClipboard;
    case QuickInsertSectionType::kLinks:
      return QuickInsertCategory::kLinks;
    case QuickInsertSectionType::kLocalFiles:
      return QuickInsertCategory::kLocalFiles;
    case QuickInsertSectionType::kDriveFiles:
      return QuickInsertCategory::kDriveFiles;
  }
}

std::u16string GetSearchFieldPlaceholderText(QuickInsertModeType mode,
                                             bool is_editor_available) {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  switch (mode) {
    case QuickInsertModeType::kUnfocused:
      return l10n_util::GetStringUTF16(
          IDS_PICKER_SEARCH_FIELD_NO_FOCUS_PLACEHOLDER_TEXT);
    case QuickInsertModeType::kNoSelection:
      return l10n_util::GetStringUTF16(
          is_editor_available
              ? IDS_PICKER_SEARCH_FIELD_NO_SELECTION_WITH_EDITOR_PLACEHOLDER_TEXT
              : IDS_PICKER_SEARCH_FIELD_NO_SELECTION_PLACEHOLDER_TEXT);
    case QuickInsertModeType::kHasSelection:
      return l10n_util::GetStringUTF16(
          is_editor_available
              ? IDS_PICKER_SEARCH_FIELD_HAS_SELECTION_WITH_EDITOR_PLACEHOLDER_TEXT
              : IDS_PICKER_SEARCH_FIELD_HAS_SELECTION_PLACEHOLDER_TEXT);
    default:
      NOTREACHED();
  }
#else
  return u"Placeholder";
#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
}

std::u16string GetNoResultsFoundDescription(QuickInsertCategory category) {
  switch (category) {
    case QuickInsertCategory::kLinks:
      return l10n_util::GetStringUTF16(
          IDS_PICKER_NO_RESULTS_FOR_BROWSING_HISTORY_LABEL_TEXT);
    case QuickInsertCategory::kClipboard:
      return l10n_util::GetStringUTF16(
          IDS_PICKER_NO_RESULTS_FOR_CLIPBOARD_LABEL_TEXT);
    case QuickInsertCategory::kDriveFiles:
      return l10n_util::GetStringUTF16(
          IDS_PICKER_NO_RESULTS_FOR_DRIVE_FILES_LABEL_TEXT);
    case QuickInsertCategory::kLocalFiles:
      return l10n_util::GetStringUTF16(
          IDS_PICKER_NO_RESULTS_FOR_LOCAL_FILES_LABEL_TEXT);
    case QuickInsertCategory::kDatesTimes:
    case QuickInsertCategory::kUnitsMaths:
      // TODO: b/345303965 - Add finalized strings for dates and maths.
      return l10n_util::GetStringUTF16(IDS_PICKER_NO_RESULTS_TEXT);
    case QuickInsertCategory::kEditorWrite:
    case QuickInsertCategory::kEditorRewrite:
    case QuickInsertCategory::kLobsterWithNoSelectedText:
    case QuickInsertCategory::kLobsterWithSelectedText:
    case QuickInsertCategory::kEmojisGifs:
    case QuickInsertCategory::kEmojis:
      NOTREACHED();
    case QuickInsertCategory::kGifs:
      // TODO: b/345303965 - Add finalized strings for GIFs.
      return l10n_util::GetStringUTF16(IDS_PICKER_NO_RESULTS_TEXT);
  }
}

ui::ImageModel GetNoResultsFoundIllustration() {
  return ui::ResourceBundle::GetSharedInstance().GetThemedLottieImageNamed(
      IDR_QUICK_INSERT_NO_RESULTS_ILLUSTRATION);
}

bool IsEditorAvailable(
    base::span<const QuickInsertCategory> available_categories) {
  return std::ranges::contains(available_categories,
                               QuickInsertCategory::kEditorWrite) ||
         std::ranges::contains(available_categories,
                               QuickInsertCategory::kEditorRewrite);
}

}  // namespace

QuickInsertView::QuickInsertView(QuickInsertViewDelegate* delegate,
                                 const gfx::Rect& anchor_bounds,
                                 QuickInsertLayoutType layout_type,
                                 QuickInsertPositionType position_type,
                                 const base::TimeTicks trigger_event_timestamp)
    : performance_metrics_(trigger_event_timestamp), delegate_(delegate) {
  SetShowCloseButton(false);
  SetProperty(views::kElementIdentifierKey, kQuickInsertElementId);
  // TODO: b/357991165 - The desired bounds delegate here is *not* used directly
  // by the widget, because QuickInsertWidget does not use `autosize`. Rather,
  // QuickInsertView manually calls GetDesiredWidgetBounds to adjust the Widget
  // bounds to realign the search field with the caret position. Move this logic
  // to a standalone class.
  if (position_type == QuickInsertPositionType::kNearAnchor) {
    set_desired_bounds_delegate(base::BindRepeating(
        &QuickInsertView::GetTargetBounds, base::Unretained(this),
        anchor_bounds, layout_type));
  }

  SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::LayoutOrientation::kVertical,
      /*inside_border_insets=*/gfx::Insets(),
      /*between_child_spacing=*/kVerticalPaddingBetweenQuickInsertContainers));

  AddMainContainerView(layout_type);
  if (std::ranges::contains(delegate_->GetAvailableCategories(),
                            QuickInsertCategory::kEmojisGifs) ||
      std::ranges::contains(delegate_->GetAvailableCategories(),
                            QuickInsertCategory::kEmojis)) {
    AddEmojiBarView();
  }

  // Automatically focus on the search field.
  SetInitiallyFocusedView(search_field_view_);

  AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
  AddAccelerator(ui::Accelerator(ui::VKEY_BROWSER_BACK, ui::EF_NONE));
  key_event_handler_.SetActivePseudoFocusHandler(this);

  pseudo_focused_view_tracker_.SetTrackEntireViewHierarchy(true);
  preview_bubble_observation_.Observe(&preview_controller_);
}

QuickInsertView::~QuickInsertView() = default;

bool QuickInsertView::AcceleratorPressed(const ui::Accelerator& accelerator) {
  switch (accelerator.key_code()) {
    case ui::VKEY_ESCAPE:
      if (preview_controller_.IsBubbleVisible()) {
        preview_controller_.CloseBubble();
      } else if (submenu_controller_.GetSubmenuView() != nullptr) {
        submenu_controller_.Close();
      } else if (auto* widget = GetWidget()) {
        // Otherwise, close the Quick Insert widget.
        widget->CloseWithReason(views::Widget::ClosedReason::kEscKeyPressed);
      }
      return true;
    case ui::VKEY_BROWSER_BACK:
      OnSearchBackButtonPressed();
      return true;
    default:
      NOTREACHED();
  }
}

std::unique_ptr<views::FrameView> QuickInsertView::CreateFrameView(
    views::Widget* widget) {
  auto frame =
      std::make_unique<views::BubbleFrameView>(gfx::Insets(), gfx::Insets());
  frame->SetBubbleBorder(CreateBorder());
  return frame;
}

void QuickInsertView::AddedToWidget() {
  performance_metrics_.StartRecording(*GetWidget());
  // Due to layout considerations, only populate the emoji bar after the
  // QuickInsertView has been added to a widget.
  ResetEmojiBarToZeroState();
}

void QuickInsertView::RemovedFromWidget() {
  performance_metrics_.StopRecording();
}

void QuickInsertView::Layout(PassKey) {
  LayoutSuperclass<views::View>(this);

  if (widget_bounds_needs_update_ && GetWidget() != nullptr) {
    GetWidget()->SetBounds(GetDesiredWidgetBounds());
    widget_bounds_needs_update_ = false;
  }
}

void QuickInsertView::SelectZeroStateCategory(QuickInsertCategory category) {
  SelectCategory(category);
}

void QuickInsertView::SelectZeroStateResult(
    const QuickInsertSearchResult& result) {
  SelectSearchResult(result);
}

QuickInsertActionType QuickInsertView::GetActionForResult(
    const QuickInsertSearchResult& result) {
  return delegate_->GetActionForResult(result);
}

void QuickInsertView::OnSearchResultsViewHeightChanged() {
  SetWidgetBoundsNeedsUpdate();
}

void QuickInsertView::GetZeroStateSuggestedResults(
    SuggestedResultsCallback callback) {
  delegate_->GetZeroStateSuggestedResults(std::move(callback));
}

void QuickInsertView::RequestPseudoFocus(views::View* view) {
  // Only allow `view` to become pseudo focused if it is visible and part of the
  // active item container.
  if (view == nullptr || !view->IsDrawn() ||
      active_item_container_ == nullptr ||
      !active_item_container_->ContainsItem(view)) {
    return;
  }
  SetPseudoFocusedView(view);
}

void QuickInsertView::OnZeroStateViewHeightChanged() {
  SetWidgetBoundsNeedsUpdate();
}

QuickInsertCapsLockPosition QuickInsertView::GetCapsLockPosition() {
  return delegate_->GetCapsLockPosition();
}

void QuickInsertView::SetCapsLockDisplayed(bool displayed) {
  delegate_->GetSessionMetrics().SetCapsLockDisplayed(displayed);
}

void QuickInsertView::SelectSearchResult(
    const QuickInsertSearchResult& result) {
  if (const QuickInsertCategoryResult* category_data =
          std::get_if<QuickInsertCategoryResult>(&result)) {
    SelectCategory(category_data->category);
  } else if (const QuickInsertSearchRequestResult* search_request_data =
                 std::get_if<QuickInsertSearchRequestResult>(&result)) {
    UpdateSearchQueryAndActivePage(search_request_data->primary_text);
  } else if (const QuickInsertEditorResult* editor_data =
                 std::get_if<QuickInsertEditorResult>(&result)) {
    delegate_->GetSessionMetrics().SetOutcome(
        QuickInsertSessionMetrics::SessionOutcome::kRedirected);
    delegate_->ShowEditor(
        editor_data->preset_query_id,
        base::UTF16ToUTF8(search_field_view_->GetQueryText()));
  } else if (std::get_if<QuickInsertLobsterResult>(&result)) {
    delegate_->GetSessionMetrics().SetOutcome(
        QuickInsertSessionMetrics::SessionOutcome::kRedirected);
    delegate_->ShowLobster(
        base::UTF16ToUTF8(search_field_view_->GetQueryText()));
  } else {
    delegate_->GetSessionMetrics().SetSelectedResult(
        result, search_results_view_->GetIndex(result));
    switch (delegate_->GetActionForResult(result)) {
      case QuickInsertActionType::kInsert:
        delegate_->CloseWidgetThenInsertResultOnNextFocus(result);
        break;
      case QuickInsertActionType::kOpen:
      case QuickInsertActionType::kDo:
        delegate_->OpenResult(result);
        GetWidget()->Close();
        break;
      case QuickInsertActionType::kCreate:
        NOTREACHED();
    }
  }
}

void QuickInsertView::SelectMoreResults(QuickInsertSectionType type) {
  SelectCategoryWithQuery(GetCategoryForMoreResults(type),
                          search_field_view_->GetQueryText());
}

void QuickInsertView::ToggleGifs(bool is_checked) {
  if (base::FeatureList::IsEnabled(features::kPickerGifs)) {
    is_gif_toggle_checked_ = is_checked;
    if (is_gif_toggle_checked_) {
      SelectCategoryWithQuery(QuickInsertCategory::kGifs,
                              search_field_view_->GetQueryText());
    } else {
      ResetSelectedCategory(/*reset_query=*/false);
    }
  } else {
    ShowEmojiPicker(ui::EmojiPickerCategory::kGifs);
  }
}

void QuickInsertView::ShowEmojiPicker(ui::EmojiPickerCategory category) {
  QuickInsertSessionMetrics& session_metrics = delegate_->GetSessionMetrics();
  session_metrics.SetSelectedCategory(QuickInsertCategory::kEmojisGifs);
  session_metrics.SetOutcome(
      QuickInsertSessionMetrics::SessionOutcome::kRedirected);
  if (auto* widget = GetWidget()) {
    widget->CloseWithReason(views::Widget::ClosedReason::kLostFocus);
  }

  delegate_->ShowEmojiPicker(category, search_field_view_->GetQueryText());
}

bool QuickInsertView::DoPseudoFocusedAction() {
  if (clear_results_timer_.IsRunning()) {
    // New results are still pending.
    // TODO: b/351920494 - Insert the first new result instead of doing nothing.
    return false;
  }

  if (auto* submenu_view = views::AsViewClass<QuickInsertItemWithSubmenuView>(
          GetPseudoFocusedView())) {
    submenu_view->ShowSubmenu();
    SetPseudoFocusedView(submenu_controller_.GetSubmenuView()->GetTopItem());
    return true;
  }

  return GetPseudoFocusedView() == nullptr
             ? false
             : DoQuickInsertPseudoFocusedActionOnView(GetPseudoFocusedView());
}

bool QuickInsertView::MovePseudoFocusUp() {
  if (views::View* item_above =
          active_item_container_->GetItemAbove(GetPseudoFocusedView())) {
    SetPseudoFocusedView(item_above);
  } else {
    AdvanceActiveItemContainer(QuickInsertPseudoFocusDirection::kBackward);
  }
  return true;
}

bool QuickInsertView::MovePseudoFocusDown() {
  if (views::View* item_below =
          active_item_container_->GetItemBelow(GetPseudoFocusedView())) {
    SetPseudoFocusedView(item_below);
  } else {
    AdvanceActiveItemContainer(QuickInsertPseudoFocusDirection::kForward);
  }
  return true;
}

bool QuickInsertView::MovePseudoFocusLeft() {
  views::View* pseudo_focused_view = GetPseudoFocusedView();
  if (IsContainedInSubmenu(pseudo_focused_view)) {
    SetPseudoFocusedView(submenu_controller_.GetAnchorView());
    submenu_controller_.Close();
    return true;
  }

  if (search_field_view_->Contains(pseudo_focused_view)) {
    if (search_field_view_->LeftEventShouldMoveCursor(pseudo_focused_view)) {
      return false;
    }
    views::View* left_view =
        search_field_view_->GetViewLeftOf(pseudo_focused_view);
    SetPseudoFocusedView(left_view);
    search_field_view_->OnGainedPseudoFocusFromLeftEvent(left_view);
    return true;
  }

  if (views::View* left_item =
          active_item_container_->GetItemLeftOf(pseudo_focused_view)) {
    SetPseudoFocusedView(left_item);
    return true;
  }
  return false;
}

bool QuickInsertView::MovePseudoFocusRight() {
  views::View* pseudo_focused_view = GetPseudoFocusedView();
  if (views::IsViewClass<QuickInsertItemWithSubmenuView>(pseudo_focused_view)) {
    views::AsViewClass<QuickInsertItemWithSubmenuView>(pseudo_focused_view)
        ->ShowSubmenu();
    SetPseudoFocusedView(submenu_controller_.GetSubmenuView()->GetTopItem());
    return true;
  }

  if (search_field_view_->Contains(pseudo_focused_view)) {
    if (search_field_view_->RightEventShouldMoveCursor(pseudo_focused_view)) {
      return false;
    }
    views::View* right_view =
        search_field_view_->GetViewRightOf(pseudo_focused_view);
    SetPseudoFocusedView(right_view);
    search_field_view_->OnGainedPseudoFocusFromRightEvent(right_view);
    return true;
  }

  if (views::View* right_item =
          active_item_container_->GetItemRightOf(pseudo_focused_view)) {
    SetPseudoFocusedView(right_item);
    return true;
  }
  return false;
}

bool QuickInsertView::AdvancePseudoFocus(
    QuickInsertPseudoFocusDirection direction) {
  if (preview_controller_.IsBubbleVisible()) {
    preview_controller_.CloseBubble();
  }
  if (GetPseudoFocusedView() == nullptr) {
    return false;
  }
  SetPseudoFocusedView(GetNextQuickInsertPseudoFocusableView(
      GetPseudoFocusedView(), direction, /*should_loop=*/true));
  return true;
}

void QuickInsertView::OnPreviewBubbleVisibilityChanged(bool visible) {
  if (views::Widget* widget = GetWidget()) {
    // When the bubble is visible, turn off hiding the cursor on Esc key.
    // If the cursor hides on Esc, the preview bubble is closed due to its
    // OnMouseExit event handler, before QuickInsertView has a chance to handle
    // the Esc key.
    widget->GetNativeWindow()->SetProperty(kShowCursorOnKeypress, visible);
  }
}

gfx::Rect QuickInsertView::GetTargetBounds(const gfx::Rect& anchor_bounds,
                                           QuickInsertLayoutType layout_type) {
  return delegate_->GetMode() == QuickInsertModeType::kHasSelection
             ? GetQuickInsertViewBoundsWithSelectedText(anchor_bounds,
                                                        layout_type, size())
             : GetQuickInsertViewBoundsWithoutSelectedText(
                   anchor_bounds, layout_type, size(),
                   search_field_view_->bounds().CenterPoint().y() +
                       main_container_view_->bounds().y());
}

void QuickInsertView::UpdateSearchQueryAndActivePage(std::u16string query) {
  search_field_view_->SetQueryText(std::move(query));
  search_field_view_->RequestFocus();
  UpdateActivePage();
}

void QuickInsertView::UpdateActivePage() {
  std::u16string_view query =
      base::TrimWhitespace(search_field_view_->GetQueryText(), base::TRIM_ALL);

  if (query == last_query_ && selected_category_ == last_selected_category_ &&
      is_gif_toggle_checked_ == last_is_gif_toggle_checked_) {
    return;
  }
  last_query_ = std::u16string(query);
  last_selected_category_ = selected_category_;
  last_is_gif_toggle_checked_ = is_gif_toggle_checked_;

  delegate_->GetSessionMetrics().UpdateSearchQuery(query);

  if (!query.empty()) {
    // Don't switch the active page immediately to the search view - this will
    // be done when the clear results timer fires, or when
    // `PublishSearchResults` is called.
    clear_results_timer_.Start(
        FROM_HERE, kClearResultsTimeout,
        base::BindOnce(&QuickInsertView::OnClearResultsTimerFired,
                       weak_ptr_factory_.GetWeakPtr()));
    delegate_->StartEmojiSearch(
        query, base::BindOnce(&QuickInsertView::PublishEmojiResults,
                              weak_ptr_factory_.GetWeakPtr()));
    delegate_->StartSearch(
        query, selected_category_,
        base::BindRepeating(&QuickInsertView::PublishSearchResults,
                            weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  if (selected_category_.has_value()) {
    SetActivePage(category_results_view_);
    if (last_suggested_results_category_ != selected_category_) {
      // Getting suggested results for a category can be slow, so show a
      // loading animation.
      category_results_view_->ShowLoadingAnimation();
      delegate_->GetResultsForCategory(
          *selected_category_,
          base::BindRepeating(&QuickInsertView::PublishCategoryResults,
                              weak_ptr_factory_.GetWeakPtr(),
                              *selected_category_));
      last_suggested_results_category_ = selected_category_;
    }
  } else {
    SetActivePage(zero_state_view_);
  }
  delegate_->StopSearch();
  clear_results_timer_.Stop();
  search_results_view_->ClearSearchResults();
  ResetEmojiBarToZeroState();
}

void QuickInsertView::PublishEmojiResults(
    std::vector<QuickInsertEmojiResult> results) {
  if (emoji_bar_view_ == nullptr) {
    return;
  }

  emoji_bar_view_->SetSearchResults(std::move(results));
  search_results_view_->SetNumEmojiResultsForA11y(
      emoji_bar_view_->GetNumItems());
}

void QuickInsertView::OnClearResultsTimerFired() {
  // `QuickInsertView::UpdateActivePage` ensures that if the active page was set
  // to the zero state or category view, the timer that this is called from is
  // cancelled - which guarantees that this can't be called.
  SetActivePage(search_results_view_);

  search_results_view_->ClearSearchResults();
  performance_metrics_.MarkSearchResultsUpdated(
      QuickInsertPerformanceMetrics::SearchResultsUpdate::kClear);
}

void QuickInsertView::PublishSearchResults(
    std::vector<QuickInsertSearchResultsSection> results) {
  // `QuickInsertView::UpdateActivePage` ensures that if the active page was set
  // to the zero state or category view, the delegate's search is stopped -
  // which guarantees that this can't be called.
  SetActivePage(search_results_view_);

  bool clear_stale_results = clear_results_timer_.IsRunning();
  if (clear_stale_results) {
    clear_results_timer_.Stop();
    search_results_view_->ClearSearchResults();
  }

  if (results.empty()) {
    bool no_results_found_shown = search_results_view_->SearchStopped(
        /*illustration=*/{},
        l10n_util::GetStringUTF16(IDS_PICKER_NO_RESULTS_TEXT));
    if (no_results_found_shown) {
      performance_metrics_.MarkSearchResultsUpdated(
          QuickInsertPerformanceMetrics::SearchResultsUpdate::kNoResultsFound);
    } else {
      CHECK(!clear_stale_results)
          << "Stale results were cleared when no results were found, but the "
             "\"no results found\" screen was not shown";
      // `clear_stale_results` must be false here, so nothing happened.
    }
    return;
  }

  for (QuickInsertSearchResultsSection& result : results) {
    search_results_view_->AppendSearchResults(std::move(result));
  }

  QuickInsertPerformanceMetrics::SearchResultsUpdate update;
  if (clear_stale_results) {
    update = QuickInsertPerformanceMetrics::SearchResultsUpdate::kReplace;
  } else {
    update = QuickInsertPerformanceMetrics::SearchResultsUpdate::kAppend;
  }
  performance_metrics_.MarkSearchResultsUpdated(update);
}

void QuickInsertView::SelectCategory(QuickInsertCategory category) {
  SelectCategoryWithQuery(category, /*query=*/u"");
}

void QuickInsertView::SelectCategoryWithQuery(QuickInsertCategory category,
                                              std::u16string_view query) {
  QuickInsertSessionMetrics& session_metrics = delegate_->GetSessionMetrics();
  session_metrics.SetSelectedCategory(category);
  selected_category_ = category;

  if (category == QuickInsertCategory::kEmojisGifs ||
      category == QuickInsertCategory::kEmojis) {
    session_metrics.SetOutcome(
        QuickInsertSessionMetrics::SessionOutcome::kRedirected);
    if (auto* widget = GetWidget()) {
      // TODO(b/316936394): Correctly handle opening of emoji picker. Probably
      // best to wait for the IME on focus event, or save some coordinates and
      // open emoji picker in the correct location in some other way.
      widget->CloseWithReason(views::Widget::ClosedReason::kLostFocus);
    }
    delegate_->ShowEmojiPicker(ui::EmojiPickerCategory::kEmojis, query);
    return;
  }

  if (category == QuickInsertCategory::kEditorWrite ||
      category == QuickInsertCategory::kEditorRewrite) {
    session_metrics.SetOutcome(
        QuickInsertSessionMetrics::SessionOutcome::kRedirected);
    if (auto* widget = GetWidget()) {
      widget->CloseWithReason(views::Widget::ClosedReason::kLostFocus);
    }
    CHECK(query.empty());
    delegate_->ShowEditor(/*preset_query_id*/ std::nullopt,
                          /*freeform_text=*/std::nullopt);
    return;
  }

  if (category == QuickInsertCategory::kLobsterWithNoSelectedText ||
      category == QuickInsertCategory::kLobsterWithSelectedText) {
    session_metrics.SetOutcome(
        QuickInsertSessionMetrics::SessionOutcome::kRedirected);
    if (auto* widget = GetWidget()) {
      widget->CloseWithReason(views::Widget::ClosedReason::kLostFocus);
    }
    delegate_->ShowLobster(/*query=*/std::nullopt);
    return;
  }

  search_field_view_->SetPlaceholderText(
      GetSearchFieldPlaceholderTextForQuickInsertCategory(category));
  search_field_view_->SetBackButtonVisible(category !=
                                           QuickInsertCategory::kGifs);
  SetEmojiBarVisibleIfEnabled(category == QuickInsertCategory::kGifs);
  UpdateSearchQueryAndActivePage(std::u16string(query));
}

void QuickInsertView::PublishCategoryResults(
    QuickInsertCategory category,
    std::vector<QuickInsertSearchResultsSection> results) {
  category_results_view_->ClearSearchResults();

  for (QuickInsertSearchResultsSection& section : results) {
    if (!section.results().empty()) {
      category_results_view_->AppendSearchResults(std::move(section));
    }
  }

  category_results_view_->SearchStopped(GetNoResultsFoundIllustration(),
                                        GetNoResultsFoundDescription(category));
}

void QuickInsertView::AddMainContainerView(QuickInsertLayoutType layout_type) {
  main_container_view_ =
      AddChildView(std::make_unique<QuickInsertMainContainerView>());

  // `base::Unretained` is safe here because this class owns
  // `main_container_view_`, which owns `search_field_view_`.
  search_field_view_ = main_container_view_->AddSearchFieldView(
      views::Builder<QuickInsertSearchFieldView>(
          std::make_unique<QuickInsertSearchFieldView>(
              base::IgnoreArgs<const std::u16string&>(base::BindRepeating(
                  &QuickInsertView::UpdateActivePage, base::Unretained(this))),
              base::BindRepeating(&QuickInsertView::OnSearchBackButtonPressed,
                                  base::Unretained(this)),
              &key_event_handler_, &performance_metrics_))
          .SetPlaceholderText(GetSearchFieldPlaceholderText(
              delegate_->GetMode(),
              IsEditorAvailable(delegate_->GetAvailableCategories())))
          .Build());
  main_container_view_->AddContentsView(layout_type);

  zero_state_view_ =
      main_container_view_->AddPage(std::make_unique<QuickInsertZeroStateView>(
          this, delegate_->GetAvailableCategories(), kQuickInsertViewWidth,
          delegate_->GetAssetFetcher(), &submenu_controller_,
          &preview_controller_));
  category_results_view_ = main_container_view_->AddPage(
      std::make_unique<QuickInsertSearchResultsView>(
          this, kQuickInsertViewWidth, delegate_->GetAssetFetcher(),
          &submenu_controller_, &preview_controller_));
  category_results_view_->SetLocalFileResultStyle(
      QuickInsertSearchResultsView::LocalFileResultStyle::kGrid);
  search_results_view_ = main_container_view_->AddPage(
      std::make_unique<QuickInsertSearchResultsView>(
          this, kQuickInsertViewWidth, delegate_->GetAssetFetcher(),
          &submenu_controller_, &preview_controller_));

  SetActivePage(zero_state_view_);
}

void QuickInsertView::AddEmojiBarView() {
  emoji_bar_view_ =
      AddChildViewAt(std::make_unique<QuickInsertEmojiBarView>(
                         this, kQuickInsertViewWidth,
                         /*is_gifs_enabled*/ delegate_->IsGifsEnabled()),
                     0);
}

void QuickInsertView::SetActivePage(QuickInsertPageView* page_view) {
  main_container_view_->SetActivePage(page_view);
  SetPseudoFocusedView(nullptr);
  active_item_container_ = page_view;
  SetPseudoFocusedView(active_item_container_->GetTopItem());
  SetWidgetBoundsNeedsUpdate();
}

void QuickInsertView::SetEmojiBarVisibleIfEnabled(bool visible) {
  if (emoji_bar_view_ == nullptr) {
    return;
  }
  emoji_bar_view_->SetVisible(visible);
  SetWidgetBoundsNeedsUpdate();
}

void QuickInsertView::AdvanceActiveItemContainer(
    QuickInsertPseudoFocusDirection direction) {
  if (active_item_container_ == submenu_controller_.GetSubmenuView()) {
    // Just keep the submenu as the active item container.
  } else if (emoji_bar_view_ == nullptr ||
             active_item_container_ == emoji_bar_view_) {
    active_item_container_ = main_container_view_;
  } else {
    active_item_container_ = emoji_bar_view_;
  }
  SetPseudoFocusedView(direction == QuickInsertPseudoFocusDirection::kForward
                           ? active_item_container_->GetTopItem()
                           : active_item_container_->GetBottomItem());
}

void QuickInsertView::SetPseudoFocusedView(views::View* view) {
  if (view == nullptr) {
    SetPseudoFocusedView(search_field_view_->textfield());
    return;
  }

  if (pseudo_focused_view_tracker_.view() == view) {
    return;
  }

  if (IsContainedInSubmenu(view)) {
    active_item_container_ = submenu_controller_.GetSubmenuView();
  } else {
    submenu_controller_.Close();
    if (emoji_bar_view_ != nullptr && emoji_bar_view_->Contains(view)) {
      active_item_container_ = emoji_bar_view_;
    } else {
      active_item_container_ = main_container_view_;
    }
  }

  RemoveQuickInsertPseudoFocusFromView(pseudo_focused_view_tracker_.view());

  pseudo_focused_view_tracker_.SetView(view);
  // base::Unretained() is safe here because this class owns
  // `pseudo_focused_view_tracker_`.
  pseudo_focused_view_tracker_.SetIsDeletingCallback(base::BindOnce(
      &QuickInsertView::SetPseudoFocusedView, base::Unretained(this), nullptr));

  search_field_view_->SetTextfieldActiveDescendant(view);
  view->ScrollViewToVisible();
  ApplyQuickInsertPseudoFocusToView(view);
}

views::View* QuickInsertView::GetPseudoFocusedView() {
  return pseudo_focused_view_tracker_.view();
}

void QuickInsertView::ResetSelectedCategory(bool reset_query) {
  search_field_view_->SetPlaceholderText(GetSearchFieldPlaceholderText(
      delegate_->GetMode(),
      IsEditorAvailable(delegate_->GetAvailableCategories())));
  search_field_view_->SetBackButtonVisible(false);
  SetEmojiBarVisibleIfEnabled(true);
  selected_category_ = std::nullopt;
  if (reset_query) {
    UpdateSearchQueryAndActivePage(u"");
  } else {
    UpdateActivePage();
  }
}

void QuickInsertView::OnSearchBackButtonPressed() {
  ResetSelectedCategory(/*reset_query=*/true);
  CHECK_EQ(main_container_view_->active_page(), zero_state_view_)
      << "UpdateSearchQueryAndActivePage did not set active page to zero state "
         "view";
}

void QuickInsertView::ResetEmojiBarToZeroState() {
  if (emoji_bar_view_ == nullptr) {
    return;
  }
  emoji_bar_view_->SetSearchResults(delegate_->GetSuggestedEmoji());
}

bool QuickInsertView::IsContainedInSubmenu(views::View* view) {
  return submenu_controller_.GetSubmenuView() != nullptr &&
         submenu_controller_.GetSubmenuView()->Contains(view);
}

void QuickInsertView::SetWidgetBoundsNeedsUpdate() {
  widget_bounds_needs_update_ = true;
}

BEGIN_METADATA(QuickInsertView)
END_METADATA

}  // namespace ash
