// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_SESSIONS_SESSION_SERVICE_BASE_H_
#define CHROME_BROWSER_SESSIONS_SESSION_SERVICE_BASE_H_

#include <map>
#include <optional>
#include <string>
#include <utility>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/sessions/session_common_utils.h"
#include "chrome/browser/ui/browser_window/public/browser_collection_observer.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/sessions/content/session_tab_helper_delegate.h"
#include "components/sessions/core/command_storage_manager_delegate.h"
#include "components/sessions/core/session_service_commands.h"
#include "components/sessions/core/tab_restore_service_client.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/mojom/window_show_state.mojom-forward.h"
#include "ui/base/ui_base_types.h"

class Profile;
class ProfileBrowserCollection;
class SessionServiceBaseObserver;

namespace base {
class Value;
}

namespace content {
class WebContents;
}  // namespace content

namespace sessions {
class CommandStorageManager;
class SessionCommand;
struct SessionWindow;
}  // namespace sessions

// SessionServiceBase -----
// SessionServiceBase is used by SessionService and AppSessionService to
// restore browser windows and apps respectively. Base is never intended
// to be instantiated and contains the common functionality that both
// SessionService and AppSessionService share in common. Some functions
// are implemented by both if they have differing behavior while others
// exist in one but not the other.
class SessionServiceBase : public sessions::CommandStorageManagerDelegate,
                           public sessions::SessionTabHelperDelegate,
                           public KeyedService,
                           public BrowserCollectionObserver {
 public:
  enum class SessionServiceType { kAppRestore, kSessionRestore };

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

  ~SessionServiceBase() override;

  static BrowserWindowInterface::Type GetBrowserTypeFromWebContents(
      content::WebContents* web_contents);

  Profile* profile() const { return profile_; }

  bool is_saving_enabled() const { return is_saving_enabled_; }

  // Sets whether the window is visible on all workspaces or not.
  void SetWindowVisibleOnAllWorkspaces(SessionID window_id,
                                       bool visible_on_all_workspaces);

  // Resets the contents of the file from the current state of all open
  // apps whose profile matches our profile.
  void ResetFromCurrentBrowsers();

  // Associates a tab with a window.
  void SetTabWindow(SessionID window_id, SessionID tab_id);

  // Sets the bounds of a window.
  void SetWindowBounds(SessionID window_id,
                       const gfx::Rect& bounds,
                       ui::mojom::WindowShowState show_state);

  // Sets the workspace the window resides in.
  void SetWindowWorkspace(SessionID window_id, const std::string& workspace);

  // Sets the visual index of the tab in its parent window.
  void SetTabIndexInWindow(SessionID window_id,
                           SessionID tab_id,
                           int new_index);

  // Note: this is invoked from the NavigationController's destructor, which is
  // after the actual tab has been removed.
  virtual void TabClosed(SessionID window_id, SessionID tab_id) = 0;

  // Notification a window has opened.
  virtual void WindowOpened(BrowserWindowInterface* browser) = 0;

  // Notification the window is about to close.
  virtual void WindowClosing(SessionID window_id) = 0;

  // Notification a window has finished closing.
  virtual void WindowClosed(SessionID window_id) = 0;

  // Called when a tab is inserted.
  void TabInserted(content::WebContents* contents);

  // Called when a tab is closing.
  void TabClosing(content::WebContents* contents);

  // Notification that a tab has restored its entries or a closed tab is being
  // reused.
  void TabRestored(content::WebContents* tab, bool pinned);

  // Sets the type of window. In order for the contents of a window to be
  // tracked SetWindowType must be invoked with a type we track
  // (ShouldRestoreOfWindowType returns true).
  virtual void SetWindowType(SessionID window_id,
                             BrowserWindowInterface::Type type) = 0;

  // Sets the index of the selected tab in the specified window.
  void SetSelectedTabInWindow(SessionID window_id, int index);

  // Sets the application extension id of the specified tab.
  void SetTabExtensionAppID(SessionID window_id,
                            SessionID tab_id,
                            const std::string& extension_app_id);

  // Sets the last active time of the tab.
  void SetLastActiveTime(SessionID window_id,
                         SessionID tab_id,
                         base::Time last_active_time);

  // Fetches the contents of the last session, notifying the callback when
  // done. If the callback is supplied an empty vector of SessionWindows
  // it means the session could not be restored.
  void GetLastSession(sessions::GetLastSessionCallback callback);

  // Sets the application name of the specified window.
  void SetWindowAppName(SessionID window_id, const std::string& app_name);

  // Sets the pinned state of the tab.
  void SetPinnedState(SessionID window_id, SessionID tab_id, bool is_pinned);

  // CommandStorageManagerDelegate:
  bool ShouldUseDelayedSave() override;
  void OnWillSaveCommands() override;
  // This implementation in SessionServiceBase is mostly a no-op.
  // Full support for Session Service logging will come with
  // https://crbug.com/40175339
  void OnErrorWritingSessionCommands() override;

  // sessions::SessionTabHelperDelegate:
  void SetTabUserAgentOverride(SessionID window_id,
                               SessionID tab_id,
                               const sessions::SerializedUserAgentOverride&
                                   user_agent_override) override;
  void SetSelectedNavigationIndex(SessionID window_id,
                                  SessionID tab_id,
                                  int index) override;
  void UpdateTabNavigation(
      SessionID window_id,
      SessionID tab_id,
      const sessions::SerializedNavigationEntry& navigation) override;
  void TabNavigationPathPruned(SessionID window_id,
                               SessionID tab_id,
                               int index,
                               int count) override;
  void TabNavigationPathEntriesDeleted(SessionID window_id,
                                       SessionID tab_id) override;

#if DCHECK_IS_ON()
  // Returns the state of this class and logs for the
  // chrome://internals/session-service debug page. The logs are in reverse
  // order for truncation ease. This value is NOT STABLE -
  // do not rely on it's contents for anything.
  virtual base::Value ToDebugValue() const;
#endif  // DCHECK_IS_ON()

  // Some platforms support windowing system level session management, in such
  // cases, GetPlatformSessionId() returns a non-empty string identifier
  // provided by the platform at session initialization and persisted in the
  // session command backing storage for future session restoration.
  // See ui/ozone/public/platfrom_session_manager.h for more details.
  std::optional<std::string> GetPlatformSessionId();
  void SetPlatformSessionIdForTesting(const std::string& id);

  void AddObserver(SessionServiceBaseObserver* observer);
  void RemoveObserver(SessionServiceBaseObserver* observer);

 protected:
  // Creates a SessionService for the specified profile.
  SessionServiceBase(Profile* profile, SessionServiceType type);

  // This method is implemented by child classes to pass us the type.
  virtual BrowserWindowInterface::Type
  GetDesiredBrowserTypeForWebContents() = 0;

  bool rebuild_on_next_save() const { return rebuild_on_next_save_; }
  void set_rebuild_on_next_save(bool value) { rebuild_on_next_save_ = value; }
  std::map<SessionID, int>* last_selected_tab_in_window() {
    return &last_selected_tab_in_window_;
  }

  using IdToRange = std::map<SessionID, std::pair<int, int>>;
  IdToRange* tab_to_available_range() { return &tab_to_available_range_; }

  sessions::CommandStorageManager* command_storage_manager() {
    return command_storage_manager_.get();
  }

  using WindowsTracking = std::set<SessionID>;
  WindowsTracking* windows_tracking() { return &windows_tracking_; }

  // This should only be used by derived classes in their destructors.
  void DestroyCommandStorageManager();

  // Returns true if a window of given |window_type| should get
  // restored upon session restore.
  virtual bool ShouldRestoreWindowOfType(
      sessions::SessionWindow::WindowType type) const = 0;

  // Removes unrestorable windows from the previous windows list.
  void RemoveUnusedRestoreWindows(
      std::vector<std::unique_ptr<sessions::SessionWindow>>* window_list);

  // BrowserCollectionObserver
  void OnBrowserActivated(BrowserWindowInterface* browser) override;

  // Converts |commands| to SessionWindows and notifies the callback.
  void OnGotSessionCommands(
      sessions::GetLastSessionCallback callback,
      std::vector<std::unique_ptr<sessions::SessionCommand>> commands,
      bool read_error);

  // Adds commands to commands that will recreate the state of the specified
  // tab. This adds at most kMaxNavigationCountToPersist navigations (in each
  // direction from the current navigation index).
  // A pair is added to tab_to_available_range indicating the range of
  // indices that were written.
  virtual void BuildCommandsForTab(SessionID window_id,
                                   content::WebContents* tab,
                                   int index_in_window,
                                   std::optional<tab_groups::TabGroupId> group,
                                   std::optional<split_tabs::SplitTabId> split,
                                   bool is_pinned,
                                   IdToRange* tab_to_available_range);

  // Adds commands to create the specified browser, and invokes
  // BuildCommandsForTab for each of the tabs in the browser. This ignores
  // any tabs not in the profile we were created with.
  virtual void BuildCommandsForBrowser(BrowserWindowInterface* browser,
                                       IdToRange* tab_to_available_range,
                                       std::set<SessionID>* windows_to_track);

  // Iterates over all the known browsers invoking BuildCommandsForBrowser.
  // This only adds browsers that should be tracked (|ShouldRestoreWindowOfType|
  // returns true). All browsers that are tracked are added to windows_to_track
  // (as long as it is non-null).
  void BuildCommandsFromBrowsers(IdToRange* tab_to_available_range,
                                 std::set<SessionID>* windows_to_track);

  // Schedules a reset of the existing commands. A reset means the contents
  // of the file are recreated from the state of the browser.
  virtual void ScheduleResetCommands() = 0;

  // Schedules the specified command.
  void ScheduleCommand(std::unique_ptr<sessions::SessionCommand> command);

  virtual void DidScheduleCommand() {}

  // Returns true if changes to tabs in the specified window should be tracked.
  bool ShouldTrackChangesToWindow(SessionID window_id) const;

  // Returns true if we track changes to the specified browser.
  bool ShouldTrackBrowser(BrowserWindowInterface* browser) const;

  // Will rebuild session commands if rebuild_on_next_save_ is true.
  virtual void RebuildCommandsIfRequired() = 0;

  // Unit test accessors.
  sessions::CommandStorageManager* GetCommandStorageManagerForTest();

  void SetAvailableRangeForTest(SessionID tab_id,
                                const std::pair<int, int>& range);
  bool GetAvailableRangeForTest(SessionID tab_id, std::pair<int, int>* range);

  // Sets whether commands are saved. If false, SessionCommands are effectively
  // dropped (deleted). This is intended for use after a crash to ensure no
  // commands are written before the user acknowledges/restores the crash.
  void SetSavingEnabled(bool enabled);

 private:
  friend class SessionServiceBaseTestHelper;
  friend class SessionServiceTestHelper;

  // If supported by the platform, initializes windowing system level session
  // and requests `discarded_window_ids` to be removed from the session. If it
  // succeeds, `platform_session_id_` should contain the session id to be used
  // by browser windows to associate them to a given platform session.
  void InitializePlatformSessionIfNeeded(
      const std::string& restored_platform_session_id,
      const std::set<SessionID>& discarded_window_ids);

  // This is always non-null.
  raw_ptr<Profile> profile_;

  // Whether to use delayed save. Set to false when constructed with a FilePath
  // (which should only be used for testing).
  bool should_use_delayed_save_ = true;

  std::unique_ptr<sessions::CommandStorageManager> command_storage_manager_;

  // Maps from session tab id to the range of navigation entries that has
  // been written to disk.
  //
  // This is only used if not all the navigation entries have been
  // written.
  IdToRange tab_to_available_range_;

  // Force session commands to be rebuild before next save event.
  bool rebuild_on_next_save_ = false;

  // Don't send duplicate SetSelectedTabInWindow commands when the selected
  // tab's index hasn't changed.
  std::map<SessionID, int> last_selected_tab_in_window_;

  // Set of windows we're tracking changes to. This is only browsers that
  // return true from |ShouldRestoreWindowOfType|.
  WindowsTracking windows_tracking_;

  bool is_saving_enabled_ = true;

  bool did_save_commands_at_least_once_ = false;

  // The platform session identifier, if supported and successfully initialized.
  // See GetPlatformSessionId() for more details.
  std::optional<std::string> platform_session_id_;

  base::ObserverList<SessionServiceBaseObserver>
      session_service_base_observers_;

  base::ScopedObservation<ProfileBrowserCollection, BrowserCollectionObserver>
      browser_collection_observer_{this};

  base::WeakPtrFactory<SessionServiceBase> weak_factory_{this};
};

#endif  // CHROME_BROWSER_SESSIONS_SESSION_SERVICE_BASE_H_
