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

#include "android_webview/browser/aw_dark_mode.h"

#include "android_webview/browser/aw_contents.h"
#include "android_webview/common/aw_features.h"
#include "base/android/scoped_java_ref.h"
#include "base/memory/ptr_util.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "android_webview/browser_jni_headers/AwDarkMode_jni.h"

using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;

namespace android_webview {
namespace {
const void* const kAwDarkModeUserDataKey = &kAwDarkModeUserDataKey;
bool sEnableLegacyDarkMode = false;

bool IsForceDarkEnabled(content::WebContents* web_contents) {
  AwContents* contents = AwContents::FromWebContents(web_contents);
  return contents && contents->GetViewTreeForceDarkState();
}
}  // namespace

// static
static int64_t JNI_AwDarkMode_Init(JNIEnv* env,
                                   const JavaRef<jobject>& obj,
                                   const JavaRef<jobject>& java_web_contents) {
  content::WebContents* web_contents =
      content::WebContents::FromJavaWebContents(java_web_contents);
  DCHECK(web_contents);
  return reinterpret_cast<intptr_t>(new AwDarkMode(env, obj, web_contents));
}

static void JNI_AwDarkMode_EnableLegacyDarkMode(JNIEnv* env) {
  sEnableLegacyDarkMode = true;
}

static void JNI_AwDarkMode_ResetForTesting(JNIEnv* env) {
  sEnableLegacyDarkMode = false;
}

AwDarkMode* AwDarkMode::FromWebContents(content::WebContents* contents) {
  return static_cast<AwDarkMode*>(
      contents->GetUserData(kAwDarkModeUserDataKey));
}

AwDarkMode::AwDarkMode(JNIEnv* env,
                       const jni_zero::JavaRef<jobject>& obj,
                       content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents), jobj_(env, obj) {
  web_contents->SetUserData(kAwDarkModeUserDataKey, base::WrapUnique(this));
}

AwDarkMode::~AwDarkMode() {
  JNIEnv* env = base::android::AttachCurrentThread();
  ScopedJavaLocalRef<jobject> scoped_obj = jobj_.get(env);
  if (scoped_obj)
    Java_AwDarkMode_onNativeObjectDestroyed(env, scoped_obj);
}

void AwDarkMode::PopulateWebPreferences(
    blink::web_pref::WebPreferences* web_prefs,
    int force_dark_mode,
    int force_dark_behavior,
    bool algorithmic_darkening_allowed) {
  if (sEnableLegacyDarkMode) {
    PopulateWebPreferencesForLegacy(web_prefs, force_dark_mode,
                                    force_dark_behavior);
    return;
  }
  prefers_dark_from_theme_ = IsAppUsingDarkTheme();
  web_prefs->preferred_color_scheme =
      prefers_dark_from_theme_ ? blink::mojom::PreferredColorScheme::kDark
                               : blink::mojom::PreferredColorScheme::kLight;
  web_prefs->force_dark_mode_enabled = false;
  is_force_dark_applied_ = false;
  if (IsForceDarkEnabled(web_contents())) {
    is_force_dark_applied_ = true;
    web_prefs->force_dark_mode_enabled = true;
    web_prefs->preferred_color_scheme =
        blink::mojom::PreferredColorScheme::kDark;
  } else if (prefers_dark_from_theme_) {
    is_force_dark_applied_ = algorithmic_darkening_allowed;
    web_prefs->force_dark_mode_enabled = algorithmic_darkening_allowed;
  }
}

void AwDarkMode::PopulateWebPreferencesForLegacy(
    blink::web_pref::WebPreferences* web_prefs,
    int force_dark_mode,
    int force_dark_behavior) {
  prefers_dark_from_theme_ = false;
  switch (force_dark_mode) {
    case AwSettings::ForceDarkMode::FORCE_DARK_OFF:
      is_force_dark_applied_ = false;
      break;
    case AwSettings::ForceDarkMode::FORCE_DARK_ON:
      is_force_dark_applied_ = true;
      break;
    case AwSettings::ForceDarkMode::FORCE_DARK_AUTO: {
      is_force_dark_applied_ = IsForceDarkEnabled(web_contents());
      if (!is_force_dark_applied_)
        prefers_dark_from_theme_ = IsAppUsingDarkTheme();
      break;
    }
  }
  web_prefs->preferred_color_scheme =
      is_force_dark_applied_ ? blink::mojom::PreferredColorScheme::kDark
                             : blink::mojom::PreferredColorScheme::kLight;
  if (is_force_dark_applied_) {
    switch (force_dark_behavior) {
      case AwSettings::ForceDarkBehavior::FORCE_DARK_ONLY: {
        web_prefs->preferred_color_scheme =
            blink::mojom::PreferredColorScheme::kLight;
        web_prefs->force_dark_mode_enabled = true;
        break;
      }
      case AwSettings::ForceDarkBehavior::MEDIA_QUERY_ONLY: {
        web_prefs->preferred_color_scheme =
            blink::mojom::PreferredColorScheme::kDark;
        web_prefs->force_dark_mode_enabled = false;
        break;
      }
      // Blink's behavior is that if the preferred color scheme matches the
      // supported color scheme, then force dark will be disabled, otherwise
      // the preferred color scheme will be reset to 'light'. Therefore
      // when enabling force dark, we also set the preferred color scheme to
      // dark so that dark themed content will be preferred over force
      // darkening.
      case AwSettings::ForceDarkBehavior::PREFER_MEDIA_QUERY_OVER_FORCE_DARK: {
        web_prefs->preferred_color_scheme =
            blink::mojom::PreferredColorScheme::kDark;
        web_prefs->force_dark_mode_enabled = true;
        break;
      }
    }
  } else if (prefers_dark_from_theme_) {
    web_prefs->preferred_color_scheme =
        blink::mojom::PreferredColorScheme::kDark;
  } else {
    web_prefs->preferred_color_scheme =
        blink::mojom::PreferredColorScheme::kLight;
    web_prefs->force_dark_mode_enabled = false;
  }
}

bool AwDarkMode::IsAppUsingDarkTheme() {
  JNIEnv* env = base::android::AttachCurrentThread();
  ScopedJavaLocalRef<jobject> scoped_obj = jobj_.get(env);
  if (!scoped_obj)
    return false;
  return Java_AwDarkMode_isAppUsingDarkTheme(env, scoped_obj);
}

void AwDarkMode::DetachFromJavaObject(JNIEnv* env) {
  jobj_.reset();
}

}  // namespace android_webview

DEFINE_JNI(AwDarkMode)
