// Copyright 2018 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/sync/session_sync_service_factory.h"

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "build/build_config.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/data_type_store_service_factory.h"
#include "chrome/browser/sync/device_info_sync_service_factory.h"
#include "chrome/browser/sync/glue/sync_start_util.h"
#include "chrome/browser/sync/sessions/sync_sessions_web_contents_router.h"
#include "chrome/browser/sync/sessions/sync_sessions_web_contents_router_factory.h"
#include "chrome/browser/ui/sync/browser_synced_window_delegates_getter.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/url_constants.h"
#include "components/dom_distiller/core/url_constants.h"
#include "components/history/core/browser/history_service.h"
#include "components/sync/model/data_type_store_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 "components/sync_device_info/device_name_util.h"
#include "components/sync_sessions/session_sync_prefs.h"
#include "components/sync_sessions/session_sync_service_impl.h"
#include "components/sync_sessions/sync_sessions_client.h"
#include "content/public/common/url_utils.h"

#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/sync/glue/synced_window_delegates_getter_android.h"
#endif  // BUILDFLAG(IS_ANDROID)

namespace {

bool ShouldSyncURLImpl(const GURL& url) {
  return url.is_valid() && !content::HasWebUIScheme(url) &&
         !url.SchemeIs(chrome::kChromeNativeScheme) && !url.SchemeIsFile() &&
         !url.SchemeIs(dom_distiller::kDomDistillerScheme);
}

// Chrome implementation of SyncSessionsClient.
class SyncSessionsClientImpl final : public sync_sessions::SyncSessionsClient {
 public:
  explicit SyncSessionsClientImpl(Profile* profile)
      : profile_(profile), session_sync_prefs_(profile->GetPrefs()) {
    window_delegates_getter_ =
#if BUILDFLAG(IS_ANDROID)
        // Android doesn't have multi-profile support, so no need to pass the
        // profile in.
        std::make_unique<browser_sync::SyncedWindowDelegatesGetterAndroid>();
#else   // BUILDFLAG(IS_ANDROID)
        std::make_unique<browser_sync::BrowserSyncedWindowDelegatesGetter>(
            profile);
#endif  // BUILDFLAG(IS_ANDROID)
  }

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

  ~SyncSessionsClientImpl() override = default;

  std::optional<std::string> GetSessionDisplayNameFromDeviceInfo(
      const std::string& session_tag) const override {
    syncer::DeviceInfoSyncService* service =
        DeviceInfoSyncServiceFactory::GetForProfile(profile_);
    CHECK(service);

    const syncer::DeviceInfoTracker* tracker = service->GetDeviceInfoTracker();
    CHECK(tracker);

    const syncer::DeviceInfo* device_info = tracker->GetDeviceInfo(session_tag);
    if (!device_info) {
      return std::nullopt;
    }
    return syncer::GetDisplayNameCandidates(device_info)
        .preferred_name_if_unique;
  }

  // SyncSessionsClient implementation.
  sync_sessions::SessionSyncPrefs* GetSessionSyncPrefs() override {
    return &session_sync_prefs_;
  }

  syncer::RepeatingDataTypeStoreFactory GetStoreFactory() override {
    return DataTypeStoreServiceFactory::GetForProfile(profile_)
        ->GetStoreFactory();
  }

  void ClearAllOnDemandFavicons() override {
    history::HistoryService* history_service =
        HistoryServiceFactory::GetForProfile(
            profile_, ServiceAccessType::EXPLICIT_ACCESS);
    if (!history_service) {
      return;
    }
    history_service->ClearAllOnDemandFavicons();
  }

  bool ShouldSyncURL(const GURL& url) const override {
    return ShouldSyncURLImpl(url);
  }

  bool IsRecentLocalCacheGuid(const std::string& cache_guid) const override {
    return DeviceInfoSyncServiceFactory::GetForProfile(profile_)
        ->GetDeviceInfoTracker()
        ->IsRecentLocalCacheGuid(cache_guid);
  }

  sync_sessions::SyncedWindowDelegatesGetter* GetSyncedWindowDelegatesGetter()
      override {
    return window_delegates_getter_.get();
  }

  sync_sessions::LocalSessionEventRouter* GetLocalSessionEventRouter()
      override {
    syncer::SyncableService::StartSyncFlare flare(
        sync_start_util::GetFlareForSyncableService(profile_->GetPath()));
    sync_sessions::SyncSessionsWebContentsRouter* router =
        sync_sessions::SyncSessionsWebContentsRouterFactory::GetForProfile(
            profile_);
    // TODO(mastiz): Injecting a start flare as a side effect of what seems to
    // be a getter is error-prone. In fact, we seem to call this function very
    // early for the USS implementation.
    router->InjectStartSyncFlare(flare);
    return router;
  }

  base::WeakPtr<SyncSessionsClient> AsWeakPtr() override {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  const raw_ptr<Profile> profile_;
  std::unique_ptr<sync_sessions::SyncedWindowDelegatesGetter>
      window_delegates_getter_;
  sync_sessions::SessionSyncPrefs session_sync_prefs_;
  base::WeakPtrFactory<SyncSessionsClientImpl> weak_ptr_factory_{this};
};

}  // namespace

// static
sync_sessions::SessionSyncService* SessionSyncServiceFactory::GetForProfile(
    Profile* profile) {
  return static_cast<sync_sessions::SessionSyncService*>(
      GetInstance()->GetServiceForBrowserContext(profile, true));
}

// static
SessionSyncServiceFactory* SessionSyncServiceFactory::GetInstance() {
  static base::NoDestructor<SessionSyncServiceFactory> instance;
  return instance.get();
}

// static - exposed for testing and metrics.
bool SessionSyncServiceFactory::ShouldSyncURLForTestingAndMetrics(
    const GURL& url) {
  return ShouldSyncURLImpl(url);
}

SessionSyncServiceFactory::SessionSyncServiceFactory()
    : ProfileKeyedServiceFactory(
          "SessionSyncService",
          ProfileSelections::Builder()
              .WithRegular(ProfileSelection::kOriginalOnly)
              .WithGuest(ProfileSelection::kNone)
              .WithAshInternals(ProfileSelection::kNone)
              .Build()) {
  DependsOn(DataTypeStoreServiceFactory::GetInstance());
  DependsOn(DeviceInfoSyncServiceFactory::GetInstance());
  DependsOn(FaviconServiceFactory::GetInstance());
  DependsOn(HistoryServiceFactory::GetInstance());
  DependsOn(sync_sessions::SyncSessionsWebContentsRouterFactory::GetInstance());
}

SessionSyncServiceFactory::~SessionSyncServiceFactory() = default;

std::unique_ptr<KeyedService>
SessionSyncServiceFactory::BuildServiceInstanceForBrowserContext(
    content::BrowserContext* context) const {
  Profile* profile = Profile::FromBrowserContext(context);
  return std::make_unique<sync_sessions::SessionSyncServiceImpl>(
      chrome::GetChannel(), std::make_unique<SyncSessionsClientImpl>(profile));
}
