// 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.

#include "android_webview/browser/prefetch/aw_prefetch_handle_wrapper.h"

#include <ostream>

#include "android_webview/browser/prefetch/aw_preloading_utils.h"
#include "android_webview/common/aw_features.h"
#include "base/state_transitions.h"
#include "content/public/browser/browser_thread.h"

namespace android_webview {

AwPrefetchHandleWrapper::AwPrefetchHandleWrapper(
    const GURL& url,
    std::optional<net::HttpNoVarySearchData> expected_no_vary_search)
    : url_(url),
      expected_no_vary_search_(std::move(expected_no_vary_search)),
      state_(State::kReserved) {
  // TODO(crbug.com/452406598): The transition to
  // either `kPrePrefetchHandleCommitted` or `kPrefetchHandleCommitted` will be
  // strictly bound to the writer interface granting write permission to
  // `this`, which ensures the handle is eventually committed.
  CheckState();
}

void AwPrefetchHandleWrapper::CommitInitialPrePrefetchHandle(
    std::unique_ptr<content::PrePrefetchHandle> pre_prefetch_handle) {
  CHECK_EQ(state_, State::kReserved);
  // A valid handle should be provided to commit.
  CHECK(pre_prefetch_handle);

  pre_prefetch_handle_ = std::move(pre_prefetch_handle);
  SetState(State::kPrePrefetchHandleCommitted);
}

void AwPrefetchHandleWrapper::CommitInitialPrefetchHandle(
    std::unique_ptr<content::PrefetchHandle> prefetch_handle) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  CHECK_EQ(state_, State::kReserved);
  // A valid handle should be provided to commit.
  CHECK(prefetch_handle);

  prefetch_handle_ =
      content::CrossThreadPrefetchHandle::Create(std::move(prefetch_handle));
  SetState(State::kPrefetchHandleCommitted);
}

AwPrefetchHandleWrapper::AwPrefetchHandleWrapper(
    const GURL& url,
    std::optional<net::HttpNoVarySearchData> expected_no_vary_search,
    std::unique_ptr<content::PrefetchHandle> prefetch_handle)
    : url_(url),
      expected_no_vary_search_(std::move(expected_no_vary_search)),
      prefetch_handle_(content::CrossThreadPrefetchHandle::Create(
          std::move(prefetch_handle))),
      state_(State::kPrefetchHandleCommitted) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  CHECK(!IsWebViewPrefetchOffTheMainThreadEnabled());
  CheckState();
}

AwPrefetchHandleWrapper::~AwPrefetchHandleWrapper() = default;

void AwPrefetchHandleWrapper::CheckState() const {
  switch (state_) {
    case State::kReserved:
      CHECK(!pre_prefetch_handle_);
      CHECK(!prefetch_handle_);
      break;
    case State::kPrePrefetchHandleCommitted:
      CHECK(pre_prefetch_handle_);
      CHECK(!prefetch_handle_);
      break;
    case State::kPrePrefetchConsumeStarted:
      CHECK(!pre_prefetch_handle_);
      CHECK(!prefetch_handle_);
      break;
    case State::kPrefetchHandleCommitted:
      CHECK(!pre_prefetch_handle_);
      CHECK(prefetch_handle_);
      break;
  }
}

std::ostream& operator<<(std::ostream& os,
                         AwPrefetchHandleWrapper::State state) {
  switch (state) {
    case AwPrefetchHandleWrapper::State::kReserved:
      return os << "kReserved";
    case AwPrefetchHandleWrapper::State::kPrePrefetchHandleCommitted:
      return os << "kPrePrefetchHandleCommitted";
    case AwPrefetchHandleWrapper::State::kPrePrefetchConsumeStarted:
      return os << "kPrePrefetchConsumeStarted";
    case AwPrefetchHandleWrapper::State::kPrefetchHandleCommitted:
      return os << "kPrefetchHandleCommitted";
  }
}

void AwPrefetchHandleWrapper::SetState(State new_state) {
  {
    using T = State;
    static const base::NoDestructor<base::StateTransitions<T>> transitions(
        base::StateTransitions<T>({
            {T::kReserved,
             {T::kPrePrefetchHandleCommitted, T::kPrefetchHandleCommitted}},
            {T::kPrePrefetchHandleCommitted, {T::kPrePrefetchConsumeStarted}},
            {T::kPrePrefetchConsumeStarted, {T::kPrefetchHandleCommitted}},
            {T::kPrefetchHandleCommitted, {}},
        }));
    CHECK_STATE_TRANSITION(transitions, state_, new_state);
  }
  state_ = new_state;
  CheckState();
}

bool AwPrefetchHandleWrapper::CanTakePrePrefetchHandleForConsume() const {
  return state_ == State::kPrePrefetchHandleCommitted;
}

std::unique_ptr<content::PrePrefetchHandle>
AwPrefetchHandleWrapper::TakePrePrefetchHandleForConsume() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  CHECK_EQ(state_, State::kPrePrefetchHandleCommitted);
  // TODO(crbug.com/452406598): The transition to
  // `kPrePrefetchConsumeStarted` will be strictly bound to the writer
  // interface granting write permission to `this`, which ensures the
  // handle is eventually committed.
  auto pre_prefetch_handle = std::move(pre_prefetch_handle_);
  SetState(State::kPrePrefetchConsumeStarted);
  return pre_prefetch_handle;
}

void AwPrefetchHandleWrapper::CommitPrefetchHandleAfterConsume(
    std::unique_ptr<content::PrefetchHandle> prefetch_handle) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  CHECK_EQ(state_, State::kPrePrefetchConsumeStarted);

  // A valid handle should be provided to commit.
  CHECK(prefetch_handle);
  prefetch_handle_ =
      content::CrossThreadPrefetchHandle::Create(std::move(prefetch_handle));
  SetState(State::kPrefetchHandleCommitted);
}

const GURL& AwPrefetchHandleWrapper::GetURL() const {
  return url_;
}

const std::optional<net::HttpNoVarySearchData>&
AwPrefetchHandleWrapper::GetNoVarySearchHint() const {
  return expected_no_vary_search_;
}

bool AwPrefetchHandleWrapper::IsPrefetchStale() const {
  if (state_ == State::kPrefetchHandleCommitted) {
    CHECK(prefetch_handle_);
    return prefetch_handle_->IsPrefetchStale();
  }
  return false;
}

}  // namespace android_webview
