// 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 "chrome/browser/device_notifications/device_status_icon_renderer.h"

#include "base/i18n/message_formatter.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/status_icons/status_tray.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/grit/generated_resources.h"
#include "extensions/buildflags/buildflags.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/paint_vector_icon.h"

#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

namespace {

constexpr int kMaxDeviceStatusTrayIcons = 40;
constexpr int IDC_DEVICE_SYSTEM_TRAY_ICON_LAST =
    IDC_DEVICE_SYSTEM_TRAY_ICON_FIRST + kMaxDeviceStatusTrayIcons - 1;

// Returns profile username.
std::u16string GetProfileUserName(Profile* profile) {
  ProfileAttributesEntry* profile_attributes =
      g_browser_process->profile_manager()
          ->GetProfileAttributesStorage()
          .GetProfileAttributesWithPath(profile->GetPath());
  if (profile_attributes) {
    return profile_attributes->GetName();
  }
  return base::UTF8ToUTF16(profile->GetProfileUserName());
}

// Returns a label that displays the number of active connections for the
// specified origin.
std::u16string GetOriginConnectionCountLabel(Profile* profile,
                                             const url::Origin& origin,
                                             int connection_count,
                                             const std::string& name) {
#if BUILDFLAG(ENABLE_EXTENSIONS)
  if (origin.scheme() == extensions::kExtensionScheme) {
    return base::i18n::MessageFormatter::FormatWithNumberedArgs(
        l10n_util::GetStringUTF16(IDS_DEVICE_CONNECTED_BY_EXTENSION),
        connection_count, base::UTF8ToUTF16(name));
  }
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
  NOTREACHED();
}

}  // namespace

DeviceStatusIconRenderer::DeviceStatusIconRenderer(
    DeviceSystemTrayIcon* device_system_tray_icon,
    const chrome::HelpSource help_source,
    const int about_device_message_id)
    : DeviceSystemTrayIconRenderer(device_system_tray_icon),
      help_source_(help_source),
      about_device_message_id_(about_device_message_id) {}

DeviceStatusIconRenderer::~DeviceStatusIconRenderer() {
  if (status_icon_) {
    auto* status_tray = g_browser_process->status_tray();
    DCHECK(status_tray);
    std::unique_ptr<StatusIcon> removed_icon =
        status_tray->RemoveStatusIcon(status_icon_);
    status_icon_ = nullptr;
    removed_icon.reset();
  }
}

std::u16string DeviceStatusIconRenderer::GetAboutDeviceLabel() {
  return l10n_util::GetStringUTF16(about_device_message_id_);
}

void DeviceStatusIconRenderer::AddProfile(Profile* profile) {
  if (device_system_tray_icon_->profiles().size() == 1) {
    auto* profile_manager = g_browser_process->profile_manager();
    CHECK(profile_manager);
    profile_attributes_storage_observation_.Observe(
        &profile_manager->GetProfileAttributesStorage());
  }
  RefreshIcon();
}

void DeviceStatusIconRenderer::RemoveProfile(Profile* profile) {
  RefreshIcon();
  if (device_system_tray_icon_->profiles().empty()) {
    profile_attributes_storage_observation_.Reset();
  }
}

void DeviceStatusIconRenderer::NotifyConnectionUpdated(Profile* profile) {
  RefreshIcon();
}

void DeviceStatusIconRenderer::ExecuteCommand(int command_id, int event_flags) {
  CHECK_GE(command_id, IDC_DEVICE_SYSTEM_TRAY_ICON_FIRST);
  CHECK_LE(command_id, IDC_DEVICE_SYSTEM_TRAY_ICON_LAST);
  size_t command_idx = command_id - IDC_DEVICE_SYSTEM_TRAY_ICON_FIRST;
  if (command_idx < command_id_callbacks_.size()) {
    command_id_callbacks_[command_idx].Run();
  }
}

void DeviceStatusIconRenderer::ShowHelpCenterUrl() {
  auto* profile = ProfileManager::GetLastUsedProfileAllowedByPolicy();
  CHECK(profile);
  chrome::ShowHelpForProfile(profile, help_source_);
}

void DeviceStatusIconRenderer::ShowContentSettings(
    base::WeakPtr<Profile> profile) {
  auto* connection_tracker =
      device_system_tray_icon_->GetConnectionTracker(profile);
  if (connection_tracker) {
    connection_tracker->ShowContentSettingsExceptions();
  }
}

void DeviceStatusIconRenderer::ShowSiteSettings(base::WeakPtr<Profile> profile,
                                                const url::Origin& origin) {
  auto* connection_tracker =
      device_system_tray_icon_->GetConnectionTracker(profile);
  if (connection_tracker) {
    connection_tracker->ShowSiteSettings(origin);
  }
}

void DeviceStatusIconRenderer::RefreshIcon() {
  command_id_callbacks_.clear();
  auto* status_tray = g_browser_process->status_tray();
  DCHECK(status_tray);
  if (device_system_tray_icon_->profiles().empty()) {
    if (status_icon_) {
      std::unique_ptr<StatusIcon> removed_icon =
          status_tray->RemoveStatusIcon(status_icon_);
      status_icon_ = nullptr;
      removed_icon.reset();
    }
    return;
  }

  // This tries to construct this:
  // ------------------------------------------------
  // |Google Chrome is accessing Device device(s)   |
  // |About Device device                           |
  // |---------------Separator----------------------|
  // |Profile1 section (see below profile for loop) |
  // |...                                           |
  // |---------------Separator----------------------|
  // |ProfileN section                              |
  auto menu = std::make_unique<StatusIconMenuModel>(this);
  size_t total_connection_count = 0;
  size_t total_origin_count = 0;
  // Title will be updated after looping through profiles below.
  menu->AddTitle(u"");
  AddItem(menu.get(), GetAboutDeviceLabel(),
          base::BindRepeating(&DeviceStatusIconRenderer::ShowHelpCenterUrl,
                              weak_factory_.GetWeakPtr()));
  for (const auto& [profile, staging] : device_system_tray_icon_->profiles()) {
    // Each profile section looks like this:
    // |---------------Separator---------------|
    // |profile name                           |
    // |Device settings                        |
    // |origin 1 is connecting to x device(s)  |
    // |...                                    |
    // |origin n is connecting to y device(s)  |
    menu->AddSeparator(ui::NORMAL_SEPARATOR);
    menu->AddTitle(GetProfileUserName(profile));
    AddItem(
        menu.get(), device_system_tray_icon_->GetContentSettingsLabel(),
        base::BindRepeating(&DeviceStatusIconRenderer::ShowContentSettings,
                            weak_factory_.GetWeakPtr(), profile->GetWeakPtr()));

    auto* connection_tracker =
        device_system_tray_icon_->GetConnectionTracker(profile->GetWeakPtr());
    CHECK(connection_tracker);
    total_origin_count += connection_tracker->origins().size();
    for (const auto& [origin, state] : connection_tracker->origins()) {
      AddItem(menu.get(),
              GetOriginConnectionCountLabel(profile, origin, state.count,
                                            state.name),
              base::BindRepeating(&DeviceStatusIconRenderer::ShowSiteSettings,
                                  weak_factory_.GetWeakPtr(),
                                  profile->GetWeakPtr(), origin));
      // Only consider the count that will be shown to the system tray icon, so
      // that connection count on title and extension buttons can match.
      total_connection_count += state.count;
    }
  }
  auto title_label = device_system_tray_icon_->GetTitleLabel(
      total_origin_count, total_connection_count);
  menu->SetLabel(0, title_label);

  if (!status_icon_) {
    status_icon_ = status_tray->CreateStatusIcon(
        StatusTray::OTHER_ICON,
        gfx::CreateVectorIcon(device_system_tray_icon_->GetIcon(),
                              gfx::kGoogleGrey300),
        title_label);
  } else {
    status_icon_->SetToolTip(title_label);
  }
  status_icon_->SetContextMenu(std::move(menu));
}

void DeviceStatusIconRenderer::AddItem(StatusIconMenuModel* menu,
                                       std::u16string label,
                                       base::RepeatingClosure callback) {
  size_t index =
      command_id_callbacks_.size() + IDC_DEVICE_SYSTEM_TRAY_ICON_FIRST;
  if (index > IDC_DEVICE_SYSTEM_TRAY_ICON_LAST) {
    // This case should be fairly rare, but if we have more items than
    // pre-defined command ids, we don't put those in the status icon menu.
    return;
  }
  menu->AddItem(index, label);
  command_id_callbacks_.push_back(std::move(callback));
}

void DeviceStatusIconRenderer::OnProfileNameChanged(
    const base::FilePath& profile_path,
    const std::u16string& old_profile_name) {
  auto* profile =
      g_browser_process->profile_manager()->GetProfileByPath(profile_path);
  if (device_system_tray_icon_->profiles().contains(profile)) {
    NotifyConnectionUpdated(profile);
  }
}
