// Copyright 2024 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/web_applications/commands/internal/command_internal.h"

#include <string_view>
#include <type_traits>
#include <utility>

#include "base/atomic_sequence_num.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/location.h"
#include "base/values.h"
#include "chrome/browser/web_applications/locks/all_apps_lock.h"
#include "chrome/browser/web_applications/locks/app_lock.h"
#include "chrome/browser/web_applications/locks/noop_lock.h"
#include "chrome/browser/web_applications/locks/shared_web_contents_lock.h"
#include "chrome/browser/web_applications/locks/shared_web_contents_with_app_lock.h"
#include "chrome/browser/web_applications/locks/web_app_lock_manager.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/common/chrome_features.h"

namespace web_app::internal {
namespace {
int GetNextCommandId() {
  static base::AtomicSequenceNumber g_incrementing_id_;
  return g_incrementing_id_.GetNext();
}
}  // namespace

CommandBase::CommandBase(std::string name)
    : id_(GetNextCommandId()), name_(std::move(name)) {
  DETACH_FROM_SEQUENCE(command_sequence_checker_);
  // We don't have an easy way to enforce construction of class on the
  // WebAppProvider sequence without requiring a UI thread in unittests, so just
  // allow this construction to happen from any thread.

  base::DictValue* metadata = GetMutableDebugValue().EnsureDict("!metadata");
  metadata->Set("!name", name_);
  metadata->Set("id", id_);
  metadata->Set("started", false);
}
CommandBase::~CommandBase() = default;

content::WebContents* CommandBase::GetInstallingWebContents(
    base::PassKey<WebAppCommandManager>) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
  return nullptr;
}

void CommandBase::OnShutdown(base::PassKey<WebAppCommandManager>) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
}

bool CommandBase::IsStarted() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
  return started_;
}

base::WeakPtr<CommandBase> CommandBase::GetBaseCommandWeakPtr() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
  return weak_factory_.GetWeakPtr();
}

void CommandBase::SetScheduledLocation(base::PassKey<WebAppCommandManager>,
                                       const base::Location& location) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
  GetMutableDebugValue()
      .EnsureDict("!metadata")
      ->Set("scheduled_location", location.ToString());
}

void CommandBase::SetScheduledAt(base::PassKey<WebAppCommandManager>) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
  if (base::FeatureList::IsEnabled(features::kRecordWebAppDebugInfo)) {
    GetMutableDebugValue()
        .EnsureDict("!metadata")
        ->Set("scheduled_at",
              base::TimeFormatTimeOfDayWithMilliseconds(base::Time::Now()));
  }
}

void CommandBase::SetCommandManager(base::PassKey<WebAppCommandManager>,
                                    WebAppCommandManager* command_manager) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
  command_manager_ = command_manager;
}

const base::DictValue& CommandBase::GetDebugValue() const {
  return debug_value_;
}

WebAppCommandManager* CommandBase::command_manager() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
  return command_manager_;
}

base::DictValue& CommandBase::GetMutableDebugValue() {
  return debug_value_;
}

void CommandBase::SetStarted() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
  started_ = true;
  auto* metadata = GetMutableDebugValue().EnsureDict("!metadata");
  metadata->Set("started", true);

  if (base::FeatureList::IsEnabled(features::kRecordWebAppDebugInfo)) {
    metadata->Set("started_at",
                  base::TimeFormatTimeOfDayWithMilliseconds(base::Time::Now()));
  }
}

void CommandBase::CompleteAndSelfDestructInternal(
    CommandResult result,
    base::OnceClosure after_destruction) {
  command_manager()->OnCommandComplete(base::PassKey<CommandBase>(), this,
                                       result, std::move(after_destruction));
}

template <typename LockType>
CommandWithLock<LockType>::CommandWithLock(const std::string& name,
                                           LockDescription initial_lock_request)
    : CommandBase(name),
      initial_lock_request_(std::move(initial_lock_request)),
      initial_lock_(std::make_unique<LockType>()) {
  GetMutableDebugValue()
      .EnsureDict("!metadata")
      ->Set("initial_lock_request", initial_lock_request_.AsDebugValue());
}

template <typename LockType>
CommandWithLock<LockType>::~CommandWithLock() = default;

template <typename LockType>
void CommandWithLock<LockType>::RequestLock(
    base::PassKey<WebAppCommandManager>,
    WebAppLockManager* lock_manager,
    LockAcquiredCallback on_lock_acquired,
    const base::Location& location) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
  lock_manager->AcquireLock(
      initial_lock_request_, *initial_lock_,
      base::BindOnce(&CommandWithLock::PrepareForStart,
                     weak_factory_.GetWeakPtr(), std::move(on_lock_acquired)),
      location);
}

template <typename LockType>
void CommandWithLock<LockType>::PrepareForStart(
    LockAcquiredCallback on_lock_acquired) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(command_sequence_checker_);
  CHECK(command_manager());
  CHECK(!IsStarted());
  SetStarted();
  std::move(on_lock_acquired)
      .Run(base::BindOnce(&CommandWithLock::StartWithLock,
                          weak_factory_.GetWeakPtr(),
                          std::move(initial_lock_)));
}

template <typename LockType>
bool CommandWithLock<LockType>::ShouldPrepareWebContentsBeforeStart(
    base::PassKey<WebAppCommandManager>) const {
  return initial_lock_request_.IncludesSharedWebContents();
}

template class CommandWithLock<NoopLock>;
template class CommandWithLock<SharedWebContentsLock>;
template class CommandWithLock<AppLock>;
template class CommandWithLock<SharedWebContentsWithAppLock>;
template class CommandWithLock<AllAppsLock>;

}  // namespace web_app::internal
