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

#ifndef ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_H_
#define ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_H_

#include "ash/ash_export.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_multi_source_observation.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/wm/core/transient_window_observer.h"

namespace gfx {
class Rect;
}  // namespace gfx

namespace views {
class Widget;
}  // namespace views

namespace ash {

class SplitViewController;
class SplitViewDividerView;

// Observes the windows in the split view and controls the stacking orders among
// the split view divider and its observed windows. The divider widget should
// always be placed above its observed windows to be able to receive events
// unless it's being dragged.
class ASH_EXPORT SplitViewDivider : public aura::WindowObserver,
                                    public ::wm::TransientWindowObserver {
 public:
  explicit SplitViewDivider(SplitViewController* controller);
  SplitViewDivider(const SplitViewDivider&) = delete;
  SplitViewDivider& operator=(const SplitViewDivider&) = delete;
  ~SplitViewDivider() override;

  // static version of GetDividerBoundsInScreen(bool is_dragging) function.
  static gfx::Rect GetDividerBoundsInScreen(
      const gfx::Rect& work_area_bounds_in_screen,
      bool landscape,
      int divider_position,
      bool is_dragging);

  views::Widget* divider_widget() const { return divider_widget_; }

  // Do the divider spawning animation that adds a finishing touch to the
  // snapping animation of a window.
  void DoSpawningAnimation(int spawn_position);

  // Updates `divider_widget_`'s bounds.
  void UpdateDividerBounds();

  // Calculates the divider's expected bounds according to the divider's
  // position.
  gfx::Rect GetDividerBoundsInScreen(bool is_dragging);

  // Sets the adjustability of the divider bar. Unadjustable divider does not
  // receive event and the divider bar view is not visible. When the divider is
  // moved for the virtual keyboard, the divider will be set unadjustable.
  void SetAdjustable(bool adjustable);

  // Returns true if the divider bar is adjustable.
  bool IsAdjustable() const;

  void AddObservedWindow(aura::Window* window);
  void RemoveObservedWindow(aura::Window* window);

  // Called when a window tab(s) are being dragged around the workspace. The
  // divider should be placed beneath the dragged window during dragging and be
  // placed above the dragged window when drag is completed.
  void OnWindowDragStarted(aura::Window* dragged_window);
  void OnWindowDragEnded();

  // aura::WindowObserver:
  void OnWindowDestroying(aura::Window* window) override;
  void OnWindowBoundsChanged(aura::Window* window,
                             const gfx::Rect& old_bounds,
                             const gfx::Rect& new_bounds,
                             ui::PropertyChangeReason reason) override;
  void OnWindowStackingChanged(aura::Window* window) override;
  void OnWindowAddedToRootWindow(aura::Window* window) override;

  // ::wm::TransientWindowObserver:
  void OnTransientChildAdded(aura::Window* window,
                             aura::Window* transient) override;
  void OnTransientChildRemoved(aura::Window* window,
                               aura::Window* transient) override;

  SplitViewDividerView* divider_view_for_testing() { return divider_view_; }
  const aura::Window::Windows& observed_windows_for_testing() const {
    return observed_windows_;
  }

 private:
  void CreateDividerWidget(SplitViewController* controller);

  // Refreshes the stacking order of the `divider_widget_` to be right on top of
  // the `observed_windows_` and reparents the split view divider to be on the
  // same parent container of the above window of the `observed_windows_` while
  // not dragging. The `divider_widget` will be temporarily stacked below the
  // window being dragged and reparented if the window being dragged has
  // different parent with the divider widget native window.
  void RefreshStackingOrder();

  void StartObservingTransientChild(aura::Window* transient);
  void StopObservingTransientChild(aura::Window* transient);

  raw_ptr<SplitViewController, ExperimentalAsh> controller_;

  // Split view divider widget. It's a black bar stretching from one edge of the
  // screen to the other, containing a small white drag bar in the middle. As
  // the user presses on it and drag it to left or right, the left and right
  // window will be resized accordingly.
  raw_ptr<views::Widget, ExperimentalAsh> divider_widget_ = nullptr;

  // The contents view of the `divider_widget_`.
  raw_ptr<SplitViewDividerView, ExperimentalAsh> divider_view_ = nullptr;

  // This variable indicates the dragging state and records the window being
  // dragged which will be used to refresh the stacking order of the
  // `divider_widget_` to be stacked below the `dragged_window_`.
  aura::Window* dragged_window_ = nullptr;

  // The window(s) observed by the divider which will be updated upon adding or
  // removing window.
  aura::Window::Windows observed_windows_;

  // If true, skip the stacking order update. This is used to avoid recursive
  // update when updating the stacking order.
  bool pause_update_ = false;

  // Tracks observed transient windows.
  base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
      transient_windows_observations_{this};
};

}  // namespace ash

#endif  // ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_H_