// 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 "services/on_device_model/public/cpp/test_support/fake_service.h"

#include <string>
#include <variant>

#include "base/check.h"
#include "base/containers/span.h"
#include "base/files/memory_mapped_file.h"
#include "base/json/json_writer.h"
#include "base/no_destructor.h"
#include "base/notimplemented.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/trace_event/trace_event.h"
#include "services/on_device_model/ml/chrome_ml_audio_buffer.h"
#include "services/on_device_model/ml/chrome_ml_types.h"
#include "services/on_device_model/public/mojom/on_device_model.mojom-shared.h"
#include "third_party/re2/src/re2/re2.h"
#include "third_party/skia/include/core/SkBitmap.h"

namespace on_device_model {

namespace {

std::string ReadFile(base::File& file) {
  if (file.GetLength() == 0) {
    return "";
  }
  // Using MemoryMappedFile to handle async file.
  base::MemoryMappedFile map;
  CHECK(map.Initialize(std::move(file)));
  return std::string(base::as_string_view(base::as_chars(map.bytes())));
}

std::string Placeholder(ml::Token token) {
  switch (token) {
    case ml::Token::kEnd:
      return "E";
    case ml::Token::kModel:
      return "M";
    case ml::Token::kSystem:
      return "S";
    case ml::Token::kUser:
      return "U";
    case ml::Token::kToolCall:
      return "TC";
    case ml::Token::kToolResponse:
      return "TR";
  }
}

std::string OnDeviceInputToString(const mojom::Input& input,
                                  const Capabilities& capabilities) {
  std::string result;
  using Tag = mojom::InputPiece::Tag;
  for (const auto& piece : input.pieces) {
    switch (piece->which()) {
      case Tag::kToken:
        result += Placeholder(piece->get_token());
        break;
      case Tag::kText:
        result += piece->get_text();
        break;
      case Tag::kBitmap:
        if (capabilities.Has(CapabilityFlags::kImageInput)) {
          result += "<image>";
        } else {
          result += "<unsupported>";
        }
        break;
      case Tag::kAudio:
        if (capabilities.Has(CapabilityFlags::kAudioInput)) {
          result += "<audio>";
        } else {
          result += "<unsupported>";
        }
        break;
      case Tag::kToolCall: {
        const auto& call = piece->get_tool_call();
        std::string arguments_json;
        base::JSONWriter::Write(call->arguments, &arguments_json);
        base::StrAppend(&result,
                        {"<tool-call id=", call->call_id, " name=", call->name,
                         " arguments=", arguments_json, ">"});
        break;
      }
      case Tag::kToolResponse: {
        const auto& response = piece->get_tool_response();
        base::StrAppend(&result, {"<tool-response id=", response->call_id,
                                  " name=", response->name});
        if (response->result) {
          std::string result_json;
          base::JSONWriter::Write(*response->result, &result_json);
          base::StrAppend(&result, {" result=", result_json});
        }
        if (response->error_message) {
          base::StrAppend(&result,
                          {" error=\"", *response->error_message, "\""});
        }
        result += ">";
        break;
      }
      case Tag::kToolDeclaration: {
        const auto& decl = piece->get_tool_declaration();
        base::StrAppend(&result, {"<tool name=", decl->name, ">"});
        break;
      }
      case Tag::kUnknownType:
        result += "<unknown>";
        break;
    }
  }
  return result;
}

std::string CtxToString(const mojom::AppendOptions& input,
                        const Capabilities& capabilities) {
  std::string suffix;
  std::string context = OnDeviceInputToString(*input.input, capabilities);
  if (input.max_tokens > 0) {
    if (input.max_tokens < context.size()) {
      context.resize(input.max_tokens);
    }
    suffix += " max:" + base::NumberToString(input.max_tokens);
  }
  return context + suffix;
}

const re2::RE2& LangExprRE() {
  static base::NoDestructor<re2::RE2> re("lang:(\\w+)=(\\d+\\.\\d+)");
  return *re;
}

mojom::LanguageDetectionResultPtr DummyDetectLanguage(std::string_view text) {
  if (text.find("esperanto") != std::string::npos) {
    return mojom::LanguageDetectionResult::New("eo", 1.0);
  }
  std::array<std::string_view, 3> matches;
  if (LangExprRE().Match(text, 0, text.length(), re2::RE2::UNANCHORED,
                         matches.data(), matches.size())) {
    double score = 0.0;
    base::StringToDouble(matches[2], &score);
    return mojom::LanguageDetectionResult::New(std::string(matches[1]), score);
  };
  return nullptr;
}

}  // namespace

FakeOnDeviceServiceSettings::FakeOnDeviceServiceSettings() = default;
FakeOnDeviceServiceSettings::~FakeOnDeviceServiceSettings() = default;

FakeOnDeviceSession::FakeOnDeviceSession(FakeOnDeviceServiceSettings* settings,
                                         FakeOnDeviceModel* model,
                                         mojom::SessionParamsPtr params)
    : settings_(settings), model_(model), params_(std::move(params)) {}

FakeOnDeviceSession::~FakeOnDeviceSession() {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceSession::~FakeOnDeviceSession",
              perfetto::TerminatingFlow::FromPointer(this));
}

void FakeOnDeviceSession::Append(
    mojom::AppendOptionsPtr options,
    mojo::PendingRemote<mojom::ContextClient> client) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceSession::Append",
              perfetto::Flow::FromPointer(this));
  mojo::Remote<mojom::ContextClient> remote;
  if (client) {
    // Bind now to catch disconnects.
    remote.Bind(std::move(client));
  }
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&FakeOnDeviceSession::AppendImpl,
                     weak_factory_.GetWeakPtr(), std::move(options),
                     std::move(remote)),
      settings_->append_delay);
}

void FakeOnDeviceSession::Generate(
    mojom::GenerateOptionsPtr options,
    mojo::PendingRemote<mojom::StreamingResponder> responder) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceSession::Generate",
              perfetto::Flow::FromPointer(this));
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&FakeOnDeviceSession::GenerateImpl,
                     weak_factory_.GetWeakPtr(), std::move(options),
                     std::move(responder)),
      settings_->execute_delay);
}

void FakeOnDeviceSession::GetSizeInTokens(mojom::InputPtr input,
                                          GetSizeInTokensCallback callback) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceSession::GetSizeInTokens",
              perfetto::Flow::FromPointer(this));
  if (settings_->size_in_tokens != 0) {
    std::move(callback).Run(settings_->size_in_tokens);
    return;
  }

  std::move(callback).Run(
      OnDeviceInputToString(*input, params_->capabilities).size());
}

void FakeOnDeviceSession::Score(const std::string& text,
                                ScoreCallback callback) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceSession::Score");
  std::move(callback).Run(0.5);
}

void FakeOnDeviceSession::GetProbabilitiesBlocking(
    const std::string& text,
    GetProbabilitiesBlockingCallback callback) {
  TRACE_EVENT("optimization_guide",
              "FakeOnDeviceSession::GetProbabilitiesBlocking",
              perfetto::Flow::FromPointer(this));
  std::move(callback).Run({0.5});
}

void FakeOnDeviceSession::Clone(
    mojo::PendingReceiver<on_device_model::mojom::Session> session) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceSession::Clone",
              perfetto::Flow::FromPointer(this));
  // Post a task to sequence with calls to Append.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&FakeOnDeviceSession::CloneImpl,
                     weak_factory_.GetWeakPtr(), std::move(session)));
}

void FakeOnDeviceSession::AsrStream(
    on_device_model::mojom::AsrStreamOptionsPtr options,
    mojo::PendingReceiver<on_device_model::mojom::AsrStreamInput> stream,
    mojo::PendingRemote<on_device_model::mojom::AsrStreamResponder> responder) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceSession::AsrStream",
              perfetto::Flow::FromPointer(this));
  if (settings_->execute_delay.is_zero()) {
    AsrStreamImpl(std::move(options), std::move(stream), std::move(responder));
    return;
  }
  // Post a task to sequence with calls to Append.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&FakeOnDeviceSession::AsrStreamImpl,
                                weak_factory_.GetWeakPtr(), std::move(options),
                                std::move(stream), std::move(responder)));
}

void FakeOnDeviceSession::SetPriority(mojom::Priority priority) {
  priority_ = priority;
}

void FakeOnDeviceSession::Hint(mojom::HintOptionsPtr options) {
  hint_options_ = std::move(options);
}

void FakeOnDeviceSession::GenerateImpl(
    mojom::GenerateOptionsPtr options,
    mojo::PendingRemote<mojom::StreamingResponder> responder) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceSession::GenerateImpl",
              perfetto::Flow::FromPointer(this));
  mojo::Remote<mojom::StreamingResponder> remote(std::move(responder));

  if (settings_->execute_error) {
    remote.ResetWithReason(static_cast<uint32_t>(*settings_->execute_error),
                           "Test error");
    return;
  }

  if (model_->backend_type() == ml::ModelBackendType::kCpuBackend) {
    auto chunk = mojom::ResponseChunk::New();
    chunk->text = "CPU backend";
    remote->OnResponse(std::move(chunk));
  }
  if (model_->performance_hint() ==
      ml::ModelPerformanceHint::kFastestInference) {
    auto chunk = mojom::ResponseChunk::New();
    chunk->text = "Fastest inference";
    remote->OnResponse(std::move(chunk));
  }
  if (model_->data().base_weight != "0") {
    auto chunk = mojom::ResponseChunk::New();
    chunk->text = "Base model: " + model_->data().base_weight;
    remote->OnResponse(std::move(chunk));
  }
  if (!model_->data().adaptation_model_weight.empty()) {
    auto chunk = mojom::ResponseChunk::New();
    chunk->text = "Adaptation model: " + model_->data().adaptation_model_weight;
    remote->OnResponse(std::move(chunk));
  }
  if (!model_->data().cache_weight.empty()) {
    auto chunk = mojom::ResponseChunk::New();
    chunk->text = "Cache weight: " + model_->data().cache_weight;
    remote->OnResponse(std::move(chunk));
  }
  if (!model_->data().encoder_cache_weight.empty()) {
    auto chunk = mojom::ResponseChunk::New();
    chunk->text =
        "Encoder cache weight: " + model_->data().encoder_cache_weight;
    remote->OnResponse(std::move(chunk));
  }
  if (!model_->data().adapter_cache_weight.empty()) {
    auto chunk = mojom::ResponseChunk::New();
    chunk->text =
        "Adapter cache weight: " + model_->data().adapter_cache_weight;
    remote->OnResponse(std::move(chunk));
  }
  if (!model_->data().shader_cache_data.empty()) {
    auto chunk = mojom::ResponseChunk::New();
    chunk->text = "Shader cache data: " + model_->data().shader_cache_data;
    remote->OnResponse(std::move(chunk));
  }

  if (priority_ == on_device_model::mojom::Priority::kBackground) {
    auto chunk = mojom::ResponseChunk::New();
    chunk->text = "Priority: background";
    remote->OnResponse(std::move(chunk));
  }

  if (hint_options_ && hint_options_->constrained_decoding_hint) {
    auto chunk = mojom::ResponseChunk::New();
    chunk->text = "Hint: constrained_decoding ";
    remote->OnResponse(std::move(chunk));
  }

  if (options->constraint) {
    const auto& constraint = *options->constraint;
    auto chunk = mojom::ResponseChunk::New();
    if (constraint.is_json_schema()) {
      chunk->text = "Constraint: json " + constraint.get_json_schema();
    } else if (constraint.is_regex()) {
      chunk->text = "Constraint: regex " + constraint.get_regex();
    } else {
      chunk->text = "Constraint: unknown";
    }
    remote->OnResponse(std::move(chunk));
  }

  uint32_t output_token_count = 0;
  if (settings_->model_execute_result.empty()) {
    for (const auto& context : context_) {
      std::string text = CtxToString(*context, params_->capabilities);
      if (text.empty()) {
        continue;
      }
      output_token_count += text.size();
      auto chunk = mojom::ResponseChunk::New();
      chunk->text = text;
      remote->OnResponse(std::move(chunk));
    }
    if (params_->top_k != ml::kMinTopK ||
        params_->temperature != ml::kMinTemperature) {
      auto chunk = mojom::ResponseChunk::New();
      chunk->text += "TopK: " + base::NumberToString(params_->top_k) +
                     ", Temp: " + base::NumberToString(params_->temperature);
      remote->OnResponse(std::move(chunk));
    }
  } else {
    for (const auto& text : settings_->model_execute_result) {
      output_token_count += text.size();
      auto chunk = mojom::ResponseChunk::New();
      chunk->text = text;
      remote->OnResponse(std::move(chunk));
    }
  }

  // Simulate tool calls if configured.
  if (!settings_->simulated_tool_calls.empty()) {
    std::vector<mojom::ToolCallPtr> tool_calls;
    for (const auto& tc : settings_->simulated_tool_calls) {
      tool_calls.push_back(tc->Clone());
    }
    remote->OnToolCalls(std::move(tool_calls));
  }

  auto summary = mojom::ResponseSummary::New();
  constexpr int kEosTokenCount = 1;
  summary->output_token_count = output_token_count + kEosTokenCount;
  if (options->max_output_tokens &&
      summary->output_token_count > options->max_output_tokens) {
    summary->output_token_count = options->max_output_tokens;
  }
  remote->OnComplete(std::move(summary));
}

void FakeOnDeviceSession::AppendImpl(
    mojom::AppendOptionsPtr options,
    mojo::Remote<mojom::ContextClient> client) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceSession::AppendImpl",
              perfetto::Flow::FromPointer(this));
  // If the client was bound but is now disconnected, cancel the request.
  if (client && !client.is_connected()) {
    return;
  }
  uint32_t input_tokens = static_cast<uint32_t>(
      OnDeviceInputToString(*options->input, params_->capabilities).size());
  uint32_t max_tokens =
      options->max_tokens > 0 ? options->max_tokens : input_tokens;
  uint32_t tokens_processed = std::min(input_tokens, max_tokens);
  context_.emplace_back(std::move(options));
  if (client) {
    client->OnComplete(tokens_processed);
  }
}

void FakeOnDeviceSession::CloneImpl(
    mojo::PendingReceiver<on_device_model::mojom::Session> session) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceSession::CloneImpl",
              perfetto::Flow::FromPointer(this));
  auto new_session =
      std::make_unique<FakeOnDeviceSession>(settings_, model_, params_.Clone());
  for (const auto& c : context_) {
    new_session->context_.push_back(c->Clone());
  }
  new_session->priority_ = priority_;
  model_->AddSession(std::move(session), std::move(new_session));
}

void FakeOnDeviceSession::AsrStreamImpl(
    on_device_model::mojom::AsrStreamOptionsPtr options,
    mojo::PendingReceiver<on_device_model::mojom::AsrStreamInput> stream,
    mojo::PendingRemote<on_device_model::mojom::AsrStreamResponder> responder) {
  NOTIMPLEMENTED_LOG_ONCE();
}

FakeOnDeviceModel::Data::Data() = default;
FakeOnDeviceModel::Data::Data(const Data&) = default;
FakeOnDeviceModel::Data::~Data() = default;

FakeOnDeviceModel::FakeOnDeviceModel(FakeOnDeviceServiceSettings* settings,
                                     FakeOnDeviceModel::Data&& data,
                                     ml::ModelPerformanceHint performance_hint,
                                     ml::ModelBackendType backend_type)
    : settings_(settings),
      data_(std::move(data)),
      performance_hint_(performance_hint),
      backend_type_(backend_type) {}

FakeOnDeviceModel::~FakeOnDeviceModel() = default;

void FakeOnDeviceModel::StartSession(
    mojo::PendingReceiver<mojom::Session> session,
    mojom::SessionParamsPtr params) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceModel::StartSession",
              perfetto::Flow::FromPointer(this));
  if (!params) {
    params = mojom::SessionParams::New();
  }
  AddSession(std::move(session), std::make_unique<FakeOnDeviceSession>(
                                     settings_, this, std::move(params)));
}

void FakeOnDeviceModel::AddSession(
    mojo::PendingReceiver<mojom::Session> receiver,
    std::unique_ptr<FakeOnDeviceSession> session) {
  receivers_.Add(std::move(session), std::move(receiver));
}

void FakeOnDeviceModel::DetectLanguage(const std::string& text,
                                       DetectLanguageCallback callback) {
  NOTREACHED();
}

void FakeOnDeviceModel::ClassifyTextSafety(
    const std::string& text,
    ClassifyTextSafetyCallback callback) {
  NOTREACHED();
}

void FakeOnDeviceModel::LoadAdaptation(
    mojom::LoadAdaptationParamsPtr params,
    mojo::PendingReceiver<mojom::OnDeviceModel> model,
    LoadAdaptationCallback callback) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceModel::LoadAdaptation",
              perfetto::Flow::FromPointer(this));
  Data data = data_;
  data.adaptation_model_weight = ReadFile(params->assets.weights);
  auto test_model = std::make_unique<FakeOnDeviceModel>(
      settings_, std::move(data), performance_hint_, backend_type_);
  model_adaptation_receivers_.Add(std::move(test_model), std::move(model));
  std::move(callback).Run(mojom::LoadModelResult::kSuccess);
}

FakeTextSafetyModel::FakeTextSafetyModel(
    on_device_model::mojom::TextSafetyModelParamsPtr params) {
  if (params->safety_model.IsValid()) {
    CHECK_EQ(ReadFile(params->safety_model), FakeTsData());
    has_safety_model_ = true;
  }
  if (params->language_model.IsValid()) {
    CHECK_EQ(ReadFile(params->language_model), FakeLanguageModel());
    has_language_model_ = true;
  }
}
FakeTextSafetyModel::~FakeTextSafetyModel() {
  TRACE_EVENT("optimization_guide", "FakeTextSafetyModel::~FakeTextSafetyModel",
              perfetto::TerminatingFlow::FromPointer(this));
}

void FakeTextSafetyModel::StartSession(
    mojo::PendingReceiver<mojom::TextSafetySession> session) {
  sessions_.Add(this, std::move(session));
}

void FakeTextSafetyModel::ClassifyTextSafety(
    const std::string& text,
    ClassifyTextSafetyCallback callback) {
  TRACE_EVENT("optimization_guide", "FakeTextSafetyModel::ClassifyTextSafety",
              perfetto::Flow::FromPointer(this), "text", text);
  CHECK(has_safety_model_);
  auto safety_info = mojom::SafetyInfo::New();
  // Text is unsafe if it contains "unsafe".
  bool has_unsafe = text.find("unsafe") != std::string::npos;
  safety_info->class_scores.emplace_back(has_unsafe ? 0.8 : 0.2);

  bool has_reasonable = text.find("reasonable") != std::string::npos;
  safety_info->class_scores.emplace_back(has_reasonable ? 0.2 : 0.8);

  if (has_language_model_) {
    safety_info->language = DummyDetectLanguage(text);
  }
  std::move(callback).Run(std::move(safety_info));
}

void FakeTextSafetyModel::DetectLanguage(const std::string& text,
                                         DetectLanguageCallback callback) {
  TRACE_EVENT("optimization_guide", "FakeTextSafetyModel::DetectLanguage",
              perfetto::Flow::FromPointer(this), "text", text);
  CHECK(has_language_model_);
  std::move(callback).Run(DummyDetectLanguage(text));
}

void FakeTextSafetyModel::Clone(
    mojo::PendingReceiver<mojom::TextSafetySession> session) {
  TRACE_EVENT("optimization_guide", "FakeTextSafetyModel::Clone",
              perfetto::Flow::FromPointer(this));
  StartSession(std::move(session));
}

FakeSafetyModelHolder::FakeSafetyModelHolder() = default;
FakeSafetyModelHolder::~FakeSafetyModelHolder() = default;

void FakeSafetyModelHolder::Reset(
    mojom::TextSafetyModelParamsPtr params,
    mojo::PendingReceiver<mojom::TextSafetyModel> model_receiver) {
  model_.Clear();
  model_.Add(std::make_unique<FakeTextSafetyModel>(std::move(params)),
             std::move(model_receiver));
}

FakeOnDeviceModelService::FakeOnDeviceModelService(
    FakeOnDeviceServiceSettings* settings)
    : settings_(settings) {}

FakeOnDeviceModelService::~FakeOnDeviceModelService() {
  TRACE_EVENT("optimization_guide",
              "FakeOnDeviceModelService::~FakeOnDeviceModelService",
              perfetto::TerminatingFlow::FromPointer(this));
}

void FakeOnDeviceModelService::LoadModel(
    mojom::LoadModelParamsPtr params,
    mojo::PendingReceiver<mojom::OnDeviceModel> model,
    LoadModelCallback callback) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceModelService::LoadModel",
              perfetto::Flow::FromPointer(this));
  FakeOnDeviceModel::Data data;
  data.base_weight = ReadFile(params->assets.weights.file());
  if (params->assets.cache.IsValid()) {
    data.cache_weight = ReadFile(params->assets.cache);
  }
  if (params->assets.encoder_cache.IsValid()) {
    data.encoder_cache_weight = ReadFile(params->assets.encoder_cache);
  }
  if (params->assets.adapter_cache.IsValid()) {
    data.adapter_cache_weight = ReadFile(params->assets.adapter_cache);
  }
  if (params->assets.program_cache.IsValid()) {
    data.shader_cache_data = ReadFile(params->assets.program_cache);
  }
  data.adaptation_ranks = params->adaptation_ranks;
  auto test_model = std::make_unique<FakeOnDeviceModel>(
      settings_, std::move(data), params->performance_hint,
      params->backend_type);
  if (settings_->drop_connection_request) {
    mojo::Receiver<mojom::OnDeviceModel>(test_model.get(), std::move(model))
        .ResetWithReason(
            static_cast<uint32_t>(*settings_->drop_connection_request), "");
    std::move(callback).Run(mojom::LoadModelResult::kSuccess);
    return;
  }
  auto* raw_model = test_model.get();
  model_receivers_.Add(std::move(test_model), std::move(model), raw_model);
  std::move(callback).Run(mojom::LoadModelResult::kSuccess);
}

void FakeOnDeviceModelService::GetCapabilities(
    ModelFile model_file,
    GetCapabilitiesCallback callback) {
  TRACE_EVENT("optimization_guide", "FakeOnDeviceModelService::GetCapabilities",
              perfetto::Flow::FromPointer(this));
  std::string contents = ReadFile(model_file.file());
  Capabilities capabilities;
  if (contents.find("image") != std::string::npos) {
    capabilities.Put(CapabilityFlags::kImageInput);
  }
  if (contents.find("audio") != std::string::npos) {
    capabilities.Put(CapabilityFlags::kAudioInput);
  }
  std::move(callback).Run(capabilities);
}

void FakeOnDeviceModelService::LoadTextSafetyModel(
    mojom::TextSafetyModelParamsPtr params,
    mojo::PendingReceiver<mojom::TextSafetyModel> model) {
#if !BUILDFLAG(IS_FUCHSIA)
  TRACE_EVENT("optimization_guide",
              "FakeOnDeviceModelService::LoadTextSafetyModel",
              perfetto::Flow::FromPointer(this));
  safety_model_holder_.Reset(std::move(params), std::move(model));
#endif
}

void FakeOnDeviceModelService::GetDeviceAndPerformanceInfo(
    GetDeviceAndPerformanceInfoCallback callback) {
  TRACE_EVENT("optimization_guide",
              "FakeOnDeviceModelService::GetDeviceAndPerformanceInfo",
              perfetto::Flow::FromPointer(this));
  auto performance_info = mojom::DevicePerformanceInfo::New();
  performance_info->performance_class = settings_->performance_class;
  performance_info->vram_mb = settings_->vram_mb;
  auto device_info = mojom::DeviceInfo::New();
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), std::move(performance_info),
                     std::move(device_info)),
      settings_->estimated_performance_delay);
}

FakeServiceLauncher::FakeServiceLauncher(
    on_device_model::FakeOnDeviceServiceSettings* settings)
    : settings_(settings), weak_ptr_factory_(this) {}
FakeServiceLauncher::~FakeServiceLauncher() = default;

void FakeServiceLauncher::LaunchService(
    mojo::PendingReceiver<on_device_model::mojom::OnDeviceModelService>
        pending_receiver) {
  did_launch_service_ = true;
  if (settings_->service_disconnect_reason) {
    pending_receiver.ResetWithReason(
        static_cast<uint32_t>(*settings_->service_disconnect_reason),
        "Fake error");
    return;
  }
  auto service =
      std::make_unique<on_device_model::FakeOnDeviceModelService>(settings_);
  auto* raw_service = service.get();
  services_.Add(std::move(service), std::move(pending_receiver), raw_service);
}

}  // namespace on_device_model
