// 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 "ash/login/ui/disabled_auth_message_view.h"

#include <memory>
#include <string>
#include <string_view>

#include "ash/public/cpp/login_types.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "base/i18n/icubridge/date_time_formatter.h"
#include "base/i18n/icubridge/icu_bridge.h"
#include "base/i18n/time_formatting.h"
#include "base/memory/raw_ptr.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/time_format.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_manager.h"
#include "ui/views/view.h"

namespace ash {
namespace {

using ::base::i18n::IcuBridge;

constexpr int kVerticalBorderDp = 16;
constexpr int kHorizontalBorderDp = 16;
constexpr int kChildrenSpacingDp = 4;
constexpr int kTimeWidthDp = 204;
constexpr int kMultiprofileWidthDp = 304;
constexpr int kIconSizeDp = 24;
constexpr int kTitleFontSizeDeltaDp = 3;
constexpr int kContentsFontSizeDeltaDp = -1;
constexpr int kRoundedCornerRadiusDp = 8;

// The content needed to render the disabled auth message view.
struct LockScreenMessage {
  std::u16string title;
  std::u16string content;
  ui::ImageModel icon;
};

// Returns the message used when the device was locked due to a time window
// limit.
LockScreenMessage GetWindowLimitMessage(const base::Time& unlock_time,
                                        bool use_24hour_clock) {
  LockScreenMessage message;
  message.title = l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TIME_FOR_BED_MESSAGE);

  base::Time local_midnight = base::Time::Now().LocalMidnight();

  std::u16string time_to_display;
  if (use_24hour_clock) {
    time_to_display = base::TimeFormatTimeOfDayWithHourClockType(
        unlock_time, base::k24HourClock, base::kDropAmPm);
  } else {
    time_to_display = base::TimeFormatTimeOfDayWithHourClockType(
        unlock_time, base::k12HourClock, base::kKeepAmPm);
  }

  if (unlock_time < local_midnight + base::Days(1)) {
    // Unlock time is today.
    message.content = l10n_util::GetStringFUTF16(
        IDS_ASH_LOGIN_COME_BACK_MESSAGE, time_to_display);
  } else if (unlock_time < local_midnight + base::Days(2)) {
    // Unlock time is tomorrow.
    message.content = l10n_util::GetStringFUTF16(
        IDS_ASH_LOGIN_COME_BACK_TOMORROW_MESSAGE, time_to_display);
  } else {
    message.content = l10n_util::GetStringFUTF16(
        IDS_ASH_LOGIN_COME_BACK_DAY_OF_WEEK_MESSAGE,
        IcuBridge::GetInstance().date_time_formatter().Format(
            unlock_time, base::i18n::datetime_options::E::Long()),
        time_to_display);
  }
  message.icon = ui::ImageModel::FromVectorIcon(
      kLockScreenTimeLimitMoonIcon, kColorAshIconColorPrimary, kIconSizeDp);
  return message;
}

// Returns the message used when the device was locked due to a time usage
// limit.
LockScreenMessage GetUsageLimitMessage(const base::TimeDelta& used_time) {
  LockScreenMessage message;

  // 1 minute is used instead of 0, because the device is used for a few
  // milliseconds before locking.
  if (used_time < base::Minutes(1)) {
    // The device was locked all day.
    message.title = l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TAKE_BREAK_MESSAGE);
    message.content =
        l10n_util::GetStringUTF16(IDS_ASH_LOGIN_LOCKED_ALL_DAY_MESSAGE);
  } else {
    // The usage limit is over.
    message.title = l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TIME_IS_UP_MESSAGE);

    const std::u16string used_time_string = ui::TimeFormat::Detailed(
        ui::TimeFormat::Format::FORMAT_DURATION,
        ui::TimeFormat::Length::LENGTH_LONG, /*cutoff=*/3, used_time);
    message.content = l10n_util::GetStringFUTF16(
        IDS_ASH_LOGIN_SCREEN_TIME_USED_MESSAGE, used_time_string);
  }
  message.icon = ui::ImageModel::FromVectorIcon(
      kLockScreenTimeLimitTimerIcon, kColorAshIconColorPrimary, kIconSizeDp);
  return message;
}

// Returns the message used when the device was locked due to a time limit
// override.
LockScreenMessage GetOverrideMessage() {
  LockScreenMessage message;
  message.title =
      l10n_util::GetStringUTF16(IDS_ASH_LOGIN_TIME_FOR_A_BREAK_MESSAGE);
  message.content =
      l10n_util::GetStringUTF16(IDS_ASH_LOGIN_MANUAL_LOCK_MESSAGE);
  message.icon = ui::ImageModel::FromVectorIcon(
      kLockScreenTimeLimitLockIcon, kColorAshIconColorPrimary, kIconSizeDp);
  return message;
}

LockScreenMessage GetLockScreenMessage(AuthDisabledReason lock_reason,
                                       const base::Time& unlock_time,
                                       const base::TimeDelta& used_time,
                                       bool use_24hour_clock) {
  switch (lock_reason) {
    case AuthDisabledReason::kTimeWindowLimit:
      return GetWindowLimitMessage(unlock_time, use_24hour_clock);
    case AuthDisabledReason::kTimeUsageLimit:
      return GetUsageLimitMessage(used_time);
    case AuthDisabledReason::kTimeLimitOverride:
      return GetOverrideMessage();
    default:
      NOTREACHED();
  }
}

}  // namespace

DisabledAuthMessageView::TestApi::TestApi(DisabledAuthMessageView* view)
    : view_(view) {}

DisabledAuthMessageView::TestApi::~TestApi() = default;

std::u16string_view
DisabledAuthMessageView::TestApi::GetDisabledAuthMessageContent() const {
  return view_->message_contents_->GetText();
}

void DisabledAuthMessageView::TestApi::SetDisabledAuthMessageTitleForTesting(
    std::u16string message_title) {
  view_->SetAuthDisabledMessage(message_title, u"");
}

DisabledAuthMessageView::DisabledAuthMessageView() {
  SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kVertical,
      gfx::Insets::VH(kVerticalBorderDp, kHorizontalBorderDp),
      kChildrenSpacingDp));
  preferred_width_ = kTimeWidthDp;
  SetPaintToLayer();
  layer()->SetFillsBoundsOpaquely(false);
  SetFocusBehavior(FocusBehavior::ALWAYS);
  SetBackground(views::CreateRoundedRectBackground(kColorAshShieldAndBaseOpaque,
                                                   kRoundedCornerRadiusDp));

  // The icon size has to be defined later if the image will be visible.
  message_icon_ = AddChildView(std::make_unique<views::ImageView>());
  message_icon_->SetImageSize(gfx::Size(kIconSizeDp, kIconSizeDp));

  auto decorate_label = [](views::Label* label) {
    label->SetSubpixelRenderingEnabled(false);
    label->SetAutoColorReadabilityEnabled(false);
    label->SetFocusBehavior(FocusBehavior::ALWAYS);
  };
  message_title_ = AddChildView(std::make_unique<views::Label>(
      std::u16string(), views::style::CONTEXT_LABEL,
      views::style::STYLE_PRIMARY));
  message_title_->SetFontList(gfx::FontList().Derive(
      kTitleFontSizeDeltaDp, gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM));
  decorate_label(message_title_);
  message_title_->SetEnabledColor(kColorAshTextColorPrimary);

  message_contents_ = AddChildView(std::make_unique<views::Label>(
      std::u16string(), views::style::CONTEXT_LABEL,
      views::style::STYLE_PRIMARY));
  message_contents_->SetFontList(gfx::FontList().Derive(
      kContentsFontSizeDeltaDp, gfx::Font::NORMAL, gfx::Font::Weight::NORMAL));
  decorate_label(message_contents_);
  message_contents_->SetMultiLine(true);
  message_contents_->SetEnabledColor(kColorAshTextColorPrimary);

  GetViewAccessibility().SetRole(ax::mojom::Role::kPane);
  UpdateAccessibleName();
  message_title_changed_subscription_ = message_title_->AddTextChangedCallback(
      base::BindRepeating(&DisabledAuthMessageView::OnMessageTitleChanged,
                          weak_ptr_factory_.GetWeakPtr()));
}

DisabledAuthMessageView::~DisabledAuthMessageView() = default;

void DisabledAuthMessageView::SetAuthDisabledMessage(
    const AuthDisabledData& auth_disabled_data,
    bool use_24hour_clock) {
  LockScreenMessage message = GetLockScreenMessage(
      auth_disabled_data.reason, auth_disabled_data.auth_reenabled_time,
      auth_disabled_data.device_used_time, use_24hour_clock);
  preferred_width_ = kTimeWidthDp;
  message_icon_->SetVisible(true);
  CHECK(!message.icon.IsEmpty());
  message_icon_->SetImage(message.icon);
  message_title_->SetText(message.title);
  message_title_->SetEnabledColor(kColorAshTextColorPrimary);
  message_contents_->SetText(message.content);
  message_contents_->SetEnabledColor(kColorAshTextColorPrimary);
}

void DisabledAuthMessageView::SetAuthDisabledMessage(
    const std::u16string& title,
    const std::u16string& content) {
  preferred_width_ = kMultiprofileWidthDp;
  message_icon_->SetVisible(false);
  message_title_->SetText(title);
  message_contents_->SetText(content);
}

void DisabledAuthMessageView::RequestFocus() {
  message_title_->RequestFocus();
}

gfx::Size DisabledAuthMessageView::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  int height =
      GetLayoutManager()->GetPreferredHeightForWidth(this, preferred_width_);
  return gfx::Size(preferred_width_, height);
}

void DisabledAuthMessageView::OnMessageTitleChanged() {
  UpdateAccessibleName();
}

void DisabledAuthMessageView::UpdateAccessibleName() {
  // Any view which claims to be focusable is expected to have an accessible
  // name so that screen readers know what to present to the user when it gains
  // focus. In the case of this particular view, `RequestFocus` gives focus to
  // the `message_title_` label. As a result, this view is not end-user
  // focusable. However, its official focusability will cause the accessibility
  // paint checks to fail. If the `message_title_` has text, set the accessible
  // name to that text; otherwise set the name explicitly empty to prevent
  // the paint check from failing during tests.
  if (message_title_->GetText().empty()) {
    GetViewAccessibility().SetName(
        std::string(), ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
  } else {
    GetViewAccessibility().SetName(std::u16string(message_title_->GetText()));
  }
}

}  // namespace ash
