// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/controls/editable_combobox/editable_password_combobox.h"

#include <algorithm>
#include <memory>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "ui/base/ime/text_input_flags.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/render_text.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/image_button_factory.h"
#include "ui/views/controls/combobox/combobox_util.h"
#include "ui/views/controls/editable_combobox/editable_combobox.h"
#include "ui/views/controls/focus_ring.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/vector_icons.h"

namespace views {

namespace {

constexpr int kEyePaddingWidth = 4;

// Creates the eye-styled icon that serves as a button to toggle the password
// visibility.
std::unique_ptr<ToggleImageButton> CreateEye(
    ImageButton::PressedCallback callback) {
  auto button = Builder<ToggleImageButton>()
                    .SetInstallFocusRingOnFocus(true)
                    .SetRequestFocusOnPress(true)
                    .SetImageVerticalAlignment(ImageButton::ALIGN_MIDDLE)
                    .SetImageHorizontalAlignment(ImageButton::ALIGN_CENTER)
                    .SetCallback(std::move(callback))
                    .SetBorder(CreateEmptyBorder(kEyePaddingWidth))
                    .Build();
  // Add the outset for the focus ring to match the behavior of the `Arrow`
  // element in `EditableCombobox`.
  views::FocusRing::Get(button.get())->SetOutsetFocusRingDisabled(false);
  SetImageFromVectorIconWithColor(
      button.get(),
      features::IsRoundedIconsEnabled() ? kVisibilityFilledIcon : kEyeOldIcon,
      {ui::kColorIcon, ui::kColorIconDisabled});
  SetToggledImageFromVectorIconWithColor(
      button.get(),
      features::IsRoundedIconsEnabled() ? kVisibilityOffFilledIcon
                                        : kEyeCrossedOldIcon,
      {ui::kColorIcon, ui::kColorIconDisabled});

  ConfigureComboboxButtonInkDrop(button.get());
  // We need this so the eye icon is not covered when the combo box view is
  // hovered
  button->SetPaintToLayer();
  button->layer()->SetFillsBoundsOpaquely(false);

  return button;
}

class PasswordMenuDecorationStrategy
    : public EditableCombobox::MenuDecorationStrategy {
 public:
  explicit PasswordMenuDecorationStrategy(
      const EditablePasswordCombobox* parent)
      : parent_(parent) {
    DCHECK(parent);
  }

  std::u16string DecorateItemText(std::u16string text) const override {
    return parent_->ArePasswordsRevealed()
               ? text
               : std::u16string(text.length(),
                                gfx::RenderText::kPasswordReplacementChar);
  }

 private:
  const raw_ptr<const EditablePasswordCombobox> parent_;
};

}  // namespace

EditablePasswordCombobox::EditablePasswordCombobox() = default;

EditablePasswordCombobox::EditablePasswordCombobox(
    std::unique_ptr<ui::ComboboxModel> combobox_model,
    int text_context,
    int text_style,
    bool display_arrow,
    Button::PressedCallback eye_callback)
    : EditableCombobox(std::move(combobox_model),
                       /*filter_on_edit=*/false,
                       /*show_on_empty=*/true,
                       text_context,
                       text_style,
                       display_arrow) {
  // By default, clicking on the eye reveals/hides passwords.
  if (!eye_callback) {
    eye_callback = base::BindRepeating(
        [](views::EditablePasswordCombobox* combobox_ptr) {
          combobox_ptr->RevealPasswords(!combobox_ptr->ArePasswordsRevealed());
        },
        base::Unretained(this));
  }

  eye_ = AddControlElement(CreateEye(std::move(eye_callback)));

  GetTextfield().SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD);
  SetMenuDecorationStrategy(
      std::make_unique<PasswordMenuDecorationStrategy>(this));
}

EditablePasswordCombobox::~EditablePasswordCombobox() = default;

void EditablePasswordCombobox::SetPasswordIconTooltips(
    const std::u16string& tooltip_text,
    const std::u16string& toggled_tooltip_text) {
  eye_->SetTooltipText(tooltip_text);
  eye_->SetToggledTooltipText(toggled_tooltip_text);
  // The eye is implemented as a `ToggleImageButton`. Screen readers typically
  // announce whether the toggle is selected, and as a result the accessible
  // name should not change when selected. The state is conveyed by the
  // "selected" or "unselected" status instead.
  eye_->SetToggledAccessibleName(tooltip_text);
}

void EditablePasswordCombobox::RevealPasswords(bool revealed) {
  if (revealed == are_passwords_revealed_) {
    return;
  }
  are_passwords_revealed_ = revealed;
  GetTextfield().SetTextInputType(revealed ? ui::TEXT_INPUT_TYPE_TEXT
                                           : ui::TEXT_INPUT_TYPE_PASSWORD);
  GetTextfield().SetTextInputFlags(GetTextfield().GetTextInputFlags() |
                                   ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD);
  eye_->SetToggled(revealed);
  UpdateMenu();
}

bool EditablePasswordCombobox::ArePasswordsRevealed() const {
  return are_passwords_revealed_;
}

BEGIN_METADATA(EditablePasswordCombobox)
END_METADATA

}  // namespace views
