// Copyright 2022 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/translate/translate_bubble_controller.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/translate/chrome_translate_client.h"
#include "chrome/browser/ui/actions/chrome_action_id.h"
#include "chrome/browser/ui/browser_actions.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/translate/partial_translate_bubble_model.h"
#include "chrome/browser/ui/translate/partial_translate_bubble_model_impl.h"
#include "chrome/browser/ui/translate/partial_translate_bubble_ui_action_logger.h"
#include "chrome/browser/ui/translate/translate_bubble_model_impl.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/translate/partial_translate_bubble_view.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/tabs/public/tab_interface.h"
#include "components/touch_to_search/core/browser/contextual_search_delegate_impl.h"
#include "components/translate/content/browser/contextual_translate_delegate.h"
#include "components/translate/content/browser/partial_translate_manager.h"
#include "components/translate/core/browser/translate_language_list.h"
#include "components/translate/core/browser/translate_manager.h"
#include "components/translate/core/browser/translate_ui_delegate.h"
#include "components/translate/core/browser/translate_ui_languages_manager.h"
#include "components/translate/core/common/translate_constants.h"
#include "components/translate/core/common/translate_errors.h"
#include "components/translate/core/common/translate_features.h"
#include "components/translate/core/common/translate_util.h"
#include "content/public/browser/web_contents.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "ui/actions/actions.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/text_elider.h"

namespace {

const char kTranslatePartialTranslationSelectionCharacterCount[] =
    "Translate.PartialTranslation.Selection.CharacterCount";

actions::ActionItem* GetTranslateActionItem(
    actions::ActionItem* root_action_item) {
  actions::ActionItem* translate_action_item =
      actions::ActionManager::Get().FindAction(kActionShowTranslate,
                                               root_action_item);
  CHECK(translate_action_item);
  return translate_action_item;
}

}  // namespace

DEFINE_USER_DATA(TranslateBubbleController);

TranslateBubbleController::TranslateBubbleController(
    BrowserWindowInterface* browser_window,
    actions::ActionItem* root_action_item)
    : browser_window_(browser_window),
      action_item_(GetTranslateActionItem(root_action_item)),
      scoped_unowned_user_data_(browser_window->GetUnownedUserDataHost(),
                                *this),
      scoped_translate_controller_(browser_window->GetUnownedUserDataHost(),
                                   *this) {}

TranslateBubbleController::~TranslateBubbleController() = default;

// static
TranslateBubbleController* TranslateBubbleController::From(
    BrowserWindowInterface* window) {
  return Get(window->GetUnownedUserDataHost());
}

views::Widget* TranslateBubbleController::ShowTranslateBubble(
    content::WebContents* web_contents,
    views::BubbleAnchor anchor,
    std::optional<ui::ElementIdentifier> highlighted_element,
    translate::TranslateStep step,
    const std::string& source_language,
    const std::string& target_language,
    translate::TranslateErrors error_type,
    LocationBarBubbleDelegateView::DisplayReason reason) {
  // If the Partial Translate bubble is already being shown, close it before
  // showing the full translate bubble.
  if (partial_translate_bubble_view_) {
    partial_translate_bubble_view_->CloseBubble();
  }

  if (translate_bubble_view_) {
    // When the user reads the advanced setting panel, the bubble should not be
    // changed because they are focusing on the bubble.
    if (translate_bubble_view_->model()->GetViewState() ==
            TranslateBubbleModel::VIEW_STATE_SOURCE_LANGUAGE ||
        translate_bubble_view_->model()->GetViewState() ==
            TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE) {
      return nullptr;
    }
    translate_bubble_view_->SetViewState(step, error_type);
    return nullptr;
  }

  if (step == translate::TRANSLATE_STEP_AFTER_TRANSLATE &&
      reason == LocationBarBubbleDelegateView::AUTOMATIC) {
    return nullptr;
  }

  std::unique_ptr<TranslateBubbleModel> model;
  if (model_factory_callback_) {
    model = model_factory_callback_.Run();
  } else {
    auto ui_delegate = std::make_unique<translate::TranslateUIDelegate>(
        ChromeTranslateClient::GetManagerFromWebContents(web_contents)
            ->GetWeakPtr(),
        source_language, target_language);
    model = std::make_unique<TranslateBubbleModelImpl>(step,
                                                       std::move(ui_delegate));
  }

  auto translate_bubble_view = std::make_unique<TranslateBubbleView>(
      anchor, std::move(model), error_type, web_contents,
      GetOnTranslateBubbleClosedCallback());
  translate_bubble_view_ = translate_bubble_view.get();

  if (highlighted_element) {
    translate_bubble_view_->SetHighlightedElement(*highlighted_element);
  }
  views::Widget* bubble_widget = views::BubbleDialogDelegateView::CreateBubble(
      std::move(translate_bubble_view));

  // TAB UI has the same view throughout. Select the right tab based on |step|
  // upon initialization.
  translate_bubble_view_->SetViewState(step, error_type);
  translate_bubble_view_->ShowForReason(reason);

  translate_bubble_view_->model()->ReportUIChange(true);

  action_item_->SetIsShowingBubble(true);

  // Trigger PDF translate IPH
  if (web_contents &&
      base::FeatureList::IsEnabled(translate::kEnableTranslatePdf)) {
    if (auto* translate_manager =
            ChromeTranslateClient::GetManagerFromWebContents(web_contents)) {
      if (translate_manager->GetLanguageState()->pdf_translatability_status() ==
          translate::LanguageState::PdfTranslatabilityStatus::kTranslatable) {
        if (auto* user_education =
                BrowserUserEducationInterface::From(browser_window_)) {
          user_education->MaybeShowFeaturePromo(
              feature_engagement::kIPHPdfTranslateBubbleFeature);
        }
      }
    }
  }

  return bubble_widget;
}

void TranslateBubbleController::StartPartialTranslate(
    const std::string& source_language,
    const std::string& target_language,
    const std::u16string& text_selection) {
  tabs::TabInterface* active_tab = browser_window_->GetActiveTabInterface();
  if (!active_tab) {
    return;
  }
  content::WebContents* web_contents = active_tab->GetContents();
  if (!web_contents) {
    return;
  }

  // Show the Translate icon and enabled the associated command to show the
  // Translate UI.
  ChromeTranslateClient* client =
      ChromeTranslateClient::FromWebContents(web_contents);
  if (client) {
    client->GetTranslateManager()->GetLanguageState()->SetTranslateEnabled(
        true);
  }

  views::BubbleAnchor anchor;
  std::optional<ui::ElementIdentifier> highlighted_element;

  if (anchor_view_for_testing_) {
    anchor = views::BubbleAnchor(anchor_view_for_testing_);
  } else {
    BrowserView* browser_view =
        BrowserView::GetBrowserViewForBrowser(browser_window_);
    if (browser_view) {
      anchor = browser_view->toolbar_button_provider()->GetBubbleAnchor(
          kActionShowTranslate);
      highlighted_element = kTranslatePageActionElementId;
    }
  }

  CreatePartialTranslateBubble(web_contents, anchor, highlighted_element,
                               PartialTranslateBubbleModel::VIEW_STATE_WAITING,
                               source_language, target_language, text_selection,
                               /*target_text=*/u"",
                               translate::TranslateErrors::NONE);
  // If response time is <=kDesktopPartialTranslateBubbleShowDelayMs, the bubble
  // will be shown directly with the translation. If response time is longer,
  // the bubble will be shown in a loading state until the translation is ready.
  partial_translate_timer_.Start(
      FROM_HERE,
      base::Milliseconds(translate::kDesktopPartialTranslateBubbleShowDelayMs),
      base::BindOnce(&TranslateBubbleController::OnPartialTranslateWaitExpired,
                     weak_ptr_factory_.GetWeakPtr()));

  partial_translate_bubble_view_->model()->Translate(web_contents);
}

void TranslateBubbleController::OnPartialTranslateWaitExpired() {
  if (!partial_translate_bubble_view_) {
    return;
  }

  if (partial_translate_bubble_view_->GetViewState() ==
      PartialTranslateBubbleModel::VIEW_STATE_WAITING) {
    partial_translate_bubble_view_->ShowForReason(
        LocationBarBubbleDelegateView::USER_GESTURE);
    translate::ReportPartialTranslateBubbleUiAction(
        translate::PartialTranslateBubbleUiEvent::BUBBLE_SHOWN);
  }
}

void TranslateBubbleController::OnPartialTranslateComplete() {
  // Stop the wait timer so we don't revert back to the waiting view.
  partial_translate_timer_.Stop();

  if (!partial_translate_bubble_view_) {
    return;
  }

  if (partial_translate_bubble_view_->model()->GetError() !=
      translate::TranslateErrors::NONE) {
    partial_translate_bubble_view_->SetViewState(
        PartialTranslateBubbleModel::VIEW_STATE_ERROR,
        partial_translate_bubble_view_->model()->GetError());
  } else {
    partial_translate_bubble_view_->SetViewState(
        PartialTranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE,
        translate::TranslateErrors::NONE);
  }

  partial_translate_bubble_view_->MaybeUpdateSourceLanguageCombobox();
  partial_translate_bubble_view_->ShowForReason(
      LocationBarBubbleDelegateView::USER_GESTURE);
  translate::ReportPartialTranslateBubbleUiAction(
      translate::PartialTranslateBubbleUiEvent::BUBBLE_SHOWN);
}

void TranslateBubbleController::CreatePartialTranslateBubble(
    content::WebContents* web_contents,
    views::BubbleAnchor anchor,
    std::optional<ui::ElementIdentifier> highlighted_element,
    PartialTranslateBubbleModel::ViewState view_state,
    const std::string& source_language,
    const std::string& target_language,
    const std::u16string& source_text,
    const std::u16string& target_text,
    translate::TranslateErrors error_type) {
  // If the other Translate bubble is already being shown, close it before
  // showing this one.
  if (translate_bubble_view_) {
    translate_bubble_view_->CloseBubble();
  }

  // Truncate text selection, if needed. Log the length of the text selection
  // before truncating.
  base::UmaHistogramCounts100000(
      kTranslatePartialTranslationSelectionCharacterCount,
      source_text.length());
  std::u16string truncated_source_text = gfx::TruncateString(
      source_text,
      translate::kDesktopPartialTranslateTextSelectionMaxCharacters,
      gfx::WORD_BREAK);
  bool is_truncated = (source_text.compare(truncated_source_text) != 0);

  if (partial_translate_bubble_view_) {
    PartialTranslateBubbleModel* model =
        partial_translate_bubble_view_->model();
    model->SetSourceLanguage(source_language);
    model->SetTargetLanguage(target_language);
    model->SetSourceText(truncated_source_text);
    model->SetSourceTextTruncated(is_truncated);
    model->SetTargetText(target_text);
    // When the user reads the advanced setting panel, the bubble should not be
    // changed because they are focusing on the bubble.
    if (partial_translate_bubble_view_->model()->GetViewState() ==
            PartialTranslateBubbleModel::VIEW_STATE_SOURCE_LANGUAGE ||
        partial_translate_bubble_view_->model()->GetViewState() ==
            PartialTranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE) {
      return;
    }
    partial_translate_bubble_view_->SetViewState(view_state, error_type);
    return;
  }

  std::unique_ptr<PartialTranslateBubbleModel> model;
  if (partial_model_factory_callback_) {
    model = partial_model_factory_callback_.Run();
  } else {
    std::vector<std::string> language_codes;
    translate::TranslateLanguageList::GetSupportedPartialTranslateLanguages(
        &language_codes);
    auto translate_ui_languages_manager =
        std::make_unique<translate::TranslateUILanguagesManager>(
            language_codes, source_language, target_language);

    Profile* profile =
        Profile::FromBrowserContext(web_contents->GetBrowserContext());
    auto partial_translate_manager = std::make_unique<PartialTranslateManager>(
        std::make_unique<ContextualSearchDelegateImpl>(
            profile->GetURLLoaderFactory(),
            TemplateURLServiceFactory::GetForProfile(profile)),
        std::make_unique<ContextualTranslateDelegate>(
            profile->GetURLLoaderFactory()));

    model = std::make_unique<PartialTranslateBubbleModelImpl>(
        view_state, error_type, truncated_source_text, target_text,
        std::move(partial_translate_manager),
        std::move(translate_ui_languages_manager));
  }
  model->SetSourceTextTruncated(is_truncated);

  model->AddObserver(this);

  auto partial_translate_bubble_view =
      std::make_unique<PartialTranslateBubbleView>(
          anchor, std::move(model), web_contents,
          GetOnPartialTranslateBubbleClosedCallback());
  partial_translate_bubble_view_ = partial_translate_bubble_view.get();
  if (highlighted_element) {
    partial_translate_bubble_view_->SetHighlightedElement(*highlighted_element);
  }
  views::BubbleDialogDelegateView::CreateBubble(
      std::move(partial_translate_bubble_view));
  partial_translate_bubble_view_->SetViewState(view_state, error_type);

  action_item_->SetIsShowingBubble(true);
}

void TranslateBubbleController::CloseBubble() {
  if (translate_bubble_view_) {
    translate_bubble_view_->CloseBubble();
  } else if (partial_translate_bubble_view_) {
    partial_translate_bubble_view_->CloseBubble();
  }
}

TranslateBubbleView* TranslateBubbleController::GetTranslateBubble() const {
  return translate_bubble_view_;
}

PartialTranslateBubbleView*
TranslateBubbleController::GetPartialTranslateBubble() const {
  return partial_translate_bubble_view_;
}

void TranslateBubbleController::SetTranslateBubbleModelFactory(
    base::RepeatingCallback<std::unique_ptr<TranslateBubbleModel>()> callback) {
  model_factory_callback_ = std::move(callback);
}

void TranslateBubbleController::SetPartialTranslateBubbleModelFactory(
    base::RepeatingCallback<std::unique_ptr<PartialTranslateBubbleModel>()>
        callback) {
  partial_model_factory_callback_ = std::move(callback);
}

void TranslateBubbleController::SetAnchorViewForTesting(
    views::View* anchor_view) {
  anchor_view_for_testing_ = anchor_view;
}

base::OnceClosure
TranslateBubbleController::GetOnTranslateBubbleClosedCallback() {
  return base::BindOnce(&TranslateBubbleController::OnTranslateBubbleClosed,
                        weak_ptr_factory_.GetWeakPtr());
}

base::OnceClosure
TranslateBubbleController::GetOnPartialTranslateBubbleClosedCallback() {
  return base::BindOnce(
      &TranslateBubbleController::OnPartialTranslateBubbleClosed,
      weak_ptr_factory_.GetWeakPtr());
}

void TranslateBubbleController::OnTranslateBubbleClosed() {
  translate_bubble_view_ = nullptr;
  action_item_->SetIsShowingBubble(false);
}

void TranslateBubbleController::OnPartialTranslateBubbleClosed() {
  partial_translate_bubble_view_ = nullptr;
  action_item_->SetIsShowingBubble(false);
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(TranslateBubbleController);
