// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file contains the document autocomplete provider. This experimental
// provider uses an experimental API with keys and endpoint provided at
// developer build-time, so it is feature-flagged off by default.

#ifndef COMPONENTS_OMNIBOX_BROWSER_DOCUMENT_PROVIDER_H_
#define COMPONENTS_OMNIBOX_BROWSER_DOCUMENT_PROVIDER_H_

#include <memory>
#include <optional>
#include <string>

#include "base/compiler_specific.h"
#include "base/containers/lru_cache.h"
#include "base/memory/raw_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "components/history/core/browser/history_types.h"
#include "components/omnibox/browser/autocomplete_enums.h"
#include "components/omnibox/browser/autocomplete_provider.h"
#include "components/omnibox/browser/autocomplete_provider_debouncer.h"
#include "third_party/metrics_proto/omnibox_event.pb.h"
#include "url/gurl.h"

class AutocompleteProviderListener;
struct AutocompleteMatch;
class AutocompleteProviderClient;

namespace base {
class Value;
}

namespace network {
class SimpleURLLoader;
}

// Autocomplete provider for personalized documents owned or readable by the
// signed-in user. In practice this is a second request in parallel with that
// to the default search provider.
class DocumentProvider : public AutocompleteProvider {
 public:
  // Creates and returns an instance of this provider.
  static DocumentProvider* Create(AutocompleteProviderClient* client,
                                  AutocompleteProviderListener* listener);

  // AutocompleteProvider:
  void Start(const AutocompleteInput& input, bool minimal_changes) override;
  void Stop(AutocompleteStopReason stop_reason) override;
  void DeleteMatch(const AutocompleteMatch& match) override;
  void AddProviderInfo(ProvidersInfo* provider_info) const override;

  // Returns a set of classifications that highlight all the occurrences of
  // |input_text| at word breaks in |text|. E.g., given |input_text|
  // "rain if you dare" and |text| "how to tell if your kitten is a rainbow",
  // will return the classifications:
  //             __ ___              ____
  // how to tell if your kitten is a rainbow
  // ^           ^ ^^   ^            ^  ^
  // NONE        M |M   |            |  NONE
  //               NONE NONE         MATCH
  static ACMatchClassifications Classify(const std::u16string& input_text,
                                         const std::u16string& text);

  // Builds a GURL to use for deduping against other document/history
  // suggestions. Multiple URLs may refer to the same document.
  // Returns an empty GURL if not a recognized Docs URL.
  // The URL returned is not guaranteed to be navigable and should only be used
  // as a deduping token.
  static const GURL GetURLForDeduping(const GURL& url);

 private:
  friend class FakeDocumentProvider;

  using MatchesCache = base::LRUCache<GURL, AutocompleteMatch>;

  DocumentProvider(AutocompleteProviderClient* client,
                   AutocompleteProviderListener* listener);

  ~DocumentProvider() override;

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

  // Determines whether the profile/session/window meet the feature
  // prerequisites.
  bool IsDocumentProviderAllowed(const AutocompleteInput& input);

  // Determines if the input is a URL, or is the start of the user entering one.
  // We avoid queries for these cases for quality and scaling reasons.
  static bool IsInputLikelyURL(const AutocompleteInput& input);

  // Called by |debouncer_|, queued when |start| is called.
  void Run(const AutocompleteInput& input);

  // Called when the network request for suggestions has completed.
  void OnURLLoadComplete(const network::SimpleURLLoader* source,
                         const int response_code,
                         std::optional<std::string> response_body);

  // Resets the backoff state on DocumentSuggestionsService to false.
  void ResetBackoffState();

  // The function updates |matches_| with data parsed from |json_data|.
  // The update is not performed if |json_data| is invalid.
  // Returns whether |matches_| changed.
  bool UpdateResults(const std::string& json_data);

  // Callback for when the loader is available with a valid token.
  void OnDocumentSuggestionsLoaderAvailable(
      std::unique_ptr<network::SimpleURLLoader> loader);

  // Parses document search result JSON.
  // Returns true if |matches| was populated with fresh suggestions.
  ACMatches ParseDocumentSearchResults(const base::Value& root_val);

  // Appends |matches_cache_| to |matches_|. Updates their classifications
  // according to |input_.text()|.
  void CopyCachedMatchesToMatches();

  // Sets the scores of all cached matches to 0. This is invoked before pushing
  // the latest async response returns so that the scores aren't preserved for
  // further inputs. E.g., the input 'london' shouldn't display cached docs from
  // a previous input 'paris'. This can't be done by automatically (i.e. set
  // scores to 0 before pushing to the cache), as the scores are needed for the
  // async pass if the user continued their input.
  void SetCachedMatchesScoresTo0();

  // Sets the scores of matches beyond the first |provider_max_matches_| to 0.
  // This ensures the doc provider doesn't exceed it's allocated suggestions
  // while also allowing docs from other providers to be deduped and styled like
  // docs from the doc provider.
  void DemoteMatchesBeyondMax();

  // Generates the localized last-modified timestamp to present to the user.
  // Full date for old files, mm/dd within the same calendar year, or time-of-
  // day if a file was modified on the same date.
  // |now| should generally be base::Time::Now() but is passed in for testing.
  static std::u16string GenerateLastModifiedString(
      const std::string& modified_timestamp_string,
      base::Time now);

  // Convert mimetype (e.g. "application/vnd.google-apps.document") to a string
  // that can be used in the match description (e.g. "Google Docs").
  static std::u16string GetProductDescriptionString(
      const std::string& mimetype);

  // Construct match description; e.g. "Jan 12 - First Last - Google Docs".
  static std::u16string GetMatchDescription(const std::string& update_time,
                                            const std::string& mimetype,
                                            const std::string& owner);

  // Whether the server has instructed us to backoff. Used when the backoff
  // state is scoped to the current window/AutocompleteController.
  bool backoff_for_this_instance_only_ = false;

  // Client for accessing TemplateUrlService, prefs, etc.
  raw_ptr<AutocompleteProviderClient> client_;

  // Saved when starting a new autocomplete request so that it can be retrieved
  // when responses return asynchronously.
  AutocompleteInput input_;

  // Loader used to retrieve results.
  std::unique_ptr<network::SimpleURLLoader> loader_;

  // The time `Run()` was invoked. Used for histogram logging.
  base::TimeTicks time_run_invoked_;
  // The time `OnDocumentSuggestionsLoaderAvailable()` was invoked and the
  // remote request was sent. Used for histogram logging.
  base::TimeTicks time_request_sent_;

  // Used to ensure that we don't send multiple requests in quick succession.
  std::unique_ptr<AutocompleteProviderDebouncer> debouncer_;

  // Because the drive server is async and may intermittently provide a
  // particular suggestion for consecutive inputs, without caching, doc
  // suggestions flicker between drive format (title - date - doc_type) and URL
  // format (title - URL) suggestions from the history and bookmark providers.
  // Appending cached doc suggestions with relevance 0 ensures cached
  // suggestions only display if deduped with a non-cached suggestion and do not
  // affect which autocomplete results are displayed and their ranks.
  MatchesCache matches_cache_;

  // Used to schedule a reset of the backoff state.
  scoped_refptr<base::SequencedTaskRunner> task_runner_;

  // For callbacks that may be run after destruction. Must be declared last.
  base::WeakPtrFactory<DocumentProvider> weak_ptr_factory_{this};
};

#endif  // COMPONENTS_OMNIBOX_BROWSER_DOCUMENT_PROVIDER_H_
