// Copyright 2025 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/ash/browser_delegate/browser_controller_impl.h"

#include <memory>
#include <unordered_map>

#include "base/check.h"
#include "base/containers/span.h"
#include "chrome/browser/ash/browser_delegate/browser_delegate_impl.h"
#include "chrome/browser/ash/browser_delegate/browser_type.h"
#include "chrome/browser/ash/browser_delegate/browser_type_conversion.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/lifetime/application_lifetime_desktop.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_init_state.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/browser_window_interface_iterator.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/browser_window/public/profile_browser_collection.h"
#include "chrome/browser/ui/intent_picker_tab_helper.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/tabs/tab_strip_model_delegate.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/simple_web_view_dialog.h"
#include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "components/account_id/account_id.h"
#include "ui/aura/window.h"

namespace {

bool BrowserMatchesURL(BrowserWindowInterface* browser, const GURL& url) {
  return browser->GetTabStripModel()
      ->GetActiveWebContents()
      ->GetVisibleURL()
      .EqualsIgnoringRef(url);
}

bool BrowserMatches(BrowserWindowInterface* browser,
                    Profile* profile,
                    webapps::AppId app_id,
                    Browser::Type type,
                    const GURL& url) {
  return browser->GetProfile() == profile && browser->GetType() == type &&
         web_app::GetAppIdFromApplicationName(
             BrowserInitState::From(browser)->create_params().app_name) ==
             app_id &&
         (url.is_empty() || BrowserMatchesURL(browser, url));
}

// Returns the most recently activated tabbed browser for |profile| that is on
// the current workspace (virtual desk), or nullptr if none exists.
BrowserWindowInterface* FindTabbedBrowserOnCurrentWorkspace(Profile* profile) {
  BrowserWindowInterface* match = nullptr;
  ProfileBrowserCollection::GetForProfile(profile)->ForEach(
      [&match](BrowserWindowInterface* browser) {
        if (browser->GetType() != BrowserWindowInterface::TYPE_NORMAL ||
            browser->IsDeleteScheduled()) {
          return true;
        }
        BrowserWindow* browser_window = BrowserWindow::FromBrowser(browser);
        if (!browser_window || !browser_window->IsOnCurrentWorkspace()) {
          return true;
        }
        match = browser;
        return false;  // stop iterating
      },
      BrowserCollection::Order::kActivation);
  return match;
}

}  // namespace

namespace ash {

BrowserControllerImpl::BrowserControllerImpl() {
  observation_.Observe(GlobalBrowserCollection::GetInstance());
  GlobalBrowserCollection::GetInstance()->ForEach(
      [this](BrowserWindowInterface* browser) {
        browser->GetTabStripModel()->AddObserver(this);
        return true;
      });
}

BrowserControllerImpl::~BrowserControllerImpl() {
  GlobalBrowserCollection::GetInstance()->ForEach(
      [this](BrowserWindowInterface* browser) {
        browser->GetTabStripModel()->RemoveObserver(this);
        return true;
      });
}

BrowserDelegate* BrowserControllerImpl::GetDelegate(
    BrowserWindowInterface* bwi) {
  if (!bwi) {
    return nullptr;
  }

  auto it = browsers_.find(bwi);
  if (it == browsers_.end()) {
    it = browsers_
             .emplace(bwi, std::make_unique<BrowserDelegateImpl>(
                               bwi->GetBrowserForMigrationOnly()))
             .first;
  }
  return it->second.get();
}

BrowserDelegate* BrowserControllerImpl::GetLastUsedBrowser() {
  return GetDelegate(GetLastActiveBrowserWindowInterfaceWithAnyProfile());
}

BrowserDelegate* BrowserControllerImpl::GetLastUsedVisibleBrowser() {
  BrowserDelegate* browser_delegate = nullptr;
  GlobalBrowserCollection::GetInstance()->ForEach(
      [&](BrowserWindowInterface* browser) {
        if (browser->GetWindow()->IsVisible()) {
          browser_delegate = GetDelegate(browser);
          return false;  // stop iterating
        }
        return true;  // continue iterating
      },
      BrowserCollection::Order::kActivation);
  return browser_delegate;
}

BrowserDelegate* BrowserControllerImpl::GetLastUsedVisibleOnTheRecordBrowser() {
  BrowserDelegate* browser_delegate = nullptr;
  GlobalBrowserCollection::GetInstance()->ForEach(
      [&](BrowserWindowInterface* browser) {
        if (!browser->GetProfile()->IsOffTheRecord() &&
            browser->GetWindow()->IsVisible()) {
          browser_delegate = GetDelegate(browser);
          return false;  // stop iterating
        }
        return true;  // continue iterating
      },
      BrowserCollection::Order::kActivation);
  return browser_delegate;
}

void BrowserControllerImpl::ForEachBrowser(
    BrowserOrder order,
    base::FunctionRef<IterationDirective(BrowserDelegate&)> callback) {
  BrowserCollection::Order collection_order;
  switch (order) {
    case BrowserOrder::kAscendingCreationTime:
      collection_order = BrowserCollection::Order::kCreation;
      break;
    case BrowserOrder::kAscendingActivationTime:
      collection_order = BrowserCollection::Order::kActivation;
      break;
  }

  GlobalBrowserCollection::GetInstance()->ForEach(
      [&](BrowserWindowInterface* browser) {
        return callback(*GetDelegate(browser)) != kBreakIteration;
      },
      collection_order);
}

BrowserDelegate* BrowserControllerImpl::GetBrowserForWindow(
    aura::Window* window) {
  // TODO(crbug.com/369688254): We'd like to use
  // BrowserView::GetBrowserViewForNativeWindow followed by BrowserView::browser
  // here but this can CHECK-fail during shutdown. Find a solution.
  return GetDelegate(
      GlobalBrowserCollection::GetInstance()->FindBrowserWithWindow(window));
}

BrowserDelegate* BrowserControllerImpl::GetBrowserForTab(
    content::WebContents* contents) {
  // TODO(crbug.com/369688254): We'd like to use
  // tabs::TabInterface::MaybeGetFromContents followed by
  // tabs::TabInterface::GetBrowserWindowInterface here but this can CHECK-fail
  // during shutdown. Find a solution.
  BrowserWindowInterface* found = nullptr;
  GlobalBrowserCollection::GetInstance()->ForEach(
      [&found, contents](BrowserWindowInterface* browser) {
        TabStripModel* model = browser->GetTabStripModel();
        if (model->GetIndexOfWebContents(contents) != TabStripModel::kNoTab) {
          found = browser;
        }
        return !found;
      });
  return GetDelegate(found);
}

BrowserDelegate* BrowserControllerImpl::FindWebApp(const AccountId& account_id,
                                                   webapps::AppId app_id,
                                                   BrowserType browser_type,
                                                   const GURL& url) {
  Profile* profile = Profile::FromBrowserContext(
      BrowserContextHelper::Get()->GetBrowserContextByAccountId(account_id));
  CHECK(profile);

  CHECK(browser_type == BrowserType::kApp ||
        browser_type == BrowserType::kAppPopup);
  Browser::Type internal_type = ToInternalBrowserType(browser_type);

  BrowserDelegate* browser_delegate = nullptr;
  GlobalBrowserCollection::GetInstance()->ForEach(
      [&](BrowserWindowInterface* browser) {
        if (!browser->IsDeleteScheduled() &&
            BrowserMatches(browser, profile, app_id, internal_type, url)) {
          browser_delegate = GetDelegate(browser);
          return false;  // stop iterating
        }
        return true;  // continue iterating
      },
      BrowserCollection::Order::kActivation);

  return browser_delegate;
}

BrowserDelegate* BrowserControllerImpl::NewTabWithPostData(
    const AccountId& account_id,
    const GURL& url,
    base::span<const uint8_t> post_data,
    std::string_view extra_headers) {
  Profile* profile = Profile::FromBrowserContext(
      BrowserContextHelper::Get()->GetBrowserContextByAccountId(account_id));
  CHECK(profile);

  NavigateParams navigate_params(
      profile, url,
      // TODO(crbug.com/369688254): The page transition was chosen to satisfy
      // some obsolete condition and should be revisited.
      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
                                ui::PAGE_TRANSITION_FROM_API |
                                ui::PAGE_TRANSITION_FROM_ADDRESS_BAR));
  navigate_params.window_action = NavigateParams::WindowAction::kShowWindow;
  navigate_params.post_data =
      network::ResourceRequestBody::CreateFromCopyOfBytes(post_data);
  navigate_params.extra_headers = std::string(extra_headers);

  navigate_params.browser = FindTabbedBrowserOnCurrentWorkspace(profile);
  if (!navigate_params.browser &&
      GetBrowserWindowCreationStatusForProfile(*profile) ==
          BrowserWindowInterface::CreationStatus::kOk) {
    BrowserWindowCreateParams create_params(profile,
                                            navigate_params.user_gesture);
    create_params.should_trigger_session_restore = false;
    navigate_params.browser = CreateBrowserWindow(std::move(create_params));
  }

  Navigate(&navigate_params);
  return GetDelegate(navigate_params.browser);
}

BrowserDelegate* BrowserControllerImpl::CreateWebApp(
    const AccountId& account_id,
    webapps::AppId app_id,
    BrowserType browser_type,
    const CreateParams& params) {
  CHECK(browser_type == BrowserType::kApp ||
        browser_type == BrowserType::kAppPopup)
      << "Unexpected BrowserType: " << static_cast<int>(browser_type);
  const bool popup = browser_type == BrowserType::kAppPopup;

  Profile* profile = Profile::FromBrowserContext(
      BrowserContextHelper::Get()->GetBrowserContextByAccountId(account_id));
  CHECK(profile);

  if (GetBrowserWindowCreationStatusForProfile(*profile) !=
      Browser::CreationStatus::kOk) {
    LOG(WARNING) << "Cannot create browser for given profile";
    return nullptr;
  }

  BrowserWindowCreateParams cparams =
      web_app::CreateParamsForApp(app_id, popup,
                                  /*trusted_source=*/true,
                                  /*window_bounds=*/gfx::Rect(), profile,
                                  /*user_gesture=*/true);
  cparams.restore_id = params.restore_id;
  cparams.omit_from_session_restore = true;
  cparams.initial_show_state = ui::mojom::WindowShowState::kDefault;
  cparams.can_resize = params.allow_resize;
  cparams.can_maximize = params.allow_maximize;
  cparams.can_fullscreen = params.allow_fullscreen;
  return GetDelegate(
      web_app::CreateWebAppWindowMaybeWithHomeTab(app_id, std::move(cparams)));
}

void BrowserControllerImpl::MayCloseAllBrowsers() {
  return chrome::CloseAllBrowsers();
}

void BrowserControllerImpl::MayCloseAllBrowsersAndQuit() {
  return chrome::CloseAllBrowsersAndQuit();
}

bool BrowserControllerImpl::IsTryingToQuit() {
  return browser_shutdown::IsTryingToQuit();
}

bool BrowserControllerImpl::HasShutdownStarted() {
  return browser_shutdown::HasShutdownStarted();
}

void BrowserControllerImpl::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void BrowserControllerImpl::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void BrowserControllerImpl::AddTabObserver(TabObserver* observer) {
  tab_observers_.AddObserver(observer);
}

void BrowserControllerImpl::RemoveTabObserver(TabObserver* observer) {
  tab_observers_.RemoveObserver(observer);
}

void BrowserControllerImpl::OnBrowserCreated(BrowserWindowInterface* browser) {
  ash::BrowserDelegate* browser_delegate = GetDelegate(browser);
  for (auto& observer : observers_) {
    observer.OnBrowserCreated(browser_delegate);
  }

  browser->GetTabStripModel()->AddObserver(this);
  for (size_t i = 0; i < browser_delegate->GetWebContentsCount(); ++i) {
    content::WebContents* contents = browser_delegate->GetWebContentsAt(i);
    for (auto& observer : tab_observers_) {
      observer.OnTabInserted(browser_delegate, contents);
    }
  }
}

void BrowserControllerImpl::OnBrowserActivated(
    BrowserWindowInterface* browser) {
  ash::BrowserDelegate* browser_delegate = GetDelegate(browser);
  for (auto& observer : observers_) {
    observer.OnBrowserActivated(browser_delegate);
  }
}

void BrowserControllerImpl::OnBrowserClosed(BrowserWindowInterface* browser) {
  ash::BrowserDelegate* browser_delegate = GetDelegate(browser);

  for (size_t i = browser_delegate->GetWebContentsCount(); i-- > 0;) {
    content::WebContents* contents = browser_delegate->GetWebContentsAt(i);
    for (auto& observer : tab_observers_) {
      observer.OnTabRemoved(browser_delegate, contents);
    }
  }
  browser->GetTabStripModel()->RemoveObserver(this);

  for (auto& observer : observers_) {
    observer.OnBrowserClosed(browser_delegate);
    if (GlobalBrowserCollection::GetInstance()->IsEmpty()) {
      observer.OnLastBrowserClosed();
    }
  }

  browsers_.erase(browser);
  // `browser_delegate` is now dead.
}

void BrowserControllerImpl::OnTabStripModelChanged(
    TabStripModel* tab_strip_model,
    const TabStripModelChange& change,
    const TabStripSelectionChange& selection) {
  BrowserDelegate* browser =
      GetDelegate(tab_strip_model->delegate()->GetBrowserWindowInterface());

  switch (change.type()) {
    case TabStripModelChange::kInserted:
      for (const auto& item : change.GetInsert()->contents) {
        for (auto& observer : tab_observers_) {
          observer.OnTabInserted(browser, item.contents);
        }
      }
      break;
    case TabStripModelChange::kRemoved:
      for (const auto& item : change.GetRemove()->contents) {
        for (auto& observer : tab_observers_) {
          observer.OnTabRemoved(browser, item.contents);
        }
      }
      break;
    case TabStripModelChange::kReplaced: {
      auto* replace = change.GetReplace();
      for (auto& observer : tab_observers_) {
        observer.OnTabReplaced(browser, replace->old_contents,
                               replace->new_contents);
      }
      break;
    }
    case TabStripModelChange::kMoved:
    case TabStripModelChange::kSelectionOnly:
      break;
  }

  if (selection.active_tab_changed() && !tab_strip_model->empty()) {
    for (auto& observer : tab_observers_) {
      observer.OnActiveWebContentsChanged(browser, selection.old_contents,
                                          selection.new_contents);
    }
  }
}

void BrowserControllerImpl::CreateAutofillClientForWebContents(
    content::WebContents* web_contents) {
  autofill::ChromeAutofillClient::CreateForWebContents(web_contents);
}

std::unique_ptr<views::SimpleWebView>
BrowserControllerImpl::CreateSimpleWebViewForSigninScreen(
    views::SimpleWebViewDialogDelegate* delegate) {
  return std::make_unique<SimpleWebViewDialog>(
      ash::ProfileHelper::GetSigninProfile(), delegate);
}

bool BrowserControllerImpl::ShowIntentPicker(
    base::WeakPtr<content::WebContents> web_contents,
    std::vector<apps::IntentPickerAppInfo> app_info,
    bool show_stay_in_chrome,
    bool show_remember_selection,
    apps::IntentPickerBubbleType bubble_type,
    base::optional_ref<const url::Origin> initiating_origin,
    IntentPickerResponse callback) {
  BrowserDelegate* browser =
      web_contents ? GetBrowserForTab(web_contents.get()) : nullptr;
  if (!browser || app_info.empty()) {
    return false;
  }

  auto* browser_window = BrowserWindow::FromBrowser(&browser->GetBrowser());
  if (!browser_window) {
    return false;
  }

  IntentPickerTabHelper::ShowOrHideIcon(web_contents.get(),
                                        /*should_show_icon=*/true);

  auto wrapped_callback = base::BindOnce(
      [](base::WeakPtr<content::WebContents> web_contents,
         IntentPickerResponse callback, const std::string& launch_name,
         apps::PickerEntryType entry_type,
         apps::IntentPickerCloseReason close_reason, bool should_persist) {
        if (web_contents) {
          IntentPickerTabHelper::ShowOrHideIcon(web_contents.get(),
                                                /*should_show_icon=*/false);
        }
        std::move(callback).Run(launch_name, entry_type, close_reason,
                                should_persist);
      },
      web_contents, std::move(callback));

  browser_window->ShowIntentPickerBubble(
      std::move(app_info), show_stay_in_chrome, show_remember_selection,
      bubble_type, initiating_origin.CopyAsOptional(),
      std::move(wrapped_callback));
  return true;
}

}  // namespace ash
