// Copyright 2013 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/extensions/api/sessions/sessions_api.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "base/i18n/rtl.h"
#include "base/lazy_instance.h"
#include "base/memory/raw_ptr.h"
#include "base/notimplemented.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/api/sessions/session_id.h"
#include "chrome/browser/extensions/api/tabs/windows_util.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/window_controller.h"
#include "chrome/browser/extensions/window_controller_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/sync/session_sync_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_live_tab_context.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "components/sessions/content/content_live_tab.h"
#include "components/sessions/core/tab_restore_service.h"
#include "components/sync_sessions/open_tabs_ui_delegate.h"
#include "components/sync_sessions/session_sync_service.h"
#include "components/sync_sessions/synced_session.h"
#include "components/tab_groups/tab_group_color.h"
#include "components/url_formatter/url_formatter.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_function_dispatcher.h"
#include "extensions/browser/extension_function_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "ui/base/mojom/window_show_state.mojom.h"

namespace extensions {

namespace windows = api::windows;

namespace {

namespace GetRecentlyClosed = api::sessions::GetRecentlyClosed;
namespace GetDevices = api::sessions::GetDevices;
namespace Restore = api::sessions::Restore;

const char kNoRecentlyClosedSessionsError[] =
    "There are no recently closed sessions.";
const char kInvalidSessionIdError[] = "Invalid session id: \"*\".";
const char kNoBrowserToRestoreSession[] =
    "There are no browser windows to restore the session.";
const char kSessionSyncError[] = "Synced sessions are not available.";
const char kRestoreInIncognitoError[] =
    "Can not restore sessions in incognito mode.";

// Comparator function for use with std::sort that will sort sessions by
// descending modified_time (i.e., most recent first).
bool SortSessionsByRecency(const sync_sessions::SyncedSession* s1,
                           const sync_sessions::SyncedSession* s2) {
  return s1->GetModifiedTime() > s2->GetModifiedTime();
}

// Comparator function for use with std::sort that will sort tabs in a window
// by descending timestamp (i.e., most recent first).
bool SortTabsByRecency(const sessions::SessionTab* t1,
                       const sessions::SessionTab* t2) {
  return t1->timestamp > t2->timestamp;
}

api::tabs::Tab CreateTabModelHelper(
    const sessions::SerializedNavigationEntry& current_navigation,
    const std::string& session_id,
    int index,
    bool pinned,
    bool active,
    const Extension* extension,
    mojom::ContextType context) {
  api::tabs::Tab tab_struct;

  const GURL& url = current_navigation.virtual_url();
  std::string title = base::UTF16ToUTF8(current_navigation.title());

  tab_struct.session_id = session_id;
  tab_struct.url = url.spec();
  tab_struct.fav_icon_url = current_navigation.favicon_url().spec();
  if (!title.empty()) {
    tab_struct.title = title;
  } else {
    tab_struct.title = base::UTF16ToUTF8(url_formatter::FormatUrl(url));
  }
  tab_struct.index = index;
  tab_struct.pinned = pinned;
  tab_struct.active = active;

  ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
      ExtensionTabUtil::GetScrubTabBehavior(extension, context, url);
  ExtensionTabUtil::ScrubTabForExtension(extension, nullptr, &tab_struct,
                                         scrub_tab_behavior);
  return tab_struct;
}

api::windows::Window CreateWindowModelHelper(
    std::vector<api::tabs::Tab> tabs,
    const std::string& session_id,
    const api::windows::WindowType& type,
    const api::windows::WindowState& state) {
  api::windows::Window window_struct;
  window_struct.tabs = std::move(tabs);
  window_struct.session_id = session_id;
  window_struct.incognito = false;
  window_struct.always_on_top = false;
  window_struct.focused = false;
  window_struct.type = type;
  window_struct.state = state;
  return window_struct;
}

api::sessions::Session CreateSessionModelHelper(
    int last_modified,
    std::optional<api::tabs::Tab> tab,
    std::optional<api::windows::Window> window,
    std::optional<api::tab_groups::TabGroup> group) {
  api::sessions::Session session_struct;
  session_struct.last_modified = last_modified;
  if (tab) {
    session_struct.tab = std::move(*tab);
  } else if (window) {
    session_struct.window = std::move(*window);
  } else if (group) {
    // TODO(crbug.com/40757179): Implement group support.
    NOTIMPLEMENTED();
  } else {
    NOTREACHED();
  }
  return session_struct;
}

#if !BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
bool is_window_entry(const sessions::tab_restore::Entry& entry) {
  return entry.type == sessions::tab_restore::Type::WINDOW;
}
#endif

}  // namespace

#if !BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
api::tabs::Tab SessionsGetRecentlyClosedFunction::CreateTabModel(
    const sessions::tab_restore::Tab& tab,
    bool active) {
  return CreateTabModelHelper(tab.navigations[tab.current_navigation_index],
                              base::NumberToString(tab.id.id()),
                              tab.tabstrip_index, tab.pinned, active,
                              extension(), source_context_type());
}

api::windows::Window SessionsGetRecentlyClosedFunction::CreateWindowModel(
    const sessions::tab_restore::Window& window) {
  DCHECK(!window.tabs.empty());

  std::vector<api::tabs::Tab> tabs;
  for (const auto& tab : window.tabs) {
    tabs.push_back(
        CreateTabModel(*tab, tab->tabstrip_index == window.selected_tab_index));
  }

  return CreateWindowModelHelper(
      std::move(tabs), base::NumberToString(window.id.id()),
      api::windows::WindowType::kNormal, api::windows::WindowState::kNormal);
}

api::tab_groups::TabGroup SessionsGetRecentlyClosedFunction::CreateGroupModel(
    const sessions::tab_restore::Group& group) {
  DCHECK(!group.tabs.empty());

  return ExtensionTabUtil::CreateTabGroupObject(group.group_id,
                                                group.visual_data);
}

api::sessions::Session SessionsGetRecentlyClosedFunction::CreateSessionModel(
    const sessions::tab_restore::Entry& entry) {
  std::optional<api::tabs::Tab> tab;
  std::optional<api::windows::Window> window;
  std::optional<api::tab_groups::TabGroup> group;
  switch (entry.type) {
    case sessions::tab_restore::Type::TAB:
      tab = CreateTabModel(
          static_cast<const sessions::tab_restore::Tab&>(entry), false);
      break;
    case sessions::tab_restore::Type::WINDOW:
      window = CreateWindowModel(
          static_cast<const sessions::tab_restore::Window&>(entry));
      break;
    case sessions::tab_restore::Type::GROUP:
      group = CreateGroupModel(
          static_cast<const sessions::tab_restore::Group&>(entry));
  }
  return CreateSessionModelHelper(entry.timestamp.ToTimeT(), std::move(tab),
                                  std::move(window), std::move(group));
}

ExtensionFunction::ResponseAction SessionsGetRecentlyClosedFunction::Run() {
  std::optional<GetRecentlyClosed::Params> params =
      GetRecentlyClosed::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);
  int max_results = api::sessions::MAX_SESSION_RESULTS;
  if (params->filter && params->filter->max_results)
    max_results = *params->filter->max_results;
  EXTENSION_FUNCTION_VALIDATE(max_results >= 0 &&
      max_results <= api::sessions::MAX_SESSION_RESULTS);

  std::vector<api::sessions::Session> result;
  sessions::TabRestoreService* tab_restore_service =
      TabRestoreServiceFactory::GetForProfile(
          Profile::FromBrowserContext(browser_context()));

  // TabRestoreServiceFactory::GetForProfile() can return nullptr (i.e., when in
  // incognito mode)
  if (!tab_restore_service) {
    DCHECK(browser_context()->IsOffTheRecord())
        << "sessions::TabRestoreService expected for normal profiles";
    return RespondNow(ArgumentList(GetRecentlyClosed::Results::Create(result)));
  }

  // List of entries. They are ordered from most to least recent.
  // We prune the list to contain max 25 entries at any time and removes
  // uninteresting entries.
  int counter = 0;
  for (const auto& entry : tab_restore_service->entries()) {
    // TODO(crbug.com/40757179): Support group entries in the Sessions API,
    // rather than sharding the group out into individual tabs.
    if (entry->type == sessions::tab_restore::Type::GROUP) {
      auto& group = static_cast<const sessions::tab_restore::Group&>(*entry);
      for (const auto& tab : group.tabs)
        if (counter++ < max_results)
          result.push_back(CreateSessionModel(*tab));
        else
          break;
    } else {
      if (counter++ < max_results)
        result.push_back(CreateSessionModel(*entry));
      else
        break;
    }
  }

  return RespondNow(ArgumentList(GetRecentlyClosed::Results::Create(result)));
}

api::tabs::Tab SessionsGetDevicesFunction::CreateTabModel(
    const std::string& session_tag,
    const sessions::SessionTab& tab,
    int tab_index,
    bool active) {
  std::string session_id = SessionId(session_tag, tab.tab_id.id()).ToString();
  return CreateTabModelHelper(
      tab.navigations[tab.normalized_navigation_index()], session_id, tab_index,
      tab.pinned, active, extension(), source_context_type());
}

std::optional<api::windows::Window>
SessionsGetDevicesFunction::CreateWindowModel(
    const sessions::SessionWindow& window,
    const std::string& session_tag) {
  DCHECK(!window.tabs.empty());

  // Ignore app popup window for now because we do not have a corresponding
  // api::windows::WindowType value.
  if (window.type == sessions::SessionWindow::TYPE_APP_POPUP)
    return std::nullopt;

  // Prune tabs that are not syncable or are NewTabPage. Then, sort the tabs
  // from most recent to least recent.
  std::vector<const sessions::SessionTab*> tabs_in_window;
  for (const auto& tab : window.tabs) {
    if (tab->navigations.empty())
      continue;
    const sessions::SerializedNavigationEntry& current_navigation =
        tab->navigations.at(tab->normalized_navigation_index());
    if (search::IsNTPOrRelatedURL(
            current_navigation.virtual_url(),
            Profile::FromBrowserContext(browser_context()))) {
      continue;
    }
    tabs_in_window.push_back(tab.get());
  }
  if (tabs_in_window.empty())
    return std::nullopt;
  std::sort(tabs_in_window.begin(), tabs_in_window.end(), SortTabsByRecency);

  std::vector<api::tabs::Tab> tabs;
  for (size_t i = 0; i < tabs_in_window.size(); ++i) {
    bool is_active = false;
    if (window.selected_tab_index != -1) {
      SessionID selected_tab_id =
          window.tabs[window.selected_tab_index]->tab_id;
      SessionID current_tab_id = tabs_in_window[i]->tab_id;
      is_active = selected_tab_id == current_tab_id;
    }
    tabs.push_back(
        CreateTabModel(session_tag, *tabs_in_window[i], i, is_active));
  }

  std::string session_id =
      SessionId(session_tag, window.window_id.id()).ToString();

  api::windows::WindowType type = api::windows::WindowType::kNone;
  switch (window.type) {
    case sessions::SessionWindow::TYPE_NORMAL:
      type = api::windows::WindowType::kNormal;
      break;
    case sessions::SessionWindow::TYPE_POPUP:
      type = api::windows::WindowType::kPopup;
      break;
    case sessions::SessionWindow::TYPE_APP:
      type = api::windows::WindowType::kApp;
      break;
    case sessions::SessionWindow::TYPE_DEVTOOLS:
      type = api::windows::WindowType::kDevtools;
      break;
    case sessions::SessionWindow::TYPE_APP_POPUP:
#if BUILDFLAG(IS_CHROMEOS)
    case sessions::SessionWindow::TYPE_CUSTOM_TAB:
#endif
      NOTREACHED();
  }

  api::windows::WindowState state = api::windows::WindowState::kNone;
  switch (window.show_state) {
    case ui::mojom::WindowShowState::kNormal:
      state = api::windows::WindowState::kNormal;
      break;
    case ui::mojom::WindowShowState::kMinimized:
    case ui::mojom::WindowShowState::kHidden:
      state = api::windows::WindowState::kMinimized;
      break;
    case ui::mojom::WindowShowState::kMaximized:
      state = api::windows::WindowState::kMaximized;
      break;
    case ui::mojom::WindowShowState::kFullscreen:
      state = api::windows::WindowState::kFullscreen;
      break;
    case ui::mojom::WindowShowState::kDefault:
    case ui::mojom::WindowShowState::kInactive:
    case ui::mojom::WindowShowState::kEnd:
      break;
  }

  api::windows::Window window_struct =
      CreateWindowModelHelper(std::move(tabs), session_id, type, state);
  // TODO(dwankri): Dig deeper to resolve bounds not being optional, so closed
  // windows in GetRecentlyClosed can have set values in Window helper.
  window_struct.left = window.bounds.x();
  window_struct.top = window.bounds.y();
  window_struct.width = window.bounds.width();
  window_struct.height = window.bounds.height();

  return window_struct;
}

std::optional<api::sessions::Session>
SessionsGetDevicesFunction::CreateSessionModel(
    const sessions::SessionWindow& window,
    const std::string& session_tag) {
  std::optional<api::windows::Window> window_model =
      CreateWindowModel(window, session_tag);
  // There is a chance that after pruning uninteresting tabs the window will be
  // empty.
  if (!window_model)
    return std::nullopt;

  return CreateSessionModelHelper(window.timestamp.ToTimeT(), std::nullopt,
                                  std::move(window_model), std::nullopt);
}

api::sessions::Device SessionsGetDevicesFunction::CreateDeviceModel(
    const sync_sessions::SyncedSession* session) {
  int max_results = api::sessions::MAX_SESSION_RESULTS;
  // Already validated in RunAsync().
  std::optional<GetDevices::Params> params = GetDevices::Params::Create(args());
  if (params->filter && params->filter->max_results)
    max_results = *params->filter->max_results;

  api::sessions::Device device_struct;
  device_struct.info = session->GetSessionName();
  device_struct.device_name = session->GetSessionName();

  for (auto it = session->windows.begin();
       it != session->windows.end() &&
       static_cast<int>(device_struct.sessions.size()) < max_results;
       ++it) {
    std::optional<api::sessions::Session> session_model = CreateSessionModel(
        it->second->wrapped_window, session->GetSessionTag());
    if (session_model)
      device_struct.sessions.push_back(std::move(*session_model));
  }
  return device_struct;
}

ExtensionFunction::ResponseAction SessionsGetDevicesFunction::Run() {
  sync_sessions::SessionSyncService* service =
      SessionSyncServiceFactory::GetInstance()->GetForProfile(
          Profile::FromBrowserContext(browser_context()));
  DCHECK(service);

  sync_sessions::OpenTabsUIDelegate* open_tabs =
      service->GetOpenTabsUIDelegate();
  std::vector<raw_ptr<const sync_sessions::SyncedSession, VectorExperimental>>
      sessions;
  // If the user has disabled tab sync, GetOpenTabsUIDelegate() returns null.
  if (!(open_tabs && open_tabs->GetAllForeignSessions(&sessions))) {
    return RespondNow(ArgumentList(
        GetDevices::Results::Create(std::vector<api::sessions::Device>())));
  }

  std::optional<GetDevices::Params> params = GetDevices::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);
  if (params->filter && params->filter->max_results) {
    EXTENSION_FUNCTION_VALIDATE(*params->filter->max_results >= 0 &&
        *params->filter->max_results <= api::sessions::MAX_SESSION_RESULTS);
  }

  std::vector<api::sessions::Device> result;
  // Sort sessions from most recent to least recent.
  std::sort(sessions.begin(), sessions.end(), SortSessionsByRecency);
  for (const sync_sessions::SyncedSession* session : sessions) {
    result.push_back(CreateDeviceModel(session));
  }

  return RespondNow(ArgumentList(GetDevices::Results::Create(result)));
}

ExtensionFunction::ResponseValue SessionsRestoreFunction::GetRestoredTabResult(
    content::WebContents* contents) {
  ExtensionTabUtil::ScrubTabBehavior scrub_tab_behavior =
      ExtensionTabUtil::GetScrubTabBehavior(extension(), source_context_type(),
                                            contents);
  api::tabs::Tab tab = ExtensionTabUtil::CreateTabObject(
      contents, scrub_tab_behavior, extension());
  api::sessions::Session restored_session = CreateSessionModelHelper(
      base::Time::Now().ToTimeT(), std::move(tab), std::nullopt, std::nullopt);
  return ArgumentList(Restore::Results::Create(restored_session));
}

ExtensionFunction::ResponseValue
SessionsRestoreFunction::GetRestoredWindowResult(int window_id) {
  WindowController* window_controller = nullptr;
  std::string error;
  if (!windows_util::GetControllerFromWindowID(this, window_id, 0,
                                               &window_controller, &error)) {
    return Error(error);
  }
  base::Value::Dict window_value =
      window_controller->CreateWindowValueForExtension(
          extension(), WindowController::kPopulateTabs, source_context_type());
  std::optional<api::windows::Window> window =
      api::windows::Window::FromValue(window_value);
  DCHECK(window);
  return ArgumentList(Restore::Results::Create(
      CreateSessionModelHelper(base::Time::Now().ToTimeT(), std::nullopt,
                               std::move(*window), std::nullopt)));
}

ExtensionFunction::ResponseValue
SessionsRestoreFunction::RestoreMostRecentlyClosed(Browser* browser) {
  sessions::TabRestoreService* tab_restore_service =
      TabRestoreServiceFactory::GetForProfile(
          Profile::FromBrowserContext(browser_context()));
  const sessions::TabRestoreService::Entries& entries =
      tab_restore_service->entries();

  if (entries.empty())
    return Error(kNoRecentlyClosedSessionsError);

  bool is_window = is_window_entry(*entries.front());
  sessions::LiveTabContext* context =
      BrowserLiveTabContext::FindContextForWebContents(
          browser->tab_strip_model()->GetActiveWebContents());
  std::vector<sessions::LiveTab*> restored_tabs =
      tab_restore_service->RestoreMostRecentEntry(context);
  DCHECK(restored_tabs.size());

  sessions::ContentLiveTab* first_tab =
      static_cast<sessions::ContentLiveTab*>(restored_tabs[0]);
  if (is_window) {
    return GetRestoredWindowResult(
        ExtensionTabUtil::GetWindowIdOfTab(first_tab->web_contents()));
  }

  return GetRestoredTabResult(first_tab->web_contents());
}

ExtensionFunction::ResponseValue SessionsRestoreFunction::RestoreLocalSession(
    const SessionId& session_id,
    Browser* browser) {
  sessions::TabRestoreService* tab_restore_service =
      TabRestoreServiceFactory::GetForProfile(
          Profile::FromBrowserContext(browser_context()));
  const sessions::TabRestoreService::Entries& entries =
      tab_restore_service->entries();

  if (entries.empty())
    return Error(kInvalidSessionIdError, session_id.ToString());

  // Check if the recently closed list contains an entry with the provided id.
  bool is_window = false;
  for (const auto& entry : entries) {
    if (entry->id.id() == session_id.id()) {
      // A full window is being restored only if the entry ID
      // matches the provided ID and the entry type is Window.
      is_window = is_window_entry(*entry);
      break;
    }
  }

  sessions::LiveTabContext* context =
      BrowserLiveTabContext::FindContextForWebContents(
          browser->tab_strip_model()->GetActiveWebContents());
  std::vector<sessions::LiveTab*> restored_tabs =
      tab_restore_service->RestoreEntryById(
          context, SessionID::FromSerializedValue(session_id.id()),
          WindowOpenDisposition::UNKNOWN);
  // If the ID is invalid, restored_tabs will be empty.
  if (restored_tabs.empty())
    return Error(kInvalidSessionIdError, session_id.ToString());

  sessions::ContentLiveTab* first_tab =
      static_cast<sessions::ContentLiveTab*>(restored_tabs[0]);

  // Retrieve the window through any of the tabs in restored_tabs.
  if (is_window) {
    return GetRestoredWindowResult(
        ExtensionTabUtil::GetWindowIdOfTab(first_tab->web_contents()));
  }

  return GetRestoredTabResult(first_tab->web_contents());
}

ExtensionFunction::ResponseValue SessionsRestoreFunction::RestoreForeignSession(
    const SessionId& session_id,
    Browser* browser) {
  Profile* profile = Profile::FromBrowserContext(browser_context());
  sync_sessions::SessionSyncService* service =
      SessionSyncServiceFactory::GetInstance()->GetForProfile(
          Profile::FromBrowserContext(browser_context()));
  DCHECK(service);

  sync_sessions::OpenTabsUIDelegate* open_tabs =
      service->GetOpenTabsUIDelegate();
  // If the user has disabled tab sync, GetOpenTabsUIDelegate() returns null.
  if (!open_tabs)
    return Error(kSessionSyncError);

  const sessions::SessionTab* tab = nullptr;
  if (open_tabs->GetForeignTab(session_id.session_tag(),
                               SessionID::FromSerializedValue(session_id.id()),
                               &tab)) {
    TabStripModel* tab_strip = browser->tab_strip_model();
    content::WebContents* contents = tab_strip->GetActiveWebContents();

    content::WebContents* tab_contents =
        SessionRestore::RestoreForeignSessionTab(
            contents, *tab, WindowOpenDisposition::NEW_FOREGROUND_TAB);
    return GetRestoredTabResult(tab_contents);
  }

  // Restoring a full window.
  std::vector<const sessions::SessionWindow*> windows =
      open_tabs->GetForeignSession(session_id.session_tag());
  if (windows.empty()) {
    return Error(kInvalidSessionIdError, session_id.ToString());
  }

  std::vector<const sessions::SessionWindow*>::const_iterator window =
      windows.begin();
  while (window != windows.end()
         && (*window)->window_id.id() != session_id.id()) {
    ++window;
  }
  if (window == windows.end())
    return Error(kInvalidSessionIdError, session_id.ToString());

  // Only restore one window at a time.
  std::vector<Browser*> browsers =
      SessionRestore::RestoreForeignSessionWindows(profile, window, window + 1);
  // Will always create one browser because we only restore one window per call.
  DCHECK_EQ(1u, browsers.size());
  return GetRestoredWindowResult(ExtensionTabUtil::GetWindowId(browsers[0]));
}

ExtensionFunction::ResponseAction SessionsRestoreFunction::Run() {
  std::optional<Restore::Params> params = Restore::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  Profile* profile = Profile::FromBrowserContext(browser_context());
  Browser* browser = chrome::FindBrowserWithProfile(profile);
  if (!browser)
    return RespondNow(Error(kNoBrowserToRestoreSession));

  if (profile != profile->GetOriginalProfile())
    return RespondNow(Error(kRestoreInIncognitoError));

  if (!ExtensionTabUtil::IsTabStripEditable())
    return RespondNow(Error(ExtensionTabUtil::kTabStripNotEditableError));

  if (!params->session_id)
    return RespondNow(RestoreMostRecentlyClosed(browser));

  std::unique_ptr<SessionId> session_id(SessionId::Parse(*params->session_id));
  if (!session_id)
    return RespondNow(Error(kInvalidSessionIdError, *params->session_id));

  return RespondNow(session_id->IsForeign()
                        ? RestoreForeignSession(*session_id, browser)
                        : RestoreLocalSession(*session_id, browser));
}

SessionsEventRouter::SessionsEventRouter(Profile* profile)
    : profile_(profile),
      tab_restore_service_(TabRestoreServiceFactory::GetForProfile(profile)) {
  // TabRestoreServiceFactory::GetForProfile() can return nullptr (i.e., when in
  // incognito mode)
  if (tab_restore_service_) {
    tab_restore_service_->LoadTabsFromLastSession();
    tab_restore_service_->AddObserver(this);
  }
}
#endif  // !BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)

SessionsEventRouter::~SessionsEventRouter() {
  if (tab_restore_service_)
    tab_restore_service_->RemoveObserver(this);
}

void SessionsEventRouter::TabRestoreServiceChanged(
    sessions::TabRestoreService* service) {
  EventRouter::Get(profile_)->BroadcastEvent(std::make_unique<Event>(
      events::SESSIONS_ON_CHANGED, api::sessions::OnChanged::kEventName,
      base::Value::List()));
}

void SessionsEventRouter::TabRestoreServiceDestroyed(
    sessions::TabRestoreService* service) {
  tab_restore_service_ = nullptr;
}

SessionsAPI::SessionsAPI(content::BrowserContext* context)
    : browser_context_(context) {
  EventRouter::Get(browser_context_)->RegisterObserver(this,
      api::sessions::OnChanged::kEventName);
}

SessionsAPI::~SessionsAPI() = default;

void SessionsAPI::Shutdown() {
  EventRouter::Get(browser_context_)->UnregisterObserver(this);
}

static base::LazyInstance<BrowserContextKeyedAPIFactory<SessionsAPI>>::
    DestructorAtExit g_sessions_api_factory = LAZY_INSTANCE_INITIALIZER;

BrowserContextKeyedAPIFactory<SessionsAPI>*
SessionsAPI::GetFactoryInstance() {
  return g_sessions_api_factory.Pointer();
}

void SessionsAPI::OnListenerAdded(const EventListenerInfo& details) {
  sessions_event_router_ = std::make_unique<SessionsEventRouter>(
      Profile::FromBrowserContext(browser_context_));
  EventRouter::Get(browser_context_)->UnregisterObserver(this);
}

}  // namespace extensions