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

#include "components/webapps/browser/installable/installable_task.h"

#include <algorithm>

#include "components/webapps/browser/installable/installable_manager.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/manifest/manifest_util.h"

namespace webapps {

InstallableTask::InstallableTask(
    content::WebContents* web_contents,
    base::WeakPtr<InstallableManager> installable_manager,
    const InstallableParams& params,
    InstallableCallback callback,
    InstallablePageData& page_data)
    : web_contents_(web_contents->GetWeakPtr()),
      manager_(installable_manager),
      params_(params),
      callback_(std::move(callback)),
      page_data_(page_data) {
  fetcher_ = std::make_unique<InstallableDataFetcher>(web_contents, page_data);
  evaluator_ = std::make_unique<InstallableEvaluator>(
      web_contents, page_data, params_.installable_criteria);
}

InstallableTask::InstallableTask(const InstallableParams params,
                                 InstallablePageData& page_data)
    : params_(params), page_data_(page_data) {}

InstallableTask::~InstallableTask() = default;

void InstallableTask::Start() {
  IncrementStateAndWorkOnNextTask();
}

void InstallableTask::RunCallback() {
  if (callback_) {
    InstallableData data = {
        std::move(errors_),
        page_data_->manifest_url(),
        page_data_->GetManifest(),
        page_data_->WebPageMetadata(),
        page_data_->primary_icon_url(),
        page_data_->primary_icon(),
        page_data_->primary_icon_purpose() ==
            blink::mojom::ManifestImageResource_Purpose::MASKABLE,
        page_data_->screenshots(),
        installability_check_passed_,
    };
    std::move(callback_).Run(data);
  }
}

void InstallableTask::ResetWithError(InstallableStatusCode code) {
  // Some callbacks might be already invalidated on certain resets, so we must
  // check for that.
  // Manifest is assumed to be non-null, so we create an empty one here.
  if (callback_) {
    blink::mojom::Manifest manifest;
    mojom::WebPageMetadata metadata;
    std::move(callback_).Run(InstallableData(
        /*errors=*/{code}, /*manifest_url=*/GURL(), /*manifest=*/manifest,
        /*metadata=*/metadata, /*primary_icon_url=*/GURL(),
        /*primary_icon=*/nullptr,
        /*has_maskable_primary_icon=*/false,
        /*screenshots=*/std::vector<Screenshot>(),
        /*installable_check_passed=*/false));
  }
}

void InstallableTask::IncrementStateAndWorkOnNextTask() {
  if ((!errors_.empty() && !params_.is_debug_mode) || state_ == kComplete) {
    manager_->OnTaskFinished();
    RunCallback();
    return;
  }

  state_++;
  CHECK(kInactive < state_ && state_ < kMaxState);

  switch (state_) {
    case kCheckEligibility:
      if (params_.check_eligibility) {
        CheckEligibility();
        return;
      }
      break;
    case kFetchWebPageMetadata:
      if (params_.fetch_metadata) {
        fetcher_->FetchWebPageMetadata(base::BindOnce(
            &InstallableTask::OnFetchedData, base::Unretained(this)));
        return;
      }
      break;
    case kFetchManifest:
      fetcher_->FetchManifest(base::BindOnce(&InstallableTask::OnFetchedData,
                                             base::Unretained(this)));
      return;
    case kCheckInstallability:
      CheckInstallability();
      return;
    case kFetchPrimaryIcon:
      if (params_.valid_primary_icon) {
        fetcher_->CheckAndFetchBestPrimaryIcon(
            base::BindOnce(&InstallableTask::OnFetchedData,
                           base::Unretained(this)),
            params_.prefer_maskable_icon, params_.fetch_favicon);
        return;
      }
      break;
    case kFetchScreenshots:
      if (params_.fetch_screenshots) {
        fetcher_->CheckAndFetchScreenshots(base::BindOnce(
            &InstallableTask::OnFetchedData, base::Unretained(this)));
        return;
      }
      break;
  }
  IncrementStateAndWorkOnNextTask();
}

void InstallableTask::OnFetchedData(InstallableStatusCode error) {
  if (error != InstallableStatusCode::NO_ERROR_DETECTED) {
    errors_.push_back(error);
  }
  IncrementStateAndWorkOnNextTask();
}

void InstallableTask::CheckEligibility() {
  auto errors = evaluator_->CheckEligibility(web_contents_.get());
  if (!errors.empty()) {
    errors_.insert(errors_.end(), errors.begin(), errors.end());
  }
  IncrementStateAndWorkOnNextTask();
}

void InstallableTask::CheckInstallability() {
  std::vector<InstallableStatusCode> installable_errors =
      evaluator_->CheckInstallability();
  for (auto new_error : installable_errors) {
    if (std::ranges::contains(errors_, new_error)) {
      // Don't add duplicated errors.
      continue;
    }
    errors_.push_back(new_error);
  }
  installability_check_passed_ = installable_errors.empty();
  IncrementStateAndWorkOnNextTask();
}

}  // namespace webapps
