/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3.util

import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.ApplicationInfo.FLAG_EXTERNAL_STORAGE
import android.content.pm.ApplicationInfo.FLAG_INSTALLED
import android.content.pm.ApplicationInfo.FLAG_SUSPENDED
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.os.UserHandle
import com.android.launcher3.Flags.enableSupportForArchiving
import com.android.launcher3.Utilities.ATLEAST_V
import kotlin.LazyThreadSafetyMode.NONE

/**
 * A set of utility methods around ApplicationInfo with support for fetching the actual info lazily
 */
class ApplicationInfoWrapper private constructor(provider: () -> ApplicationInfo?) {

    constructor(appInfo: ApplicationInfo?) : this({ appInfo })

    constructor(
        ctx: Context,
        pkg: String,
        user: UserHandle,
    ) : this({
        try {
            ctx.getSystemService(LauncherApps::class.java)
                ?.getApplicationInfo(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES, user)
                ?.let { ai ->
                    // its enabled and (either installed or archived)
                    if (
                        ai.enabled &&
                            (ai.flags.and(FLAG_INSTALLED) != 0 ||
                                (ATLEAST_V && enableSupportForArchiving() && ai.isArchived))
                    ) {
                        ai
                    } else {
                        null
                    }
                }
        } catch (e: NameNotFoundException) {
            null
        }
    })

    constructor(
        ctx: Context,
        intent: Intent,
    ) : this(
        provider@{
            try {
                val pm = ctx.packageManager
                val packageName: String =
                    intent.component?.packageName
                        ?: intent.getPackage()
                        ?: return@provider pm.resolveActivity(
                                intent,
                                PackageManager.MATCH_DEFAULT_ONLY,
                            )
                            ?.activityInfo
                            ?.applicationInfo
                pm.getApplicationInfo(packageName, 0)
            } catch (e: NameNotFoundException) {
                null
            }
        }
    )

    private val appInfo: ApplicationInfo? by lazy(NONE, provider)

    private fun hasFlag(flag: Int) = appInfo?.let { it.flags.and(flag) != 0 } ?: false

    /**
     * Returns true if the app can possibly be on the SDCard. This is just a workaround and doesn't
     * guarantee that the app is on SD card.
     */
    fun isOnSdCard() = hasFlag(FLAG_EXTERNAL_STORAGE)

    /** Returns whether the target app is installed for a given user */
    fun isInstalled() = hasFlag(FLAG_INSTALLED)

    /**
     * Returns whether the target app is suspended for a given user as per
     * [android.app.admin.DevicePolicyManager.isPackageSuspended].
     */
    fun isSuspended() = hasFlag(FLAG_INSTALLED) && hasFlag(FLAG_SUSPENDED)

    /** Returns whether the target app is archived for a given user */
    fun isArchived() = ATLEAST_V && enableSupportForArchiving() && appInfo?.isArchived ?: false

    /** Returns whether the target app is a system app */
    fun isSystem() = hasFlag(FLAG_SYSTEM)

    fun getInfo(): ApplicationInfo? = appInfo
}
