// Copyright 2017 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/notifications/notification_platform_bridge_chromeos.h"

#include <memory>

#include "base/callback_list.h"
#include "base/functional/callback_helpers.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/chrome_ash_message_center_client.h"
#include "chrome/browser/notifications/notification_display_service_factory.h"
#include "chrome/browser/notifications/notification_display_service_impl.h"
#include "chrome/browser/notifications/profile_notification.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/notifications/notification_operation.h"
#include "components/app_icon_loader/app_icon_loader.h"
#include "ui/gfx/image/image.h"

// static
std::unique_ptr<NotificationPlatformBridge>
NotificationPlatformBridge::Create() {
  return std::make_unique<NotificationPlatformBridgeChromeOs>();
}

// static
bool NotificationPlatformBridge::CanHandleType(
    NotificationHandler::Type notification_type) {
  return true;
}

NotificationPlatformBridgeChromeOs::NotificationPlatformBridgeChromeOs()
    : impl_(std::make_unique<ChromeAshMessageCenterClient>(this)) {}

NotificationPlatformBridgeChromeOs::~NotificationPlatformBridgeChromeOs() =
    default;

void NotificationPlatformBridgeChromeOs::Display(
    NotificationHandler::Type notification_type,
    Profile* profile,
    const message_center::Notification& notification,
    std::unique_ptr<NotificationCommon::Metadata> metadata) {
  auto active_notification = std::make_unique<ProfileNotification>(
      profile, notification, notification_type);
  impl_->Display(active_notification->type(), profile,
                 active_notification->notification(), std::move(metadata));

  std::string profile_notification_id =
      active_notification->notification().id();
  active_notifications_.erase(profile_notification_id);
  active_notifications_.emplace(profile_notification_id,
                                std::move(active_notification));
}

void NotificationPlatformBridgeChromeOs::Close(
    Profile* profile,
    const std::string& notification_id) {
  const std::string profile_notification_id =
      ProfileNotification::GetProfileNotificationId(
          notification_id, ProfileNotification::GetProfileID(profile));
  impl_->Close(profile, profile_notification_id);
}

void NotificationPlatformBridgeChromeOs::GetDisplayed(
    Profile* profile,
    GetDisplayedNotificationsCallback callback) const {
  impl_->GetDisplayed(
      profile,
      base::BindOnce(&NotificationPlatformBridgeChromeOs::OnGetDisplayed,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void NotificationPlatformBridgeChromeOs::GetDisplayedForOrigin(
    Profile* profile,
    const GURL& origin,
    GetDisplayedNotificationsCallback callback) const {
  impl_->GetDisplayedForOrigin(
      profile, origin,
      base::BindOnce(&NotificationPlatformBridgeChromeOs::OnGetDisplayed,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void NotificationPlatformBridgeChromeOs::SetReadyCallback(
    NotificationBridgeReadyCallback callback) {
  impl_->SetReadyCallback(std::move(callback));
}

void NotificationPlatformBridgeChromeOs::DisplayServiceShutDown(
    Profile* profile) {
  // Notify delegates/handlers of the service shutdown and remove the
  // notifications associated with the profile whose service is being destroyed.
  // Otherwise Ash might asynchronously notify the bridge of operations on a
  // notification associated with a profile that has already been destroyed).
  std::list<std::string> ids_to_close;
  for (const auto& iter : active_notifications_) {
    if (iter.second->profile() == profile)
      ids_to_close.push_back(iter.second->notification().id());
  }

  for (auto id : ids_to_close)
    HandleNotificationClosed(id, false);

  impl_->DisplayServiceShutDown(profile);
}

void NotificationPlatformBridgeChromeOs::HandleNotificationClosed(
    const std::string& id,
    bool by_user) {
  auto iter = active_notifications_.find(id);
  if (iter == active_notifications_.end())
    return;
  ProfileNotification* notification = iter->second.get();

  if (notification->type() == NotificationHandler::Type::TRANSIENT) {
    notification->notification().delegate()->Close(by_user);
  } else {
    NotificationDisplayServiceImpl::GetForProfile(notification->profile())
        ->ProcessNotificationOperation(
            NotificationOperation::kClose, notification->type(),
            notification->notification().origin_url(),
            notification->original_id(), std::nullopt, std::nullopt, by_user,
            std::nullopt, base::DoNothing());
  }
  active_notifications_.erase(iter);
}

void NotificationPlatformBridgeChromeOs::HandleNotificationClicked(
    const std::string& id) {
  ProfileNotification* notification = GetProfileNotification(id);
  if (!notification)
    return;

  if (notification->type() == NotificationHandler::Type::TRANSIENT) {
    notification->notification().delegate()->Click(std::nullopt, std::nullopt);
  } else {
    NotificationDisplayServiceImpl::GetForProfile(notification->profile())
        ->ProcessNotificationOperation(
            NotificationOperation::kClick, notification->type(),
            notification->notification().origin_url(),
            notification->original_id(), std::nullopt, std::nullopt,
            std::nullopt, std::nullopt, base::DoNothing());
  }
}

void NotificationPlatformBridgeChromeOs::HandleNotificationButtonClicked(
    const std::string& id,
    int button_index,
    const std::optional<std::u16string>& reply) {
  ProfileNotification* notification = GetProfileNotification(id);
  if (!notification)
    return;

  if (notification->type() == NotificationHandler::Type::TRANSIENT) {
    notification->notification().delegate()->Click(button_index, reply);
  } else {
    NotificationDisplayServiceImpl::GetForProfile(notification->profile())
        ->ProcessNotificationOperation(
            NotificationOperation::kClick, notification->type(),
            notification->notification().origin_url(),
            notification->original_id(), button_index, reply, std::nullopt,
            std::nullopt, base::DoNothing());
  }
}

void NotificationPlatformBridgeChromeOs::
    HandleNotificationSettingsButtonClicked(const std::string& id) {
  ProfileNotification* notification = GetProfileNotification(id);
  if (!notification)
    return;

  if (notification->type() == NotificationHandler::Type::TRANSIENT) {
    notification->notification().delegate()->SettingsClick();
  } else {
    NotificationDisplayServiceImpl::GetForProfile(notification->profile())
        ->ProcessNotificationOperation(
            NotificationOperation::kSettings, notification->type(),
            notification->notification().origin_url(),
            notification->original_id(), std::nullopt, std::nullopt,
            std::nullopt, std::nullopt, base::DoNothing());
  }
}

void NotificationPlatformBridgeChromeOs::DisableNotification(
    const std::string& id) {
  ProfileNotification* notification = GetProfileNotification(id);
  if (!notification)
    return;

  DCHECK_NE(NotificationHandler::Type::TRANSIENT, notification->type());
  NotificationDisplayServiceImpl::GetForProfile(notification->profile())
      ->ProcessNotificationOperation(
          NotificationOperation::kDisablePermission, notification->type(),
          notification->notification().origin_url(),
          notification->original_id(), std::nullopt, std::nullopt, std::nullopt,
          std::nullopt, base::DoNothing());
}

ProfileNotification* NotificationPlatformBridgeChromeOs::GetProfileNotification(
    const std::string& profile_notification_id) {
  auto iter = active_notifications_.find(profile_notification_id);
  if (iter == active_notifications_.end())
    return nullptr;
  return iter->second.get();
}

void NotificationPlatformBridgeChromeOs::OnGetDisplayed(
    GetDisplayedNotificationsCallback callback,
    std::set<std::string> notification_ids,
    bool supports_synchronization) const {
  std::set<std::string> original_notification_ids;
  for (const auto& id : notification_ids) {
    auto iter = active_notifications_.find(id);
    if (iter != active_notifications_.end())
      original_notification_ids.insert(iter->second->original_id());
  }
  std::move(callback).Run(std::move(original_notification_ids),
                          supports_synchronization);
}
