// 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/passwords/views_utils.h"

#include <algorithm>
#include <string>
#include <string_view>
#include <vector>

#include "chrome/browser/ui/passwords/ui_utils.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/grit/generated_resources.h"
#include "components/password_manager/core/browser/password_form.h"
#include "components/vector_icons/vector_icons.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/simple_combobox_model.h"
#include "ui/base/ui_base_features.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/range/range.h"
#include "ui/gfx/vector_icon_utils.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/editable_combobox/editable_combobox.h"
#include "ui/views/controls/editable_combobox/editable_password_combobox.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/style/typography.h"

namespace {
// Create a vector which contains only the values in |items| and no elements.
std::vector<std::u16string> ToValues(
    const password_manager::AlternativeElementVector& items) {
  std::vector<std::u16string> passwords;
  passwords.reserve(items.size());
  for (const auto& item : items) {
    passwords.push_back(item.value);
  }
  return passwords;
}

std::unique_ptr<views::View> CreateRow() {
  auto row = std::make_unique<views::View>();
  views::FlexLayout* row_layout =
      row->SetLayoutManager(std::make_unique<views::FlexLayout>());
  row_layout->SetOrientation(views::LayoutOrientation::kHorizontal)
      .SetIgnoreDefaultMainAxisMargins(true)
      .SetCollapseMargins(true)
      .SetDefault(
          views::kMarginsKey,
          gfx::Insets::VH(0, ChromeLayoutProvider::Get()->GetDistanceMetric(
                                 views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
  return row;
}
}  // namespace

CircularImageView::CircularImageView(gfx::Size size) : size_(size) {}

void CircularImageView::OnPaint(gfx::Canvas* canvas) {
  // Display the avatar picture as a circle.
  gfx::Rect bounds(GetImageBounds());
  const SkPath circular_mask = SkPath::Circle(
      SkIntToScalar(bounds.x() + bounds.right()) / 2,
      SkIntToScalar(bounds.y() + bounds.bottom()) / 2,
      SkIntToScalar(std::min(bounds.height(), bounds.width())) / 2);
  canvas->ClipPath(circular_mask, true);
  ImageView::OnPaint(canvas);
}

gfx::Size CircularImageView::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  if (!size_.IsEmpty()) {
    return size_;
  }
  return views::ImageView::CalculatePreferredSize(available_size);
}

BEGIN_METADATA(CircularImageView)
END_METADATA

std::unique_ptr<views::StyledLabel> CreateGooglePasswordManagerLabel(
    int text_message_id,
    int link_message_id,
    const std::u16string& email,
    base::RepeatingClosure open_link_closure,
    int context,
    int style) {
  const std::u16string link = l10n_util::GetStringUTF16(link_message_id);

  std::vector<size_t> offsets;
  std::u16string text =
      l10n_util::GetStringFUTF16(text_message_id, link, email, &offsets);

  auto label = std::make_unique<views::StyledLabel>();
  label->SetText(text);
  label->SetTextContext(context);
  label->SetDefaultTextStyle(style);
  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);

  label->AddStyleRange(
      gfx::Range(offsets.at(0), offsets.at(0) + link.length()),
      views::StyledLabel::RangeStyleInfo::CreateForLink(open_link_closure));

  return label;
}

std::unique_ptr<views::Label> CreateGooglePasswordManagerLabel(
    int text_message_id,
    int password_manager_message_id,
    const std::u16string& email,
    int context) {
  const std::u16string password_manager =
      l10n_util::GetStringUTF16(password_manager_message_id);
  std::u16string text =
      l10n_util::GetStringFUTF16(text_message_id, password_manager, email);

  auto label = std::make_unique<views::Label>(text, context,
                                              views::style::STYLE_SECONDARY);
  label->SetMultiLine(true);
  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  return label;
}

std::unique_ptr<views::StyledLabel> CreateGooglePasswordManagerLabel(
    int text_message_id,
    int link_message_id,
    base::RepeatingClosure open_link_closure,
    int context,
    int style) {
  const std::u16string link = l10n_util::GetStringUTF16(link_message_id);

  size_t link_offset;
  std::u16string text =
      l10n_util::GetStringFUTF16(text_message_id, link, &link_offset);

  auto label = std::make_unique<views::StyledLabel>();
  label->SetText(text);
  label->SetTextContext(context);
  label->SetDefaultTextStyle(style);
  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);

  label->AddStyleRange(
      gfx::Range(link_offset, link_offset + link.length()),
      views::StyledLabel::RangeStyleInfo::CreateForLink(open_link_closure));

  return label;
}

std::unique_ptr<views::Label> CreateUsernameLabel(
    const password_manager::PasswordForm& form) {
  auto label = std::make_unique<views::Label>(
      GetDisplayUsername(form), views::style::CONTEXT_DIALOG_BODY_TEXT,
      views::style::STYLE_SECONDARY);
  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  return label;
}

std::unique_ptr<views::Label> CreatePasswordLabel(
    const password_manager::PasswordForm& form) {
  std::unique_ptr<views::Label> label = std::make_unique<views::Label>(
      GetDisplayPassword(form), views::style::CONTEXT_DIALOG_BODY_TEXT);
  if (!form.IsFederatedCredential()) {
    label->SetTextStyle(views::style::STYLE_SECONDARY_MONOSPACED);
    label->SetObscured(true);
    label->SetElideBehavior(gfx::TRUNCATE);
  } else {
    label->SetTextStyle(views::style::STYLE_SECONDARY);
    label->SetElideBehavior(gfx::ELIDE_HEAD);
  }
  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  return label;
}

int ComboboxIconSize() {
  // Use the line height of the body small text. This allows the icons to adapt
  // if the user changes the font size.
  return views::TypographyProvider::Get().GetLineHeight(
      views::style::CONTEXT_MENU, views::style::STYLE_PRIMARY);
}

// Builds a credential row, adds the given elements to the layout.
void BuildCredentialRows(views::View* parent_view,
                         std::unique_ptr<views::View> username_field,
                         std::unique_ptr<views::View> password_field) {
  std::unique_ptr<views::Label> username_label(new views::Label(
      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_USERNAME_LABEL),
      views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY));
  username_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);

  std::unique_ptr<views::Label> password_label(new views::Label(
      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_PASSWORD_LABEL),
      views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY));
  password_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);

  int labels_width = std::max({username_label->GetPreferredSize().width(),
                               password_label->GetPreferredSize().width()});
  int fields_height = std::max({username_field->GetPreferredSize().height(),
                                password_field->GetPreferredSize().height()});

  username_label->SetPreferredSize(gfx::Size(labels_width, fields_height));
  password_label->SetPreferredSize(gfx::Size(labels_width, fields_height));

  // Username row.
  std::unique_ptr<views::View> username_row = CreateRow();
  username_row->AddChildView(std::move(username_label));
  username_field->SetProperty(
      views::kFlexBehaviorKey,
      views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
                               views::MaximumFlexSizeRule::kUnbounded));
  username_row->AddChildView(std::move(username_field));

  parent_view->AddChildView(std::move(username_row));

  // Password row.
  std::unique_ptr<views::View> password_row = CreateRow();
  password_row->AddChildView(std::move(password_label));
  password_field->SetProperty(
      views::kFlexBehaviorKey,
      views::FlexSpecification(views::MinimumFlexSizeRule::kPreferred,
                               views::MaximumFlexSizeRule::kUnbounded));
  password_row->AddChildView(std::move(password_field));

  parent_view->AddChildView(std::move(password_row));
}

// Creates an EditableCombobox from |PasswordForm.all_alternative_usernames| or
// even just |PasswordForm.username_value|.
std::unique_ptr<views::EditableCombobox> CreateUsernameEditableCombobox(
    const password_manager::PasswordForm& form) {
  std::vector<std::u16string> usernames = {form.username_value};
  for (const password_manager::AlternativeElement& other_possible_username :
       form.all_alternative_usernames) {
    if (other_possible_username.value != form.username_value) {
      usernames.push_back(other_possible_username.value);
    }
  }
  std::erase_if(usernames, [](const std::u16string& username) {
    return username.empty();
  });
  const bool kDisplayArrow = usernames.size() > 1;
  auto combobox = std::make_unique<views::EditableCombobox>(
      std::make_unique<ui::SimpleComboboxModel>(
          std::vector<ui::SimpleComboboxModel::Item>(usernames.begin(),
                                                     usernames.end())),
      /*filter_on_edit=*/false, /*show_on_empty=*/true,
      views::style::CONTEXT_BUTTON, views::style::STYLE_PRIMARY, kDisplayArrow);
  combobox->SetText(form.username_value);
  combobox->GetViewAccessibility().SetName(
      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_USERNAME_LABEL));
  // In case of long username, ensure that the beginning of value is visible.
  combobox->SelectRange(gfx::Range(0));
  return combobox;
}

// Creates an EditablePasswordCombobox from
// `PasswordForm.all_alternative_passwords` or even just
// `PasswordForm.password_value`.
std::unique_ptr<views::EditablePasswordCombobox> CreateEditablePasswordCombobox(
    const password_manager::PasswordForm& form,
    views::Button::PressedCallback reveal_password_callback) {
  DCHECK(!form.IsFederatedCredential());
  std::vector<std::u16string> passwords =
      form.all_alternative_passwords.empty()
          ? std::vector<std::u16string>(/*n=*/1, form.password_value)
          : ToValues(form.all_alternative_passwords);
  std::erase_if(passwords, [](const std::u16string& password) {
    return password.empty();
  });
  const bool kDisplayArrow = passwords.size() > 1;
  auto combobox = std::make_unique<views::EditablePasswordCombobox>(
      std::make_unique<ui::SimpleComboboxModel>(
          std::vector<ui::SimpleComboboxModel::Item>(passwords.begin(),
                                                     passwords.end())),
      views::style::CONTEXT_BUTTON, views::style::STYLE_PRIMARY_MONOSPACED,
      kDisplayArrow, std::move(reveal_password_callback));
  combobox->SetText(form.password_value);
  combobox->SetPasswordIconTooltips(
      l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_SHOW_PASSWORD),
      l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_HIDE_PASSWORD));
  combobox->GetViewAccessibility().SetName(
      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_PASSWORD_LABEL));
  return combobox;
}

std::unique_ptr<views::View> CreateTitleView(const std::u16string& title) {
  const ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
  auto header = std::make_unique<views::BoxLayoutView>();
  // Set the space between the icon and title similar to the default
  // BubbleFrameView layout.
  header->SetBetweenChildSpacing(
      layout_provider->GetInsetsMetric(views::INSETS_DIALOG_TITLE).left());
  header->AddChildView(
      std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
          GooglePasswordManagerVectorIcon(), ui::kColorIcon,
          layout_provider->GetDistanceMetric(
              views::DISTANCE_BUBBLE_HEADER_VECTOR_ICON_SIZE))));
  views::Label* title_label = header->AddChildView(
      views::BubbleFrameView::CreateDefaultTitleLabel(title));

  const int close_button_width =
      layout_provider->GetDistanceMetric(
          views::DISTANCE_RELATED_BUTTON_HORIZONTAL) +
      gfx::GetDefaultSizeOfVectorIcon(
          features::IsRoundedIconsEnabled()
              ? vector_icons::kCloseIcon
              : vector_icons::kCloseRoundedOldIcon) +
      layout_provider->GetDistanceMetric(views::DISTANCE_CLOSE_BUTTON_MARGIN);
  const int title_width =
      layout_provider->GetDistanceMetric(
          views::DISTANCE_BUBBLE_PREFERRED_WIDTH) -
      layout_provider->GetInsetsMetric(views::INSETS_DIALOG).width() -
      layout_provider->GetInsetsMetric(views::INSETS_DIALOG_TITLE).width() -
      close_button_width;
  title_label->SetMaximumWidth(title_width);
  return header;
}

void EmphasizeTokens(views::Label* label,
                     views::style::TextStyle emphasize_style,
                     const std::vector<std::u16string>& tokens) {
  const std::u16string_view text = label->GetText();
  for (const std::u16string& token : tokens) {
    size_t start = text.find(token, 0);
    while (start != std::u16string::npos) {
      const size_t end = start + token.size();
      label->SetTextStyleRange(emphasize_style, gfx::Range(start, end));
      start = text.find(token, end + 1);
    }
  }
}
