// Copyright 2019 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/media/webrtc/desktop_media_picker_controller.h"

#include <algorithm>
#include <memory>
#include <tuple>
#include <utility>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/expected.h"
#include "build/build_config.h"
#include "chrome/browser/media/webrtc/capture_policy_utils.h"
#include "chrome/browser/media/webrtc/desktop_media_picker.h"
#include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/webrtc/native_desktop_media_list.h"
#include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
#include "content/public/browser/desktop_capture.h"
#include "content/public/browser/desktop_streams_registry.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "desktop_media_picker.h"
#include "media/audio/audio_features.h"
#include "media/base/media_switches.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "ui/base/l10n/l10n_util.h"

using ::content::DesktopMediaID;

DesktopMediaPickerController::DesktopMediaPickerController(
    DesktopMediaPickerFactory* picker_factory)
    : picker_factory_(picker_factory
                          ? picker_factory
                          : DesktopMediaPickerFactoryImpl::GetInstance()) {}

DesktopMediaPickerController::~DesktopMediaPickerController() = default;

void DesktopMediaPickerController::Show(
    const Params& params,
    const std::vector<DesktopMediaList::Type>& sources,
    DoneCallback done_callback,
    base::OnceClosure on_show_picker) {
  DCHECK(!std::ranges::contains(sources, DesktopMediaList::Type::kNone));
  DCHECK(!done_callback_);

  done_callback_ = std::move(done_callback);
  on_show_picker_ = std::move(on_show_picker);
  params_ = params;

  Observe(params.web_contents);

#if BUILDFLAG(IS_ANDROID)
  // Android does not use DesktopMediaList. See
  // DesktopMediaPickerFactoryImpl::CreateMediaList.
  ShowPickerDialog();
#else
  // Keep same order as the input |sources| and avoid duplicates.
  source_lists_ = picker_factory_->CreateMediaList(
      sources, params.web_contents, params.includable_web_contents_filter);
  if (source_lists_.empty()) {
    OnPickerDialogResults("At least one source type must be specified.", {});
    return;
  }

  if (params.select_only_screen && sources.size() == 1 &&
      sources[0] == DesktopMediaList::Type::kScreen) {
    // Try to bypass the picker dialog if possible.
    DCHECK(source_lists_.size() == 1);
    auto* source_list = source_lists_[0].get();
    source_list->Update(
        base::BindOnce(&DesktopMediaPickerController::OnInitialMediaListFound,
                       weak_factory_.GetWeakPtr()));
  } else {
    ShowPickerDialog();
  }
#endif  // BUILDFLAG(IS_ANDROID)
}

void DesktopMediaPickerController::WebContentsDestroyed() {
  OnPickerDialogResults(std::string(), DesktopMediaID());
}

// static
bool DesktopMediaPickerController::IsSystemAudioCaptureSupported(
    Params::RequestSource request_source) {
  if (!media::IsSystemLoopbackCaptureSupported()) {
    return false;
  }
#if BUILDFLAG(IS_MAC)
  if (request_source == Params::RequestSource::kCast) {
    return (media::IsMacSckSystemLoopbackCaptureSupported() ||
            base::FeatureList::IsEnabled(media::kMacCatapLoopbackAudioForCast));
  } else {
    return (media::IsMacCatapSystemLoopbackCaptureSupported() &&
            base::FeatureList::IsEnabled(
                media::kMacCatapLoopbackAudioForScreenShare));
  }
#elif BUILDFLAG(IS_LINUX)
  if (request_source == Params::RequestSource::kCast) {
    return base::FeatureList::IsEnabled(media::kPulseaudioLoopbackForCast);
  } else {
    return base::FeatureList::IsEnabled(
        media::kPulseaudioLoopbackForScreenShare);
  }
#else
  return true;
#endif  // BUILDFLAG(IS_MAC)
}

void DesktopMediaPickerController::OnInitialMediaListFound() {
  DCHECK(params_.select_only_screen);
  DCHECK(source_lists_.size() == 1);
  auto* source_list = source_lists_[0].get();
  if (source_list->GetSourceCount() == 1) {
    // With only one possible source, the picker dialog is being bypassed. Apply
    // the default value of the "audio checkbox" here for desktop screen share.
    DesktopMediaID media_id = source_list->GetSource(0).id;
    DCHECK_EQ(media_id.type, DesktopMediaID::TYPE_SCREEN);
    media_id.audio_share =
        params_.request_audio &&
        IsSystemAudioCaptureSupported(params_.request_source);
    OnPickerDialogResults({}, media_id);
    return;
  }

  ShowPickerDialog();
}

void DesktopMediaPickerController::ShowPickerDialog() {
  picker_ = picker_factory_->CreatePicker(nullptr);
  if (!picker_) {
    OnPickerDialogResults(
        "Desktop Capture API is not yet implemented for this platform.", {});
    return;
  }

  if (on_show_picker_) {
    std::move(on_show_picker_).Run();
  }

  picker_->Show(
      params_, std::move(source_lists_),
      base::BindOnce(&DesktopMediaPickerController::OnPickerDialogResults,
                     // A weak pointer is used here, because although
                     // |picker_| can't outlive this object, it can
                     // schedule this callback to be invoked
                     // asynchronously after it has potentially been
                     // destroyed.
                     weak_factory_.GetWeakPtr(), std::string()));
}

void DesktopMediaPickerController::OnPickerDialogResults(
    const std::string& err,
    base::expected<content::DesktopMediaID,
                   blink::mojom::MediaStreamRequestResult> result) {
  if (done_callback_) {
    const content::DesktopMediaID source =
        result.value_or(content::DesktopMediaID());
    std::move(done_callback_).Run(err, source);
  }
}
