// Copyright 2026 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_UI_ANIMATION_BROWSER_ANIMATION_CONTROLLER_H_
#define CHROME_BROWSER_UI_ANIMATION_BROWSER_ANIMATION_CONTROLLER_H_

#include <map>
#include <memory>
#include <optional>
#include <string_view>

#include "base/callback_list.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/animation/browser_animation_provider.h"
#include "chrome/browser/ui/animation/browser_animation_provider_internal.h"
#include "chrome/browser/ui/animation/browser_animation_types.h"
#include "ui/base/unowned_user_data/scoped_unowned_user_data.h"

class BrowserWindowInterface;

namespace gfx {
class AnimationContainer;
}

namespace internal {
struct BrowserAnimationMotionSpecification;
}

namespace views {
class View;
}

// Manages multi-part, multi-stage animations for browser. These are designed to
// work a bit like CSS animations, but with more power, with the ability to have
// several UI elements each experiencing different animation segments at
// different points with different tween curves in a single overall animation
// timeline.
//
// Group: Side Panel
//  Motion: Expand
//   0 @ 0 ms | panel width (swoop) ----| 1 @ 350 ms
//         0 @ 200 ms | shadow box inset (swoop) ------| 1 @ 700 ms
//                         0 @ 500 ms | shadow depth (linear) -----| 1 @ 1000 ms
//  Motion: Collapse
//   1 @ 0 ms | panel width (swoop) -----| 0 @ 350 ms
//   1 @ 0 ms | shadow box inset (swoop) | 0 @ 350 ms
//   1 @ 0 ms | shadow depth (linear) ---| 0 @ 300 ms
//
// Full details on how to use the system are in README.md.
class BrowserAnimationController {
 public:
  DECLARE_USER_DATA(BrowserAnimationController);

  static constexpr char kFramesPerSecondHistogramSuffix[] = ".AnimationFPS";
  static constexpr char kLongestFrameHistogramSuffix[] =
      ".TimeOfLongestAnimationStep";

  explicit BrowserAnimationController(BrowserWindowInterface& browser);
  BrowserAnimationController(const BrowserAnimationController&) = delete;
  void operator=(const BrowserAnimationController&) = delete;
  ~BrowserAnimationController();

  // Call once there is a valid view. If null or never called, will use a
  // time-based rather than compositor-based animation scheme (should only
  // happen in tests).
  //
  // Instead of setting the view back to null on teardown, just destroy the
  // controller.
  void set_browser_view(views::View* browser_view) {
    browser_view_ = browser_view;
  }

  // Fetch the controller for a browser window.
  static BrowserAnimationController* From(BrowserWindowInterface* browser);
  static const BrowserAnimationController* From(
      const BrowserWindowInterface* browser);

  // Adds any animations generated by `builder`. Groups must not already have
  // been added.
  template <typename T, typename... Args>
    requires std::derived_from<T, BrowserAnimationProvider>
  T* AddAnimationProvider(std::unique_ptr<T> provider) {
    T* const result = provider.get();
    providers_.emplace_back(std::move(provider));
    return result;
  }

  // Retrieve a provider by type, or null if none.
  template <typename T>
    requires std::derived_from<T, BrowserAnimationProvider>
  const T* GetAnimationProvider() const {
    for (auto& provider : providers_) {
      if (const T* const result = provider->AsA<T>()) {
        return result;
      }
    }
    return nullptr;
  }

  // Retrieve a provider by type, or null if none.
  template <typename T, typename... Args>
    requires std::derived_from<T, BrowserAnimationProvider>
  T* GetAnimationProvider() {
    return const_cast<T*>(const_cast<const BrowserAnimationController*>(this)
                              ->GetAnimationProvider<T>());
  }

  // Is the given `group` animating?
  bool IsAnimating(BrowserAnimationGroup group) const;

  // Returns the current motion for `group` or null/falsy if it is not
  // animating.
  BrowserAnimationMotion GetCurrentMotion(BrowserAnimationGroup group) const;

  // Returns the duration of the current animation motion playing for
  // `group`, or zero if no animation is playing.
  base::TimeDelta GetMotionDuration(BrowserAnimationGroup group) const;

  // Starts animating `group` with `motion`. If the group is already animating,
  // does whatever transition behavior has been specified.
  //
  // There will be a cancel event for the current motion and a start event for
  // `motion`.
  //
  // If you do not wish to use the default group or motion histogram components,
  // you can override them. See documentation in `BrowserAnimationProvider` and
  // the README file for more info.
  void Start(
      BrowserAnimationGroup group,
      BrowserAnimationMotion motion,
      std::optional<std::string_view> group_histogram_override = std::nullopt,
      std::optional<std::string_view> motion_histogram_override = std::nullopt);

  // Resets the current state of `group` to the end of `motion`, regardless of
  // what is currently happening. If `motion` is not specified then the current
  // motion is snapped to its end (if there is no motion, this is a no-op).
  //
  // There will be a cancel event for the current motion and an ended event for
  // `motion`. If `motion` is already playing, then it simply fast-forwards to
  // the end of the current motion.
  void Reset(BrowserAnimationGroup group,
             BrowserAnimationMotion motion = BrowserAnimationMotion());

  // Cancels the current motion associated with `group` and returns true if one
  // was running. All values are discarded so `GetCurrentValue()` will return
  // null (or default) for all sequences in `group`.
  //
  // Note: You probably want to use `Reset()`.
  bool Clear(BrowserAnimationGroup group);

  // Gets the current animation value for `sequence` in `group`. If there is
  // none, returns default, or if there is no default, returns null.
  std::optional<double> GetCurrentValue(
      BrowserAnimationGroup group,
      BrowserAnimationSequence sequence) const;

  // Subscribes for updates on `group`. There is always a callback after a
  // cancel or animation end.
  base::CallbackListSubscription Subscribe(BrowserAnimationGroup group,
                                           BrowserAnimationCallback callback);

  // Fetches the given motion specification or null if not found.
  std::optional<internal::BrowserAnimationMotionSpecification>
  GetMotionSpecificationForTesting(BrowserAnimationGroup group,
                                   BrowserAnimationMotion motion) const;

  // Removes and returns the given provider. Marked [[nodiscard]] because often
  // `provider` will come from a `raw_ptr` which will need to be cleared out
  // before the provider is destroyed.
  [[nodiscard]]
  std::unique_ptr<BrowserAnimationProvider> RemoveProviderForTesting(
      BrowserAnimationProvider* provider);

  // Sets the animation container to use for `group`.
  void SetAnimationContainerForTesting(BrowserAnimationGroup group,
                                       gfx::AnimationContainer* container);

 private:
  // Holds data about a currently running motion.
  class GroupData;
  struct MotionInfo;

  std::optional<MotionInfo> GetMotionInfo(BrowserAnimationGroup group,
                                          BrowserAnimationMotion motion) const;

  GroupData& GetGroupData(BrowserAnimationGroup group);
  const GroupData& GetGroupData(BrowserAnimationGroup group) const;

  internal::BrowserAnimationSequenceParamsLookup GetAllSequenceParams(
      BrowserAnimationGroup group) const;

  std::optional<internal::BrowserAnimationSequenceParams> GetSequenceParams(
      BrowserAnimationGroup group,
      BrowserAnimationSequence sequence) const;

  // The list of providers. Must always be destructed last.
  std::vector<std::unique_ptr<BrowserAnimationProvider>> providers_;

  // Execution data is always created on-demand, as we don't necessarily know
  // the full list of animation groups or motions in the system ahead of time.
  //
  // However, execution data is limited to the number of registered animation
  // groups, which is bounded at compile time, so while this map is not cleaned
  // up until the browser window closes, it can only get so large.
  mutable std::map<BrowserAnimationGroup, std::unique_ptr<GroupData>>
      execution_data_;

  // This widget is used to create compositor-driven animations. If not set, a
  // timer-based animation is used instead.
  raw_ptr<views::View> browser_view_ = nullptr;
  ui::ScopedUnownedUserData<BrowserAnimationController> scoped_user_data_;
};

#endif  // CHROME_BROWSER_UI_ANIMATION_BROWSER_ANIMATION_CONTROLLER_H_
