// Copyright 2024 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_WEB_APPLICATIONS_ISOLATION_DATA_H_
#define CHROME_BROWSER_WEB_APPLICATIONS_ISOLATION_DATA_H_

#include <optional>
#include <set>
#include <string>

#include "chrome/browser/web_applications/model/integrity_block_data.h"
#include "components/webapps/isolated_web_apps/types/iwa_version.h"
#include "components/webapps/isolated_web_apps/types/storage_location.h"
#include "components/webapps/isolated_web_apps/types/update_channel.h"
#include "url/gurl.h"

namespace web_app {

// Represents IWA-specific pieces of a Web App.
class IsolationData {
 public:
  struct PendingUpdateInfo {
    PendingUpdateInfo(
        IsolatedWebAppStorageLocation location,
        IwaVersion version,
        std::optional<IntegrityBlockData> integrity_block_data = std::nullopt);
    ~PendingUpdateInfo();
    PendingUpdateInfo(const PendingUpdateInfo&);
    PendingUpdateInfo& operator=(const PendingUpdateInfo&);

    bool operator==(const PendingUpdateInfo&) const = default;

    base::Value AsDebugValue() const;
    friend std::ostream& operator<<(std::ostream& os,
                                    const PendingUpdateInfo& update_info) {
      return os << update_info.AsDebugValue();
    }

    IsolatedWebAppStorageLocation location;
    IwaVersion version;

    std::optional<IntegrityBlockData> integrity_block_data;
  };

  class OpenedTabsCounterNotificationState {
   public:
    explicit OpenedTabsCounterNotificationState(
        proto::IsolationData_OpenedTabsCounterNotificationState state);
    OpenedTabsCounterNotificationState(bool acknowledged, uint32_t times_shown);

    ~OpenedTabsCounterNotificationState() = default;
    OpenedTabsCounterNotificationState(
        const OpenedTabsCounterNotificationState&) = default;
    OpenedTabsCounterNotificationState& operator=(
        const OpenedTabsCounterNotificationState&) = default;
    OpenedTabsCounterNotificationState(OpenedTabsCounterNotificationState&&) =
        default;
    OpenedTabsCounterNotificationState& operator=(
        OpenedTabsCounterNotificationState&&) = default;

    bool operator==(const OpenedTabsCounterNotificationState& other) const {
      return proto_state_.SerializeAsString() ==
             other.proto_state_.SerializeAsString();
    }

    bool acknowledged() const { return proto_state_.acknowledged(); }
    uint32_t times_shown() const { return proto_state_.times_shown(); }

    const proto::IsolationData::OpenedTabsCounterNotificationState& GetState()
        const;

   private:
    proto::IsolationData::OpenedTabsCounterNotificationState proto_state_;
  };

  ~IsolationData();
  IsolationData(const IsolationData&);
  IsolationData& operator=(const IsolationData&);
  IsolationData(IsolationData&&);
  IsolationData& operator=(IsolationData&&);

  bool operator==(const IsolationData&) const = default;

  base::Value AsDebugValue() const;
  friend std::ostream& operator<<(std::ostream& os,
                                  const IsolationData& isolation_data) {
    return os << isolation_data.AsDebugValue();
  }

  const IsolatedWebAppStorageLocation& location() const { return location_; }
  const IwaVersion& version() const { return version_; }
  const std::set<std::string>& controlled_frame_partitions() const {
    return controlled_frame_partitions_;
  }
  const std::optional<PendingUpdateInfo>& pending_update_info() const {
    return pending_update_info_;
  }
  const std::optional<IntegrityBlockData>& integrity_block_data() const {
    return integrity_block_data_;
  }
  const std::optional<GURL>& update_manifest_url() const {
    return update_manifest_url_;
  }
  const std::optional<UpdateChannel>& update_channel() const {
    return update_channel_;
  }

  const std::optional<OpenedTabsCounterNotificationState>&
  opened_tabs_counter_notification_state() const {
    return opened_tabs_counter_notification_state_;
  }

 private:
  IsolationData(IsolatedWebAppStorageLocation location,
                IwaVersion version,
                std::set<std::string> controlled_frame_partitions,
                std::optional<PendingUpdateInfo> pending_update_info,
                std::optional<IntegrityBlockData> integrity_block_data,
                std::optional<GURL> update_manifest_url,
                std::optional<UpdateChannel> update_channel,
                std::optional<OpenedTabsCounterNotificationState>
                    opened_tabs_counter_notification_state);

  IsolatedWebAppStorageLocation location_;
  IwaVersion version_;

  std::set<std::string> controlled_frame_partitions_;

  // If present, signals that an update for this app is available locally and
  // waiting to be applied.
  std::optional<PendingUpdateInfo> pending_update_info_;

  // Might be nullopt if this IWA is not backed by a signed web bundle (for
  // instance, in case of a proxy mode installation).
  // This field is used to prevent redundant update attempts in case of key
  // rotation by comparing the stored public keys against the rotated key.
  // key. Please don't rely on it for anything security-critical!
  std::optional<IntegrityBlockData> integrity_block_data_;

  // Informs the browser where to look up the update manifest for this IWA and
  // which update channel to use.
  // These fields are only used for dev mode installs from update manifest via
  // chrome://web-app-internals; for all other install types they are left
  // blank. For unmanaged installs this will likely need to have a counterpart
  // in PendingUpdateInfo.
  std::optional<GURL> update_manifest_url_;

  std::optional<OpenedTabsCounterNotificationState>
      opened_tabs_counter_notification_state_;

  std::optional<UpdateChannel> update_channel_;

 public:
  class Builder {
   public:
    Builder(IsolatedWebAppStorageLocation location, IwaVersion version);
    explicit Builder(const IsolationData& isolation_data);
    ~Builder();

    Builder(const Builder&);
    Builder& operator=(const Builder&);
    Builder(Builder&&);
    Builder& operator=(Builder&&);

    Builder& SetControlledFramePartitions(
        std::set<std::string> controlled_frame_partitions) &;
    Builder&& SetControlledFramePartitions(
        std::set<std::string> controlled_frame_partitions) &&;

    // Will `CHECK` if dev mode is different between
    // `pending_update_info.location` and `location`. In other words, a dev mode
    // owned bundle can never be updated to a prod mode owned bundle.
    Builder& SetPendingUpdateInfo(
        IsolationData::PendingUpdateInfo pending_update_info) &;
    Builder&& SetPendingUpdateInfo(
        IsolationData::PendingUpdateInfo pending_update_info) &&;

    Builder& SetOpenedTabsCounterNotificationState(
        OpenedTabsCounterNotificationState notification_state) &;
    Builder&& SetOpenedTabsCounterNotificationState(
        OpenedTabsCounterNotificationState notification_state) &&;

    Builder& ClearPendingUpdateInfo() &;
    Builder&& ClearPendingUpdateInfo() &&;

    Builder& SetIntegrityBlockData(IntegrityBlockData integrity_block_data) &;
    Builder&& SetIntegrityBlockData(IntegrityBlockData integrity_block_data) &&;

    Builder& SetUpdateManifestUrl(GURL update_manifest_url) &;
    Builder&& SetUpdateManifestUrl(GURL update_manifest_url) &&;

    // Update channel can be set either in dev mode or in user installed app.
    Builder& SetUpdateChannel(UpdateChannel update_channel) &;
    Builder&& SetUpdateChannel(UpdateChannel update_channel) &&;

    // During an update the foundational pieces of the IWA (`location` and
    // `version`) of the IWA change, and hence the IsolationData has to be
    // re-built from scratch. This function is called as part of the update
    // finalize routine -- all fields that have to be persisted (such as
    // `controlled_frame_partitions`, etc) can be copied over here.
    Builder& PersistFieldsForUpdate(const IsolationData& isolation_data) &;
    Builder&& PersistFieldsForUpdate(const IsolationData& isolation_data) &&;

    // When adding new setters to the builder, make sure to update the the
    // Builder(const IsolationData&) constructor to forward the new field as
    // well as PersistFieldsForUpdate(const IsolationData&) if necessary.
    IsolationData Build() &&;

   private:
    IsolatedWebAppStorageLocation location_;
    IwaVersion version_;

    std::set<std::string> controlled_frame_partitions_;
    std::optional<IsolationData::PendingUpdateInfo> pending_update_info_;
    std::optional<IntegrityBlockData> integrity_block_data_;
    std::optional<GURL> update_manifest_url_;
    std::optional<OpenedTabsCounterNotificationState>
        opened_tabs_counter_notification_state_;
    std::optional<UpdateChannel> update_channel_;
  };
};

}  // namespace web_app

#endif  // CHROME_BROWSER_WEB_APPLICATIONS_ISOLATION_DATA_H_
