// 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 UI_BASE_INTERACTION_POLLING_STATE_OBSERVER_H_
#define UI_BASE_INTERACTION_POLLING_STATE_OBSERVER_H_

#include <optional>
#include <utility>

#include "base/functional/callback_forward.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/interactive_test_definitions.h"
#include "ui/base/interaction/state_observer.h"

namespace ui::test {

// Observer which polls a state that cannot be observed via listeners/callbacks.
// Continues to poll for the lifetime of the test.
//
// Because this is a polling observer, the value is only valid at the moment it
// is observed, and transient values may be missed. For transient values, prefer
// deterministic state observation, custom events, etc.
//
// Designed for use with the `InteractiveTestApi::PollState()` verb.
template <typename T, typename M = internal::MatcherTypeFor<T>>
class PollingStateObserver : public StateObserver<M> {
 public:
  using PollCallback = base::RepeatingCallback<T()>;

  static constexpr base::TimeDelta kDefaultPollingInterval =
      base::Milliseconds(100);

  // Calls `poll_callback` to get an updated value every `polling_interval`.
  // `poll_callback` will also be called on initialization to get an initial
  // value.
  template <typename C>
  explicit PollingStateObserver(
      C&& poll_callback,
      base::TimeDelta polling_interval = kDefaultPollingInterval)
      : poll_callback_(
            internal::MaybeBindRepeating(std::forward<C>(poll_callback))),
        timer_(FROM_HERE,
               polling_interval,
               base::BindRepeating(&PollingStateObserver<T>::OnPoll,
                                   base::Unretained(this))) {
    timer_.Reset();
  }

  ~PollingStateObserver() override = default;

  // StateObserver:
  M GetStateObserverInitialState() const final {
    return M(poll_callback_.Run());
  }

 private:
  void OnPoll() {
    StateObserver<M>::OnStateObserverStateChanged(M(poll_callback_.Run()));
  }

  // The callback that returns the value when polled.
  PollCallback poll_callback_;

  // This timer will fire every `polling_interval`
  base::RepeatingTimer timer_;
};

// Need out-of-line declaration of static class variables on some platforms.
template <typename T, typename M>
// static
constexpr base::TimeDelta PollingStateObserver<T, M>::kDefaultPollingInterval;

// Observer which polls a specific element that cannot be observed via
// listeners/callbacks. If an element with `identifier` in `context` is present,
// the observed value will be updated to the result of `poll_element_callback`,
// whereas if the element is not present in the context, it will be
// std::nullopt.
//
// If `context` is not specified, then the element will be located in any
// context.
//
// Because this is a polling observer, the value is only valid at the moment it
// is observed, and transient values may be missed. For transient values, prefer
// deterministic state observation, custom events, etc.
//
// Designed for use with the `InteractiveTestApi::PollElement()` verb.
template <typename T,
          typename Superclass =
              PollingStateObserver<std::optional<T>,
                                   std::optional<internal::MatcherTypeFor<T>>>>
class PollingElementStateObserver : public Superclass {
 public:
  using PollElementCallback = base::RepeatingCallback<T(const TrackedElement*)>;

  // Calls `poll_element_callback` on the element with `identifier` in
  // `context` to update the value every `polling_interval`. If a matching
  // element is not present, the value is `std::nullopt`. The callback will be
  // called on initialization (if the element already exists) to get an initial
  // value.
  template <typename C>
  PollingElementStateObserver(
      ElementIdentifier identifier,
      std::optional<ElementContext> context,
      C&& poll_element_callback,
      base::TimeDelta polling_interval = Superclass::kDefaultPollingInterval)
      : Superclass(
            base::BindRepeating(
                [](ElementIdentifier id,
                   std::optional<ElementContext> ctx,
                   PollElementCallback cb) {
                  auto* const el = ctx.has_value()
                                       ? ElementTracker::GetElementTracker()
                                             ->GetFirstMatchingElement(id, *ctx)
                                       : ElementTracker::GetElementTracker()
                                             ->GetElementInAnyContext(id);
                  return el ? std::make_optional(cb.Run(el)) : std::nullopt;
                },
                identifier,
                context,
                internal::MaybeBindRepeating(
                    std::forward<C>(poll_element_callback))),
            polling_interval) {}

  ~PollingElementStateObserver() override = default;
};

template <typename T>
concept IsPollingStateObserver = requires {
  typename T::ValueType;
} && std::derived_from<T, PollingStateObserver<typename T::ValueType>>;

}  // namespace ui::test

#endif  // UI_BASE_INTERACTION_POLLING_STATE_OBSERVER_H_
