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

#ifndef IOS_CHROME_BROWSER_LENS_OVERLAY_MODEL_LENS_OVERLAY_NAVIGATION_MANAGER_H_
#define IOS_CHROME_BROWSER_LENS_OVERLAY_MODEL_LENS_OVERLAY_NAVIGATION_MANAGER_H_

#import <map>
#import <vector>

#import "base/memory/raw_ptr.h"
#import "ios/public/provider/chrome/browser/lens/lens_overlay_result.h"
#import "ios/web/public/web_state_observer.h"
#import "url/gurl.h"

@protocol LensOverlayNavigationMutator;
namespace web {
class WebState;
}  // namespace web

/// A navigation manager that maintains the back list for the Lens overlay.
/// It manages `ChromeLensOverlayResult` generated by the Lens API and decides
/// which result should be loaded or discarded.
class LensOverlayNavigationManager : public web::WebStateObserver {
 public:
  /// Initializes the navigation manager with the given `mutator`.
  LensOverlayNavigationManager(id<LensOverlayNavigationMutator> mutator);
  LensOverlayNavigationManager(const LensOverlayNavigationManager&) = delete;
  LensOverlayNavigationManager& operator=(const LensOverlayNavigationManager&) =
      delete;
  ~LensOverlayNavigationManager() override;

  /// Sets the web state observed by the navigation manager.
  void SetWebState(web::WebState* web_state);

  /// Called when the Lens overlay generates a `result`.
  void LensOverlayDidGenerateResult(id<ChromeLensOverlayResult> result);

  /// Loads the unimodal omnibox navigation to `destinationURL` with
  /// `omniboxText`.
  void LoadUnimodalOmniboxNavigation(const GURL& destination_url,
                                     const std::u16string& omnibox_text);

  /// Whether there is a previous navigation.
  bool CanGoBack() const;
  /// Go back to the previous navigation.
  void GoBack();
  /// Clears the current navigation stacks.
  void ClearNavigations();

  // web::WebStateObserver methods.
  void DidStartLoading(web::WebState* web_state) override;
  void PageLoaded(
      web::WebState* web_state,
      web::PageLoadCompletionStatus load_completion_status) override;
  void DidStartNavigation(web::WebState* web_state,
                          web::NavigationContext* navigation_context) override;
  void DidFinishNavigation(web::WebState* web_state,
                           web::NavigationContext* navigation_context) override;
  void WebStateDestroyed(web::WebState* web_state) override;

 private:
  /// Item stored in `LensResultItem`, representing a sub navigation.
  struct LensSubNavigationItem {
    /// URL of the navigation.
    GURL destination_url;
    /// Text in the omnibox.
    std::u16string omnibox_text;
  };

  /// Item stored in the navigation container, representing a lens result and
  /// its sub-navigations.
  class LensResultItem {
   public:
    explicit LensResultItem(id<ChromeLensOverlayResult> lens_result);
    LensResultItem(const LensResultItem& result_item) = delete;
    LensResultItem& operator=(const LensResultItem&) = delete;
    ~LensResultItem();

    id<ChromeLensOverlayResult> lens_result() const { return lens_result_; }
    const std::string& comparison_key() const { return comparison_key_; }
    std::vector<LensSubNavigationItem>& sub_navigations() {
      return sub_navigations_;
    }
    const std::vector<LensSubNavigationItem>& sub_navigations() const {
      return sub_navigations_;
    }

    bool operator==(const LensResultItem& rhs) const {
      return this->lens_result_.isTextSelection ==
                 rhs.lens_result_.isTextSelection &&
             CGRectEqualToRect(this->lens_result_.selectionRect,
                               rhs.lens_result_.selectionRect) &&
             this->lens_result_.queryText == rhs.lens_result_.queryText;
    }

   private:
    /// Lens overlay result attached to this item.
    id<ChromeLensOverlayResult> lens_result_;
    /// Key to compare two lens results when reloading.
    std::string comparison_key_;
    /// Sub navigations that originate from the same lens result.
    std::vector<LensSubNavigationItem> sub_navigations_;
  };  // class LensResultItem

  /// Called when the navigation list has changed.
  void OnNavigationListUpdate() const;

  /// Go back to the previous sub navigation within the same lens navigation.
  void GoToPreviousSubNavigation();
  /// Go back to the previous lens navigation.
  void GoToPreviousLensNavigation();

  /// Whether the navigation from `current_url` to `destination_url` is a
  /// related search navigation. Dependent of the latest
  /// ChromeLensOverlayResult.
  BOOL IsNavigationRelatedSearch(GURL current_url, GURL destination_url);

  /// Adds the sub navigation to the current `LensResultItem` if it's not a
  /// reload.
  void RegisterSubNavigation(GURL url, const std::u16string& omnibox_text);

  /// Returns the omnibox text from the previous sub navigation.
  std::u16string PreviousOmniboxText() const;

  /// List of Lens navigation. Going back will load the previous Lens
  /// navigation or the previous sub-navigation if available.
  std::vector<std::unique_ptr<LensResultItem>> lens_navigation_items_;
  /// Map of reloaded navigation. Maps LensResultItem::comparison_key to the
  /// reloaded index in `lens_navigation_items_`.
  std::unordered_map<std::string, size_t> lens_reloaded_items_;

  /// Web state observed by this class.
  raw_ptr<web::WebState> web_state_ = nullptr;
  /// Lens navigation mutator.
  __weak id<LensOverlayNavigationMutator> mutator_;
  /// The result that is currently loading, or `nil` if none.
  __weak id<ChromeLensOverlayResult> loading_result_;
  /// The ID associated to the loading navigation of the current result.
  int loading_navigation_context_id_;
  /// The error produced during the loading of the page, or `nil` if no error.
  NSError* loading_navigation_error_;
};

#endif  // IOS_CHROME_BROWSER_LENS_OVERLAY_MODEL_LENS_OVERLAY_NAVIGATION_MANAGER_H_
