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

#ifndef COMPONENTS_SYNC_MODEL_PROCESSOR_ENTITY_H_
#define COMPONENTS_SYNC_MODEL_PROCESSOR_ENTITY_H_

#include <stdint.h>

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/time/time.h"
#include "components/sync/base/client_tag_hash.h"
#include "components/sync/base/deletion_origin.h"
#include "components/sync/model/processor_entity_metadata.h"
#include "components/sync/protocol/entity_metadata.pb.h"

namespace sync_pb {
class EntitySpecifics;
class UniquePosition;
}  // namespace sync_pb

namespace syncer {

class ClientTagHash;
struct EntityData;
struct CommitRequestData;
struct CommitResponseData;
struct UpdateResponseData;

// This class is used by the ClientTagBasedDataTypeProcessor to track the state
// of each entity with its type. It can be considered a helper class internal to
// the processor. It manages the metadata for its entity and caches entity data
// upon a local change until commit confirmation is received.
class ProcessorEntity {
 public:
  // Construct an instance representing either a new locally-created item, or a
  // newly-received remote item.
  static std::unique_ptr<ProcessorEntity> CreateNew(
      const std::string& storage_key,
      const ClientTagHash& client_tag_hash,
      const std::string& server_id,
      base::Time creation_time);

  // Construct an instance representing an item loaded from storage on init. May
  // return nullptr if the passed-in `metadata` is invalid.
  static std::unique_ptr<ProcessorEntity> CreateFromMetadata(
      const std::string& storage_key,
      sync_pb::EntityMetadata metadata);

  ~ProcessorEntity();

  const std::string& storage_key() const { return storage_key_; }
  const sync_pb::EntityMetadata& metadata() const { return metadata_.proto(); }

  ClientTagHash GetClientTagHash() const;

  // Returns true if this data is out of sync with the server.
  // A commit may or may not be in progress at this time.
  bool IsUnsynced() const;

  // Returns true if this entity was created locally and not yet committed to
  // the server (including while the commit is in flight, until a response is
  // received from the server).
  bool IsUnsyncedLocalCreation() const;

  // Returns true if this data is out of sync with the sync thread.
  //
  // There may or may not be a commit in progress for this item, but there's
  // definitely no commit in progress for this (most up to date) version of
  // this item.
  bool RequiresCommitRequest() const;

  // Whether commit data is needed to be cached before a commit request can be
  // created. Note that deletions do not require cached data.
  bool RequiresCommitData() const;

  // Whether it's safe to clear the metadata for this entity. This means that
  // the entity is deleted and it is up to date with the server (i.e. is *not*
  // unsynced).
  bool CanClearMetadata() const;

  // Returns true if the specified `update_version` is already known, i.e. is
  // smaller or equal to the last known server version.
  // This is the case for reflections, but can also be true in some other edge
  // cases (e.g. updates were received out of order).
  bool IsVersionAlreadyKnown(int64_t update_version) const;

  // Records that an update from the server was received but ignores its data.
  void RecordIgnoredRemoteUpdate(const UpdateResponseData& response_data);

  // Records an update from the server assuming its data is the new data for
  // this entity.
  void RecordAcceptedRemoteUpdate(
      const UpdateResponseData& response_data,
      sync_pb::EntitySpecifics trimmed_specifics,
      std::optional<sync_pb::UniquePosition> unique_position);

  // Squashes a pending commit with an update from the server.
  // There was a conflict and the server just won it.
  void RecordForcedRemoteUpdate(
      const UpdateResponseData& response_data,
      sync_pb::EntitySpecifics trimmed_specifics,
      std::optional<sync_pb::UniquePosition> unique_position);

  // Overrides the server ID and version. Crucial for handling server-initiated
  // metadata resets (migration) where we want to force apply updates.
  void OverrideServerMetadata(const std::string& server_id,
                              int64_t server_version);

  // Applies a local change to this item.
  void RecordLocalUpdate(
      std::unique_ptr<EntityData> data,
      sync_pb::EntitySpecifics trimmed_specifics,
      std::optional<sync_pb::UniquePosition> unique_position);

  // Applies a local deletion to this item. Returns true if entity was
  // previously committed to server and tombstone should be sent.
  bool RecordLocalDeletion(const DeletionOrigin& origin);

  // Initializes a message representing this item's uncommitted state
  // and assumes that it is forwarded to the sync engine for committing.
  void InitializeCommitRequestData(CommitRequestData* request);

  // Receives a successful commit response.
  //
  // Successful commit responses can overwrite an item's ID.
  //
  // Note that the receipt of a successful commit response does not necessarily
  // unset IsUnsynced().  If many local changes occur in quick succession, it's
  // possible that the committed item was already out of date by the time it
  // reached the server.
  void ReceiveCommitResponse(const CommitResponseData& data, bool commit_only);

  // Clears any in-memory sync state associated with outstanding commits.
  void ClearTransientSyncState();

  // Updates `storage_key_`. Allows setting storage key for datatypes that don't
  // generate storage keys from syncer::EntityData. Should only be called for
  // an entity initialized with empty storage key, or for which the storage key
  // was explicitly cleared.
  void SetStorageKey(const std::string& storage_key);

  // Undoes SetStorageKey(), which is needed in certain conflict resolution
  // scenarios.
  void ClearStorageKey();

  // Takes the passed commit data updates its fields with values from metadata
  // and caches it in the instance.
  void SetCommitData(std::unique_ptr<EntityData> data);

  // Check if the instance has cached commit data.
  bool HasCommitData() const;

  // Check whether `data` matches the stored specifics hash.
  bool MatchesData(const EntityData& data) const;

  // Check whether the current metadata of an unsynced entity matches the stored
  // base specifics hash.
  bool MatchesOwnBaseData() const;

  // Check whether `data` matches the stored base specifics hash.
  bool MatchesBaseData(const EntityData& data) const;

  // Increment sequence number in the metadata. This will also update the
  // base_specifics_hash if the entity was not already unsynced.
  void IncrementSequenceNumber(base::Time modification_time);

  // Returns the estimate of dynamically allocated memory in bytes.
  size_t EstimateMemoryUsage() const;

  const EntityData& GetCommitDataForTesting() { return *commit_data_; }

 private:
  ProcessorEntity(const std::string& storage_key,
                  ProcessorEntityMetadata metadata);

  // Updates in-memory commit data and sequence number after a remote update is
  // recorded in metadata.
  void UpdateCommitDataAndSequenceAfterRemoteUpdate();

  // Storage key.
  std::string storage_key_;

  // Serializable Sync metadata.
  ProcessorEntityMetadata metadata_;

  // Sync data that exists for items being committed only. The data is moved
  // away when sending the commit request.
  std::unique_ptr<EntityData> commit_data_;

  // The sequence number of the last item sent to the sync thread.
  int64_t commit_requested_sequence_number_;
};

}  // namespace syncer

#endif  // COMPONENTS_SYNC_MODEL_PROCESSOR_ENTITY_H_
