// Copyright 2023 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_VIEWS_TOOLBAR_PINNED_TOOLBAR_ACTIONS_CONTAINER_H_
#define CHROME_BROWSER_UI_VIEWS_TOOLBAR_PINNED_TOOLBAR_ACTIONS_CONTAINER_H_

#include <memory>
#include <optional>
#include <set>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.h"
#include "chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.h"
#include "chrome/browser/ui/views/toolbar/pinned_toolbar_actions.h"
#include "chrome/browser/ui/views/toolbar/toolbar_icon_container_view.h"
#include "ui/actions/action_id.h"
#include "ui/actions/actions.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/views/drag_controller.h"

class BrowserView;
class ToolbarButtonProvider;
class ToolbarDivider;

namespace base {
class ScopedClosureRunner;
}

namespace views {
class ActionViewController;
}

// Container for pinned and epheremeral actions shown in the toolbar.
// Pinned actions are tracked by `pinned_buttons_`. Ephemeral actions are
// tracked by `popped_out_buttons_`. Pinned actions are determined by listening
// to PinnedToolbarActionsModel. Ephemeral actions are determined by external
// callers via the methods UpdateActionState() and  UpdateEphemeralAction().
class PinnedToolbarActionsContainer
    : public ToolbarIconContainerView,
      public PinnedToolbarActionsModel::Observer,
      public views::DragController,
      public PinnedToolbarActions {
  METADATA_HEADER(PinnedToolbarActionsContainer, ToolbarIconContainerView)

 public:
  explicit PinnedToolbarActionsContainer(
      BrowserView* browser_view,
      ToolbarButtonProvider* button_provider);
  PinnedToolbarActionsContainer(const PinnedToolbarActionsContainer&) = delete;
  PinnedToolbarActionsContainer& operator=(
      const PinnedToolbarActionsContainer&) = delete;
  ~PinnedToolbarActionsContainer() override;

  // ToolbarIconContainerView:
  void UpdateAllIcons() override;

  // views::View:
  void AddedToWidget() override;
  bool GetDropFormats(int* formats,
                      std::set<ui::ClipboardFormatType>* format_types) override;
  bool AreDropTypesRequired() override;
  bool CanDrop(const ui::OSExchangeData& data) override;
  void OnDragEntered(const ui::DropTargetEvent& event) override;
  int OnDragUpdated(const ui::DropTargetEvent& event) override;
  void OnDragExited() override;
  views::View::DropCallback GetDropCallback(
      const ui::DropTargetEvent& event) override;

  // PinnedToolbarActionsModel::Observer:
  void OnActionAddedLocally(actions::ActionId id) override;
  void OnActionRemovedLocally(actions::ActionId id) override;
  void OnActionsChanged() override;

  // views::DragController:
  void WriteDragDataForView(View* sender,
                            const gfx::Point& press_pt,
                            ui::OSExchangeData* data) override;
  int GetDragOperationsForView(View* sender, const gfx::Point& p) override;
  bool CanStartDragForView(View* sender,
                           const gfx::Point& press_pt,
                           const gfx::Point& p) override;

  // ToolbarController::PinnedActionsDelegate:
  actions::ActionItem* GetActionItemFor(actions::ActionId id) override;
  bool IsOverflowed(actions::ActionId id) override;
  views::View* GetContainerView() override;
  bool ShouldAnyButtonsOverflow(gfx::Size available_size) const override;

  // PinnedToolbarActions:
  void UpdateActionState(actions::ActionId id, bool is_active) override;
  void ShowActionEphemerallyInToolbar(actions::ActionId id, bool show) override;
  bool IsActionPinned(actions::ActionId id) override;
  bool IsActionPoppedOut(actions::ActionId id) override;
  bool IsActionPinnedOrPoppedOut(actions::ActionId id) override;
  void PostOrQueueActionAfterAnimation(base::OnceClosure action) override;
  ToolbarButton* GetDownloadButton() override;
  views::BubbleAnchor GetBubbleAnchor(actions::ActionId action_id) override;
  void GetBubbleAnchorAsync(
      actions::ActionId action_id,
      base::OnceCallback<void(BubbleAnchorResult)> callback) override;
  PinnedActionToolbarButton* GetChromeLabsButton() override;
  void UpdatePinnedStateAndAnnounce(actions::ActionId id, bool pin) override;
  void MovePinnedAction(actions::ActionId action_id, int target_index) override;
  void MovePinnedActionBy(actions::ActionId action_id, int delta) override;

  // Returns the button associated with `id`. This does not return permanent
  // buttons which are currently invisible, an accessor for these can be
  // obtained on creation via `CreatePermanentButtonFor`.
  PinnedActionToolbarButton* GetButtonFor(actions::ActionId id);

  // Removes the popped out button if it should no longer remain in the toolbar.
  void MaybeRemovePoppedOutButtonFor(actions::ActionId id);

  // Ensures that if `id` is unpinned, the associated button object will not
  // get destroyed. This is useful for features which need to maintain a
  // persistent reference to the button.
  PinnedActionToolbarButton* CreatePermanentButtonFor(actions::ActionId id);

  gfx::Size GetDefaultButtonSize() const;

  const std::vector<actions::ActionId>& PinnedActionIds() const override;

  base::WeakPtr<PinnedToolbarActionsContainer> GetWeakPtrForTesting() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  friend class PinnedSidePanelInteractiveTest;
  friend class PinnedToolbarActionsContainerTest;

  // A struct representing the position and action being dragged.
  struct DropInfo;
  class BrowserObserver;

  PinnedActionToolbarButton* AddPoppedOutButtonFor(actions::ActionId id);
  void AddPinnedActionButtonFor(actions::ActionId id);
  void RemovePinnedActionButtonFor(actions::ActionId id);
  PinnedActionToolbarButton* GetPinnedButtonFor(actions::ActionId id);
  PinnedActionToolbarButton* GetPoppedOutButtonFor(actions::ActionId id);
  bool ShouldRemainPoppedOutInToolbar(PinnedActionToolbarButton* button);
  // Returns the size based on the layout manager's default flex specification.
  gfx::Size DefaultFlexRule(const views::SizeBounds& size_bounds);
  // Returns the total width of the `popped_out_buttons_` including margins
  // between them.
  int CalculatePoppedOutButtonsWidth();

  // Sorts child views to display them in the correct order.
  void ReorderViews();

  // Updates the container view to match the current state of the model.
  void UpdateViews();

  void RemoveButton(PinnedActionToolbarButton* button);
  void SetActionButtonIconVisibility(actions::ActionId id, bool visible);

  // Moves the dragged action `action_id`.
  void MovePinnedActionOnDrop(
      actions::ActionId action_id,
      size_t index,
      base::ScopedClosureRunner cleanup,
      const ui::DropTargetEvent& event,
      ui::mojom::DragOperation& output_drag_op,
      std::unique_ptr<ui::LayerTreeOwner> drag_image_layer_owner);

  std::unique_ptr<PinnedActionToolbarButton> CreateOrGetButtonForAction(
      actions::ActionId id);

  // Performs clean up after dragging.
  void DragDropCleanup(actions::ActionId dragged_action_id);

  // Utility function for going from width to icon counts.
  size_t WidthToIconCount(int x_offset);

  const std::unique_ptr<BrowserObserver> browser_observer_;
  raw_ptr<BrowserView> browser_view_;
  raw_ptr<ToolbarButtonProvider> button_provider_;

  std::unique_ptr<views::ActionViewController> action_view_controller_;
  std::vector<raw_ptr<PinnedActionToolbarButton, VectorExperimental>>
      pinned_buttons_;
  std::vector<raw_ptr<PinnedActionToolbarButton, VectorExperimental>>
      popped_out_buttons_;
  std::vector<std::unique_ptr<PinnedActionToolbarButton>> permanent_buttons_;
  raw_ptr<ToolbarDivider> toolbar_divider_;
  raw_ptr<PinnedToolbarActionsModel> model_;

  base::ScopedObservation<PinnedToolbarActionsModel,
                          PinnedToolbarActionsModel::Observer>
      model_observation_{this};

  // The DropInfo for the current drag-and-drop operation, or a null pointer if
  // there is none.
  std::unique_ptr<DropInfo> drop_info_;

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

  base::WeakPtrFactory<PinnedToolbarActionsContainer> drop_weak_ptr_factory_{
      this};
};

#endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_PINNED_TOOLBAR_ACTIONS_CONTAINER_H_
