// Copyright 2017 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/ash/login/lock_screen_utils.h"

#include <algorithm>

#include "ash/constants/ash_constants.h"
#include "ash/constants/ash_login_pref_names.h"
#include "ash/constants/ash_pref_names.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/input_method/ime_controller_client_impl.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "components/account_id/account_id.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/known_user.h"
#include "ui/base/ime/ash/ime_keyboard.h"

namespace ash::lock_screen_utils {
namespace {

bool SetUserInputMethodImpl(
    const std::string& user_input_method_id,
    input_method::InputMethodManager::State* ime_state) {
  if (!input_method::InputMethodManager::Get()->IsLoginKeyboard(
          user_input_method_id)) {
    LOG(WARNING) << "SetUserInputMethod: stored user last input method '"
                 << user_input_method_id
                 << "' is no longer Full Latin Keyboard Language"
                 << " (entry dropped). Use hardware default instead.";
    return false;
  }
  if (!std::ranges::contains(ime_state->GetEnabledInputMethodIds(),
                             user_input_method_id)) {
    if (!ime_state->EnableInputMethod(user_input_method_id)) {
      DLOG(ERROR) << "SetUserInputMethod: user input method '"
                  << user_input_method_id
                  << "' is not enabled and enabling failed (ignored!).";
      return false;
    }
  }
  ime_state->ChangeInputMethod(user_input_method_id, false /* show_message */);

  return true;
}

}  // namespace

void SetUserInputMethod(PrefService& local_state,
                        const AccountId& account_id,
                        input_method::InputMethodManager::State* ime_state,
                        bool honor_device_policy) {
  bool succeed = false;

  const std::string input_method_id =
      GetUserLastInputMethodId(local_state, account_id);

  if (honor_device_policy)
    EnforceDevicePolicyInputMethods(input_method_id);

  if (!input_method_id.empty())
    succeed = SetUserInputMethodImpl(input_method_id, ime_state);

  // This is also a case when last layout is set only for a few local users,
  // thus others need to be switched to default locale.
  // Otherwise they will end up using another user's locale to log in.
  if (!succeed) {
    DVLOG(0) << "SetUserInputMethod: failed to set user layout. Switching to "
                "default.";

    ime_state->SetInputMethodLoginDefault(false /* is_in_oobe_context */);
  }
}

std::string GetUserLastInputMethodId(PrefService& local_state,
                                     const AccountId& account_id) {
  if (!account_id.is_valid())
    return std::string();
  user_manager::KnownUser known_user(&local_state);
  if (const std::string* input_method_id =
          known_user.GetUserLastInputMethodId(account_id)) {
    return *input_method_id;
  }

  // Try profile prefs. For the ephemeral case known_user does not persist the
  // data.
  Profile* profile = Profile::FromBrowserContext(
      BrowserContextHelper::Get()->GetBrowserContextByAccountId(account_id));
  if (profile && profile->GetPrefs()) {
    std::string input_method_id =
        profile->GetPrefs()->GetString(prefs::kLastLoginInputMethod);
    if (!input_method_id.empty())
      return input_method_id;
  }

  return std::string();
}

void EnforceDevicePolicyInputMethods(std::string user_input_method_id) {
  auto* cros_settings = CrosSettings::Get();
  const base::ListValue* login_screen_input_methods = nullptr;
  if (!cros_settings->GetList(kDeviceLoginScreenInputMethods,
                              &login_screen_input_methods) ||
      login_screen_input_methods->empty()) {
    StopEnforcingPolicyInputMethods();
    return;
  }

  std::vector<std::string> allowed_input_method_ids;

  // Add user's input method first so it is pre-selected.
  if (!user_input_method_id.empty()) {
    allowed_input_method_ids.push_back(user_input_method_id);
  }

  for (const auto& input_method_entry : *login_screen_input_methods) {
    if (input_method_entry.is_string())
      allowed_input_method_ids.push_back(input_method_entry.GetString());
  }
  auto imm_state = input_method::InputMethodManager::Get()->GetActiveIMEState();
  bool managed_by_policy =
      imm_state->SetAllowedInputMethods(allowed_input_method_ids);
  if (managed_by_policy) {
    imm_state->ReplaceEnabledInputMethods(
        imm_state->GetAllowedInputMethodIds());
  }
  if (ImeControllerClientImpl::Get())  // Can be null in tests.
    ImeControllerClientImpl::Get()->SetImesManagedByPolicy(true);
}

void StopEnforcingPolicyInputMethods() {
  // Empty means all input methods are allowed
  auto imm_state = input_method::InputMethodManager::Get()->GetActiveIMEState();
  imm_state->SetAllowedInputMethods(std::vector<std::string>());
  if (ImeControllerClientImpl::Get())  // Can be null in tests.
    ImeControllerClientImpl::Get()->SetImesManagedByPolicy(false);
  imm_state->SetInputMethodLoginDefault(false /* is_in_oobe_context */);
}

void SetKeyboardSettings(PrefService& local_state,
                         const AccountId& account_id) {
  user_manager::KnownUser known_user(&local_state);
  if (std::optional<bool> auto_repeat_enabled =
          known_user.FindBoolPath(account_id, prefs::kXkbAutoRepeatEnabled);
      auto_repeat_enabled.has_value()) {
    if (!auto_repeat_enabled.value()) {
      input_method::InputMethodManager::Get()
          ->GetImeKeyboard()
          ->SetAutoRepeatEnabled(false);
      return;
    }
  }

  input_method::AutoRepeatRate rate{
      .initial_delay = kDefaultKeyAutoRepeatDelay,
      .repeat_interval = kDefaultKeyAutoRepeatInterval,
  };

  if (auto delay =
          known_user.FindIntPath(account_id, prefs::kXkbAutoRepeatDelay);
      delay) {
    rate.initial_delay = base::Milliseconds(delay.value());
  }

  if (auto interval =
          known_user.FindIntPath(account_id, prefs::kXkbAutoRepeatInterval);
      interval) {
    rate.repeat_interval = base::Milliseconds(interval.value());
  }

  input_method::InputMethodManager::Get()
      ->GetImeKeyboard()
      ->SetAutoRepeatEnabled(true);
  input_method::InputMethodManager::Get()->GetImeKeyboard()->SetAutoRepeatRate(
      rate);
}

std::vector<LocaleItem> FromListValueToLocaleItem(base::ListValue locales) {
  std::vector<LocaleItem> result;
  for (const auto& locale : locales) {
    if (!locale.is_dict())
      continue;
    const auto& dictionary = locale.GetDict();
    LocaleItem locale_item;
    if (const std::string* language_code = dictionary.FindString("value")) {
      locale_item.language_code = *language_code;
    }
    if (const std::string* title = dictionary.FindString("title")) {
      locale_item.title = *title;
    }
    if (const std::string* group_name =
            dictionary.FindString("optionGroupName")) {
      if (!group_name->empty())
        locale_item.group_name = *group_name;
    }
    result.push_back(std::move(locale_item));
  }
  return result;
}

}  // namespace ash::lock_screen_utils
