// Copyright 2026 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/views/dictation/dictation_overlay_view.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/dictation/features.h"
#include "chrome/browser/ui/views/dictation/waveform_view.h"
#include "chrome/browser/ui/views/dictation/waveform_view_button.h"
#include "chrome/grit/generated_resources.h"
#include "components/vector_icons/vector_icons.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/color/color_id.h"
#include "ui/color/color_variant.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"

namespace dictation {

DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(DictationOverlayView,
                                      kViewElementIdForTesting);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(DictationOverlayView,
                                      kMicButtonElementIdForTesting);
DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(DictationOverlayView,
                                      kWaveformElementIdForTesting);

namespace {

constexpr int kCornerRadius = 16;
constexpr int kTeardropCornerRadius = 4;

class DictationOverlayContentsView : public views::View {
  METADATA_HEADER(DictationOverlayContentsView, views::View)
 public:
  explicit DictationOverlayContentsView(
      base::RepeatingClosure toggle_active_stream_callback)
      : toggle_active_stream_callback_(
            std::move(toggle_active_stream_callback)) {
    SetProperty(views::kElementIdentifierKey,
                DictationOverlayView::kViewElementIdForTesting);

    auto layout = std::make_unique<views::BoxLayout>(
        views::BoxLayout::Orientation::kHorizontal, gfx::Insets(6));
    SetLayoutManager(std::move(layout));

    auto mic_button = views::CreateVectorImageButtonWithNativeTheme(
        toggle_active_stream_callback_, vector_icons::kMicIcon, 20,
        ui::kColorSysOnSurface, ui::kColorIconDisabled, ui::kColorSysOnSurface);
    mic_button->SetBorder(nullptr);
    mic_button->SetAccessibleName(
        l10n_util::GetStringUTF16(IDS_DICTATION_ACCNAME_OVERLAY_MIC_BUTTON));
    mic_button->SetPreferredSize(gfx::Size(20, 20));
    mic_button->SetProperty(
        views::kElementIdentifierKey,
        DictationOverlayView::kMicButtonElementIdForTesting);
    mic_button_ = AddChildView(std::move(mic_button));

    auto waveform_view = std::make_unique<WaveformViewButton>(
        /*full_size=*/false, toggle_active_stream_callback_);
    waveform_view->SetProperty(
        views::kElementIdentifierKey,
        DictationOverlayView::kWaveformElementIdForTesting);
    if (kSessionEndsOnStreamEnd.Get()) {
      // When `kSessionEndsOnStreamEnd` is enabled, there is no point
      // in showing the mic. It cannot start a new stream.
      mic_button_->SetVisible(false);
      waveform_view->SetVisible(true);
    } else {
      waveform_view->SetVisible(false);
    }
    waveform_view_ = AddChildView(std::move(waveform_view));
  }

  ~DictationOverlayContentsView() override = default;

  void SetState(UiState state) {
    if (state_ == state) {
      return;
    }
    state_ = state;

    bool mic_visible = false;
    bool waveform_visible = false;
    if (kSessionEndsOnStreamEnd.Get()) {
      // When `kSessionEndsOnStreamEnd` is enabled, there is no point
      // in showing the mic. It cannot start a new stream.
      waveform_visible = true;
    } else {
      switch (state) {
        case UiState::kInactive:
        case UiState::kInitializing:
          mic_visible = true;
          break;
        case UiState::kTranscribing:
        case UiState::kFinalizing:
          waveform_visible = true;
          break;
      }
    }

    mic_button_->SetVisible(mic_visible);

    waveform_view_->SetVisible(waveform_visible);
    waveform_view_->SetState(state);
    waveform_view_->SetEnabled(state == UiState::kTranscribing);

    PreferredSizeChanged();
  }

  void UpdateAudioLevel(float audio_level) {
    if (waveform_view_) {
      waveform_view_->SetAudioLevel(audio_level);
    }
  }

  UiState state() const { return state_; }

 private:
  base::RepeatingClosure toggle_active_stream_callback_;
  UiState state_ = UiState::kInactive;
  raw_ptr<views::ImageButton> mic_button_ = nullptr;
  raw_ptr<WaveformViewButton> waveform_view_ = nullptr;
};

BEGIN_METADATA(DictationOverlayContentsView)
END_METADATA

}  // namespace

DictationOverlayView::DictationOverlayView(
    gfx::NativeView parent_window,
    base::RepeatingClosure toggle_active_stream_callback)
    : BubbleDialogDelegate(nullptr,
                           views::BubbleBorder::TOP_LEFT,
                           views::BubbleBorder::STANDARD_SHADOW,
                           /*autosize=*/true) {
  set_parent_window(parent_window);
  SetBackgroundColor(ui::kColorBubbleBackground);
  SetContentsView(std::make_unique<DictationOverlayContentsView>(
      std::move(toggle_active_stream_callback)));
  SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone));
  SetShowCloseButton(false);
  set_margins(gfx::Insets(0));
  set_corner_radius(kCornerRadius);
  set_shadow(views::BubbleBorder::STANDARD_SHADOW);
}

DictationOverlayView::~DictationOverlayView() = default;

void DictationOverlayView::OnWidgetInitialized() {
  views::BubbleDialogDelegate::OnWidgetInitialized();
  if (GetBubbleFrameView()) {
    GetBubbleFrameView()->SetRoundedCorners(
        base::i18n::IsRTL()
            ? gfx::RoundedCornersF(kCornerRadius, kTeardropCornerRadius,
                                   kCornerRadius, kCornerRadius)
            : gfx::RoundedCornersF(kTeardropCornerRadius, kCornerRadius,
                                   kCornerRadius, kCornerRadius));
  }
}

void DictationOverlayView::Show() {
  if (!widget_) {
    widget_ = views::BubbleDialogDelegate::CreateBubble(this);
  }
  widget_->ShowInactive();
}

// TODO(b/525859277): Make sure this works for RTL text.
void DictationOverlayView::UpdatePosition(
    const gfx::Point& focus_selection_point) {
  SetAnchorRect(gfx::Rect(focus_selection_point, gfx::Size()));
  SizeToContents();
}

void DictationOverlayView::OnStartedStream(content::GlobalDOMNodeId target_id) {
  focus_selection_bounds_changed_subscription_ = {};

  content::RenderFrameHost* target_rfh =
      target_id.document.AsRenderFrameHostIfValid();
  if (!target_rfh) {
    return;
  }
  content::WebContents* web_contents =
      content::WebContents::FromRenderFrameHost(target_rfh);
  if (!web_contents) {
    return;
  }

  last_target_document_ = target_id.document;

  focus_selection_bounds_changed_subscription_ =
      web_contents->RegisterFocusSelectionBoundsChanged(base::BindRepeating(
          &DictationOverlayView::OnFocusSelectionBoundsChanged,
          base::Unretained(this)));

  UpdatePosition(target_rfh);
}

void DictationOverlayView::OnFocusSelectionBoundsChanged(
    content::RenderWidgetHostView* render_widget_host_view) {
  content::RenderFrameHost* target_rfh =
      last_target_document_.AsRenderFrameHostIfValid();
  if (!target_rfh || target_rfh->GetView() != render_widget_host_view) {
    return;
  }

  UpdatePosition(target_rfh);
}

void DictationOverlayView::UpdatePosition(
    content::RenderFrameHost* target_rfh) {
  content::WebContents* web_contents =
      content::WebContents::FromRenderFrameHost(target_rfh);
  if (!web_contents) {
    return;
  }

  std::optional<gfx::Rect> bounds =
      web_contents->GetFocusSelectionBounds(target_rfh);
  if (!bounds.has_value()) {
    return;
  }

  if (widget_ && !web_contents->IsFocusedElementEditable()) {
    // If the user's selection changed to something that isn't editable, leave
    // the icon where it is. Since the last editable is where new text will go
    // for a new stream.
    return;
  }

  gfx::Point point = bounds->origin() + gfx::Vector2d(bounds->width(), 0);
  UpdatePosition(point);
  Show();
}

void DictationOverlayView::SetState(UiState state) {
  if (state_ == state) {
    return;
  }
  state_ = state;
  if (GetContentsView()) {
    views::AsViewClass<DictationOverlayContentsView>(GetContentsView())
        ->SetState(state);
  }
  if (GetWidget()) {
    SizeToContents();
  }
}

void DictationOverlayView::UpdateAudioLevel(float audio_level) {
  if (GetContentsView()) {
    views::AsViewClass<DictationOverlayContentsView>(GetContentsView())
        ->UpdateAudioLevel(audio_level);
  }
}

UiState DictationOverlayView::state_for_testing() const {
  return state_;
}

}  // namespace dictation
