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

#include <map>
#include <memory>

#include "base/memory/singleton.h"
#include "base/memory/weak_ptr.h"
#include "chromeos/ash/components/boca/proto/bundle.pb.h"
#include "components/policy/core/browser/url_list/url_blocklist_manager.h"
#include "components/sessions/core/session_id.h"
#include "content/public/browser/web_contents.h"
#include "url/gurl.h"

// The OnTaskBlocklist is responsible for setting the appropriate url navigation
// restrictions for each tab.
class OnTaskBlocklist {
 public:
  // BlocklistSource implementation that blocks all traffic with the
  // exception of URLs specified by the teacher's navigation restriction level.
  // Note that this implementation only supports one observer at a time. Adding
  // a new observer will remove the previous one. These should only be called
  // from the main thread.
  class OnTaskBlocklistSource : public policy::BlocklistSource {
   public:
    OnTaskBlocklistSource(
        const GURL& url,
        ::boca::LockedNavigationOptions::NavigationType restriction_type);
    OnTaskBlocklistSource(const OnTaskBlocklistSource&) = delete;
    OnTaskBlocklistSource& operator=(const OnTaskBlocklistSource&) = delete;
    ~OnTaskBlocklistSource() override = default;

    const base::ListValue* GetBlocklistSpec() const override;
    const base::ListValue* GetAllowlistSpec() const override;
    bool DowngradeAllowlistWildcardToNeutral() const override;
    void SetBlocklistObserver(base::RepeatingClosure observer) override {}

   private:
    base::ListValue blocklist_;
    base::ListValue allowlist_;
  };

  explicit OnTaskBlocklist(
      std::unique_ptr<policy::URLBlocklistManager> url_blocklist_manager);
  OnTaskBlocklist(const OnTaskBlocklist&) = delete;
  OnTaskBlocklist& operator=(const OnTaskBlocklist&) = delete;
  ~OnTaskBlocklist();

  // Returns whether the `url` is in the same domain as `domain_url` (including
  // sub-domains). This should ideally be a standalone util method, but we leave
  // this in here for now so we can reuse domain level filters from the domain
  // nav restriction setup.
  static bool IsURLInDomain(const GURL& url, const GURL& domain_url);

  // Returns the URLBlocklistState for the given url in the context of the
  // specified tab.
  policy::URLBlocklist::URLBlocklistState GetURLBlocklistState(
      const GURL& url,
      content::WebContents* tab) const;

  // Sets the url restrictions for the given `url` with `restriction_level`.
  // This is different from `SetParentURLRestrictionLevel` since this can be
  // called on newly navigated urls not sent by the boca producer. True
  // represents we are able to set the restrictions for the tab, false
  // otherwise. It should only be true if it's a new tab.
  bool MaybeSetURLRestrictionLevel(
      content::WebContents* tab,
      const GURL& url,
      ::boca::LockedNavigationOptions::NavigationType restriction_level);

  // Convenient helper to register the parent-child tab association between the
  // specified child and parent tabs. Triggered early on from the navigation
  // throttle to prepare downstream blocklist checks.
  void SetParentForTab(content::WebContents* child_tab,
                       content::WebContents* parent_tab);

  // Sets the url restrictions for the given `url` with `restriction_level`.
  // Should only be called for the set of urls sent by the boca producer.
  void SetParentURLRestrictionLevel(
      content::WebContents* tab,
      const GURL& url,
      ::boca::LockedNavigationOptions::NavigationType restriction_level);

  // Updates the blocklist that is associated with the given `tab`. This is
  // triggered on an active tab change or when the current tab changes.
  void RefreshForUrlBlocklist(content::WebContents* tab);

  // Remove the `tab` from the `parent_tab_to_nav_filters_`;
  void RemoveParentFilter(content::WebContents* tab);

  // Remove the `tab` from the `child_tab_to_nav_filters_`;
  void RemoveChildFilter(content::WebContents* tab);

  void CleanupBlocklist();

  // Returns true if the tab can perform one level deep. If the current
  // restriction level is not `kOneLevelDeepNavigation`, then this will return
  // false. This should only be called in a block that checks that the current
  // restriction level is for one level deep navigation.
  bool CanPerformOneLevelNavigation(content::WebContents* tab) const;

  bool IsTabRestrictionOneLevelDeep(content::WebContents* tab) const;

  // Returns true if the `tab` is a parent tab. A parent tab is any tab that was
  // sent as part of a session bundle. Any other tab created (either via
  // ctrl+left click or a link click that sets itself to open in a new window)
  // during the session by the user is a child tab. Parent tabs should not
  // be closed during any point of an ongoing session.
  bool IsParentTab(content::WebContents* tab) const;

  ::boca::LockedNavigationOptions::NavigationType GetRestrictionLevelForTab(
      content::WebContents* tab) const;

  // Gets the original URL that was committed on creation of the specified tab.
  // Especially useful for domain level and 1LD checks.
  GURL GetOriginalURLForTab(content::WebContents* tab) const;

  SessionID GetParentTabId(content::WebContents* tab) const;

  // Gets the original URL for a tab under 1LD restrictions.
  GURL GetOneLevelDeepOriginalURL(SessionID tab_id) const;

  const policy::URLBlocklistManager* url_blocklist_manager();
  std::map<SessionID, ::boca::LockedNavigationOptions::NavigationType>
  parent_tab_to_nav_filters() const;
  std::map<SessionID, ::boca::LockedNavigationOptions::NavigationType>
  child_tab_to_nav_filters() const;

 private:
  SessionID previous_tab_id_ = SessionID::InvalidValue();
  GURL previous_url_;
  std::map<SessionID, ::boca::LockedNavigationOptions::NavigationType>
      parent_tab_to_nav_filters_;
  std::map<SessionID, ::boca::LockedNavigationOptions::NavigationType>
      child_tab_to_nav_filters_;
  std::map<SessionID, GURL> one_level_deep_original_url_;

  // Maps a tab to its original navigation URL. This is used for validating
  // permitted domains synchronously under DOMAIN_NAVIGATION.
  std::map<SessionID, GURL> tab_to_original_url_;

  // Maps a child tab to its parent tab. This allows child tabs to
  // resolve domain and 1LD restriction checks against their parent
  // tab's original URL.
  std::map<SessionID, SessionID> child_to_parent_tab_id_;
  const std::unique_ptr<policy::URLBlocklistManager> url_blocklist_manager_;
  base::WeakPtrFactory<OnTaskBlocklist> weak_pointer_factory_{this};
};
#endif  // CHROMEOS_ASH_COMPONENTS_BOCA_ON_TASK_ON_TASK_BLOCKLIST_H_
