// 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.

#ifndef CHROME_BROWSER_TAB_TAB_STATE_STORAGE_DATABASE_H_
#define CHROME_BROWSER_TAB_TAB_STATE_STORAGE_DATABASE_H_

#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/types/pass_key.h"
#include "chrome/browser/tab/storage_id.h"
#include "chrome/browser/tab/storage_loaded_data.h"
#include "chrome/browser/tab/tab_storage_type.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/transaction.h"

namespace tabs {

// This class is responsible for all database operations.
class TabStateStorageDatabase {
 public:
  // Represents a transaction. Used as a key for database updates, and must be
  // returned to commit the transaction.
  class OpenTransaction {
   public:
    OpenTransaction(sql::Database* db, base::PassKey<TabStateStorageDatabase>);

    ~OpenTransaction();
    OpenTransaction(const OpenTransaction&) = delete;
    OpenTransaction& operator=(const OpenTransaction&) = delete;

    // Marks the transaction as failed. Rolls back the transaction once
    // returned.
    void MarkFailed();

    // Returns whether the transaction has failed.
    bool HasFailed();

    // Returns whether the transaction is valid.
    static bool IsValid(OpenTransaction* transaction);

    // Returns the underlying transaction.
    sql::Transaction* GetTransaction(base::PassKey<TabStateStorageDatabase>);

    // Adds a callback to be run after the transaction is successfully
    // committed.
    void AddCallback(base::OnceClosure callback);

    // Takes the callbacks out of the transaction.
    std::vector<base::OnceClosure> TakeCallbacks();

   private:
    sql::Transaction transaction_;
    bool mark_failed_ = false;
    std::vector<base::OnceClosure> callbacks_;
  };

  TabStateStorageDatabase(const base::FilePath& profile_path,
                          bool support_off_the_record_data);
  ~TabStateStorageDatabase();
  TabStateStorageDatabase(const TabStateStorageDatabase&) = delete;
  TabStateStorageDatabase& operator=(const TabStateStorageDatabase&) = delete;

  // Initializes the database.
  bool Initialize();

  // Saves a node to the database.
  bool SaveNode(OpenTransaction* transaction,
                StorageId id,
                std::string_view window_tag,
                bool is_off_the_record,
                TabStorageType type,
                std::vector<uint8_t> payload,
                std::vector<uint8_t> children);

  // Saves a node payload to the database.
  // This will silently fail if the node does not already exist.
  bool SaveNodePayload(OpenTransaction* transaction,
                       StorageId id,
                       std::string_view window_tag,
                       bool is_off_the_record,
                       std::vector<uint8_t> payload);

  // Saves the children of a node to the database.
  // This will silently fail if the node does not already exist.
  bool SaveNodeChildren(OpenTransaction* transaction,
                        StorageId id,
                        std::vector<uint8_t> children);

  // Inserts or updates a divergent node.
  bool SaveDivergentNode(OpenTransaction* transaction,
                         StorageId id,
                         std::string_view window_tag,
                         bool is_off_the_record,
                         std::vector<uint8_t> children);

  // Removes a node from the database.
  // This will silently fail if the node does not already exist.
  bool RemoveNode(OpenTransaction* transaction, StorageId id);

  // Creates an open transaction.
  OpenTransaction* CreateTransaction();

  // Closes the existing transaction.
  bool CloseTransaction(OpenTransaction* transaction);

  // Loads all nodes from the database.
  std::unique_ptr<StorageLoadedData> LoadAllNodes(
      std::string_view window_tag,
      bool is_off_the_record,
      std::unique_ptr<StorageLoadedData::Builder> builder);

  // Clears all nodes from the database.
  void ClearAllNodes();

  // Clears all divergent nodes from the database.
  void ClearAllDivergentNodes();

  // Clears all nodes for a given window from the database.
  void ClearWindow(std::string_view window_tag);

  // Clears all divergent nodes for a given window from the database.
  void ClearDivergentNodesForWindow(std::string_view window_tag,
                                    bool is_off_the_record);

  // Clears a divergence window from the database.
  void ClearDivergenceWindow(std::string_view window_tag);

  // Clears all windows except for those with the provided tags.
  bool ClearAllWindowsExcept(const std::vector<std::string>& window_tags);

  // Clears all nodes for a given window from the database except for the
  // provided storage IDs.
  bool ClearNodesForWindowExcept(std::string_view window_tag,
                                 bool is_off_the_record,
                                 const std::vector<StorageId>& ids);

  // Counts the number of tabs for a given window.
  int CountTabsForWindow(std::string_view window_tag, bool is_off_the_record);

  // Sets the key to seal OTR payloads with. The window tag is moved
  // internally and this is always called in a posted callback hence
  // the use of std::string.
  void SetKey(std::string window_tag, std::vector<uint8_t> key);

  // Remove key for OTR sealing from a given window.
  void RemoveKey(std::string_view window_tag);

#if defined(NDEBUG)
  // Dumps the entire state of the database to the log for debugging. Do not use
  // in production.
  //
  // Because `StorageId` tokens are randomly generated and difficult to visually
  // parse, this method maps them to sequential, temporary integers
  // (e.g., 1, 2, 3...) for the duration of the dump.
  //
  // The output consists of:
  // 1. A list of all nodes, using the temporary integers for `id` and
  // `children`.
  // 2. A legend mapping each temporary integer back to its original `StorageId`
  // token.
  void PrintAll();
#endif

 private:
  std::optional<std::vector<uint8_t>> Seal(StorageId id,
                                           std::string_view window_tag,
                                           base::span<const uint8_t> payload);
  std::optional<std::vector<uint8_t>> Open(StorageId storage_id,
                                           std::string_view window_tag,
                                           base::span<const uint8_t> payload);

  const base::FilePath profile_path_;
  const bool support_off_the_record_data_;
  sql::Database db_;
  sql::MetaTable meta_table_;
  std::optional<OpenTransaction> open_transaction_;
  int open_transaction_count_ = 0;

  // A map of window tags to their associated keys for OTR payloads.
  absl::flat_hash_map<std::string, std::vector<uint8_t>> keys_;
};

}  // namespace tabs

#endif  // CHROME_BROWSER_TAB_TAB_STATE_STORAGE_DATABASE_H_
