// 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_UI_INTENT_PICKER_TAB_HELPER_H_
#define CHROME_BROWSER_UI_INTENT_PICKER_TAB_HELPER_H_

#include <optional>
#include <vector>

#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "chrome/browser/apps/link_capturing/apps_intent_picker_delegate.h"
#include "chrome/browser/web_applications/web_app_install_manager.h"
#include "chrome/browser/web_applications/web_app_install_manager_observer.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "components/apps/link_capturing/intent_picker_info.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/base/models/image_model.h"
#include "ui/base/unowned_user_data/scoped_unowned_user_data.h"
#include "url/origin.h"

// Controls the visibility of IntentPickerView by updating the visibility based
// on stored state. This class is instantiated for both web apps and SWAs. It
// is owned by the tab's TabFeatures.
class IntentPickerTabHelper : public content::WebContentsObserver,
                              public web_app::WebAppInstallManagerObserver {
 public:
  using ShowIntentPickerBubbleCallback = base::OnceCallback<void(bool)>;

  DECLARE_USER_DATA(IntentPickerTabHelper);

  // `web_contents` is passed explicitly because during a discard the helper
  // is recreated for the incoming WebContents before `tab` swaps its
  // contents.
  IntentPickerTabHelper(tabs::TabInterface& tab,
                        content::WebContents* web_contents);

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

  static IntentPickerTabHelper* From(tabs::TabInterface* tab);

  // Updates visibility of the intent picker page action based on the current
  // tab and whether the icon should be showed.
  void UpdatePageAction(tabs::TabInterface* tab_interface,
                        bool should_show_icon);

  ~IntentPickerTabHelper() override;

  // Starts an async icon update before maybe showing the intent picker icon in
  // the omnibox, based on the last committed URL for the current web_contents.
  void MaybeShowIntentPickerIcon();

  // Shows the intent picker bubble to present a choice between apps to handle
  // |url|. May launch directly into an app based on user preferences and
  // installed apps. The callback will always be called asynchronously, and is
  // called with true if the user chooses to launch the app, otherwise, false
  // is called.
  void ShowIntentPickerBubbleOrLaunchApp(
      const GURL& url,
      bool always_show = false,
      ShowIntentPickerBubbleCallback callback = base::DoNothing(),
      std::optional<webapps::AppId> scoped_app_id = std::nullopt);

  // Shows or hides the intent picker icon for |web_contents|. Always shows a
  // generic picker icon, even if MaybeShowIconForApps() had previously applied
  // app-specific customizations.
  static void ShowOrHideIcon(content::WebContents* web_contents,
                             bool should_show_icon);

  // Returns the size, in dp, of app icons shown in the intent picker bubble.
  static int GetIntentPickerBubbleIconSize();

  // Shows or hides the intent picker icon for this tab a list of |apps| which
  // can handle a link intent. Visible for testing.
  void MaybeShowIconForApps(std::vector<apps::IntentPickerAppInfo> apps);

  bool should_show_icon() const { return should_show_icon_; }

  // Returns true if the icon should be shown using an expanded chip-style
  // button.
  bool ShouldShowExpandedChip() const {
    return show_expanded_chip_from_usage_ || current_app_is_preferred_;
  }

  const ui::ImageModel& app_icon() const { return current_app_icon_; }

  // Sets a OnceClosure callback which will be called next time the icon is
  // updated. If include_latest_navigation is true, and the latest navigation
  // was finished, the callback is called immediately.
  void SetIconUpdateCallbackForTesting(base::OnceClosure callback,
                                       bool include_latest_navigation = false);

 private:
  using IntentPickerIconLoaderCallback =
      base::OnceCallback<void(std::vector<apps::IntentPickerAppInfo> apps)>;

  // Logic to load icons etc for apps in the intent picker:

  void OnAppIconLoaded(std::vector<apps::IntentPickerAppInfo> apps,
                       IntentPickerIconLoaderCallback callback,
                       size_t index,
                       ui::ImageModel app_icon);

  void LoadAppIcon(std::vector<apps::IntentPickerAppInfo> apps,
                   size_t index,
                   IntentPickerIconLoaderCallback callback);

  void UpdateExpandedState(bool should_show_icon);
  void OnAppIconLoadedForChip(const std::string& app_id,
                              ui::ImageModel app_icon);
  // Shows or hides the intent icon, with customizations specific to link intent
  // handling.
  void ShowIconForLinkIntent(bool should_show_icon);
  void ShowOrHideIconInternal(bool should_show_icon);

  // Logic to launch the app from the intent picker:

  void ShowIntentPickerOrLaunchAppImpl(
      const GURL& url,
      bool always_show,
      ShowIntentPickerBubbleCallback callback,
      std::optional<webapps::AppId> scoped_app_id,
      std::vector<apps::IntentPickerAppInfo> apps);

  void OnIntentPickerClosedMaybeLaunch(
      const GURL& url,
      ShowIntentPickerBubbleCallback callback,
      const std::string& launch_name,
      apps::PickerEntryType entry_type,
      apps::IntentPickerCloseReason close_reason,
      bool should_persist);

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

  // web_app::WebAppInstallManagerObserver:
  void OnWebAppWillBeUninstalled(const webapps::AppId& app_id) override;
  void OnWebAppInstallManagerDestroyed() override;

  const raw_ptr<web_app::WebAppRegistrar, DanglingUntriaged> registrar_;
  const raw_ptr<web_app::WebAppInstallManager> install_manager_;

  bool should_show_icon_ = false;
  bool icon_resolved_ = false;
  url::Origin last_shown_origin_;
  // True if the icon should be shown as in an expanded chip style due to usage
  // on this origin.
  bool show_expanded_chip_from_usage_ = false;

  // Contains the app ID of an app which can be opened through the intent
  // picker. This is only set when MaybeShowIconForApps() is called with a
  // single app. Will be set to the empty string in all other cases (e.g. when
  // there are multiple apps available, or when the icon is not visible).
  std::string current_app_id_;
  // True if |current_app_id_| is set as the preferred app for its http/https
  // links.
  bool current_app_is_preferred_ = false;
  ui::ImageModel current_app_icon_;

  base::OnceClosure icon_update_closure_for_testing_;

  std::unique_ptr<apps::AppsIntentPickerDelegate> intent_picker_delegate_;

  base::ScopedObservation<web_app::WebAppInstallManager,
                          web_app::WebAppInstallManagerObserver>
      install_manager_observation_{this};

  ui::ScopedUnownedUserData<IntentPickerTabHelper> scoped_unowned_user_data_;

  // This weak ptr factory is invalidated when a new navigation finishes.
  base::WeakPtrFactory<IntentPickerTabHelper> per_navigation_weak_factory_{
      this};
};

#endif  // CHROME_BROWSER_UI_INTENT_PICKER_TAB_HELPER_H_
