// Copyright 2020 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/omnibox/browser/autocomplete_match.h"

#include <vector>

#include "base/android/callback_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/functional/bind.h"
#include "base/uuid.h"
#include "components/omnibox/browser/actions/omnibox_action.h"
#include "components/omnibox/browser/actions/omnibox_action_factory_android.h"
#include "components/omnibox/browser/clipboard_provider.h"
#include "components/omnibox/browser/search_suggestion_parser.h"
#include "components/omnibox/common/omnibox_feature_configs.h"
#include "components/saved_tab_groups/public/android/tab_group_sync_conversions_bridge.h"
#include "components/saved_tab_groups/public/android/tab_group_sync_conversions_utils.h"
#include "third_party/jni_zero/default_conversions.h"
#include "third_party/omnibox_proto/suggest_template_info.pb.h"
#include "url/android/gurl_android.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/omnibox/browser/jni_headers/AutocompleteMatch_jni.h"

using base::android::ConvertUTF16ToJavaString;
using base::android::ConvertUTF8ToJavaString;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using base::android::ToJavaArrayOfStrings;
using base::android::ToJavaByteArray;
using base::android::ToJavaIntArray;

// static
jclass AutocompleteMatch::GetClazz(JNIEnv* env) {
  return org_chromium_components_omnibox_AutocompleteMatch_clazz(env);
}

ScopedJavaLocalRef<jobject> AutocompleteMatch::GetOrCreateJavaObject(
    JNIEnv* env,
    const TemplateURLService* template_url_service) const {
  // Short circuit if we already built the match.
  if (java_match_)
    return ScopedJavaLocalRef<jobject>(*java_match_);

  std::vector<int32_t> contents_class_offsets;
  std::vector<int32_t> contents_class_styles;
  contents_class_offsets.reserve(contents_class.size());
  contents_class_styles.reserve(contents_class.size());
  for (auto contents_class_item : contents_class) {
    contents_class_offsets.push_back(contents_class_item.offset);
    contents_class_styles.push_back(contents_class_item.style);
  }

  std::vector<int32_t> description_class_offsets;
  std::vector<int32_t> description_class_styles;
  description_class_offsets.reserve(description_class.size());
  description_class_styles.reserve(description_class.size());
  for (auto description_class_item : description_class) {
    description_class_offsets.push_back(description_class_item.offset);
    description_class_styles.push_back(description_class_item.style);
  }

  ScopedJavaLocalRef<jbyteArray> j_answer_template;
  if (answer_template) {
    std::string str_answer_template;
    if (answer_template->SerializeToString(&str_answer_template)) {
      j_answer_template =
          base::android::ToJavaByteArray(env, str_answer_template);
    }
  }

  std::string post_content_type;
  ScopedJavaLocalRef<jbyteArray> j_post_data;
  if (post_content) {
    post_content_type = post_content->first;
    j_post_data = base::android::ToJavaByteArray(env, post_content->second);
  }

  ScopedJavaLocalRef<jbyteArray> j_clipboard_image_data;
  if (search_terms_args.get()) {
    j_clipboard_image_data = base::android::ToJavaByteArray(
        env, search_terms_args->image_thumbnail_content);
  }

  std::vector<int32_t> temp_subtypes(subtypes.begin(), subtypes.end());

  std::vector<jni_zero::ScopedJavaLocalRef<jobject>> actions_list =
      ToJavaOmniboxActionsList(env, actions);
  base::android::ScopedJavaLocalRef<jobject> j_takeover_action;
  if (takeover_action) {
    j_takeover_action = takeover_action->GetOrCreateJavaObject(env);
  }

  int icon_type = omnibox::SuggestTemplateInfo::IconType::
      SuggestTemplateInfo_IconType_ICON_TYPE_UNSPECIFIED;

  ScopedJavaLocalRef<jbyteArray> j_suggest_template;

  if (suggest_template.has_value()) {
    icon_type = suggest_template.value().type_icon();

    std::string str_suggest_template;
    if (suggest_template->SerializeToString(&str_suggest_template)) {
      j_suggest_template =
          base::android::ToJavaByteArray(env, str_suggest_template);
    }
  }

  int starter_pack_id = static_cast<int>(StarterPackId(template_url_service));

  java_match_ = std::make_unique<ScopedJavaGlobalRef<jobject>>(
      Java_AutocompleteMatch_build(
          env, reinterpret_cast<intptr_t>(this), type, temp_subtypes,
          IsSearchType(type), static_cast<int>(GetOmniboxSuggestionKind()),
          icon_type, transition, contents, contents_class_offsets,
          contents_class_styles, description, description_class_offsets,
          description_class_styles, j_answer_template, answer_type,
          fill_into_edit, destination_url, image_url, image_dominant_color,
          SupportsDeletion(), starter_pack_id, post_content_type, j_post_data,
          suggestion_group_id.value_or(omnibox::GROUP_INVALID),
          swap_contents_and_description, j_clipboard_image_data,
          has_tab_match.value_or(false), android_tab_id, actions_list,
          j_takeover_action, allowed_to_be_default_match, inline_autocompletion,
          additional_text,
          matching_tab_group_uuid
              ? std::make_optional(matching_tab_group_uuid->AsLowercaseString())
              : std::nullopt,
          associated_keyword, j_suggest_template, document_type));

  return ScopedJavaLocalRef<jobject>(*java_match_);
}

void AutocompleteMatch::UpdateJavaObjectNativeRef() {
  if (!java_match_)
    return;

  JNIEnv* env = base::android::AttachCurrentThread();
  Java_AutocompleteMatch_updateNativeObjectRef(
      env, *java_match_, reinterpret_cast<intptr_t>(this));
}

void AutocompleteMatch::DestroyJavaObject() {
  if (!java_match_)
    return;

  JNIEnv* env = base::android::AttachCurrentThread();
  Java_AutocompleteMatch_destroy(env, *java_match_);
  java_match_.reset();
}

void AutocompleteMatch::UpdateWithClipboardContent(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& j_callback) {
  DCHECK(provider) << "No provider available";
  DCHECK(provider->type() == AutocompleteProvider::TYPE_CLIPBOARD)
      << "Invalid provider type: " << provider->type();

  ClipboardProvider* clipboard_provider =
      static_cast<ClipboardProvider*>(provider);
  clipboard_provider->UpdateClipboardMatchWithContent(
      weak_ptr_factory_.GetWeakPtr(),
      base::BindOnce(&AutocompleteMatch::OnClipboardSuggestionContentUpdated,
                     weak_ptr_factory_.GetWeakPtr(),
                     base::android::ScopedJavaGlobalRef<jobject>(j_callback)));
}

void AutocompleteMatch::OnClipboardSuggestionContentUpdated(
    const base::android::JavaRef<jobject>& j_callback) {
  JNIEnv* env = base::android::AttachCurrentThread();
  UpdateClipboardContent(env);
  jni_zero::RunRunnable(j_callback);
}

void AutocompleteMatch::UpdateClipboardContent(JNIEnv* env) {
  if (!java_match_)
    return;

  ScopedJavaLocalRef<jbyteArray> j_clipboard_image_data;
  if (search_terms_args.get()) {
    j_clipboard_image_data = base::android::ToJavaByteArray(
        env, search_terms_args->image_thumbnail_content);
  }

  ScopedJavaLocalRef<jbyteArray> j_post_data;
  if (post_content) {
    j_post_data = base::android::ToJavaByteArray(env, post_content->second);
  }

  Java_AutocompleteMatch_updateClipboardContent(
      env, *java_match_, contents, destination_url,
      post_content ? post_content->first : "", j_post_data,
      j_clipboard_image_data);
}

void AutocompleteMatch::UpdateJavaNavigationDetails() {
  if (java_match_) {
    JNIEnv* env = base::android::AttachCurrentThread();

    std::vector<std::string> header_keys;
    std::vector<std::string> header_vals;
    for (const auto& [key, val] : extra_headers) {
      header_keys.emplace_back(key);
      header_vals.emplace_back(val);
    }

    Java_AutocompleteMatch_updateNavigationDetails(
        env, *java_match_, destination_url, header_keys, header_vals);
  }
}

void AutocompleteMatch::UpdateJavaAnswer() {
  if (java_match_) {
    JNIEnv* env = base::android::AttachCurrentThread();
    if (answer_template) {
      ScopedJavaLocalRef<jbyteArray> j_answer_template;
      std::string str_answer_template;
      if (answer_template->SerializeToString(&str_answer_template)) {
        j_answer_template =
            base::android::ToJavaByteArray(env, str_answer_template);
      }
      Java_AutocompleteMatch_setAnswerTemplate(
          env, *java_match_, answer_template ? j_answer_template : nullptr);
    }
    Java_AutocompleteMatch_setAnswerType(env, *java_match_, answer_type);
  }
}

void AutocompleteMatch::UpdateJavaDescription() {
  if (java_match_) {
    std::vector<int32_t> description_class_offsets;
    std::vector<int32_t> description_class_styles;
    description_class_offsets.reserve(description_class.size());
    description_class_styles.reserve(description_class.size());
    for (auto description_class_item : description_class) {
      description_class_offsets.push_back(description_class_item.offset);
      description_class_styles.push_back(description_class_item.style);
    }
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_AutocompleteMatch_setDescription(env, *java_match_, description,
                                          description_class_offsets,
                                          description_class_styles);
  }
}

DEFINE_JNI(AutocompleteMatch)
