// 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 "chrome/browser/download/bubble/download_bubble_utils.h"

#include "base/containers/fixed_flat_map.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/download/download_ui_model.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/download/download_item_mode.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/grit/generated_resources.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/download/public/common/download_item.h"
#include "components/offline_items_collection/core/offline_item.h"
#include "components/offline_items_collection/core/offline_item_state.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "ui/base/l10n/l10n_util.h"

base::Time GetItemStartTime(const download::DownloadItem* item) {
  return item->GetStartTime();
}

base::Time GetItemStartTime(const offline_items_collection::OfflineItem& item) {
  return item.creation_time;
}

const std::string& GetItemId(const download::DownloadItem* item) {
  return item->GetGuid();
}

const offline_items_collection::ContentId& GetItemId(
    const offline_items_collection::OfflineItem& item) {
  return item.id;
}

bool ItemIsRecent(const download::DownloadItem* item, base::Time cutoff_time) {
  return ((item->GetStartTime().is_null() && !item->IsDone()) ||
          item->GetStartTime() > cutoff_time);
}

bool ItemIsRecent(const offline_items_collection::OfflineItem& item,
                  base::Time cutoff_time) {
  // TODO(chlily): Deduplicate this code from OfflineItemModel::IsDone().
  bool is_done = false;
  switch (item.state) {
    case offline_items_collection::OfflineItemState::IN_PROGRESS:
    case offline_items_collection::OfflineItemState::PAUSED:
    case offline_items_collection::OfflineItemState::PENDING:
      break;
    case offline_items_collection::OfflineItemState::INTERRUPTED:
      is_done = item.is_resumable;
      break;
    case offline_items_collection::OfflineItemState::FAILED:
    case offline_items_collection::OfflineItemState::COMPLETE:
    case offline_items_collection::OfflineItemState::CANCELLED:
      is_done = true;
      break;
    case offline_items_collection::OfflineItemState::NUM_ENTRIES:
      NOTREACHED();
  }
  return ((item.creation_time.is_null() && !is_done) ||
          item.creation_time > cutoff_time);
}

bool DownloadUIModelIsRecent(const DownloadUIModel* model,
                             base::Time cutoff_time) {
  return ((model->GetStartTime().is_null() && !model->IsDone()) ||
          model->GetStartTime() > cutoff_time);
}

bool IsPendingDeepScanning(const download::DownloadItem* item) {
  return item->GetState() == download::DownloadItem::IN_PROGRESS &&
         item->GetDangerType() ==
             download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING;
}

bool IsPendingDeepScanning(const DownloadUIModel* model) {
  return model->GetState() == download::DownloadItem::IN_PROGRESS &&
         model->GetDangerType() ==
             download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING;
}

bool IsItemInProgress(const download::DownloadItem* item) {
  if (item->IsDangerous() || item->IsInsecure() ||
      IsPendingDeepScanning(item)) {
    return false;
  }
  return item->GetState() == download::DownloadItem::IN_PROGRESS;
}

bool IsItemInProgress(const offline_items_collection::OfflineItem& item) {
  // Offline items cannot be pending deep scanning, and insecure warnings are
  // not shown for them.
  if (item.is_dangerous) {
    return false;
  }
  return item.state ==
             offline_items_collection::OfflineItemState::IN_PROGRESS ||
         item.state == offline_items_collection::OfflineItemState::PAUSED;
}

bool IsModelInProgress(const DownloadUIModel* model) {
  if (model->IsDangerous() || model->IsInsecure() ||
      IsPendingDeepScanning(model)) {
    return false;
  }
  return model->GetState() == download::DownloadItem::IN_PROGRESS;
}

bool IsItemPaused(const download::DownloadItem* item) {
  return item->IsPaused();
}

bool IsItemPaused(const offline_items_collection::OfflineItem& item) {
  return item.state == offline_items_collection::OfflineItemState::PAUSED;
}

DownloadBubbleAccessibleAlertsMap::Alert GetAccessibleAlertForModel(
    const DownloadUIModel& model) {
  using State = download::DownloadItem::DownloadState;
  using Alert = DownloadBubbleAccessibleAlertsMap::Alert;
  const download::DownloadItemMode mode =
      download::GetDesiredDownloadItemMode(&model);
  const State state = model.GetState();
  const std::u16string filename =
      model.GetFileNameToReportUser().LossyDisplayName();

  switch (mode) {
    case download::DownloadItemMode::kNormal: {
      if (state == State::IN_PROGRESS && !model.IsPaused()) {
        return Alert{
            Alert::Urgency::kAlertWhenAppropriate,
            model.GetInProgressAccessibleAlertText(),
        };
      }
      static constexpr auto kMap = base::MakeFixedFlatMap<State, int>({
          {State::INTERRUPTED, IDS_DOWNLOAD_FAILED_ACCESSIBLE_ALERT},
          {State::COMPLETE, IDS_DOWNLOAD_COMPLETE_ACCESSIBLE_ALERT},
          {State::CANCELLED, IDS_DOWNLOAD_CANCELLED_ACCESSIBLE_ALERT},
          // If state is IN_PROGRESS but we got here to the map lookup, the
          // download is paused.
          {State::IN_PROGRESS, IDS_DOWNLOAD_PAUSED_ACCESSIBLE_ALERT},
      });
      if (const auto it = kMap.find(state); it != kMap.end()) {
        return Alert{Alert::Urgency::kAlertSoon,
                     l10n_util::GetStringFUTF16(it->second, filename)};
      }
      break;
    }
    case download::DownloadItemMode::kDangerous:
    case download::DownloadItemMode::kMalicious: {
      if (model.GetDangerType() ==
          download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING) {
        return Alert{Alert::Urgency::kAlertSoon,
                     l10n_util::GetStringFUTF16(
                         IDS_PROMPT_DEEP_SCANNING_ACCESSIBLE_ALERT, filename)};
      }
      size_t ignored;
      return Alert{Alert::Urgency::kAlertSoon,
                   model.GetWarningText(filename, &ignored)};
    }
    case download::DownloadItemMode::kInsecureDownloadWarn:
    case download::DownloadItemMode::kInsecureDownloadBlock:
      return Alert{
          Alert::Urgency::kAlertSoon,
          l10n_util::GetStringFUTF16(
              IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED_ACCESSIBLE_ALERT, filename)};
    case download::DownloadItemMode::kDeepScanning:
      // Note: kAlertSoon (as opposed to kAlertWhenAppropriate) is correct here,
      // because observers are updated when a deep scan begins, and not
      // continuously during a deep scan.
      return Alert{Alert::Urgency::kAlertSoon,
                   l10n_util::GetStringFUTF16(
                       IDS_DEEP_SCANNING_ACCESSIBLE_ALERT, filename)};
  }

  // An empty alert will not be added.
  return Alert{Alert::Urgency::kAlertWhenAppropriate, u""};
}

BrowserWindowInterface* FindBrowserToShowAnimation(
    download::DownloadItem* item,
    Profile* profile) {
  content::WebContents* web_contents =
      content::DownloadItemUtils::GetWebContents(item);
  // For the case of DevTools web contents, we'd like to use target browser
  // shelf although saving from the DevTools web contents.
  if (web_contents && DevToolsWindow::IsDevToolsWindow(web_contents)) {
    DevToolsWindow* devtools_window =
        DevToolsWindow::AsDevToolsWindow(web_contents);
    content::WebContents* inspected =
        devtools_window->GetInspectedWebContents();
    // Do not overwrite web contents for the case of remote debugging.
    if (inspected) {
      web_contents = inspected;
    }
  }
  BrowserWindowInterface* browser_to_show_animation =
      web_contents ? GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
                         web_contents)
                   : nullptr;

  // As a last resort, use the last active browser for this profile. Not ideal,
  // but better than not showing the download at all.
  if (browser_to_show_animation == nullptr) {
    browser_to_show_animation = ProfileBrowserCollection::GetForProfile(profile)
                                    ->GetLastActiveBrowser();
  }
  return browser_to_show_animation;
}

const webapps::AppId* GetWebAppIdForBrowser(
    const BrowserWindowInterface* browser) {
  const web_app::AppBrowserController* controller =
      web_app::AppBrowserController::From(browser);
  return controller ? &controller->app_id() : nullptr;
}
