// Copyright 2012 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/extensions/media_galleries_dialog_views.h"

#include <stddef.h>

#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/extensions/media_gallery_checkbox_view.h"
#include "chrome/grit/locale_settings.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/web_modal/web_contents_modal_dialog_manager.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/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/separator.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view.h"

namespace {

const int kScrollAreaHeight = 192;

// This container has the right Layout() impl to use within a ScrollView.
class ScrollableView : public views::View {
  METADATA_HEADER(ScrollableView, views::View)

 public:
  ScrollableView() = default;
  ScrollableView(const ScrollableView&) = delete;
  ScrollableView& operator=(const ScrollableView&) = delete;
  ~ScrollableView() override = default;

  void Layout(PassKey) override;
};

void ScrollableView::Layout(PassKey) {
  gfx::Size pref = GetPreferredSize();
  int width = pref.width();
  int height = pref.height();
  if (parent()) {
    width = parent()->width();
    height = std::max(parent()->height(), height);
  }
  SetBounds(x(), y(), width, height);

  LayoutSuperclass<views::View>(this);
}

BEGIN_METADATA(ScrollableView)
END_METADATA

}  // namespace

MediaGalleriesDialogViews::MediaGalleriesDialogViews(
    MediaGalleriesDialogController* controller)
    : controller_(controller),
      contents_(new views::View()),
      auxiliary_button_(nullptr),
      confirm_available_(false),
      accepted_(false) {
  SetButtonLabel(ui::mojom::DialogButton::kOk,
                 controller_->GetAcceptButtonText());
  SetAcceptCallback(base::BindOnce(
      [](MediaGalleriesDialogViews* dialog) { dialog->accepted_ = true; },
      base::Unretained(this)));
  SetModalType(ui::mojom::ModalType::kChild);
  SetShowCloseButton(false);
  SetTitle(controller_->GetHeader());
  RegisterDeleteDelegateCallback(
      RegisterDeleteCallbackPassKey(),
      base::BindOnce(
          [](MediaGalleriesDialogViews* dialog) {
            dialog->controller_->DialogFinished(dialog->accepted_);
          },
          this));

  std::u16string label = controller_->GetAuxiliaryButtonText();
  if (!label.empty()) {
    auxiliary_button_ = SetExtraView(std::make_unique<views::MdTextButton>(
        base::BindRepeating(
            &MediaGalleriesDialogViews::ButtonPressed, base::Unretained(this),
            base::BindRepeating(
                &MediaGalleriesDialogController::DidClickAuxiliaryButton,
                base::Unretained(controller_))),
        label));
  }

  InitChildViews();
  if (ControllerHasWebContents()) {
    constrained_window::ShowWebModalDialogViews(this,
                                                controller->WebContents());
  }
}

MediaGalleriesDialogViews::~MediaGalleriesDialogViews() {
  if (!ControllerHasWebContents()) {
    delete contents_;
  }
}

void MediaGalleriesDialogViews::AcceptDialogForTesting() {
  accepted_ = true;

  web_modal::WebContentsModalDialogManager* manager =
      web_modal::WebContentsModalDialogManager::FromWebContents(
          controller_->WebContents());
  DCHECK(manager);
  web_modal::WebContentsModalDialogManager::TestApi(manager).CloseAllDialogs();
}

void MediaGalleriesDialogViews::InitChildViews() {
  // Outer dialog layout.
  contents_->RemoveAllChildViews();
  checkbox_map_.clear();

  ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
  const auto insets = provider->GetDialogInsetsForContentType(
      views::DialogContentType::kText, views::DialogContentType::kControl);
  const int vertical_padding =
      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL);
  contents_->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical, insets, vertical_padding));

  // Message text.
  auto* subtext = contents_->AddChildView(
      std::make_unique<views::Label>(controller_->GetSubtext()));
  subtext->SetMultiLine(true);
  subtext->SetHorizontalAlignment(gfx::ALIGN_LEFT);

  // Scrollable area for checkboxes.
  const int small_vertical_padding =
      provider->GetDistanceMetric(DISTANCE_RELATED_CONTROL_VERTICAL_SMALL);
  auto scroll_container = std::make_unique<ScrollableView>();
  scroll_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical, gfx::Insets(),
      small_vertical_padding));
  scroll_container->SetBorder(views::CreateEmptyBorder(
      gfx::Insets::TLBR(vertical_padding, 0, vertical_padding, 0)));

  std::vector<std::u16string> section_headers =
      controller_->GetSectionHeaders();
  for (size_t i = 0; i < section_headers.size(); i++) {
    MediaGalleriesDialogController::Entries entries =
        controller_->GetSectionEntries(i);

    // Header and separator line.
    if (!section_headers[i].empty() && !entries.empty()) {
      scroll_container->AddChildView(std::make_unique<views::Separator>());

      auto header = std::make_unique<views::Label>(section_headers[i]);
      header->SetMultiLine(true);
      header->SetHorizontalAlignment(gfx::ALIGN_LEFT);
      header->SetBorder(views::CreateEmptyBorder(gfx::Insets::TLBR(
          vertical_padding,
          provider->GetInsetsMetric(views::INSETS_DIALOG).left(),
          vertical_padding, 0)));
      scroll_container->AddChildView(std::move(header));
    }

    // Checkboxes.
    MediaGalleriesDialogController::Entries::const_iterator iter;
    for (iter = entries.begin(); iter != entries.end(); ++iter) {
      int spacing = iter + 1 == entries.end() ? small_vertical_padding : 0;
      AddOrUpdateGallery(*iter, scroll_container.get(), spacing);
    }
  }

  confirm_available_ = controller_->IsAcceptAllowed();

  // Add the scrollable area to the outer dialog view. It will squeeze against
  // the title/subtitle and buttons to occupy all available space in the dialog.
  auto* scroll_view =
      contents_->AddChildView(views::ScrollView::CreateScrollViewWithBorder());
  scroll_view->SetContents(std::move(scroll_container));
  const int dialog_content_width = views::Widget::GetLocalizedContentsWidth(
      IDS_MEDIA_GALLERIES_DIALOG_CONTENT_WIDTH_CHARS);
  scroll_view->SetPreferredSize(
      gfx::Size(dialog_content_width, kScrollAreaHeight));
}

void MediaGalleriesDialogViews::UpdateGalleries() {
  InitChildViews();
  contents_->DeprecatedLayoutImmediately();

  if (ControllerHasWebContents()) {
    DialogModelChanged();
  }
}

bool MediaGalleriesDialogViews::AddOrUpdateGallery(
    const MediaGalleriesDialogController::Entry& gallery,
    views::View* container,
    int trailing_vertical_space) {
  auto iter = checkbox_map_.find(gallery.pref_info.pref_id);
  if (iter != checkbox_map_.end()) {
    views::Checkbox* checkbox = iter->second->checkbox();
    checkbox->SetChecked(gallery.selected);
    checkbox->SetText(gallery.pref_info.GetGalleryDisplayName());
    checkbox->SetTooltipText(gallery.pref_info.GetGalleryTooltip());
    std::u16string details = gallery.pref_info.GetGalleryAdditionalDetails();
    iter->second->secondary_text()->SetText(details);
    iter->second->secondary_text()->SetVisible(details.length() > 0);
    return false;
  }

  auto* gallery_view =
      container->AddChildView(std::make_unique<MediaGalleryCheckboxView>(
          gallery.pref_info, trailing_vertical_space, this));
  gallery_view->checkbox()->SetCallback(base::BindRepeating(
      &MediaGalleriesDialogViews::ButtonPressed, base::Unretained(this),
      base::BindRepeating(
          [](MediaGalleriesDialogController* controller,
             MediaGalleryPrefId pref_id, views::Checkbox* checkbox) {
            controller->DidToggleEntry(pref_id, checkbox->GetChecked());
          },
          controller_, gallery.pref_info.pref_id, gallery_view->checkbox())));
  gallery_view->checkbox()->SetChecked(gallery.selected);
  checkbox_map_[gallery.pref_info.pref_id] = gallery_view;
  return true;
}

views::Widget* MediaGalleriesDialogViews::GetWidget() {
  return contents_->GetWidget();
}

const views::Widget* MediaGalleriesDialogViews::GetWidget() const {
  return contents_->GetWidget();
}

views::View* MediaGalleriesDialogViews::GetContentsView() {
  return contents_;
}

bool MediaGalleriesDialogViews::IsDialogButtonEnabled(
    ui::mojom::DialogButton button) const {
  return button != ui::mojom::DialogButton::kOk || confirm_available_;
}

void MediaGalleriesDialogViews::ShowContextMenuForViewImpl(
    views::View* source,
    const gfx::Point& point,
    ui::mojom::MenuSourceType source_type) {
  for (CheckboxMap::const_iterator iter = checkbox_map_.begin();
       iter != checkbox_map_.end(); ++iter) {
    if (iter->second->Contains(source)) {
      ShowContextMenu(point, source_type, iter->first);
      return;
    }
  }
}

void MediaGalleriesDialogViews::ShowContextMenu(
    const gfx::Point& point,
    ui::mojom::MenuSourceType source_type,
    MediaGalleryPrefId id) {
  context_menu_runner_ = std::make_unique<views::MenuRunner>(
      controller_->GetContextMenu(id),
      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU,
      base::BindRepeating(&MediaGalleriesDialogViews::OnMenuClosed,
                          base::Unretained(this)));

  context_menu_runner_->RunMenuAt(
      GetWidget(), nullptr, gfx::Rect(point.x(), point.y(), 0, 0),
      views::MenuAnchorPosition::kTopLeft, source_type);
}

bool MediaGalleriesDialogViews::ControllerHasWebContents() const {
  return controller_->WebContents() != nullptr;
}

void MediaGalleriesDialogViews::ButtonPressed(base::RepeatingClosure closure) {
  confirm_available_ = true;

  if (ControllerHasWebContents()) {
    DialogModelChanged();
  }

  closure.Run();
}

void MediaGalleriesDialogViews::OnMenuClosed() {
  context_menu_runner_.reset();
}

// MediaGalleriesDialogViewsController -----------------------------------------

// static
MediaGalleriesDialog* MediaGalleriesDialog::Create(
    MediaGalleriesDialogController* controller) {
  return new MediaGalleriesDialogViews(controller);
}
