// 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 "chrome/browser/ui/webui/ash/mako/mako_rewrite_view.h"

#include "ash/constants/ash_features.h"
#include "chrome/common/chrome_render_frame.mojom.h"
#include "components/input/native_web_keyboard_event.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/mojom/page/draggable_region.mojom.h"
#include "ui/aura/window.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"

namespace ash {

namespace {

constexpr int kMakoAnchorVerticalPadding = 16;
constexpr int kMakoScreenEdgePadding = 16;
constexpr int kMakoRewriteCornerRadius = 20;

constexpr int kMakoInitialWidth = 440;
constexpr int kMakoInitialHeight = 343;

// Height threshold of the mako rewrite UI which determines its screen position.
// Tall UI is centered on the display screen containing the caret, while short
// UI is anchored at the caret.
constexpr int kMakoRewriteHeightThreshold = 400;

// Compute initial widget bounds.
gfx::Rect ComputeInitialWidgetBounds(gfx::Rect caret_bounds,
                                     gfx::Insets inset,
                                     bool can_fallback_to_center_position) {
  gfx::Rect screen_work_area =
      display::Screen::Get()->GetDisplayMatching(caret_bounds).work_area();
  screen_work_area.Inset(kMakoScreenEdgePadding);

  gfx::Size initial_size = gfx::Size(kMakoInitialWidth, kMakoInitialHeight);

  // Otherwise, try to place it under at the bottom left of the selection.
  gfx::Rect anchor = caret_bounds;
  anchor.Outset(gfx::Outsets::VH(kMakoAnchorVerticalPadding, 0));

  gfx::Rect mako_contents_bounds =
      can_fallback_to_center_position &&
              (caret_bounds == gfx::Rect() ||
               !screen_work_area.Contains(caret_bounds))
          ? gfx::Rect(screen_work_area.x() + screen_work_area.width() / 2 -
                          initial_size.width() / 2,
                      screen_work_area.y() + screen_work_area.height() / 2 -
                          initial_size.height() / 2,
                      initial_size.width(), initial_size.height())
          : gfx::Rect(anchor.bottom_left(), initial_size);

  // If horizontally offscreen, just move it to the right edge of the screen.
  if (mako_contents_bounds.right() > screen_work_area.right()) {
    mako_contents_bounds.set_x(screen_work_area.right() - initial_size.width());
  }

  // If vertically offscreen, try above the selection.
  if (mako_contents_bounds.bottom() > screen_work_area.bottom()) {
    mako_contents_bounds.set_y(anchor.y() - initial_size.height());
  }

  // If still vertically offscreen, just move it to the bottom of the screen.
  if (mako_contents_bounds.y() < screen_work_area.y()) {
    mako_contents_bounds.set_y(screen_work_area.bottom() -
                               initial_size.height());
  }

  // Compute widget bounds, which includes the border and shadow around the main
  // contents. Then, adjust again to ensure the whole widget is onscreen.
  gfx::Rect widget_bounds(mako_contents_bounds);
  widget_bounds.Inset(inset);
  widget_bounds.AdjustToFit(screen_work_area);

  return widget_bounds;
}

}  // namespace

MakoRewriteView::MakoRewriteView(WebUIContentsWrapper* contents_wrapper,
                                 const gfx::Rect& caret_bounds,
                                 bool can_fallback_to_center_position)
    : DraggableBubbleDialogView(contents_wrapper),
      caret_bounds_(caret_bounds),
      can_fallback_to_center_position_(can_fallback_to_center_position) {
  set_has_parent(false);
  set_corner_radius(kMakoRewriteCornerRadius);
  // Disable the default offscreen adjustment so that we can customise it.
  set_adjust_if_offscreen(false);
  resizing_initialized_ = false;
}

MakoRewriteView::~MakoRewriteView() = default;

// TODO(b/321585827): Remove MakoRewriteView::ResizeDueToAutoResize() once
// resizing support is lanched.
void MakoRewriteView::ResizeDueToAutoResize(content::WebContents* source,
                                            const gfx::Size& new_size) {
  // When resizing support is enabled, we should only correct widget bounds
  // during ShowUI().
  if (base::FeatureList::IsEnabled(ash::features::kOrcaResizingSupport)) {
    return;
  }

  WebUIBubbleDialogView::ResizeDueToAutoResize(source, new_size);
  gfx::Rect screen_work_area =
      display::Screen::Get()->GetDisplayMatching(caret_bounds_).work_area();
  screen_work_area.Inset(kMakoScreenEdgePadding);

  // If the contents is very tall, just place it at the center of the screen.
  if (new_size.height() > kMakoRewriteHeightThreshold) {
    SetArrowWithoutResizing(views::BubbleBorder::FLOAT);
    SetAnchorRect(screen_work_area);
    return;
  }

  // Otherwise, try to place it under at the bottom left of the selection.
  gfx::Rect anchor = caret_bounds_;
  anchor.Outset(gfx::Outsets::VH(kMakoAnchorVerticalPadding, 0));

  gfx::Rect mako_contents_bounds =
      can_fallback_to_center_position_ &&
              (caret_bounds_ == gfx::Rect() ||
               !screen_work_area.Contains(caret_bounds_))
          ? gfx::Rect(screen_work_area.x() + screen_work_area.width() / 2 -
                          new_size.width() / 2,
                      screen_work_area.y() + screen_work_area.height() / 2 -
                          new_size.height() / 2,
                      new_size.width(), new_size.height())
          : gfx::Rect(anchor.bottom_left(), new_size);

  // If horizontally offscreen, just move it to the right edge of the screen.
  if (mako_contents_bounds.right() > screen_work_area.right()) {
    mako_contents_bounds.set_x(screen_work_area.right() - new_size.width());
  }

  // If vertically offscreen, try above the selection.
  if (mako_contents_bounds.bottom() > screen_work_area.bottom()) {
    mako_contents_bounds.set_y(anchor.y() - new_size.height());
  }
  // If still vertically offscreen, just move it to the bottom of the screen.
  if (mako_contents_bounds.y() < screen_work_area.y()) {
    mako_contents_bounds.set_y(screen_work_area.bottom() - new_size.height());
  }

  // Compute widget bounds, which includes the border and shadow around the main
  // contents. Then, adjust again to ensure the whole widget is onscreen.
  gfx::Rect widget_bounds(mako_contents_bounds);
  widget_bounds.Inset(-GetBubbleFrameView()->bubble_border()->GetInsets());
  widget_bounds.AdjustToFit(screen_work_area);

  GetWidget()->SetBounds(widget_bounds);
}

void MakoRewriteView::SetContentsBounds(content::WebContents* source,
                                        const gfx::Rect& bounds) {
  if (views::WebView* web_view_ptr = web_view()) {
    web_view_ptr->SetPreferredSize(bounds.size());
  }
  GetWidget()->SetBounds(bounds);
  content_bounds_updated_by_webui_ = true;
}

void MakoRewriteView::ShowUI() {
  WebUIBubbleDialogView::ShowUI();
  // TODO(b/321585877): Remove feature flag for dragging support.
  if (base::FeatureList::IsEnabled(ash::features::kOrcaDraggingSupport)) {
    SetupDraggingSupport();
  }
  if (base::FeatureList::IsEnabled(ash::features::kOrcaResizingSupport)) {
    SetupResizingSupport();
  }
  content_bounds_updated_by_webui_ = false;
}

bool MakoRewriteView::IsDraggingEnabled() {
  // Once the content bounds is updated by Web UI, we should stop resizing
  // support. This is currently used by Feedback UI.
  if (content_bounds_updated_by_webui_) {
    return false;
  }
  return base::FeatureList::IsEnabled(ash::features::kOrcaDraggingSupport);
}

bool MakoRewriteView::IsResizingEnabled() {
  // Once the content bounds is updated by Web UI, we should stop resizing
  // support. This is currently used by Feedback UI.
  if (content_bounds_updated_by_webui_) {
    return false;
  }
  return base::FeatureList::IsEnabled(ash::features::kOrcaResizingSupport);
}

bool MakoRewriteView::HandleKeyboardEvent(
    content::WebContents* source,
    const input::NativeWebKeyboardEvent& event) {
  if (event.GetType() == input::NativeWebKeyboardEvent::Type::kRawKeyDown &&
      event.dom_key == ui::DomKey::ESCAPE) {
    return true;
  }

  return WebUIBubbleDialogView::HandleKeyboardEvent(source, event);
}

void MakoRewriteView::SetupResizingSupport() {
  // This avoids setting initial bounds twice.
  if (resizing_initialized_) {
    return;
  }
  // When orca resizing support is enabled, there will be no
  // `ResizeDueToAutoResize` callback and thus we need to setup widget bounds
  // here as part of ShowUI.
  GetWidget()->SetBounds(ComputeInitialWidgetBounds(
      caret_bounds_, -GetBubbleFrameView()->bubble_border()->GetInsets(),
      can_fallback_to_center_position_));
  resizing_initialized_ = true;
}

BEGIN_METADATA(MakoRewriteView)
END_METADATA

}  // namespace ash
