// Copyright 2026 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/headless/headless_mode_init.h"

#include "base/check_deref.h"
#include "build/build_config.h"
#include "ui/gfx/switches.h"

// New headless mode is available on Linux, Windows and Mac platforms.
// More platforms will be added later, so avoid function level clutter
// by providing stub implementations at the end of the file.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "chrome/browser/headless/headless_mode_platform.h"
#include "chrome/browser/headless/headless_mode_switches.h"
#include "chrome/common/chrome_paths_internal.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/common/content_switches.h"

#if BUILDFLAG(IS_LINUX)
#include "ui/gl/gl_switches.h"               // nogncheck
#include "ui/ozone/public/ozone_switches.h"  // nogncheck
#endif

#if BUILDFLAG(IS_WIN)
#include "chrome/chrome_elf/chrome_elf_main.h"
#endif

namespace headless {

namespace {

class HeadlessModeHandleImpl : public HeadlessModeHandle {
 public:
  HeadlessModeHandleImpl() = default;
  ~HeadlessModeHandleImpl() override = default;

  static base::expected<std::unique_ptr<HeadlessModeHandle>, std::string>
  Initialize() {
    auto handle = std::make_unique<HeadlessModeHandleImpl>();

#if BUILDFLAG(IS_MAC)
    InitializePlatform();
#endif
    if (auto result = handle->SetUpCommandLine(); !result.has_value()) {
      return base::unexpected(std::move(result.error()));
    }

    return base::ok(std::move(handle));
  }

 private:
  base::expected<void, std::string> SetUpCommandLine() {
    base::CommandLine& command_line =
        CHECK_DEREF(base::CommandLine::ForCurrentProcess());

    // Default to incognito mode unless it is forced or user data directory is
    // explicitly specified.
    if (!command_line.HasSwitch(::switches::kIncognito) &&
        !command_line.HasSwitch(::switches::kUserDataDir)) {
      command_line.AppendSwitch(::switches::kIncognito);
    }

    // Enable unattended mode.
    if (!command_line.HasSwitch(::switches::kNoErrorDialogs)) {
      command_line.AppendSwitch(::switches::kNoErrorDialogs);
    }

    // Disable first run user experience.
    if (!command_line.HasSwitch(::switches::kNoFirstRun)) {
      command_line.AppendSwitch(::switches::kNoFirstRun);
    }

    // Explicitly specify unique user data dir because if there is no one
    // provided, Chrome will fall back to the default one which will prevent
    // parallel headless processes execution, see https://crbug.com/40280278.
    if (!command_line.HasSwitch(::switches::kUserDataDir) &&
        !command_line.HasSwitch(::switches::kProcessType)) {
      if (auto result = CreateUniqueUserDataDir(); !result.has_value()) {
        return base::unexpected(result.error());
      }
      const base::FilePath& user_data_dir = user_data_dir_.GetPath();
      if (!base::DirectoryExists(user_data_dir)) {
        return base::unexpected("Missing headless user data directory.");
      }
      command_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir);
    }

#if BUILDFLAG(IS_LINUX)
    // Headless mode on Linux relies on ozone/headless platform.
    command_line.AppendSwitchASCII(::switches::kOzonePlatform,
                                   switches::kHeadless);
    if (!command_line.HasSwitch(switches::kScreenInfo) &&
        !command_line.HasSwitch(switches::kOzoneOverrideScreenSize)) {
      command_line.AppendSwitchASCII(switches::kOzoneOverrideScreenSize,
                                     "800,600");
    }

    // If Ozone/Headless is enabled, Vulkan initialization crashes unless
    // Angle implementation is specified explicitly.
    if (!command_line.HasSwitch(switches::kUseGL) &&
        !command_line.HasSwitch(switches::kUseANGLE) &&
        !command_line.HasSwitch(switches::kEnableGPU)) {
      command_line.AppendSwitchASCII(
          switches::kUseANGLE, gl::kANGLEImplementationSwiftShaderForWebGLName);
    }
#endif  // BUILDFLAG(IS_LINUX)

    return base::ok();
  }

  base::expected<void, std::string> CreateUniqueUserDataDir() {
    CHECK(!user_data_dir_.IsValid());

#if BUILDFLAG(IS_WIN)
    // On Windows user data dir is handled before chrome.dll is loaded in
    // chrome_elf, see chrome/install_static/user_data_dir.h/cc, so check to
    // see if the temporary data dir for headless mode was created there and
    // if so, associate it with our code for cleanup on exit.
    if (IsTemporaryUserDataDirectoryCreatedForHeadless()) {
      wchar_t user_data_dir_buf[MAX_PATH], invalid_user_data_dir_buf[MAX_PATH];
      if (GetUserDataDirectoryThunk(user_data_dir_buf,
                                    std::size(user_data_dir_buf),
                                    invalid_user_data_dir_buf,
                                    std::size(invalid_user_data_dir_buf))) {
        base::FilePath user_data_dir(user_data_dir_buf);
        if (!user_data_dir.empty()) {
          if (!user_data_dir_.Set(user_data_dir)) {
            return base::unexpected(
                "Invalid unique headless user data directory.");
          }
        }
      }
    }
#else   // BUILDFLAG(IS_WIN)
    base::FilePath default_user_data_dir;
    if (!chrome::GetDefaultUserDataDirectory(&default_user_data_dir)) {
      return base::unexpected("Failed to get default user data directory.");
    }

    base::FilePath headless_user_data_dir(default_user_data_dir.value() +
                                          FILE_PATH_LITERAL("-headless"));
    if (!base::CreateDirectory(headless_user_data_dir)) {
      return base::unexpected(
          "Failed to create headless user data directory container.");
    }
    if (!user_data_dir_.CreateUniqueTempDirUnderPath(headless_user_data_dir)) {
      return base::unexpected(
          "Failed to create a unique user data directory for headless.");
    }
#endif  // BUILDFLAG(IS_WIN)

    return base::ok();
  }

  base::ScopedTempDir user_data_dir_;
};

}  // namespace

base::expected<std::unique_ptr<HeadlessModeHandle>, std::string>
InitHeadlessMode() {
  CHECK(IsHeadlessMode());
  return HeadlessModeHandleImpl::Initialize();
}

}  // namespace headless

#else  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

namespace headless {

base::expected<std::unique_ptr<HeadlessModeHandle>, std::string>
InitHeadlessMode() {
  return base::unexpected("Headless mode is not supported on this platform");
}

}  // namespace headless

#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
