/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_dom_ViewTransition_h
#define mozilla_dom_ViewTransition_h

#include "mozilla/Attributes.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/layers/IpcResourceUpdateQueue.h"
#include "nsAtomHashKeys.h"
#include "nsClassHashtable.h"
#include "nsRect.h"
#include "nsRefPtrHashtable.h"
#include "nsWrapperCache.h"

class nsIGlobalObject;
class nsITimer;

namespace mozilla {

class ErrorResult;
struct Keyframe;
struct PseudoStyleRequest;
struct StyleLockedDeclarationBlock;

namespace layers {
class RenderRootStateManager;
}

namespace wr {
struct ImageKey;
class IpcResourceUpdateQueue;
}  // namespace wr

namespace dom {

class ViewTransitionTypeSet;
extern LazyLogModule gViewTransitionsLog;

#define VT_LOG(...)                                                    \
  MOZ_LOG(mozilla::dom::gViewTransitionsLog, mozilla::LogLevel::Debug, \
          (__VA_ARGS__))

#ifdef DEBUG
#  define VT_LOG_DEBUG(...) VT_LOG(__VA_ARGS__)
#else
#  define VT_LOG_DEBUG(...)
#endif

class Document;
class Element;
class Promise;
class ViewTransitionUpdateCallback;

enum class SkipTransitionReason : uint8_t {
  JS,
  DocumentHidden,
  RootRemoved,
  ClobberedActiveTransition,
  Timeout,
  UpdateCallbackRejected,
  DuplicateTransitionNameCapturingOldState,
  DuplicateTransitionNameCapturingNewState,
  PseudoUpdateFailure,
  Resize,
  PageSwap,
  // Can happen due to various recoverable internal errors such as GPU process
  // crashes or GPU device resets.
  ResetRendering,
};

// https://drafts.csswg.org/css-view-transitions-1/#viewtransition-phase
enum class ViewTransitionPhase : uint8_t {
  PendingCapture = 0,
  UpdateCallbackCalled,
  Animating,
  PendingDone,
  Done,
};

struct ViewTransitionCapturedElement;

using ViewTransitionNamedElements =
    nsClassHashtable<nsAtomHashKey, ViewTransitionCapturedElement>;

// https://drafts.csswg.org/css-view-transitions-2/#view-transition-params
// See the same members on ViewTransition
struct ViewTransitionParams {
  ViewTransitionNamedElements namedElements;
  AutoTArray<RefPtr<nsAtom>, 8> names;
  nsSize initialSnapshotContainingBlockSize;

  ~ViewTransitionParams();
};

class ViewTransition final : public nsISupports, public nsWrapperCache {
 public:
  using Phase = ViewTransitionPhase;
  using TypeList = nsTArray<RefPtr<nsAtom>>;

  NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL
  NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ViewTransition)

  ViewTransition(Document&, ViewTransitionUpdateCallback*, TypeList&&);

  static already_AddRefed<ViewTransition> CreateCrossDocument(
      Document&, UniquePtr<ViewTransitionParams>, TypeList&&);

  Promise* GetUpdateCallbackDone(ErrorResult&);
  Promise* GetReady(ErrorResult&);
  Promise* GetFinished(ErrorResult&);
  ViewTransitionTypeSet* Types();
  const TypeList& GetTypeList() const { return mTypeList; }
  TypeList& GetTypeList() { return mTypeList; }

  void SkipTransition(SkipTransitionReason = SkipTransitionReason::JS);
  MOZ_CAN_RUN_SCRIPT void PerformPendingOperations();

  void FinishDone();

  // Get the snapshot containing block, which is the top-layer for rendering the
  // view transition tree.
  Element* GetSnapshotContainingBlock() const {
    return mSnapshotContainingBlock;
  }
  // Get ::view-transition pseudo element, which is the view transition tree
  // root. We find the pseudo element of this tree from this node.
  Element* GetViewTransitionTreeRoot() const;
  // Get all currently captured element frames if accessible.
  void GetCapturedFrames(nsTArray<nsIFrame*>& aCapturedFrames) const;

  Maybe<nsRect> GetOldInkOverflowRect(nsAtom* aName) const;
  Maybe<nsRect> GetNewInkOverflowRect(nsAtom* aName) const;
  Maybe<nsSize> GetOldBorderBoxSize(nsAtom* aName) const;
  Maybe<nsSize> GetNewBorderBoxSize(nsAtom* aName) const;
  Maybe<nsRect> GetOldActiveRect(nsAtom* aName) const;
  Maybe<nsRect> GetNewActiveRect(nsAtom* aName) const;
  // Use this to generate the old state image key for use in a stacking context.
  // Do not use the returned image key in an image display item, use
  // ReadOldImageKey instead.
  const wr::ImageKey* GetOrCreateOldImageKey(nsAtom* aName,
                                             layers::RenderRootStateManager*,
                                             wr::IpcResourceUpdateQueue&) const;
  // Use this to get the already-created old state image key for use in an image
  // display item.
  // This marks the old state image key as used which influences the how eagerly
  // it can be deleted.
  const wr::ImageKey* ReadOldImageKey(nsAtom* aName,
                                      layers::RenderRootStateManager*,
                                      wr::IpcResourceUpdateQueue&) const;
  const wr::ImageKey* GetNewImageKey(nsAtom* aName) const;
  const wr::ImageKey* GetImageKeyForCapturedFrame(
      nsIFrame* aFrame, layers::RenderRootStateManager*,
      wr::IpcResourceUpdateQueue&) const;
  void UpdateActiveRectForCapturedFrame(
      nsIFrame* capturedFrame, const gfx::MatrixScales& aInheritedScale,
      nsRect& aOutCapturedRect);

  Element* FindPseudo(const PseudoStyleRequest&) const;

  const StyleLockedDeclarationBlock* GetDynamicRuleFor(const Element&) const;

  static constexpr nsLiteralString kGroupAnimPrefix =
      u"-ua-view-transition-group-anim-"_ns;

  [[nodiscard]] bool GetGroupKeyframes(nsAtom* aAnimationName,
                                       const StyleComputedTimingFunction&,
                                       nsTArray<Keyframe>&);

  bool MatchClassList(nsAtom*, const nsTArray<StyleAtom>&) const;

  nsIGlobalObject* GetParentObject() const;
  JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) override;

  static nsRect SnapshotContainingBlockRect(nsPresContext*);
  static nsRect CapturedInkOverflowRectForFrame(nsIFrame*, bool aIsRoot);
  MOZ_CAN_RUN_SCRIPT void CallUpdateCallback(ErrorResult&);

  void Activate();

 private:
  using CapturedElement = ViewTransitionCapturedElement;

  MOZ_CAN_RUN_SCRIPT void MaybeScheduleUpdateCallback();

  void ClearActiveTransition(bool aIsDocumentHidden);
  void Timeout();
  MOZ_CAN_RUN_SCRIPT void Setup();
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Maybe<SkipTransitionReason>
  CaptureOldState();
  [[nodiscard]] Maybe<SkipTransitionReason> CaptureNewState();
  void SetupTransitionPseudoElements();
  [[nodiscard]] bool UpdatePseudoElementStyles(bool aNeedsInvalidation);
  void ClearNamedElements();
  void HandleFrame();
  bool CheckForActiveAnimations() const;
  void SkipTransition(SkipTransitionReason, JS::Handle<JS::Value>);
  void ClearTimeoutTimer();

  nsRect SnapshotContainingBlockRect() const;

  Maybe<uint64_t> GetElementIdentifier(Element* aElement) const;
  uint64_t EnsureElementIdentifier(Element* aElement);

  already_AddRefed<nsAtom> DocumentScopedTransitionNameFor(nsIFrame* aFrame);

  ~ViewTransition();

  // Stored for the whole lifetime of the object (until CC).
  RefPtr<Document> mDocument;
  RefPtr<ViewTransitionUpdateCallback> mUpdateCallback;

  // https://drafts.csswg.org/css-view-transitions/#viewtransition-named-elements
  ViewTransitionNamedElements mNamedElements;
  // mNamedElements is an unordered map, we need to keep the tree order. This
  // also keeps the strong reference to the view-transition-name which may be
  // auto-generated for this view transition.
  AutoTArray<RefPtr<nsAtom>, 8> mNames;

  using OldCaptureFramesArray =
      AutoTArray<std::pair<nsIFrame*, RefPtr<nsAtom>>, 32>;
  // Short lived array pointer used to mark old captures for DL building.
  OldCaptureFramesArray* mOldCaptureElements = nullptr;

  // The element identifier for the elements which need the auto-generated
  // view-transition-name. The lifetime of those element identifiers is
  // element’s node document’s active view transition.
  // Note: Use a non-owning element pointer because we never dereference it.
  // It is just a key to map an id. The size of this hashmap is fixed after we
  // capture the old state and new state,
  using ElementIdentifiers = nsTHashMap<Element*, uint64_t>;
  ElementIdentifiers mElementIdentifiers;

  // https://drafts.csswg.org/css-view-transitions/#viewtransition-initial-snapshot-containing-block-size
  nsSize mInitialSnapshotContainingBlockSize;

  // Allocated lazily, but same object once allocated (again until CC).
  RefPtr<Promise> mUpdateCallbackDonePromise;
  RefPtr<Promise> mReadyPromise;
  RefPtr<Promise> mFinishedPromise;

  TypeList mTypeList;
  RefPtr<ViewTransitionTypeSet> mTypes;

  static void TimeoutCallback(nsITimer*, void*);
  RefPtr<nsITimer> mTimeoutTimer;

  Phase mPhase = Phase::PendingCapture;
  // The wrapper of the pseudo-elements tree, to make sure it is always
  // out-of-flow. This is the top-layer for rendering the view transition tree.
  // So in general, its child (and only one) is the transition root
  // pseudo-element.
  RefPtr<Element> mSnapshotContainingBlock;
};

}  // namespace dom
}  // namespace mozilla

#endif
