// Copyright 2025 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/ui/desktop_to_mobile_promos/ios_promo_trigger_service.h"

#include "base/functional/callback_helpers.h"
#include "base/uuid.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sharing/sharing_service_factory.h"
#include "chrome/browser/sync/device_info_sync_service_factory.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "components/desktop_to_mobile_promos/desktop_to_mobile_promo_utils.h"
#include "components/desktop_to_mobile_promos/features.h"
#include "components/desktop_to_mobile_promos/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/sharing_message/sharing_service.h"
#include "components/sync_device_info/device_info.h"
#include "components/sync_device_info/device_info_sync_service.h"
#include "components/sync_device_info/device_info_tracker.h"
#include "ui/base/l10n/l10n_util.h"

// The value corresponds to the iOS push notification client ID.
// TODO(crbug.com/438769954): Consider moving the PushNotificationClientId enum
// to components so it can be shared between iOS and Desktop.
constexpr char kCrossPlatformPromosClientId[] = "8";

IOSPromoTriggerService::IOSPromoTriggerService(Profile* profile)
    : profile_(profile) {
  auto* collection = ProfileBrowserCollection::GetForProfile(profile_);
  browser_collection_observer_.Observe(collection);

  // Register as an observer for all existing browsers in this profile.
  collection->ForEach([this](BrowserWindowInterface* browser) {
    OnBrowserCreated(browser);
    return true;
  });
}
IOSPromoTriggerService::~IOSPromoTriggerService() = default;

void IOSPromoTriggerService::NotifyPromoShouldBeShown(
    desktop_to_mobile_promos::PromoType promo_type) {
  callback_list_.Notify(promo_type);
}

const syncer::DeviceInfo* IOSPromoTriggerService::GetIOSDeviceToRemind() {
  if (GetMobilePromoOnDesktopForcePromoType() ==
      IOSPromoBubbleForceType::kQRCode) {
    return nullptr;
  }

  syncer::DeviceInfoSyncService* device_info_sync_service =
      DeviceInfoSyncServiceFactory::GetForProfile(profile_);
  if (!device_info_sync_service) {
    return nullptr;
  }

  syncer::DeviceInfoTracker* device_info_tracker =
      device_info_sync_service->GetDeviceInfoTracker();
  if (!device_info_tracker) {
    return nullptr;
  }

  const syncer::DeviceInfo* preferred_device = nullptr;

  for (const syncer::DeviceInfo* device :
       device_info_tracker->GetAllDeviceInfo()) {
    // Skip non-iOS devices.
    if (device->os_type() != syncer::DeviceInfo::OsType::kIOS) {
      continue;
    }

    if (IsMorePreferredDevice(device, preferred_device)) {
      preferred_device = device;
    }
  }

  return preferred_device;
}

void IOSPromoTriggerService::OnTabStripModelChanged(
    TabStripModel* tab_strip_model,
    const TabStripModelChange& change,
    const TabStripSelectionChange& selection) {
  if (MobilePromoOnDesktopTypeEnabled(
          MobilePromoOnDesktopPromoType::kTabGroups)) {
    if (selection.active_tab_changed() && selection.new_tab &&
        selection.new_tab->GetGroup().has_value()) {
      // Trigger the Tab Groups promo if a user consults a tab within a group.
      NotifyPromoShouldBeShown(desktop_to_mobile_promos::PromoType::kTabGroups);
    }
  }
}

void IOSPromoTriggerService::OnTabGroupChanged(const TabGroupChange& change) {
  if (MobilePromoOnDesktopTypeEnabled(
          MobilePromoOnDesktopPromoType::kTabGroups)) {
    if (change.type == TabGroupChange::kCreated ||
        change.type == TabGroupChange::kVisualsChanged) {
      // Trigger the Tab Groups promo if a user creates or edits a tab group.
      NotifyPromoShouldBeShown(desktop_to_mobile_promos::PromoType::kTabGroups);
    }
  }
}

void IOSPromoTriggerService::OnBrowserCreated(BrowserWindowInterface* browser) {
  browser->GetTabStripModel()->AddObserver(this);
}

void IOSPromoTriggerService::OnBrowserClosed(BrowserWindowInterface* browser) {
  browser->GetTabStripModel()->RemoveObserver(this);
}

bool IOSPromoTriggerService::IsMorePreferredDevice(
    const syncer::DeviceInfo* current_preference,
    const syncer::DeviceInfo* another_device) {
  if (!another_device) {
    return true;
  }
  if (!current_preference) {
    return false;
  }

  // iPhone is always preferred over iPad.
  if (current_preference->form_factor() ==
          syncer::DeviceInfo::FormFactor::kPhone &&
      another_device->form_factor() ==
          syncer::DeviceInfo::FormFactor::kTablet) {
    return true;
  }
  if (current_preference->form_factor() ==
          syncer::DeviceInfo::FormFactor::kTablet &&
      another_device->form_factor() == syncer::DeviceInfo::FormFactor::kPhone) {
    return false;
  }

  // If same form factor, prefer the more recently updated device.
  return current_preference->last_updated_timestamp() >
         another_device->last_updated_timestamp();
}

void IOSPromoTriggerService::SetReminderForIOSDevice(
    desktop_to_mobile_promos::PromoType promo_type,
    const std::string& device_guid) {
  // Set the prefs for the in-app notification.
  base::DictValue promo_reminder_data;
  promo_reminder_data.Set(prefs::kIOSPromoReminderPromoType,
                          static_cast<int>(promo_type));
  promo_reminder_data.Set(prefs::kIOSPromoReminderDeviceGUID, device_guid);
  promo_reminder_data.Set(prefs::kIOSPromoReminderTriggerId,
                          base::Uuid::GenerateRandomV4().AsLowercaseString());
  profile_->GetPrefs()->SetDict(prefs::kIOSPromoReminder,
                                std::move(promo_reminder_data));

  // Create and send payload for push notification.
  if (IsMobilePromoOnDesktopNotificationsEnabled()) {
    SharingService* sharing_service =
        SharingServiceFactory::GetForBrowserContext(profile_);
    std::optional<SharingTargetDeviceInfo> device_info =
        sharing_service->GetDeviceByGuid(device_guid);
    if (device_info) {
      sharing_service->SendIosPushMessageToDevice(
          *device_info, CreateNotificationPayload(promo_type, device_guid),
          base::DoNothing());
    }
  }
}

base::CallbackListSubscription IOSPromoTriggerService::RegisterPromoCallback(
    PromoCallback callback) {
  return callback_list_.Add(std::move(callback));
}

sync_pb::UnencryptedSharingMessage
IOSPromoTriggerService::CreateNotificationPayload(
    desktop_to_mobile_promos::PromoType promo_type,
    const std::string& device_guid) {
  desktop_to_mobile_promos::PromoNotificationStringIDs string_ids =
      desktop_to_mobile_promos::GetPromoNotificationStringIDs(promo_type);
  CHECK(string_ids.title_id != 0 && string_ids.body_id != 0);

  sync_pb::UnencryptedSharingMessage sharing_message;
  sync_pb::DesktopToMobilePromoMessage* promo_message =
      sharing_message.mutable_desktop_to_mobile_promo_message();
  sync_pb::PushNotificationMessage* push_notification =
      promo_message->mutable_push_notification();

  promo_message->set_promo_type(static_cast<int>(promo_type));
  push_notification->set_title(l10n_util::GetStringUTF8(string_ids.title_id));
  push_notification->set_text(l10n_util::GetStringUTF8(string_ids.body_id));
  push_notification->set_push_notification_client_id(
      kCrossPlatformPromosClientId);

  return sharing_message;
}
