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

#include "chrome/browser/ui/views/frame/browser_native_widget_ash.h"

#include <memory>

#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/wm/window_properties.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_state_delegate.h"
#include "ash/wm/window_util.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_init_state.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/browser_widget.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/frame/frame_utils.h"
#include "components/app_restore/app_restore_info.h"
#include "components/app_restore/app_restore_utils.h"
#include "components/app_restore/full_restore_utils.h"
#include "components/app_restore/window_properties.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/views/view.h"

namespace {

// BrowserWindowStateDelegate class handles a user's fullscreen
// request (Shift+F4/F4).
class BrowserWindowStateDelegate : public ash::WindowStateDelegate {
 public:
  explicit BrowserWindowStateDelegate(BrowserWindowInterface* browser)
      : browser_(browser) {
    DCHECK(browser_);
  }

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

  ~BrowserWindowStateDelegate() override = default;

  // Overridden from ash::WindowStateDelegate.
  bool ToggleFullscreen(ash::WindowState* window_state) override {
    DCHECK(window_state->IsFullscreen() || window_state->CanFullscreen());
    if (!window_state->IsFullscreen() && !window_state->CanFullscreen()) {
      return true;
    }
    chrome::ToggleFullscreenMode(browser_, /*user_initiated=*/true);
    return true;
  }

  // Overridden from ash::WindowStateDelegate.
  void ToggleLockedFullscreen(ash::WindowState* window_state) override {
    ash::Shell::Get()->shell_delegate()->SetUpEnvironmentForLockedFullscreen(
        *window_state);
  }

 private:
  raw_ptr<BrowserWindowInterface> browser_;  // not owned.
};

}  // namespace

///////////////////////////////////////////////////////////////////////////////
// BrowserNativeWidgetAsh, public:

BrowserNativeWidgetAsh::BrowserNativeWidgetAsh(BrowserWidget* browser_widget,
                                               BrowserView* browser_view)
    : views::NativeWidgetAura(browser_widget), browser_view_(browser_view) {
  widget_observation_.Observe(browser_widget);
  GetNativeWindow()->SetName("BrowserNativeWidgetAsh");
  BrowserWindowInterface* browser = browser_view->browser();

  created_from_drag_ = browser_widget->tab_drag_kind() != TabDragKind::kNone;

  // Turn on auto window management if we don't need an explicit bounds.
  // This way the requested bounds are honored.
  if (!BrowserInitState::From(browser)->bounds_overridden() &&
      !BrowserInitState::From(browser)->is_session_restore()) {
    SetWindowAutoManaged();
  }
}

BrowserNativeWidgetAsh::~BrowserNativeWidgetAsh() = default;

///////////////////////////////////////////////////////////////////////////////
// BrowserNativeWidgetAsh, views::NativeWidgetAura overrides:

void BrowserNativeWidgetAsh::OnWidgetInitDone() {
  BrowserWindowInterface* browser = browser_view_->browser();
  ash::WindowState* window_state = ash::WindowState::Get(GetNativeWindow());
  window_state->SetDelegate(
      std::make_unique<BrowserWindowStateDelegate>(browser));
  // For legacy reasons v1 apps (like Secure Shell) are allowed to consume keys
  // like brightness, volume, etc. Otherwise these keys are handled by the
  // Ash window manager.
  window_state->SetCanConsumeSystemKeys(
      browser->GetType() == BrowserWindowInterface::Type::TYPE_APP ||
      browser->GetType() == BrowserWindowInterface::Type::TYPE_APP_POPUP);

  app_restore::AppRestoreInfo::GetInstance()->OnWidgetInitialized(GetWidget());
}

void BrowserNativeWidgetAsh::OnWindowTargetVisibilityChanged(bool visible) {
  if (visible) {
    // Once the window has been shown we know the requested bounds
    // (if provided) have been honored and we can switch on window management.
    SetWindowAutoManaged();
  }
  views::NativeWidgetAura::OnWindowTargetVisibilityChanged(visible);
}

////////////////////////////////////////////////////////////////////////////////
// BrowserNativeWidgetAsh, BrowserNativeWidget implementation:

bool BrowserNativeWidgetAsh::ShouldSaveWindowPlacement() const {
  return nullptr == GetWidget()->GetNativeWindow()->GetProperty(
                        ash::kRestoreBoundsOverrideKey);
}

void BrowserNativeWidgetAsh::GetWindowPlacement(
    gfx::Rect* bounds,
    ui::mojom::WindowShowState* show_state) const {
  aura::Window* window = GetWidget()->GetNativeWindow();
  gfx::Rect* override_bounds =
      window->GetProperty(ash::kRestoreBoundsOverrideKey);
  if (override_bounds && !override_bounds->IsEmpty()) {
    *bounds = *override_bounds;
    *show_state = chromeos::ToWindowShowState(
        window->GetProperty(ash::kRestoreWindowStateTypeOverrideKey));
  } else {
    // Snapped state is a ash only state which is normally not restored except
    // when the full restore feature is turned on. `Widget::GetRestoreBounds()`
    // will not return the restore bounds for a snapped window because to
    // Widget/NativeWidgetAura the window is a normal window, so get the restore
    // bounds directly from the ash window state.
    bool used_window_state_restore_bounds = false;
    auto* window_state = ash::WindowState::Get(window);
    if (window_state->IsSnapped() && window_state->HasRestoreBounds()) {
      // Additionally, if the window is closed, and not from logging out we
      // want to use the regular restore bounds, otherwise the next time the
      // user opens a window it will be in a different place than closed,
      // since session restore does not restore ash snapped state.
      if (browser_shutdown::IsTryingToQuit() || !GetWidget()->IsClosed()) {
        used_window_state_restore_bounds = true;
        *bounds = window_state->GetRestoreBoundsInScreen();
      }
    }

    if (!used_window_state_restore_bounds) {
      *bounds = GetWidget()->GetRestoredBounds();
    }
    *show_state = window->GetProperty(aura::client::kShowStateKey);
  }

  // Session restore might be unable to correctly restore other states.
  // For the record, https://crbug.com/40375827
  if (*show_state != ui::mojom::WindowShowState::kMaximized &&
      *show_state != ui::mojom::WindowShowState::kMinimized) {
    *show_state = ui::mojom::WindowShowState::kNormal;
  }
}

content::KeyboardEventProcessingResult
BrowserNativeWidgetAsh::PreHandleKeyboardEvent(
    const input::NativeWebKeyboardEvent& event) {
  return content::KeyboardEventProcessingResult::NOT_HANDLED;
}

bool BrowserNativeWidgetAsh::HandleKeyboardEvent(
    const input::NativeWebKeyboardEvent& event) {
  return false;
}

views::Widget::InitParams BrowserNativeWidgetAsh::GetWidgetParams(
    views::Widget::InitParams::Ownership ownership) {
  views::Widget::InitParams params(ownership);
  params.native_widget = this;
  params.context = ash::Shell::GetPrimaryRootWindow();

  BrowserWindowInterface* browser = browser_view_->browser();
  const int32_t restore_id =
      BrowserInitState::From(browser)->create_params().restore_id;
  params.init_properties_container.SetProperty(app_restore::kWindowIdKey,
                                               browser->GetSessionID().id());
  params.init_properties_container.SetProperty(app_restore::kRestoreWindowIdKey,
                                               restore_id);

  params.init_properties_container.SetProperty(
      app_restore::kAppTypeBrowser,
      (browser->GetType() == BrowserWindowInterface::Type::TYPE_APP ||
       browser->GetType() == BrowserWindowInterface::Type::TYPE_APP_POPUP));

  params.init_properties_container.SetProperty(
      app_restore::kBrowserAppNameKey,
      BrowserInitState::From(browser)->create_params().app_name);
  params.init_properties_container.SetProperty(
      chromeos::kShouldHaveHighlightBorderOverlay, true);

  bool is_app =
      browser->GetType() == BrowserWindowInterface::Type::TYPE_APP ||
      browser->GetType() == BrowserWindowInterface::Type::TYPE_APP_POPUP;
  web_app::AppBrowserController* controller =
      web_app::AppBrowserController::From(browser);
  if (controller && controller->system_app()) {
    params.init_properties_container.SetProperty(chromeos::kAppTypeKey,
                                                 chromeos::AppType::SYSTEM_APP);
  } else {
    params.init_properties_container.SetProperty(
        chromeos::kAppTypeKey,
        is_app ? chromeos::AppType::CHROME_APP : chromeos::AppType::BROWSER);
  }

  app_restore::ModifyWidgetParams(restore_id, &params);
  // Override session restore bounds with Full Restore bounds if they exist.
  if (!params.bounds.IsEmpty()) {
    BrowserInitState::From(browser)->set_override_bounds(params.bounds);
  } else {
    params.bounds =
        BrowserInitState::From(browser)->create_params().initial_bounds;
  }
  params.display_id =
      BrowserInitState::From(browser)->create_params().display_id;
  params.rounded_corners = chromeos::GetWindowRoundedCorners();

  return params;
}

bool BrowserNativeWidgetAsh::UseCustomFrame() const {
  return true;
}

bool BrowserNativeWidgetAsh::UsesNativeSystemMenu() const {
  return false;
}

bool BrowserNativeWidgetAsh::ShouldRestorePreviousBrowserWidgetState() const {
  CHECK(browser_view_);
  // If there is no window info from full restore, maybe use the session
  // restore.
  const int32_t restore_id = BrowserInitState::From(browser_view_->browser())
                                 ->create_params()
                                 .restore_id;
  // Don't restore unresizable browser apps, because they can get stuck at a
  // broken size, or the browser being dragged because it should use the
  // specified bounds.
  return !app_restore::HasWindowInfo(restore_id) &&
         BrowserInitState::From(browser_view_->browser())
             ->create_params()
             .can_resize &&
         !BrowserInitState::From(browser_view_->browser())
              ->create_params()
              .in_tab_dragging;
}

bool BrowserNativeWidgetAsh::ShouldUseInitialVisibleOnAllWorkspaces() const {
  return !created_from_drag_;
}

void BrowserNativeWidgetAsh::OnWidgetDestroyed(views::Widget* widget) {
  // If BrowserNativeWidgetAsh's NativeWindow has not been destroyed by the time
  // the Browser's Widget has been destroyed, clear any Browser refs to mitigate
  // risks from dangling pointers.
  if (GetNativeWindow()) {
    ash::WindowState* window_state = ash::WindowState::Get(GetNativeWindow());
    window_state->SetDelegate(nullptr);
  }
  browser_view_ = nullptr;
  widget_observation_.Reset();
}

///////////////////////////////////////////////////////////////////////////////
// BrowserNativeWidgetAsh, private:

void BrowserNativeWidgetAsh::SetWindowAutoManaged() {
  if (!browser_view_) {
    return;
  }
  // For browser window in Chrome OS, we should only enable the auto window
  // management logic for tabbed browser.
  if (browser_view_->browser()->GetType() ==
      BrowserWindowInterface::Type::TYPE_NORMAL) {
    GetNativeWindow()->SetProperty(ash::kWindowPositionManagedTypeKey, true);
  }
}
