/*
 * This file is part of LSPosed.
 *
 * LSPosed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LSPosed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LSPosed.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2020 EdXposed Contributors
 * Copyright (C) 2021 LSPosed Contributors
 */

package org.lsposed.manager.adapters;

import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.SearchView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.checkbox.MaterialCheckBox;

import org.lsposed.lspd.models.Application;
import org.lsposed.manager.App;
import org.lsposed.manager.BuildConfig;
import org.lsposed.manager.ConfigManager;
import org.lsposed.manager.R;
import org.lsposed.manager.databinding.ItemMasterSwitchBinding;
import org.lsposed.manager.databinding.ItemModuleBinding;
import org.lsposed.manager.ui.dialog.BlurBehindDialogBuilder;
import org.lsposed.manager.ui.fragment.AppListFragment;
import org.lsposed.manager.ui.fragment.CompileDialogFragment;
import org.lsposed.manager.ui.widget.EmptyStateRecyclerView;
import org.lsposed.manager.util.GlideApp;
import org.lsposed.manager.util.ModuleUtil;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import rikka.core.util.ResourceUtils;
import rikka.material.app.LocaleDelegate;
import rikka.widget.mainswitchbar.MainSwitchBar;
import rikka.widget.mainswitchbar.OnMainSwitchChangeListener;

public class ScopeAdapter extends EmptyStateRecyclerView.EmptyStateAdapter<ScopeAdapter.ViewHolder> implements Filterable {

    private final Activity activity;
    private final AppListFragment fragment;
    private final PackageManager pm;
    private final SharedPreferences preferences;
    private final ModuleUtil moduleUtil;

    private final ModuleUtil.InstalledModule module;

    private Set<ApplicationWithEquals> recommendedList = new HashSet<>();
    private Set<ApplicationWithEquals> checkedList = new HashSet<>();
    private List<AppInfo> searchList = new ArrayList<>();
    private List<AppInfo> showList = new ArrayList<>();
    private List<String> denyList = new ArrayList<>();

    public RecyclerView.Adapter<RecyclerView.ViewHolder> switchAdaptor = new RecyclerView.Adapter<>() {
        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            return new RecyclerView.ViewHolder(ItemMasterSwitchBinding.inflate(activity.getLayoutInflater(), parent, false).masterSwitch) {
            };
        }

        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
            var mainSwitchBar = (MainSwitchBar) holder.itemView;
            mainSwitchBar.setChecked(enabled);
            mainSwitchBar.addOnSwitchChangeListener(switchBarOnCheckedChangeListener);
        }

        @Override
        public int getItemCount() {
            return 1;
        }
    };

    private final OnMainSwitchChangeListener switchBarOnCheckedChangeListener = new OnMainSwitchChangeListener() {
        @Override
        public void onSwitchChanged(Switch view, boolean isChecked) {
            enabled = isChecked;
            if (!moduleUtil.setModuleEnabled(module.packageName, isChecked)) {
                view.setChecked(!isChecked);
                enabled = !isChecked;
            }
            var tmpChkList = new HashSet<>(checkedList);
            if (isChecked && !tmpChkList.isEmpty() && !ConfigManager.setModuleScope(module.packageName, module.legacy, tmpChkList)) {
                view.setChecked(false);
                enabled = false;
            }
            fragment.runOnUiThread(ScopeAdapter.this::notifyDataSetChanged);
        }
    };

    private ApplicationInfo selectedApplicationInfo;
    private boolean isLoaded = false;
    private boolean enabled = true;

    public ScopeAdapter(AppListFragment fragment, ModuleUtil.InstalledModule module) {
        this.fragment = fragment;
        this.activity = fragment.requireActivity();
        this.module = module;
        moduleUtil = ModuleUtil.getInstance();
        preferences = App.getPreferences();
        pm = activity.getPackageManager();
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(ItemModuleBinding.inflate(activity.getLayoutInflater(), parent, false));
    }

    private boolean shouldHideApp(PackageInfo info, ApplicationWithEquals app, HashSet<ApplicationWithEquals> tmpChkList) {
        if (info.packageName.equals("system")) {
            return false;
        }
        if (tmpChkList.contains(app)) {
            return false;
        }
        if (preferences.getBoolean("filter_denylist", false)) {
            if (denyList.contains(info.packageName)) {
                return true;
            }
        }
        if (preferences.getBoolean("filter_modules", true)) {
            if (ModuleUtil.getInstance().getModule(info.packageName, info.applicationInfo.uid / App.PER_USER_RANGE) != null) {
                return true;
            }
        }
        if (preferences.getBoolean("filter_games", true)) {
            if (info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
                return true;
            }
            //noinspection deprecation
            if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_GAME) != 0) {
                return true;
            }
        }
        return preferences.getBoolean("filter_system_apps", true) && (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
    }

    private int sortApps(AppInfo x, AppInfo y) {
        Comparator<PackageInfo> comparator = AppHelper.getAppListComparator(preferences.getInt("list_sort", 0), pm);
        Comparator<AppInfo> frameworkComparator = (a, b) -> {
            if (a.packageName.equals("system") == b.packageName.equals("system")) {
                return comparator.compare(a.packageInfo, b.packageInfo);
            } else if (a.packageName.equals("system")) {
                return -1;
            } else {
                return 1;
            }
        };
        Comparator<AppInfo> recommendedComparator = (a, b) -> {
            boolean aRecommended = !recommendedList.isEmpty() && recommendedList.contains(a.application);
            boolean bRecommended = !recommendedList.isEmpty() && recommendedList.contains(b.application);
            if (aRecommended == bRecommended) {
                return frameworkComparator.compare(a, b);
            } else if (aRecommended) {
                return -1;
            } else {
                return 1;
            }
        };
        boolean aChecked = checkedList.contains(x.application);
        boolean bChecked = checkedList.contains(y.application);
        if (aChecked == bChecked) {
            return recommendedComparator.compare(x, y);
        } else if (aChecked) {
            return -1;
        } else {
            return 1;
        }
    }

    private void checkRecommended() {
        if (!enabled) {
            fragment.showHint(R.string.module_is_not_activated_yet, false);
            return;
        }
        fragment.runAsync(() -> {
            var tmpChkList = new HashSet<>(checkedList);
            tmpChkList.removeIf(i -> i.userId == module.userId);
            tmpChkList.addAll(recommendedList);
            ConfigManager.setModuleScope(module.packageName, module.legacy, tmpChkList);
            checkedList = tmpChkList;
            fragment.runOnUiThread(this::notifyDataSetChanged);
        });
    }

    @SuppressLint("NotifyDataSetChanged")
    private void setLoaded(List<AppInfo> list, boolean loaded) {
        fragment.runOnUiThread(() -> {
            if (list != null) showList = list;
            isLoaded = loaded;
            notifyDataSetChanged();
        });
    }

    public boolean onOptionsItemSelected(MenuItem item) {
        int itemId = item.getItemId();
        if (itemId == R.id.use_recommended) {
            if (!checkedList.isEmpty()) {
                new BlurBehindDialogBuilder(activity, R.style.ThemeOverlay_MaterialAlertDialog_Centered_FullWidthButtons)
                        .setMessage(R.string.use_recommended_message)
                        .setPositiveButton(android.R.string.ok, (dialog, which) -> checkRecommended())
                        .setNegativeButton(android.R.string.cancel, null)
                        .show();
            } else {
                checkRecommended();
            }
            return true;
        } else if (itemId == R.id.item_filter_system) {
            item.setChecked(!item.isChecked());
            preferences.edit().putBoolean("filter_system_apps", item.isChecked()).apply();
        } else if (itemId == R.id.item_filter_games) {
            item.setChecked(!item.isChecked());
            preferences.edit().putBoolean("filter_games", item.isChecked()).apply();
        } else if (itemId == R.id.item_filter_modules) {
            item.setChecked(!item.isChecked());
            preferences.edit().putBoolean("filter_modules", item.isChecked()).apply();
        } else if (itemId == R.id.item_filter_denylist) {
            item.setChecked(!item.isChecked());
            preferences.edit().putBoolean("filter_denylist", item.isChecked()).apply();
        } else if (itemId == R.id.backup) {
            LocalDateTime now = LocalDateTime.now();
            try {
                fragment.backupLauncher.launch(String.format(LocaleDelegate.getDefaultLocale(),
                        "%s_%s.lsp", module.getAppName(), now.toString()));
                return true;
            } catch (ActivityNotFoundException e) {
                fragment.showHint(R.string.enable_documentui, true);
                return false;
            }
        } else if (itemId == R.id.restore) {
            try {
                fragment.restoreLauncher.launch(new String[]{"*/*"});
                return true;
            } catch (ActivityNotFoundException e) {
                fragment.showHint(R.string.enable_documentui, true);
                return false;
            }
        } else if (itemId == R.id.select_all) {
            var tmpChkList = new HashSet<ApplicationWithEquals>(ConfigManager.getModuleScope(module.packageName));
            for (AppInfo info : searchList) {
                if (info.packageName.equals("android")) {
                    fragment.showHint(R.string.reboot_required, true, R.string.reboot, v -> ConfigManager.reboot());
                }
                tmpChkList.add(info.application);
            }
            ConfigManager.setModuleScope(module.packageName, module.legacy, tmpChkList);
        } else if (itemId == R.id.select_none) {
            var tmpChkList = new HashSet<ApplicationWithEquals>(ConfigManager.getModuleScope(module.packageName));
            for (AppInfo info : searchList) {
                if (tmpChkList.remove(info.application) && info.packageName.equals("android")) {
                    fragment.showHint(R.string.reboot_required, true, R.string.reboot, v -> ConfigManager.reboot());
                }
            }
            ConfigManager.setModuleScope(module.packageName, module.legacy, tmpChkList);
        } else if (itemId == R.id.auto_include) {
            item.setChecked(!item.isChecked());
            ConfigManager.setAutoInclude(module.packageName, item.isChecked());
        } else if (!AppHelper.onOptionsItemSelected(item, preferences)) {
            return false;
        }
        refresh();
        return true;
    }

    public boolean onContextItemSelected(@NonNull MenuItem item) {
        var info = selectedApplicationInfo;
        if (info == null) {
            return false;
        }
        int itemId = item.getItemId();
        if (itemId == R.id.menu_launch) {
            Intent launchIntent = AppHelper.getLaunchIntentForPackage(info.packageName, info.uid / App.PER_USER_RANGE);
            if (launchIntent != null) {
                ConfigManager.startActivityAsUserWithFeature(launchIntent, module.userId);
            }
        } else if (itemId == R.id.menu_compile_speed) {
            CompileDialogFragment.speed(fragment.getChildFragmentManager(), info);
        } else if (itemId == R.id.menu_other_app) {
            var intent = new Intent(Intent.ACTION_SHOW_APP_INFO);
            intent.putExtra(Intent.EXTRA_PACKAGE_NAME, module.packageName);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            ConfigManager.startActivityAsUserWithFeature(intent, module.userId);
        } else if (itemId == R.id.menu_app_info) {
            ConfigManager.startActivityAsUserWithFeature(new Intent(ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", info.packageName, null)), module.userId);
        } else if (itemId == R.id.menu_force_stop) {
            if (info.packageName.equals("system")) {
                new BlurBehindDialogBuilder(activity, R.style.ThemeOverlay_MaterialAlertDialog_Centered_FullWidthButtons)
                        .setTitle(R.string.reboot)
                        .setPositiveButton(android.R.string.ok, (dialog, which) -> ConfigManager.reboot())
                        .setNegativeButton(android.R.string.cancel, null)
                        .show();
            } else {
                new BlurBehindDialogBuilder(activity, R.style.ThemeOverlay_MaterialAlertDialog_Centered_FullWidthButtons)
                        .setTitle(R.string.force_stop_dlg_title)
                        .setMessage(R.string.force_stop_dlg_text)
                        .setPositiveButton(android.R.string.ok, (dialog, which) -> ConfigManager.forceStopPackage(info.packageName, info.uid / 100000))
                        .setNegativeButton(android.R.string.cancel, null)
                        .show();
            }
        } else {
            return false;
        }
        return true;
    }

    public void onPrepareOptionsMenu(@NonNull Menu menu) {
        List<String> scopeList = module.getScopeList();
        if (scopeList == null || scopeList.isEmpty()) {
            menu.removeItem(R.id.use_recommended);
        }
        menu.findItem(R.id.item_filter_system).setChecked(preferences.getBoolean("filter_system_apps", true));
        menu.findItem(R.id.item_filter_games).setChecked(preferences.getBoolean("filter_games", true));
        menu.findItem(R.id.item_filter_modules).setChecked(preferences.getBoolean("filter_modules", true));
        menu.findItem(R.id.item_filter_denylist).setChecked(preferences.getBoolean("filter_denylist", false));
        switch (preferences.getInt("list_sort", 0)) {
            case 7 -> {
                menu.findItem(R.id.item_sort_by_update_time).setChecked(true);
                menu.findItem(R.id.reverse).setChecked(true);
            }
            case 6 -> menu.findItem(R.id.item_sort_by_update_time).setChecked(true);
            case 5 -> {
                menu.findItem(R.id.item_sort_by_install_time).setChecked(true);
                menu.findItem(R.id.reverse).setChecked(true);
            }
            case 4 -> menu.findItem(R.id.item_sort_by_install_time).setChecked(true);
            case 3 -> {
                menu.findItem(R.id.item_sort_by_package_name).setChecked(true);
                menu.findItem(R.id.reverse).setChecked(true);
            }
            case 2 -> menu.findItem(R.id.item_sort_by_package_name).setChecked(true);
            case 1 -> {
                menu.findItem(R.id.item_sort_by_name).setChecked(true);
                menu.findItem(R.id.reverse).setChecked(true);
            }
            case 0 -> menu.findItem(R.id.item_sort_by_name).setChecked(true);
        }
        menu.findItem(R.id.auto_include).setChecked(ConfigManager.getAutoInclude(module.packageName));
    }

    @Override
    public void onViewRecycled(@NonNull ViewHolder holder) {
        if (holder.checkbox != null) {
            holder.checkbox.setOnCheckedChangeListener(null);
        }
        super.onViewRecycled(holder);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        AppInfo appInfo = showList.get(position);
        boolean deny = denyList.contains(appInfo.packageName);
        holder.root.setAlpha(!deny && enabled ? 1.0f : .5f);
        boolean system = appInfo.packageName.equals("system");
        CharSequence appName;
        int userId = appInfo.applicationInfo.uid / App.PER_USER_RANGE;
        appName = system ? activity.getString(R.string.android_framework) : appInfo.label;
        holder.appName.setText(appName);
        GlideApp.with(holder.appIcon).load(appInfo.packageInfo).into(new CustomTarget<Drawable>() {
            @Override
            public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
                holder.appIcon.setImageDrawable(resource);
            }

            @Override
            public void onLoadCleared(@Nullable Drawable placeholder) {

            }

            @Override
            public void onLoadFailed(@Nullable Drawable errorDrawable) {
                holder.appIcon.setImageDrawable(pm.getDefaultActivityIcon());
            }
        });
        if (system) {
            //noinspection SetTextI18n
            holder.appPackageName.setText("system");
            holder.appVersionName.setVisibility(View.GONE);
        } else {
            holder.appVersionName.setVisibility(View.VISIBLE);
            holder.appPackageName.setText(appInfo.packageName);
        }
        holder.appPackageName.setVisibility(View.VISIBLE);
        holder.appVersionName.setText(activity.getString(R.string.app_version, appInfo.packageInfo.versionName));
        var sb = new SpannableStringBuilder();
        if (!recommendedList.isEmpty() && recommendedList.contains(appInfo.application)) {
            String recommended = activity.getString(R.string.requested_by_module);
            sb.append(recommended);
            final ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(ResourceUtils.resolveColor(activity.getTheme(), com.google.android.material.R.attr.colorPrimary));
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                final TypefaceSpan typefaceSpan = new TypefaceSpan(Typeface.create("sans-serif-medium", Typeface.NORMAL));
                sb.setSpan(typefaceSpan, sb.length() - recommended.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
            } else {
                final StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
                sb.setSpan(styleSpan, sb.length() - recommended.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
            }
            sb.setSpan(foregroundColorSpan, sb.length() - recommended.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        }
        if (deny) {
            if (sb.length() != 0) sb.append("\n");
            String denylist = activity.getString(R.string.deny_list_info);
            sb.append(denylist);
            final ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(ResourceUtils.resolveColor(activity.getTheme(), com.google.android.material.R.attr.colorError));
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                final TypefaceSpan typefaceSpan = new TypefaceSpan(Typeface.create("sans-serif-medium", Typeface.NORMAL));
                sb.setSpan(typefaceSpan, sb.length() - denylist.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
            } else {
                final StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
                sb.setSpan(styleSpan, sb.length() - denylist.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
            }
            sb.setSpan(foregroundColorSpan, sb.length() - denylist.length(), sb.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
        }
        if (sb.length() == 0) {
            holder.hint.setVisibility(View.GONE);
        } else {
            holder.hint.setText(sb);
            holder.hint.setVisibility(View.VISIBLE);
        }

        holder.itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
            activity.getMenuInflater().inflate(R.menu.menu_app_item, menu);
            menu.setHeaderTitle(appName);
            Intent launchIntent = AppHelper.getLaunchIntentForPackage(appInfo.packageName, userId);
            if (launchIntent == null) {
                menu.removeItem(R.id.menu_launch);
            }
            if (system) {
                menu.findItem(R.id.menu_force_stop).setTitle(R.string.reboot);
                menu.removeItem(R.id.menu_compile_speed);
                menu.removeItem(R.id.menu_other_app);
                menu.removeItem(R.id.menu_app_info);
            }
        });

        holder.checkbox.setChecked(checkedList.contains(appInfo.application));

        holder.checkbox.setOnCheckedChangeListener((v, isChecked) -> onCheckedChange(v, isChecked, appInfo));

        holder.itemView.setOnClickListener(v -> {
            if (enabled) holder.checkbox.toggle();
        });
        holder.itemView.setOnLongClickListener(v -> {
            fragment.searchView.clearFocus();
            selectedApplicationInfo = appInfo.applicationInfo;
            return false;
        });
    }

    @Override
    public long getItemId(int position) {
        PackageInfo info = showList.get(position).packageInfo;
        return (info.packageName + "!" + info.applicationInfo.uid / App.PER_USER_RANGE).hashCode();
    }

    @Override
    public Filter getFilter() {
        return new ApplicationFilter();
    }

    @Override
    public int getItemCount() {
        return showList.size();
    }

    public void refresh() {
        refresh(false);
    }

    public void refresh(boolean force) {
        setLoaded(null, false);
        enabled = moduleUtil.isModuleEnabled(module.packageName);
        fragment.runAsync(() -> {
            List<PackageInfo> appList = AppHelper.getAppList(force);
            denyList = AppHelper.getDenyList(force);
            var tmpRecList = new HashSet<ApplicationWithEquals>();
            var tmpChkList = new HashSet<>(ConfigManager.getModuleScope(module.packageName));
            final var tmpList = new ArrayList<AppInfo>();
            final HashSet<ApplicationWithEquals> installedList = new HashSet<>();
            List<String> scopeList = module.getScopeList();
            boolean emptyCheckedList = tmpChkList.isEmpty();
            appList.parallelStream().forEach(info -> {
                int userId = info.applicationInfo.uid / App.PER_USER_RANGE;
                String packageName = info.packageName;
                if (packageName.equals("system") && userId != 0 ||
                        packageName.equals(module.packageName) ||
                        packageName.equals(BuildConfig.APPLICATION_ID)) {
                    return;
                }

                ApplicationWithEquals application = new ApplicationWithEquals(packageName, userId);

                synchronized (installedList) {
                    installedList.add(application);
                }

                if (userId != module.userId) {
                    return;
                }

                if (scopeList != null && scopeList.contains(packageName)) {
                    synchronized (tmpRecList) {
                        tmpRecList.add(application);
                    }
                } else if (shouldHideApp(info, application, tmpChkList)) {
                    return;
                }

                AppInfo appInfo = new AppInfo();
                appInfo.packageInfo = info;
                appInfo.label = AppHelper.getAppLabel(info, pm);
                appInfo.application = application;
                appInfo.packageName = info.packageName;
                appInfo.applicationInfo = info.applicationInfo;
                synchronized (tmpList) {
                    tmpList.add(appInfo);
                }
            });
            tmpChkList.retainAll(installedList);
            checkedList = tmpChkList;
            recommendedList = tmpRecList;
            searchList = tmpList.parallelStream().sorted(this::sortApps).collect(Collectors.toList());

            String queryStr = fragment.searchView != null ? fragment.searchView.getQuery().toString() : "";

            fragment.runOnUiThread(() -> getFilter().filter(queryStr));
        });
    }

    protected void onCheckedChange(CompoundButton buttonView, boolean isChecked, AppInfo appInfo) {
        var tmpChkList = new HashSet<>(checkedList);
        if (isChecked) {
            tmpChkList.add(appInfo.application);
        } else {
            tmpChkList.remove(appInfo.application);
        }
        if (!ConfigManager.setModuleScope(module.packageName, module.legacy, tmpChkList)) {
            fragment.showHint(R.string.failed_to_save_scope_list, true);
            if (!isChecked) {
                tmpChkList.add(appInfo.application);
            } else {
                tmpChkList.remove(appInfo.application);
            }
            buttonView.setChecked(!isChecked);
        } else if (appInfo.packageName.equals("system")) {
            fragment.showHint(R.string.reboot_required, true, R.string.reboot, v -> ConfigManager.reboot());
        } else if (denyList.contains(appInfo.packageName)) {
            fragment.showHint(activity.getString(R.string.deny_list, appInfo.label), true);
        }
        checkedList = tmpChkList;
    }

    @Override
    public boolean isLoaded() {
        return isLoaded;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        ConstraintLayout root;
        ImageView appIcon;
        TextView appName;
        TextView appPackageName;
        TextView appVersionName;
        TextView hint;
        MaterialCheckBox checkbox;

        ViewHolder(ItemModuleBinding binding) {
            super(binding.getRoot());
            root = binding.itemRoot;
            appIcon = binding.appIcon;
            appName = binding.appName;
            appPackageName = binding.appPackageName;
            appVersionName = binding.appVersionName;
            checkbox = binding.checkbox;
            hint = binding.hint;
            checkbox.setVisibility(View.VISIBLE);
        }
    }

    private class ApplicationFilter extends Filter {

        private boolean lowercaseContains(String s, String filter) {
            return !TextUtils.isEmpty(s) && s.toLowerCase().contains(filter);
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            FilterResults filterResults = new FilterResults();
            List<AppInfo> filtered = new ArrayList<>();
            String filter = constraint.toString().toLowerCase();
            for (AppInfo info : searchList) {
                if (lowercaseContains(info.label.toString(), filter)
                        || lowercaseContains(info.packageName, filter)) {
                    filtered.add(info);
                }
            }
            filterResults.values = filtered;
            filterResults.count = filtered.size();
            return filterResults;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            //noinspection unchecked
            setLoaded((List<AppInfo>) results.values, true);
        }
    }

    public SearchView.OnQueryTextListener getSearchListener() {
        return new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                getFilter().filter(query);
                return true;
            }

            @Override
            public boolean onQueryTextChange(String query) {
                getFilter().filter(query);
                return true;
            }
        };
    }

    public void onBackPressed() {
        fragment.searchView.clearFocus();
        if (isLoaded && enabled && checkedList.isEmpty()) {
            var builder = new BlurBehindDialogBuilder(activity, R.style.ThemeOverlay_MaterialAlertDialog_Centered_FullWidthButtons);
            builder.setMessage(!recommendedList.isEmpty() ? R.string.no_scope_selected_has_recommended : R.string.no_scope_selected);
            if (!recommendedList.isEmpty()) {
                builder.setPositiveButton(android.R.string.ok, (dialog, which) -> checkRecommended());
            } else {
                builder.setPositiveButton(android.R.string.cancel, null);
            }
            builder.setNegativeButton(!recommendedList.isEmpty() ? android.R.string.cancel : android.R.string.ok, (dialog, which) -> {
                moduleUtil.setModuleEnabled(module.packageName, false);
                Toast.makeText(activity, activity.getString(R.string.module_disabled_no_selection, module.getAppName()), Toast.LENGTH_LONG).show();
                fragment.navigateUp();
            });
            builder.show();
        } else {
            fragment.navigateUp();
        }
    }

    public static class AppInfo {
        public PackageInfo packageInfo;
        public ApplicationWithEquals application;
        public ApplicationInfo applicationInfo;
        public String packageName;
        public CharSequence label = null;
    }

    public static class ApplicationWithEquals extends Application {
        public ApplicationWithEquals(String packageName, int userId) {
            this.packageName = packageName;
            this.userId = userId;
        }

        public ApplicationWithEquals(Application application) {
            packageName = application.packageName;
            userId = application.userId;
        }

        @Override
        public boolean equals(@Nullable Object obj) {
            if (!(obj instanceof Application)) {
                return false;
            }
            return packageName.equals(((Application) obj).packageName) && userId == ((Application) obj).userId;
        }

        @Override
        public int hashCode() {
            return Objects.hash(packageName, userId);
        }
    }
}
