// Copyright 2019 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_OPTIMIZATION_GUIDE_OPTIMIZATION_GUIDE_WEB_CONTENTS_OBSERVER_H_
#define CHROME_BROWSER_OPTIMIZATION_GUIDE_OPTIMIZATION_GUIDE_WEB_CONTENTS_OBSERVER_H_

#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "components/optimization_guide/core/hints/insertion_ordered_set.h"
#include "components/optimization_guide/core/hints/optimization_guide_navigation_data.h"
#include "content/public/browser/navigation_handle_user_data.h"
#include "content/public/browser/page_user_data.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"

namespace content {
class NavigationHandle;
}  // namespace content

namespace optimization_guide {
class ChromeHintsManager;
class ChromeHintsManagerFetchingTest;
}  // namespace optimization_guide

class OptimizationGuideKeyedService;

// Observes navigation events.
class OptimizationGuideWebContentsObserver
    : public content::WebContentsObserver,
      public content::WebContentsUserData<
          OptimizationGuideWebContentsObserver> {
 public:
  OptimizationGuideWebContentsObserver(
      const OptimizationGuideWebContentsObserver&) = delete;
  OptimizationGuideWebContentsObserver& operator=(
      const OptimizationGuideWebContentsObserver&) = delete;

  ~OptimizationGuideWebContentsObserver() override;

  // Notifies |this| to flush |last_navigation_data| so metrics are recorded.
  void FlushLastNavigationData();

  // Tell the observer that hints for this URL should be fetched once we reach
  // onload in this |web_contents|. Note that |web_contents| must be the
  // WebContents being observed by this object.
  void AddURLsToBatchFetchBasedOnPrediction(std::vector<GURL> urls,
                                            content::WebContents* web_contents);

 private:
  friend class content::WebContentsUserData<
      OptimizationGuideWebContentsObserver>;
  friend class optimization_guide::ChromeHintsManagerFetchingTest;

  explicit OptimizationGuideWebContentsObserver(
      content::WebContents* web_contents);

  // Gets the OptimizationGuideNavigationData associated with the
  // |navigation_handle|. If one does not exist already, one will be created for
  // it.
  OptimizationGuideNavigationData* GetOrCreateOptimizationGuideNavigationData(
      content::NavigationHandle* navigation_handle);

  // content::WebContentsObserver implementation:
  void DidStartNavigation(
      content::NavigationHandle* navigation_handle) override;
  void DidRedirectNavigation(
      content::NavigationHandle* navigation_handle) override;
  void DidFinishNavigation(
      content::NavigationHandle* navigation_handle) override;
  void WebContentsDestroyed() override;

  void DocumentOnLoadCompletedInPrimaryMainFrame() override;

  // Ask |hints_manager| to fetch hints for navigations that were predicted for
  // the current page load.
  void FetchHintsUsingManager(
      optimization_guide::ChromeHintsManager* hints_manager,
      base::WeakPtr<content::Page> page);

  // Notifies |optimization_guide_keyed_service_| that the navigation has
  // finished.
  void NotifyNavigationFinish(
      std::unique_ptr<OptimizationGuideNavigationData> navigation_data,
      const std::vector<GURL>& navigation_redirect_chain);

  // Data scoped to a single page. PageData has the same lifetime as the page's
  // main document.
  class PageData : public content::PageUserData<PageData> {
   public:
    explicit PageData(content::Page& page);
    PageData(const PageData&) = delete;
    PageData& operator=(const PageData&) = delete;
    ~PageData() override;

    bool is_sent_batched_hints_request() const {
      return sent_batched_hints_request_;
    }
    void set_sent_batched_hints_request() {
      sent_batched_hints_request_ = true;
    }
    void InsertHintTargetUrls(const std::vector<GURL>& urls);
    std::vector<GURL> GetHintsTargetUrls();

    void SetNavigationData(
        std::unique_ptr<OptimizationGuideNavigationData> navigation_data) {
      navigation_data_ = std::move(navigation_data);
    }

    PAGE_USER_DATA_KEY_DECL();

   private:
    // List of predicted URLs to fetch hints for once the page reaches onload.
    optimization_guide::InsertionOrderedSet<GURL> hints_target_urls_;

    // Whether a hints request for predicted URLs has been fired off for this
    // page loads. Used to avoid sending more than one predicted URLs hints
    // request per page load.
    bool sent_batched_hints_request_ = false;

    // The navigation data for the completed navigation.
    std::unique_ptr<OptimizationGuideNavigationData> navigation_data_;
  };

  class NavigationHandleData
      : public content::NavigationHandleUserData<NavigationHandleData> {
   public:
    explicit NavigationHandleData(content::NavigationHandle&);
    NavigationHandleData(const NavigationHandleData&) = delete;
    NavigationHandleData& operator=(const NavigationHandleData&) = delete;
    ~NavigationHandleData() override;

    std::unique_ptr<OptimizationGuideNavigationData>
    TakeOptimizationGuideNavigationData() {
      return std::move(optimization_guide_navigation_data_);
    }

    OptimizationGuideNavigationData* GetOptimizationGuideNavigationData() {
      return optimization_guide_navigation_data_.get();
    }

    void SetOptimizationGuideNavigationData(
        std::unique_ptr<OptimizationGuideNavigationData> navigation_data) {
      optimization_guide_navigation_data_ = std::move(navigation_data);
    }

    NAVIGATION_HANDLE_USER_DATA_KEY_DECL();

   private:
    // The data related to a given navigation ID.
    std::unique_ptr<OptimizationGuideNavigationData>
        optimization_guide_navigation_data_;
  };

  // Returns the PageData for the specified |page|.
  PageData& GetPageData(content::Page& page);

  // Initialized in constructor. It may be null if the
  // OptimizationGuideKeyedService feature is not enabled.
  raw_ptr<OptimizationGuideKeyedService> optimization_guide_keyed_service_ =
      nullptr;

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

  WEB_CONTENTS_USER_DATA_KEY_DECL();
};

#endif  // CHROME_BROWSER_OPTIMIZATION_GUIDE_OPTIMIZATION_GUIDE_WEB_CONTENTS_OBSERVER_H_
