// Copyright 2023 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_VIEWS_TABS_HOVERCARD_FADE_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_HOVERCARD_FADE_VIEW_H_

#include <type_traits>

#include "ui/base/metadata/metadata_cache.h"
#include "ui/base/metadata/metadata_types.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/animation/tween.h"
#include "ui/views/background.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"

// Adapts any view `T` so that the view can fade when used by a FadeView
template <typename T, typename V>
class FadeWrapper : public T {
  static_assert(std::is_base_of<views::View, T>::value,
                "Class T does not descend from views::View");
  METADATA_TEMPLATE_HEADER(FadeWrapper, T)

 public:
  template <typename... Args>
  explicit FadeWrapper(Args&&... args) : T(std::forward<Args>(args)...) {}

  virtual void SetData(const V& data) = 0;
  const V& GetData() { return data_; }

  // Sets the fade of this FadeWrapper's fade as `percent` in the range [0, 1]
  virtual void SetFade(double percent) = 0;

 protected:
  V data_;
};

// Cross fades any view so that view `U` is the old view that would be faded
// away. This allows view `T` to fade in for the user to ensure a smooth
// transition from the view with the old data `U` to the updated data in `T`
template <typename T, typename U, typename V>
class FadeView : public views::View {
  METADATA_TEMPLATE_HEADER(FadeView, views::View)

 public:
  FadeView(std::unique_ptr<T> primary_view, std::unique_ptr<U> fade_out_view) {
    SetUseDefaultFillLayout(true);
    primary_view_ = AddChildView(std::move(primary_view));
    fade_out_view_ = AddChildView(std::move(fade_out_view));
  }

  ~FadeView() override = default;

  void SetData(const V& data) {
    fade_out_view_->SetData(primary_view_->GetData());
    primary_view_->SetData(data);
  }

  // Sets the fade-out of the `fade_out_view_` as `percent` in the range [0, 1].
  // Since FadeView is designed to mask new view with the old and then fade
  // away, the higher the percentage the less opaque the view.
  void SetFade(double percent) {
    percent_ = percent;
    fade_out_view_->SetFade(percent);
    if (percent == 1.0) {
      fade_out_view_->SetData(V());
    }
  }

  gfx::Size GetMinimumSize() const override {
    return primary_view_->GetMinimumSize();
  }

  gfx::Size CalculatePreferredSize(
      const views::SizeBounds& available_size) const override {
    return primary_view_->CalculatePreferredSize(available_size);
  }

  gfx::Size GetMaximumSize() const override {
    return gfx::Tween::SizeValueBetween(percent_,
                                        fade_out_view_->GetPreferredSize(),
                                        primary_view_->GetPreferredSize());
  }

  T* GetPrimaryViewForTesting() { return primary_view_; }

 protected:
  raw_ptr<T> primary_view_ = nullptr;
  raw_ptr<U> fade_out_view_ = nullptr;
  double percent_ = 1.0;
};

#endif  // CHROME_BROWSER_UI_VIEWS_TABS_HOVERCARD_FADE_VIEW_H_
