// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_SPELLCHECK_RENDERER_SPELLCHECK_PROVIDER_H_
#define COMPONENTS_SPELLCHECK_RENDERER_SPELLCHECK_PROVIDER_H_

#include <memory>
#include <set>
#include <string>
#include <vector>

#include "base/containers/id_map.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "components/spellcheck/common/spellcheck.mojom.h"
#include "components/spellcheck/common/spelling_marker.h"
#include "components/spellcheck/spellcheck_buildflags.h"
#include "content/public/renderer/render_frame_observer.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/web/web_text_check_client.h"

#if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
#include <unordered_map>
#endif  // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)

class SpellCheck;
struct SpellCheckResult;

namespace base {
class TimeTicks;
}

namespace blink {
class WebTextCheckingCompletion;
struct WebTextCheckingResult;
}

namespace service_manager {
class LocalInterfaceProvider;
}

// This class deals with asynchronously invoking text spelling and grammar
// checking services provided by the browser process (host).
class SpellCheckProvider : public content::RenderFrameObserver,
                           public blink::WebTextCheckClient {
 public:
  using WebTextCheckCompletions =
      base::IDMap<std::unique_ptr<blink::WebTextCheckingCompletion>>;

#if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  // A struct to hold information related to hybrid spell check requests.
  struct HybridSpellCheckRequestInfo {
    bool used_hunspell;
    bool used_native;
    base::TimeTicks request_start_ticks;
  };
#endif  // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)

  SpellCheckProvider(content::RenderFrame* render_frame,
                     SpellCheck* spellcheck);

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

  ~SpellCheckProvider() override;

  // Requests async spell and grammar checks from the platform text checker
  // available in the browser process. The function does not have special
  // handling for partial words, as Blink guarantees that no request is made
  // when typing in the middle of a word.
  void RequestTextChecking(
      const std::u16string& text,
      const std::vector<spellcheck::SpellingMarker>& spelling_markers,
      blink::WebTextCheckClient::ShouldForceRefreshTextCheckService
          should_force_refresh,
      std::unique_ptr<blink::WebTextCheckingCompletion> completion);

  // The number of ongoing spell check host requests.
  size_t pending_text_request_size() const {
    return text_check_completions_.size();
  }

  // Replace shared spellcheck data.
  void set_spellcheck(SpellCheck* spellcheck) { spellcheck_ = spellcheck; }

  // content::RenderFrameObserver:
  void FocusedElementChanged(const blink::WebElement& element) override;
  void DidCreateNewDocument() override;

  // Returns the SpellCheckHost.
  spellcheck::mojom::SpellCheckHost& GetSpellCheckHost();

  // The per-document custom dictionary word set supplied by the
  // SpellCheckCustomDictionary web API.
  const std::set<std::u16string>& document_custom_words() const {
    return document_custom_words_;
  }

 private:
  friend class TestingSpellCheckProvider;
  class DictionaryUpdateObserverImpl;

  // Sets the SpellCheckHost (for unit tests).
  void SetSpellCheckHostForTesting(
      mojo::PendingRemote<spellcheck::mojom::SpellCheckHost> host) {
    spell_check_host_.Bind(std::move(host));
  }

  // Reset dictionary_update_observer_ in TestingSpellCheckProvider dtor.
  void ResetDictionaryUpdateObserverForTesting();

  // Tries to satisfy a spellcheck request from the cache in |last_request_|.
  // Returns true (and cancels/finishes the completion) if it can, false
  // if the provider should forward the query on.
  bool SatisfyRequestFromCache(const std::u16string& text,
                               blink::WebTextCheckingCompletion* completion);

  // content::RenderFrameObserver:
  void OnDestruct() override;

  // blink::WebTextCheckClient:
  bool IsSpellCheckingEnabled() const override;
  void CheckSpelling(
      const blink::WebString& text,
      size_t& offset,
      size_t& length,
      std::vector<blink::WebString>* optional_suggestions) override;
  void RequestCheckingOfText(
      const blink::WebString& text,
      const std::vector<blink::WebSpellingMarker>& spelling_markers,
      blink::WebTextCheckClient::ShouldForceRefreshTextCheckService
          should_force_refresh,
      std::unique_ptr<blink::WebTextCheckingCompletion> completion) override;

  void SpellCheckCustomDictionaryChanged(
      const std::vector<std::string>& words_added,
      const std::vector<std::string>& words_removed) override;

  // If the misspelled span [offset, offset+length) of |word| matches an entry
  // in |document_custom_words_|, clears |offset|/|length| so the word counts
  // as correctly spelled.
  void ApplyDocumentCustomWords(const std::u16string& word,
                                size_t& offset,
                                size_t& length) const;

#if BUILDFLAG(USE_RENDERER_SPELLCHECKER)
  void OnRespondSpellingService(int identifier,
                                const std::u16string& text,
                                bool success,
                                const std::vector<SpellCheckResult>& results);
#endif

  // Returns whether |text| has word characters, i.e. whether a spellchecker
  // needs to check this text.
  bool HasWordCharacters(const std::u16string& text, size_t index) const;

#if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  void OnRespondTextCheck(int identifier,
                          const std::u16string& line,
                          const std::vector<SpellCheckResult>& results);

  // Makes mojo calls to the browser process to perform platform spellchecking.
  void RequestTextCheckingFromBrowser(
      const std::u16string& text,
      const std::vector<spellcheck::SpellingMarker>& spelling_markers);

#if BUILDFLAG(IS_WIN)
  // Callback for when spellcheck service has been initialized on demand.
  void OnRespondInitializeDictionaries(
      const std::u16string& text,
      std::vector<spellcheck::mojom::SpellCheckBDictLanguagePtr> dictionaries,
      const std::vector<std::string>& custom_words,
      bool enable);

  // Flag indicating that the spellcheck service has been initialized and
  // the dictionaries have been loaded initially. Used to avoid an unnecessary
  // mojo call to determine this in every text check request.
  bool dictionaries_loaded_ = false;
#endif  // BUILDFLAG(IS_WIN)
#endif  // BUILDFLAG(USE_BROWSER_SPELLCHECKER)

  // Holds ongoing spellchecking operations.
  WebTextCheckCompletions text_check_completions_;

  // The last text sent to the browser process for spellchecking, and its
  // spellcheck results and WebTextCheckCompletions identifier.
  std::u16string last_request_;
  std::vector<blink::WebTextCheckingResult> last_results_;
  int last_identifier_;

  // Weak pointer to shared (per renderer) spellcheck data.
  raw_ptr<SpellCheck, DanglingUntriaged> spellcheck_;

  // Not owned. |embedder_provider_| should outlive SpellCheckProvider.
  raw_ptr<service_manager::LocalInterfaceProvider> embedder_provider_;

  // Interface to the SpellCheckHost.
  mojo::Remote<spellcheck::mojom::SpellCheckHost> spell_check_host_;

  // Dictionary updated observer.
  std::unique_ptr<DictionaryUpdateObserverImpl> dictionary_update_observer_;

  // Live per-document word set supplied by the SpellCheckCustomDictionary web
  // API. Maintained on every platform: its size enforces
  // kMaxDocumentCustomDictionaryWords, and because additions and removals both
  // move the size, the cap bounds the resident set rather than lifetime churn.
  std::set<std::u16string> document_custom_words_;

  // Tracks whether a console warning has already been emitted for this
  // document.
  bool document_custom_dictionary_overflow_warned_ = false;

#if BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)
  std::unordered_map<int, HybridSpellCheckRequestInfo> hybrid_requests_info_;
#endif  // BUILDFLAG(IS_WIN) && BUILDFLAG(USE_BROWSER_SPELLCHECKER)

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

#endif  // COMPONENTS_SPELLCHECK_RENDERER_SPELLCHECK_PROVIDER_H_
