// Copyright 2014 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/settings_window_manager_chromeos.h"

#include <utility>

#include "ash/public/cpp/app_list/internal_app_id_constants.h"
#include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
#include "ash/public/cpp/shelf_item.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/webui/settings/public/constants/routes_util.h"
#include "ash/wm/window_properties.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/app_mode/app_mode_utils.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/ash/browser_delegate/browser_delegate.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/create_browser_window.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/navigator/browser_navigator.h"
#include "chrome/browser/ui/navigator/browser_navigator_params.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/webui_url_constants.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/components/system_web_apps/system_web_app_type.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tracker.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"

namespace chrome {

namespace {

bool g_force_deprecated_settings_window_for_testing = false;
SettingsWindowManager* g_instance = nullptr;

class LegacySettingsTitleUpdater : public aura::WindowTracker {
 public:
  LegacySettingsTitleUpdater() = default;
  LegacySettingsTitleUpdater(const LegacySettingsTitleUpdater&) = delete;
  LegacySettingsTitleUpdater& operator=(const LegacySettingsTitleUpdater&) =
      delete;
  ~LegacySettingsTitleUpdater() override = default;

  // aura::WindowTracker:
  void OnWindowTitleChanged(aura::Window* window) override {
    // Name the window "Settings" instead of "Google Chrome - Settings".
    window->SetTitle(l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE));
  }
};

}  // namespace

SettingsWindowManager::SettingsWindowManager()
    : legacy_settings_title_updater_(
          std::make_unique<LegacySettingsTitleUpdater>()) {
  CHECK(!g_instance);
  g_instance = this;
}

SettingsWindowManager::~SettingsWindowManager() {
  CHECK_EQ(g_instance, this);
  g_instance = nullptr;
}

// static
SettingsWindowManager* SettingsWindowManager::GetInstance() {
  return g_instance;
}

// static
void SettingsWindowManager::ForceDeprecatedSettingsWindowForTesting() {
  g_force_deprecated_settings_window_for_testing = true;
}

// static
bool SettingsWindowManager::UseDeprecatedSettingsWindow(Profile* profile) {
  if (g_force_deprecated_settings_window_for_testing) {
    return true;
  }

  // Use deprecated settings window in Kiosk session only if SWA is disabled.
  if (IsRunningInForcedAppMode()) {
    return true;
  }

  return !web_app::AreWebAppsEnabled(profile);
}

void SettingsWindowManager::Open(const user_manager::User& user,
                                 OpenParams params) {
  if (params.entry_point.has_value()) {
    base::UmaHistogramEnumeration("AppManagement.EntryPoints",
                                  params.entry_point.value());
  }

  Profile* profile = Profile::FromBrowserContext(
      ash::BrowserContextHelper::Get()->GetBrowserContextByUser(&user));

  // TODO(crbug.com/47287122): unify SettingsWindowManager::ShowOSSettings,
  // after callers are updated.
  if (params.setting_id.has_value()) {
    ShowOSSettings(profile, params.sub_page, *params.setting_id,
                   params.display_id);
  } else {
    ShowOSSettings(profile, params.sub_page, params.display_id);
  }
}

void SettingsWindowManager::ShowChromePageForProfile(
    Profile* profile,
    const GURL& gurl,
    int64_t display_id,
    apps::LaunchCallback callback) {
  // Use the original (non off-the-record) profile for settings unless
  // this is a guest session.
  if (!profile->IsGuestSession() && profile->IsOffTheRecord()) {
    profile = profile->GetOriginalProfile();
  }

  // If this profile isn't allowed to create browser windows (e.g. the login
  // screen profile) then bail out. Neither the new SWA code path nor the legacy
  // code path can successfully open the window for these profiles.
  if (GetBrowserWindowCreationStatusForProfile(*profile) !=
      Browser::CreationStatus::kOk) {
    LOG(ERROR) << "Unable to open settings for this profile, url "
               << gurl.spec();
    if (callback) {
      std::move(callback).Run(apps::LaunchResult::kFailed);
    }
    return;
  }

  // TODO(crbug.com/1067073): Remove legacy Settings Window.
  if (!UseDeprecatedSettingsWindow(profile)) {
    ash::SystemAppLaunchParams params;
    params.url = gurl;
    ash::LaunchSystemWebAppAsync(
        profile, ash::SystemWebAppType::SETTINGS, params,
        std::make_unique<apps::WindowInfo>(display_id),
        callback ? std::make_optional(std::move(callback)) : std::nullopt);
    // SWA OS Settings don't use SettingsWindowManager to manage windows, don't
    // notify SettingsWindowObservers.
    return;
  }

  // Look for an existing browser window.
  BrowserWindowInterface* browser = FindBrowserForProfile(profile);
  if (browser) {
    DCHECK(browser->GetProfile() == profile);
    content::WebContents* web_contents =
        browser->GetTabStripModel()->GetWebContentsAt(0);
    if (web_contents && web_contents->GetURL() == gurl) {
      browser->GetWindow()->Show();
      if (callback) {
        std::move(callback).Run(apps::LaunchResult::kSuccess);
      }
      return;
    }

    NavigateParams params(browser, gurl, ui::PAGE_TRANSITION_AUTO_BOOKMARK);
    params.window_action = NavigateParams::WindowAction::kShowWindow;
    params.user_gesture = true;
    Navigate(&params);
    if (callback) {
      std::move(callback).Run(apps::LaunchResult::kSuccess);
    }
    return;
  }

  // No existing browser window, create one.
  NavigateParams params(profile, gurl, ui::PAGE_TRANSITION_AUTO_BOOKMARK);
  params.disposition = WindowOpenDisposition::NEW_POPUP;
  params.trusted_source = true;
  params.window_action = NavigateParams::WindowAction::kShowWindow;
  params.user_gesture = true;
  params.path_behavior = NavigateParams::IGNORE_AND_NAVIGATE;
  Navigate(&params);
  CHECK(params.browser);  // See https://crbug.com/40746844
  browser = params.browser;

  // operator[] not used because SessionID has no default constructor.
  settings_session_map_.emplace(profile, SessionID::InvalidValue())
      .first->second = browser->GetSessionID();
  DCHECK(WindowFeatureController::From(browser)->IsTrustedSource());

  // Configure the created window property.
  auto* window = browser->GetWindow()->GetNativeWindow();
  window->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::CHROME_APP);
  window->SetProperty(ash::kOverrideWindowIconResourceIdKey,
                      IDR_SETTINGS_LOGO_192);
  window->SetTitle(l10n_util::GetStringUTF16(IDS_SETTINGS_TITLE));
  const ash::ShelfID shelf_id(ash::kInternalAppIdSettings);
  window->SetProperty(ash::kShelfIDKey, shelf_id.Serialize());
  window->SetProperty(ash::kAppIDKey, shelf_id.app_id);
  window->SetProperty<int>(ash::kShelfItemTypeKey, ash::TYPE_APP);
  legacy_settings_title_updater_->Add(window);

  if (callback) {
    std::move(callback).Run(apps::LaunchResult::kSuccess);
  }
}

void SettingsWindowManager::ShowOSSettings(Profile* profile,
                                           int64_t display_id) {
  ShowOSSettings(profile, std::string(), display_id);
}

void SettingsWindowManager::ShowOSSettings(Profile* profile,
                                           std::string_view sub_page,
                                           int64_t display_id) {
  ShowChromePageForProfile(profile,
                           chromeos::settings::GetOSSettingsUrl(sub_page),
                           display_id, /*callback=*/{});
}

void SettingsWindowManager::ShowOSSettings(
    Profile* profile,
    std::string_view sub_page,
    const chromeos::settings::mojom::Setting setting_id,
    int64_t display_id) {
  std::string path_with_setting_id =
      base::StrCat({sub_page, std::string("?settingId="),
                    base::NumberToString(std::to_underlying(setting_id))});

  ShowOSSettings(profile, path_with_setting_id, display_id);
}

BrowserWindowInterface* SettingsWindowManager::FindBrowserForProfile(
    Profile* profile) {
  if (!UseDeprecatedSettingsWindow(profile)) {
    ash::BrowserDelegate* delegate = ash::FindSystemWebAppBrowser(
        profile, ash::SystemWebAppType::SETTINGS, ash::BrowserType::kApp);
    return delegate ? &delegate->GetBrowser() : nullptr;
  }

  auto iter = settings_session_map_.find(profile);
  if (iter != settings_session_map_.end()) {
    return GlobalBrowserCollection::GetInstance()->FindBrowserWithID(
        iter->second);
  }

  return nullptr;
}

bool SettingsWindowManager::IsSettingsBrowser(
    BrowserWindowInterface* browser) const {
  DCHECK(browser);

  Profile* const profile = browser->GetProfile();
  if (!UseDeprecatedSettingsWindow(profile)) {
    const web_app::AppBrowserController* const app_controller =
        web_app::AppBrowserController::From(browser);
    if (!app_controller) {
      return false;
    }

    // TODO(calamity): Determine whether, during startup, we need to wait for
    // app install and then provide a valid answer here.
    std::optional<std::string> settings_app_id =
        ash::GetAppIdForSystemWebApp(profile, ash::SystemWebAppType::SETTINGS);
    return settings_app_id &&
           app_controller->app_id() == settings_app_id.value();
  }

  auto iter = settings_session_map_.find(profile);
  return iter != settings_session_map_.end() &&
         iter->second == browser->GetSessionID();
}

}  // namespace chrome
