// Copyright 2020 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_PRELOADING_PREFETCH_SEARCH_PREFETCH_SEARCH_PREFETCH_REQUEST_H_
#define CHROME_BROWSER_PRELOADING_PREFETCH_SEARCH_PREFETCH_SEARCH_PREFETCH_REQUEST_H_

#include <memory>

#include "base/callback_list.h"
#include "base/functional/callback.h"
#include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_url_loader.h"
#include "chrome/browser/preloading/prerender/search_preload_progress_service.h"
#include "services/network/public/cpp/resource_request.h"
#include "url/gurl.h"

class PrerenderManager;
class Profile;
class StreamingSearchPrefetchURLLoader;

namespace content {
class PreloadingAttempt;
class WebContents;
enum class PreloadingTriggeringOutcome;
enum class PreloadingFailureReason;
}  // namespace content

namespace net {
struct NetworkTrafficAnnotationTag;
}  // namespace net

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// Any updates to this class need to be propagated to enums.xml.
enum class SearchPrefetchStatus {
  // The request has not started yet. This status should ideally never be
  // recorded as Start() should be called on the same stack as creating the
  // fetcher (as of now).
  kNotStarted = 0,

  // The request is on the network and may move to any other state. Removed
  // after https://crbug.com/40217275 is implemented.
  // kInFlight = 1,

  // The request can be served to the navigation stack, but may still encounter
  // errors and move to |kRequestFailed| or it may complete and move to
  // |kComplete|.
  kCanBeServed = 2,

  // Obsolete: After updating the cancellation logic, we no longer need this
  // status to prevent a clicked prefetch from being deleted.
  // kCanBeServedAndUserClicked = 3,

  // The request can be served to the navigation stack, and has fully streamed
  // the response with no errors.
  kComplete = 4,

  // The request hit an error and cannot be served.
  kRequestFailed = 5,

  // Obsolete: After updating the cancellation logic, we no longer cancel
  // in-flight prefetch requests on autocomplete result changed.
  // kRequestCancelled = 6,

  kPrefetchServedForRealNavigation = 7,

  // Obsolete: Now search prefetch requests are reusable, i.e., can be served to
  // both real navigation and prerender navigation, so we no longer track these
  // statuses.
  // kPrerendered = 8,
  // kPrerenderedAndClicked = 9,
  // kPrerenderActivated = 10,

  kMaxValue = kPrefetchServedForRealNavigation,
};

// A class representing a prefetch used by the Search Prefetch Service.
// It plays the following roles to support search preloading.
// - Preparing a resource request to prefetch a search page.
// - Starting prerendering upon the request succeeding to upgrade prefetch to
//   prerender after the Search Prefetch Service tells it that the prefetched
//   term is prerenderable.
// - A container for a StreamingSearchPrefetchURLLoader, to support
// |TakeSearchPrefetchURLLoader()|
//   more easily.
class SearchPrefetchRequest {
 public:
  SearchPrefetchRequest(Profile& profile,
                        const GURL& canonical_search_url,
                        const GURL& prefetch_url,
                        bool navigation_prefetch,
                        content::PreloadingAttempt* prefetch_preloading_attempt,
                        base::OnceCallback<void(bool)> report_error_callback);
  ~SearchPrefetchRequest();

  SearchPrefetchRequest(const SearchPrefetchRequest&) = delete;
  SearchPrefetchRequest& operator=(const SearchPrefetchRequest&) = delete;

  // The NTA for any search prefetch request.
  static net::NetworkTrafficAnnotationTag NetworkAnnotationForPrefetch();

  // Starts the network request to prefetch `prefetch_url_`. Sets various fields
  // on a resource request. Returns `false` if the request is not started (i.e.,
  // it would be deferred by throttles).
  bool StartPrefetchRequest(Profile* profile,
                            content::WebContents& web_contents);

  // Called when SearchPrefetchService receives the hint that this prefetch
  // request can be upgraded to a prerender attempt.
  void MaybeStartPrerenderSearchResult(PrerenderManager& prerender_manager,
                                       const GURL& prerender_url,
                                       content::PreloadingAttempt& attempt);

  // Called when the prefetch encounters an error.
  void ErrorEncountered();

  // Called on the URL loader receives servable response.
  void OnServableResponseCodeReceived();

  // Update the status when the request is complete.
  void MarkPrefetchAsComplete();

  // Update the status when the request is actually served to the navigation
  // stack of a real navigation request.
  void MarkPrefetchAsServed();

  // Called when AutocompleteMatches changes. It resets PrerenderUpgrader.
  // And if the AutocompleteMatches suggests to prerender a search result,
  // `MaybeStartPrerenderSearchResult` will be called soon.
  void ResetPrerenderUpgrader();

  // Takes ownership of underlying data/objects needed to serve the response.
  scoped_refptr<StreamingSearchPrefetchURLLoader> TakeSearchPrefetchURLLoader();

  // Instead of completely letting a navigation stack own the prefetch loader,
  // creates a copy of the prefetched response so that it can be shared among
  // different clients.
  // Note: This method should be called after the response reader received
  // response headers.
  SearchPrefetchURLLoader::RequestHandler CreateResponseReader();

  // Whether the request was started as a navigation prefetch (as opposed to a
  // suggestion prefetch).
  bool navigation_prefetch() const { return navigation_prefetch_; }

  SearchPrefetchStatus current_status() const { return current_status_; }

  const GURL& prefetch_url() const { return prefetch_url_; }

  // Sets `prefetch_preloading_attempt_` PreloadingFailureReason to `reason` if
  // exists.
  void SetPrefetchAttemptFailureReason(content::PreloadingFailureReason reason);

  // Tells StreamingSearchPrefetchURLLoader to run the callback upon
  // destruction.
  void SetLoaderDestructionCallbackForTesting(
      base::OnceClosure streaming_url_loader_destruction_callback);

  void OnSearchPrewarmFinished();

 private:
  // Stops the on-going prefetch and should mark |current_status_|
  // appropriately.
  void StopPrefetch();

  // Cancels ongoing and pending prerender.
  void StopPrerender();

  // Updates the `current_status_` to status.
  void SetSearchPrefetchStatus(SearchPrefetchStatus status);

  // Sets the PreloadingTriggeringOutcome for `prefetch_preloading_attempt_` to
  // `outcome`.
  void SetPrefetchAttemptTriggeringOutcome(
      content::PreloadingTriggeringOutcome outcome);

  // Whether the request has received a servable response. See
  // `CanServePrefetchRequest` in ./streaming_search_prefetch_url_loader.cc for
  // the definition of servable response.
  bool servable_response_code_received_ = false;

  SearchPrefetchStatus current_status_ = SearchPrefetchStatus::kNotStarted;

  // The canonical representation of the search suggestion including query,
  // intent, and extra parameters that can alter the Search page.
  const GURL canonical_search_url_;

  // The URL to prefetch the search terms from.
  GURL prefetch_url_;

  // The URL to prerender the search terms from.
  // `prerender_url_` can be different from `prefetch_url_`. The latter is used
  // to send network requests, so it may contain a special parameter for the
  // server to recognize that it is a prefetch request, but the former does not
  // send network requests, i.e. this parameter is not required.
  GURL prerender_url_;

  // Whether this is for a navigation-time prefetch.
  bool navigation_prefetch_;

  // The ongoing prefetch request. Null before and after the fetch.
  scoped_refptr<StreamingSearchPrefetchURLLoader> streaming_url_loader_;

  // Once set, this is used to log the metrics corresponding to the prefetch
  // attempt. Please note this is different from `prerender_preloading_attempt_`
  // which is for corresponding prerender attempt. We store the WeakPtr because
  // it is possible that the PreloadingAttempt can be deleted (on navigation) or
  // not created (when no WebContents is present) before search prefetch uses
  // it.
  base::WeakPtr<content::PreloadingAttempt> prefetch_preloading_attempt_;

  // Called when there is a network/server error on the prefetch request.
  base::OnceCallback<void(bool)> report_error_callback_;

  base::TimeTicks time_start_prefetch_request_;

  // Once set, this request will trigger search prerender upon receiving success
  // response.
  base::WeakPtr<PrerenderManager> prerender_manager_;

  // Once set, this PreloadingAttempt corresponding to prerender attempt will be
  // passed to log various metrics. We store WeakPtr as prerender can be deleted
  // before we receive a prefetch response or the prerender is not created.
  base::WeakPtr<content::PreloadingAttempt> prerender_preloading_attempt_;

  // Prewarm page loading status tracker to throttle the concurrent requests to
  // search.
  base::WeakPtr<SearchPreloadProgressService> prewarm_progress_service_;

  base::CallbackListSubscription prewarm_finished_subscription_;

  // Used to store the arguments if the request is throttled.
  struct PendingRequest {
    PendingRequest(Profile& profile, content::WebContents* web_contents);
    ~PendingRequest();

    raw_ptr<Profile> profile;
    base::WeakPtr<content::WebContents> web_contents;
  };
  std::optional<PendingRequest> pending_request_;

  base::WeakPtrFactory<SearchPrefetchRequest> weak_factory_{this};
};

// Used when DCHECK_STATE_TRANSITION triggers.
std::ostream& operator<<(std::ostream& o, const SearchPrefetchStatus& s);

#endif  // CHROME_BROWSER_PRELOADING_PREFETCH_SEARCH_PREFETCH_SEARCH_PREFETCH_REQUEST_H_
