// 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 CHROME_BROWSER_EXTENSIONS_WEBSTORE_STANDALONE_INSTALLER_H_
#define CHROME_BROWSER_EXTENSIONS_WEBSTORE_STANDALONE_INSTALLER_H_

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

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_observer.h"
#include "extensions/browser/active_install_data.h"
#include "extensions/browser/cws_item_service.pb.h"
#include "extensions/browser/webstore_data_fetcher_delegate.h"
#include "extensions/browser/webstore_install_helper.h"
#include "extensions/browser/webstore_install_result.h"
#include "extensions/browser/webstore_installer.h"
#include "extensions/buildflags/buildflags.h"
#include "third_party/skia/include/core/SkBitmap.h"

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {
class Extension;
struct InstallApproval;
class ScopedActiveInstall;
class WebstoreDataFetcher;

// A a purely abstract base for concrete classes implementing various types of
// standalone installs:
// 1) Downloads and parses metadata from the webstore.
// 2) Optionally shows an install dialog.
// 3) Starts download once the user confirms (if confirmation was requested).
// 4) Optionally shows a post-install UI.
// Follows the Template Method pattern. Implementing subclasses must override
// the primitive hooks in the corresponding section below.

class WebstoreStandaloneInstaller
    : public base::RefCountedThreadSafe<WebstoreStandaloneInstaller>,
      public WebstoreDataFetcherDelegate,
      public ProfileObserver {
 public:
  // A callback for when the install process completes, successfully or not. If
  // there was a failure, `success` will be false and `error` may contain a
  // developer-readable error message about why it failed.
  using Callback = base::OnceCallback<void(bool success,
                                           const std::string& error,
                                           webstore_install::Result result)>;

  WebstoreStandaloneInstaller(const std::string& webstore_item_id,
                              Profile* profile,
                              Callback callback);

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

  void BeginInstall();

 protected:
  ~WebstoreStandaloneInstaller() override;

  // Runs the callback; primarily used for running a callback before it is
  // cleared in AbortInstall(). This should only be called once for the lifetime
  // of the class.
  void RunCallback(
      bool success, const std::string& error, webstore_install::Result result);

  // Called when the install should be aborted. The callback is cleared.
  void AbortInstall();

  // Checks InstallTracker and returns true if the same extension is not
  // currently being installed. Registers this install with the InstallTracker.
  bool EnsureUniqueInstall(webstore_install::Result* reason,
                           std::string* error);

  // Called when the install is complete.
  virtual void CompleteInstall(webstore_install::Result result,
                               const std::string& error);

  // Called when the installer should proceed to prompt the user.
  void ProceedWithInstallPrompt();

  // Lazily creates a dummy extension for display from the parsed manifest. This
  // is safe to call from OnManifestParsed() onwards. The manifest may be
  // invalid, thus the caller must check that the return value is not NULL.
  scoped_refptr<const Extension> GetLocalizedExtensionForDisplay();

  // Template Method's hooks to be implemented by subclasses.

  // Called at certain check points of the workflow to decide whether it makes
  // sense to proceed with installation. A requestor can be a website that
  // initiated an inline installation, or a command line option.
  virtual bool CheckRequestorAlive() const = 0;

  // Should a new tab be opened after installation to show the newly installed
  // extension's icon?
  virtual bool ShouldShowPostInstallUI() const = 0;

  // In the very least this should return a dummy WebContents (required
  // by some calls even when no prompt or other UI is shown). A non-dummy
  // WebContents is required if the prompt returned by CreateInstallPromt()
  // contains a navigable link(s). Returned WebContents should correspond
  // to `profile` passed into the constructor.
  virtual content::WebContents* GetWebContents() const = 0;

  // Should return an installation prompt with desired properties or NULL if
  // no prompt should be shown.
  virtual std::unique_ptr<InstallPromptData> CreateInstallPrompt() const = 0;

  // Will be called after the extension's manifest has been successfully parsed.
  // Subclasses can perform asynchronous checks at this point and call
  // ProceedWithInstallPrompt() to proceed with the install or otherwise call
  // CompleteInstall() with an error code. The default implementation calls
  // ProceedWithInstallPrompt().
  virtual void OnManifestParsed();

  // Returns an install UI to be shown. By default, this returns an install UI
  // that is a transient child of the host window for GetWebContents().
  virtual std::unique_ptr<ExtensionInstallPrompt> CreateInstallUI(
      std::unique_ptr<InstallPromptData> prompt);

  // Create an approval to pass installation parameters to the CrxInstaller.
  virtual std::unique_ptr<InstallApproval> CreateApproval() const;

  // Called once the install prompt has finished.
  virtual void OnInstallPromptDone(
      ExtensionInstallPrompt::DoneCallbackPayload payload);

  // Accessors to be used by subclasses.
  bool show_user_count() const { return show_user_count_; }
  const std::string& localized_user_count() const {
    return localized_user_count_;
  }
  double average_rating() const { return average_rating_; }
  int rating_count() const { return rating_count_; }
  const std::string& localized_rating_count() const {
    return localized_rating_count_;
  }
  void set_install_source(WebstoreInstaller::InstallSource source) {
    install_source_ = source;
  }
  WebstoreInstaller::InstallSource install_source() const {
    return install_source_;
  }
  Profile* profile() const { return profile_; }
  const std::string& id() const { return id_; }
  const base::DictValue& manifest() const { return manifest_.value(); }
  const Extension* localized_extension_for_display() const {
    return localized_extension_for_display_.get();
  }

 private:
  friend class base::RefCountedThreadSafe<WebstoreStandaloneInstaller>;

  // Several delegate/client interface implementations follow. The normal flow
  // (for successful installs) with the item snippets API is:
  //
  // 1. BeginInstall: starts the fetch of data from the webstore.
  // 2. WebstoreDataFetcher::OnFetchItemSnippetResponseReceived: starts the
  //    parsing of data from the webstore into a FetchItemSnippetResponse
  //    protobuf.
  // 3. OnFetchItemSnippetParseSuccess: starts the parsing of the
  //    manifest and fetching of icon data.
  // 4. OnWebstoreParseSuccess: shows the install UI
  // 5. InstallUIProceed: initiates the .crx download/install
  //
  // All flows (whether successful or not) end up in CompleteInstall, which
  // informs our delegate of success/failure.

  // WebstoreDataFetcherDelegate interface implementation.
  void OnWebstoreRequestFailure(const std::string& extension_id) override;
  void OnFetchItemSnippetParseSuccess(
      const std::string& extension_id,
      FetchItemSnippetResponse item_snippet) override;
  void OnWebstoreResponseParseFailure(const std::string& extension_id,
                                      const std::string& error) override;

  void OnWebstoreParseFinished(WebstoreParseResult result);

  // WebstoreInstaller::Delegate callbacks.
  void OnExtensionInstallSuccess(const std::string& id);
  void OnExtensionInstallFailure(const std::string& id,
                                 const std::string& error,
                                 WebstoreInstaller::FailureReason reason);

  // ProfileObserver
  void OnProfileWillBeDestroyed(Profile* profile) override;

  void ShowInstallUI();
  void OnWebStoreDataFetcherDone();

  // Called when install either completes or aborts to clean up internal
  // state and release the added reference from BeginInstall.
  void CleanUp();

  // Input configuration.
  std::string id_;
  Callback callback_;
  raw_ptr<Profile> profile_;
  base::ScopedObservation<Profile, ProfileObserver> observation_{this};
  WebstoreInstaller::InstallSource install_source_{
      WebstoreInstaller::INSTALL_SOURCE_INLINE};

  // Installation dialog and its underlying prompt.
  std::unique_ptr<ExtensionInstallPrompt> install_ui_;
  std::unique_ptr<InstallPromptData> install_prompt_;

  // For fetching webstore JSON data.
  std::unique_ptr<WebstoreDataFetcher> webstore_data_fetcher_;

  // Extracted from the webstore JSON data response.
  std::string localized_name_;
  std::string localized_description_;
  bool show_user_count_{true};
  std::string localized_user_count_;
  double average_rating_{0.0};
  std::string localized_rating_count_;
  int rating_count_{0};
  std::optional<base::DictValue> manifest_;
  SkBitmap icon_;

  // Active install registered with the InstallTracker.
  std::unique_ptr<ScopedActiveInstall> scoped_active_install_;

  // Created by ShowInstallUI() when a prompt is shown (if
  // the implementor returns a non-NULL in CreateInstallPrompt()).
  scoped_refptr<Extension> localized_extension_for_display_;

  base::WeakPtrFactory<WebstoreStandaloneInstaller> weak_ptr_factory_{this};
};

}  // namespace extensions

#endif  // CHROME_BROWSER_EXTENSIONS_WEBSTORE_STANDALONE_INSTALLER_H_
