// 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 "ash/system/unified/quick_settings_header.h"

#include <memory>

#include "ash/ash_element_identifiers.h"
#include "ash/constants/quick_settings_catalogs.h"
#include "ash/login/ui/lock_screen.h"
#include "ash/public/cpp/ash_view_ids.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/public/cpp/system_tray_client.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/ash_color_provider.h"
#include "ash/style/typography.h"
#include "ash/system/channel_indicator/channel_indicator_quick_settings_view.h"
#include "ash/system/channel_indicator/channel_indicator_utils.h"
#include "ash/system/enterprise/enterprise_domain_observer.h"
#include "ash/system/model/enterprise_domain_model.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/model/update_model.h"
#include "ash/system/supervised/supervised_icon_string.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/unified/quick_settings_metrics_util.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "ash/system/update/eol_notice_quick_settings_view.h"
#include "ash/system/update/extended_updates_notice_quick_settings_view.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/session_manager/session_manager_types.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/chromeos/devicetype_utils.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"

namespace ash {
namespace {

// The bottom padding is 0 so this view is flush with the feature tiles.
constexpr auto kHeaderPadding = gfx::Insets::TLBR(16, 16, 0, 16);

// Horizontal space between header buttons.
constexpr int kButtonSpacing = 8;

// Header button size when the button is narrow (e.g. two column layout).
constexpr gfx::Size kNarrowButtonSize(180, 32);

// Header button size when the button is wide (e.g. one column layout).
constexpr gfx::Size kWideButtonSize(408, 32);

constexpr int kManagedStateCornerRadius = 16;
constexpr float kManagedStateStrokeWidth = 1.0f;
constexpr auto kManagedStateBorderInsets = gfx::Insets::TLBR(0, 12, 0, 12);
constexpr gfx::Size kManagedStateImageSize(20, 20);

// Shows account settings in OS settings, which includes a link to install or
// open the Family Link app to see supervision settings.
void ShowAccountSettings() {
  quick_settings_metrics_util::RecordQsButtonActivated(
      QsButtonCatalogName::kSupervisedButton);
  Shell::Get()->system_tray_model()->client()->ShowAccountSettings();
}

}  // namespace

class QuickSettingsHeader::ManagedStateView : public views::Button {
  METADATA_HEADER(ManagedStateView, views::Button)

 public:
  ManagedStateView(base::OnceClosure callback,
                   int label_id,
                   const gfx::VectorIcon& icon)
      : views::Button(std::move(callback)), icon_(icon) {
    auto* layout_manager = SetLayoutManager(std::make_unique<views::BoxLayout>(
        views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
        kUnifiedSystemInfoSpacing));

    // Image goes first.
    image_ = AddChildView(std::make_unique<views::ImageView>());
    label_ = AddChildView(std::make_unique<views::Label>());

    // Inset the icon and label so they aren't too close to the rounded corners.
    layout_manager->set_inside_border_insets(kManagedStateBorderInsets);
    layout_manager->set_main_axis_alignment(
        views::BoxLayout::MainAxisAlignment::kCenter);
    label_->SetAutoColorReadabilityEnabled(false);
    label_->SetSubpixelRenderingEnabled(false);
    label_->SetText(l10n_util::GetStringUTF16(label_id));

    image_->SetPreferredSize(kManagedStateImageSize);
    label_->SetEnabledColor(cros_tokens::kCrosSysOnSurfaceVariant);
    TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosBody2,
                                          *label_);
    SetInstallFocusRingOnFocus(true);
    views::FocusRing::Get(this)->SetColorId(cros_tokens::kCrosSysFocusRing);
    views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
    views::InstallRoundRectHighlightPathGenerator(this, gfx::Insets(),
                                                  kManagedStateCornerRadius);
  }

  ManagedStateView(const ManagedStateView&) = delete;

  ManagedStateView& operator=(const ManagedStateView&) = delete;

  ~ManagedStateView() override = default;

  views::Label* label() { return label_; }

 private:
  // views::Button:
  views::View* GetTooltipHandlerForPoint(const gfx::Point& point) override {
    // Tooltip events should be handled by this top-level view.
    return HitTestPoint(point) ? this : nullptr;
  }

  // views::Button:
  // TODO(b/311234537): consider to remove this override and set color ids.
  void OnThemeChanged() override {
    views::Button::OnThemeChanged();
    const std::pair<SkColor, float> base_color_and_opacity =
        AshColorProvider::Get()->GetInkDropBaseColorAndOpacity();
    views::InkDrop::Get(this)->SetBaseColor(base_color_and_opacity.first);
    image_->SetImage(ui::ImageModel::FromVectorIcon(
        *icon_,
        GetColorProvider()->GetColor(cros_tokens::kCrosSysOnSurfaceVariant)));
  }

  // views::Button:
  void PaintButtonContents(gfx::Canvas* canvas) override {
    // Draw a button outline similar to ChannelIndicatorQuickSettingsView's
    // VersionButton outline.
    cc::PaintFlags flags;
    flags.setColor(
        GetColorProvider()->GetColor(cros_tokens::kCrosSysSeparator));
    flags.setStyle(cc::PaintFlags::kStroke_Style);
    flags.setStrokeWidth(kManagedStateStrokeWidth);
    flags.setAntiAlias(true);
    const float half_stroke_width = kManagedStateStrokeWidth / 2.0f;
    gfx::RectF bounds(GetLocalBounds());
    bounds.Inset(half_stroke_width);
    canvas->DrawRoundRect(bounds, kManagedStateCornerRadius, flags);
  }

  // Owned by views hierarchy.
  raw_ptr<views::Label> label_ = nullptr;
  raw_ptr<views::ImageView> image_ = nullptr;

  const raw_ref<const gfx::VectorIcon> icon_;
};

BEGIN_METADATA(QuickSettingsHeader, ManagedStateView)
END_METADATA

class QuickSettingsHeader::EnterpriseManagedView
    : public ManagedStateView,
      public EnterpriseDomainObserver,
      public SessionObserver {
  METADATA_HEADER(EnterpriseManagedView, ManagedStateView)

 public:
  explicit EnterpriseManagedView(UnifiedSystemTrayController* controller)
      : ManagedStateView(
            base::BindRepeating(
                &QuickSettingsHeader::ShowEnterpriseInfo,
                base::Unretained(controller),
                base::FeatureList::IsEnabled(
                    ash::features::kImprovedManagementDisclosure),
                Shell::Get()->session_controller()->IsUserSessionBlocked(),
                !Shell::Get()
                     ->system_tray_model()
                     ->enterprise_domain()
                     ->enterprise_domain_manager()
                     .empty()),
            IDS_ASH_ENTERPRISE_DEVICE_MANAGED_SHORT,
            kQuickSettingsManagedIcon) {
    DCHECK(Shell::Get());
    SetID(VIEW_ID_QS_MANAGED_BUTTON);
    SetProperty(views::kElementIdentifierKey, kEnterpriseManagedView);
    Shell::Get()->system_tray_model()->enterprise_domain()->AddObserver(this);
    Shell::Get()->session_controller()->AddObserver(this);
    Update();
  }

  EnterpriseManagedView(const EnterpriseManagedView&) = delete;
  EnterpriseManagedView& operator=(const EnterpriseManagedView&) = delete;

  ~EnterpriseManagedView() override {
    Shell::Get()->system_tray_model()->enterprise_domain()->RemoveObserver(
        this);
    Shell::Get()->session_controller()->RemoveObserver(this);
  }

  // Adjusts the layout for a narrower appearance, using a shorter label for
  // the button.
  void SetNarrowLayout(bool narrow) {
    narrow_layout_ = narrow;
    Update();
  }

 private:
  // EnterpriseDomainObserver:
  void OnDeviceEnterpriseInfoChanged() override { Update(); }
  void OnEnterpriseAccountDomainChanged() override { Update(); }

  // SessionObserver:
  void OnLoginStatusChanged(LoginStatus status) override { Update(); }

  // Updates the view visibility and displayed string.
  void Update() {
    EnterpriseDomainModel* model =
        Shell::Get()->system_tray_model()->enterprise_domain();
    SessionControllerImpl* session_controller =
        Shell::Get()->session_controller();
    const std::string enterprise_domain_manager =
        model->enterprise_domain_manager();
    const std::string account_domain_manager = model->account_domain_manager();

    const bool visible = session_controller->ShouldDisplayManagedUI() ||
                         !enterprise_domain_manager.empty() ||
                         !account_domain_manager.empty();
    SetVisible(visible);

    if (!visible) {
      return;
    }

    // Display device and user management based on the enterprise enrollment
    // state.
    std::u16string managed_string;
    if (enterprise_domain_manager.empty() && account_domain_manager.empty()) {
      managed_string = l10n_util::GetStringFUTF16(
          IDS_ASH_ENTERPRISE_DEVICE_MANAGED, ui::GetChromeOSDeviceName());
    } else if (!enterprise_domain_manager.empty() &&
               !account_domain_manager.empty() &&
               enterprise_domain_manager != account_domain_manager) {
      managed_string = l10n_util::GetStringFUTF16(
          IDS_ASH_SHORT_MANAGED_BY_MULTIPLE,
          base::UTF8ToUTF16(enterprise_domain_manager),
          base::UTF8ToUTF16(account_domain_manager));
    } else {
      const std::u16string display_domain_manager =
          enterprise_domain_manager.empty()
              ? base::UTF8ToUTF16(account_domain_manager)
              : base::UTF8ToUTF16(enterprise_domain_manager);
      managed_string = l10n_util::GetStringFUTF16(IDS_ASH_SHORT_MANAGED_BY,
                                                  display_domain_manager);
      // Narrow layout uses the string "Managed" and wide layout uses the full
      // string "Managed by example.com".
      label()->SetText(narrow_layout_
                           ? l10n_util::GetStringUTF16(
                                 IDS_ASH_ENTERPRISE_DEVICE_MANAGED_SHORT)
                           : managed_string);
    }
    SetTooltipText(managed_string);
  }

  // See SetNarrowLayout().
  bool narrow_layout_ = false;
};

BEGIN_METADATA(QuickSettingsHeader, EnterpriseManagedView)
END_METADATA

QuickSettingsHeader::QuickSettingsHeader(
    UnifiedSystemTrayController* controller) {
  SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kHorizontal, kHeaderPadding,
      kButtonSpacing));

  enterprise_managed_view_ =
      AddChildView(std::make_unique<EnterpriseManagedView>(controller));

  // A view that shows whether the user is supervised or a child.
  supervised_view_ = AddChildView(std::make_unique<ManagedStateView>(
      base::BindRepeating(&ShowAccountSettings),
      IDS_ASH_STATUS_TRAY_SUPERVISED_LABEL, GetSupervisedUserIcon()));
  supervised_view_->SetID(VIEW_ID_QS_SUPERVISED_BUTTON);
  const bool visible =
      Shell::Get()->system_tray_model()->IsInUserChildSession();
  supervised_view_->SetVisible(visible);
  if (visible) {
    supervised_view_->SetTooltipText(GetSupervisedUserMessage());
  }

  const bool is_active_state =
      Shell::Get()->session_controller()->GetSessionState() ==
      session_manager::SessionState::ACTIVE;
  if (is_active_state) {
    if (Shell::Get()->system_tray_model()->update_model()->show_eol_notice()) {
      eol_notice_ =
          AddChildView(std::make_unique<EolNoticeQuickSettingsView>());
    } else if (Shell::Get()
                   ->system_tray_model()
                   ->update_model()
                   ->show_extended_updates_notice()) {
      extended_updates_notice_ = AddChildView(
          std::make_unique<ExtendedUpdatesNoticeQuickSettingsView>());
    }
  }

  // If the release track is not "stable" then show the channel indicator UI.
  auto channel = Shell::Get()->shell_delegate()->GetChannel();
  if (channel_indicator_utils::IsDisplayableChannel(channel) && !eol_notice_ &&
      !extended_updates_notice_) {
    channel_view_ =
        AddChildView(std::make_unique<ChannelIndicatorQuickSettingsView>(
            channel, is_active_state && Shell::Get()
                                            ->system_tray_model()
                                            ->client()
                                            ->IsUserFeedbackEnabled()));
  }

  UpdateVisibilityAndLayout();
}

QuickSettingsHeader::~QuickSettingsHeader() = default;

void QuickSettingsHeader::ChildVisibilityChanged(views::View* child) {
  UpdateVisibilityAndLayout();
}

views::View* QuickSettingsHeader::GetManagedButtonForTest() {
  return enterprise_managed_view_;
}

views::View* QuickSettingsHeader::GetSupervisedButtonForTest() {
  return supervised_view_;
}

views::Label* QuickSettingsHeader::GetManagedButtonLabelForTest() {
  return enterprise_managed_view_->label();
}

views::Label* QuickSettingsHeader::GetSupervisedButtonLabelForTest() {
  return supervised_view_->label();
}

views::View* QuickSettingsHeader::GetExtendedUpdatesViewForTest() {
  return extended_updates_notice_;
}

void QuickSettingsHeader::UpdateVisibilityAndLayout() {
  // The managed view and the supervised view are never shown together.
  DCHECK(!enterprise_managed_view_->GetVisible() ||
         !supervised_view_->GetVisible());

  // The notice views are never shown together.
  DCHECK(!!channel_view_ + !!eol_notice_ + !!extended_updates_notice_ <= 1);

  // Make `this` view visible if a child is visible.
  bool managed_view_visible =
      enterprise_managed_view_->GetVisible() || supervised_view_->GetVisible();
  bool notice_view_visible =
      channel_view_ || eol_notice_ || extended_updates_notice_;

  SetVisible(managed_view_visible || notice_view_visible);

  // Update button sizes for one column vs. two columns.
  bool two_columns = managed_view_visible && notice_view_visible;
  gfx::Size size = two_columns ? kNarrowButtonSize : kWideButtonSize;
  enterprise_managed_view_->SetPreferredSize(size);
  supervised_view_->SetPreferredSize(size);
  if (channel_view_) {
    channel_view_->SetPreferredSize(size);
  }
  if (eol_notice_) {
    eol_notice_->SetPreferredSize(size);
  }
  if (extended_updates_notice_) {
    extended_updates_notice_->SetPreferredSize(size);
  }

  // Use custom narrow layouts when two columns are showing.
  enterprise_managed_view_->SetNarrowLayout(two_columns);
  if (channel_view_) {
    channel_view_->SetNarrowLayout(two_columns);
  }
  if (eol_notice_) {
    eol_notice_->SetNarrowLayout(two_columns);
  }
  if (extended_updates_notice_) {
    extended_updates_notice_->SetNarrowLayout(two_columns);
  }
}

// static
void QuickSettingsHeader::ShowEnterpriseInfo(
    UnifiedSystemTrayController* controller,
    bool show_management_disclosure_dialog,
    bool is_user_session_blocked,
    bool has_enterprise_domain_manager) {
  quick_settings_metrics_util::RecordQsButtonActivated(
      QsButtonCatalogName::kManagedButton);
  // Show the new disclosure when on the login/lock screen and feature is
  // enabled.
  if (show_management_disclosure_dialog && is_user_session_blocked &&
      has_enterprise_domain_manager) {
    LockScreen::Get()->ShowManagementDisclosureDialog();
  } else {
    controller->HandleEnterpriseInfoAction();
  }
}

BEGIN_METADATA(QuickSettingsHeader)
END_METADATA

}  // namespace ash
