// Copyright 2025 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/read_anything/read_anything_immersive_overlay_view.h"

#include <memory>
#include <utility>

#include "chrome/browser/ui/read_anything/read_anything_controller.h"
#include "chrome/browser/ui/read_anything/read_anything_immersive_web_view.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/frame/contents_web_view.h"
#include "chrome/grit/generated_resources.h"
#include "components/tabs/public/tab_interface.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/layout/fill_layout.h"

ReadAnythingImmersiveOverlayView::ReadAnythingImmersiveOverlayView(
    ContentsWebView* contents_web_view)
    : contents_web_view_(contents_web_view) {
  SetLayoutManager(std::make_unique<views::FillLayout>());
  SetID(VIEW_ID_READ_ANYTHING_OVERLAY);
  SetVisible(false);

  webcontents_attached_subscription_ =
      contents_web_view_->AddWebContentsAttachedCallback(base::BindRepeating(
          &ReadAnythingImmersiveOverlayView::OnWebContentsAttached,
          base::Unretained(this)));
  webcontents_detached_subscription_ =
      contents_web_view_->AddWebContentsDetachedCallback(base::BindRepeating(
          &ReadAnythingImmersiveOverlayView::OnWebContentsDetached,
          base::Unretained(this)));

  // If the contents_web_view already has a web contents, attach to it.
  if (contents_web_view_->web_contents()) {
    OnWebContentsAttached(contents_web_view_);
  }
}

ReadAnythingImmersiveOverlayView::~ReadAnythingImmersiveOverlayView() {
  UnsubscribeFromController();
}

void ReadAnythingImmersiveOverlayView::OnWebContentsAttached(
    views::WebView* web_view) {
  SubscribeToController(web_view);
}

void ReadAnythingImmersiveOverlayView::SubscribeToController(
    views::WebView* web_view) {
  tabs::TabInterface* tab = GetTabInterface(web_view);
  if (!tab) {
    return;
  }

  controller_ = ReadAnythingController::From(tab);
  if (!controller_) {
    return;
  }
  controller_->AddImmersiveActivationObserver(this);
  controller_->AddObserver(this);
}

void ReadAnythingImmersiveOverlayView::OnWebContentsDetached(
    views::WebView* web_view) {
  UnsubscribeFromController();
}

void ReadAnythingImmersiveOverlayView::UnsubscribeFromController() {
  if (controller_) {
    controller_->RemoveImmersiveActivationObserver(this);
    controller_->RemoveObserver(this);
    controller_ = nullptr;
  }
}

void ReadAnythingImmersiveOverlayView::OnShowImmersive(
    ReadAnythingOpenTrigger trigger) {
  if (immersive_web_view_) {
    return;
  }

  if (controller_) {
    ShowUI(controller_->GetOrCreateWebUIWrapper(
               ReadAnythingController::PresentationState::kInImmersiveOverlay),
           trigger);
  }
}

void ReadAnythingImmersiveOverlayView::OnCloseImmersive() {
  std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>> wrapper =
      CloseUI();
  if (wrapper && controller_) {
    controller_->TransferWebUiOwnership(
        std::move(wrapper),
        ReadAnythingController::PresentationState::kInImmersiveOverlay);
  }
}

void ReadAnythingImmersiveOverlayView::OnDestroyed() {
  UnsubscribeFromController();
}

void ReadAnythingImmersiveOverlayView::ShowUI(
    std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>>
        contents_wrapper,
    ReadAnythingOpenTrigger trigger) {
  CHECK(!immersive_web_view_);
  // Record has_shown_ui() before constructing ReadAnythingImmersiveWebView,
  // because attaching the WebContents inside the constructor will trigger
  // OnVisibilityChanged and set has_shown_ui() to true even on first open.
  const bool has_shown_ui_before_attaching =
      controller_ && controller_->has_shown_ui();

  auto immersive_web_view = std::make_unique<ReadAnythingImmersiveWebView>(
      base::BindOnce(&ReadAnythingImmersiveOverlayView::OnShowUI,
                     base::Unretained(this)),
      std::move(contents_wrapper), trigger);
  immersive_web_view_ = AddChildView(std::move(immersive_web_view));
  immersive_view_focus_subscription_ =
      immersive_web_view_->AddWebContentsFocusedCallback(base::BindRepeating(
          &ReadAnythingImmersiveOverlayView::OnImmersiveWebViewFocused,
          base::Unretained(this)));

  // Calling immersive_web_view_->ShowUI() is not necessary if it has
  // not been shown yet - the WebUI will call ShowUI() when it is ready. If the
  // UI has been shown once (e.g. from the Side Panel), the reused WebUI is
  // already available but won't send a new "showUI" message.
  // We manually call ShowUI() synchronously now that immersive_web_view_ is
  // attached to the view hierarchy so that RequestFocus() executes within the
  // active user gesture. This synchronous focus transition is required by
  // macOS.
  if (has_shown_ui_before_attaching) {
    immersive_web_view_->ShowUI();
  }
}

void ReadAnythingImmersiveOverlayView::OnShowUI() {
  SetVisible(true);

  // We set the underlying web contents to be not accessible while IRM is open,
  // so that it won't receive screen reader focus or be navigable by keyboard.
  scoped_accessibility_disconnecter_ =
      contents_web_view_->DisconnectWebContentsAccessibility();

  tabs::TabInterface* tab = GetTabInterface();
  if (tab && tab->CanShowModalUI()) {
    scoped_tab_modal_ui_ = tab->ShowModalUI();
  }

  GetViewAccessibility().AnnouncePolitely(l10n_util::GetStringUTF16(
      IDS_IMMERSIVE_READING_MODE_OPENED_ANNOUNCEMENT));

  DUMP_WILL_BE_CHECK(immersive_web_view_);
  // Only request focus if the tab is active so that an inactive tab in Split
  // View does not steal focus when its immersive overlay is shown.
  if (immersive_web_view_ && (!tab || tab->IsActivated())) {
    immersive_web_view_->RequestFocus();
  }
}

std::unique_ptr<WebUIContentsWrapperT<ReadAnythingUntrustedUI>>
ReadAnythingImmersiveOverlayView::CloseUI() {
  immersive_view_focus_subscription_ = {};
  SetVisible(false);

  // We want the main web contents to be accessible again if IRM is closed and
  // the main webpage is now visible.
  scoped_accessibility_disconnecter_.reset();

  scoped_tab_modal_ui_.reset();

  CHECK(immersive_web_view_);
  std::unique_ptr<ReadAnythingImmersiveWebView> web_view =
      RemoveChildViewT(immersive_web_view_);
  immersive_web_view_ = nullptr;

  // Focus on the underlying web view so that the focus is not lost entirely
  // when the immersive view is closed.
  if (contents_web_view_) {
    contents_web_view_->RequestFocus();
  }

  return web_view->CloseAndTakeContentsWrapper();
}

base::CallbackListSubscription
ReadAnythingImmersiveOverlayView::AddWebViewFocusedCallback(
    base::RepeatingCallback<void(views::WebView*)> callback) {
  return focus_callback_list_.Add(std::move(callback));
}

void ReadAnythingImmersiveOverlayView::OnImmersiveWebViewFocused(
    views::WebView* web_view) {
  focus_callback_list_.Notify(web_view);
}

tabs::TabInterface* ReadAnythingImmersiveOverlayView::GetTabInterface(
    views::WebView* web_view) {
  views::WebView* view_to_use = web_view ? web_view : contents_web_view_.get();
  if (!view_to_use || !view_to_use->web_contents()) {
    return nullptr;
  }
  return tabs::TabInterface::MaybeGetFromContents(
      view_to_use->web_contents());
}

BEGIN_METADATA(ReadAnythingImmersiveOverlayView)
END_METADATA
