import org.gradle.api.Task
import org.gradle.api.file.RelativePath
import org.gradle.api.tasks.GradleBuild
import org.gradle.api.tasks.TaskProvider
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
import org.gradle.testing.jacoco.tasks.JacocoReport
import java.io.FileInputStream
import java.util.Properties

         
                                           
                                            
                                        
                                 
                              
                              
          
 

jacoco {
    toolVersion = "0.8.11"
}

val keystorePropertiesFile = rootProject.file("app/keystore.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}

val localPropertiesFile = rootProject.file("local.properties")
val localProperties = Properties()
if (localPropertiesFile.exists()) {
    localProperties.load(FileInputStream(localPropertiesFile))
}

val isCoverageEnabled =
    project.hasProperty("coverage") ||
        gradle.startParameter.taskNames.any {
            it.contains("verifyWithCoverage", ignoreCase = true) ||
                it.contains("generateGmdCoverage", ignoreCase = true)
        }

val isScreenshotBuild = project.hasProperty("screenshotBuild")
val isBillingSandboxBuild = project.hasProperty("billingSandboxBuild")
val testVariant =
    when {
        isBillingSandboxBuild -> "staging"
        isScreenshotBuild -> "release"
        else -> "debug"
    }
val testVariantCap = testVariant.replaceFirstChar { it.uppercase() }

                                                      
val screenshotTestPackage = "com.yorvana.screenshots"
val screenshotDeviceOutputDir = "/sdcard/test-outputs/screenshots"
val flakesDeviceOutputDir = "/data/local/tmp/flakes"
val smokeAnnotation = "com.yorvana.testsupport.tiers.Smoke"
val regressionAnnotation = "com.yorvana.testsupport.tiers.Regression"
val regressionFullAnnotation = "com.yorvana.testsupport.tiers.RegressionFull"
val quarantinedAnnotation = "com.yorvana.testsupport.tiers.Quarantined"
val regressionDeviceTaskName = "pixel2api33DebugAndroidTest"
val macrobenchmarkTaskPath = ":macrobenchmark:pixel2api33BenchmarkAndroidTest"

android {
                                                                                            
    namespace = "com.yorvana"
    compileSdk = 37

    defaultConfig {
        applicationId = "com.yorvana"
        minSdk = 26
        targetSdk = 36
        versionCode = 5
        versionName = "1.0.0-rc.1"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }
    }

    testBuildType = testVariant

    signingConfigs {
        create("release") {
            storeFile = rootProject.file("app/${keystoreProperties["storeFile"] ?: System.getenv("KEYSTORE_FILE") ?: ""}")
            storePassword = (keystoreProperties["storePassword"] ?: System.getenv("KEYSTORE_PASSWORD") ?: "").toString()
            keyAlias = (keystoreProperties["keyAlias"] ?: System.getenv("KEY_ALIAS") ?: "").toString()
            keyPassword = (keystoreProperties["keyPassword"] ?: System.getenv("KEY_PASSWORD") ?: "").toString()
        }
    }

    buildTypes {
        debug {
            enableAndroidTestCoverage = isCoverageEnabled
            val dsnDebug =
                (localProperties.getProperty("sentry.dsn.debug") ?: "")
                    .replace("\\", "\\\\")
                    .replace("\"", "\\\"")
            buildConfigField("String", "SENTRY_DSN", "\"$dsnDebug\"")
        }
        release {
                                                                                 
                                                                                     
                                                                                       
                                                                                    
                                              
            if (keystorePropertiesFile.exists() ||
                !System.getenv("KEYSTORE_FILE").isNullOrEmpty()
            ) {
                signingConfig = signingConfigs.getByName("release")
            }
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro",
            )
            ndk {
                debugSymbolLevel = "SYMBOL_TABLE"
            }
            val dsnRelease =
                (localProperties.getProperty("sentry.dsn.release") ?: "")
                    .replace("\\", "\\\\")
                    .replace("\"", "\\\"")
            buildConfigField("String", "SENTRY_DSN", "\"$dsnRelease\"")
        }
        create("staging") {
            initWith(getByName("release"))
            matchingFallbacks += listOf("release")
            isMinifyEnabled = false
            if (keystorePropertiesFile.exists() ||
                !System.getenv("KEYSTORE_FILE").isNullOrEmpty()
            ) {
                signingConfig = signingConfigs.getByName("release")
            }
            val dsnRelease =
                (localProperties.getProperty("sentry.dsn.release") ?: "")
                    .replace("\\", "\\\\")
                    .replace("\"", "\\\"")
            buildConfigField("String", "SENTRY_DSN", "\"$dsnRelease\"")
        }
        create("benchmark") {
            initWith(getByName("release"))
            matchingFallbacks += listOf("release")
            signingConfig = signingConfigs.getByName("debug")
            isDebuggable = false
                                                                                                               
            isMinifyEnabled = true
        }
    }

    buildFeatures {
        compose = true
        buildConfig = true
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
            excludes += "META-INF/LICENSE.md"
            excludes += "META-INF/LICENSE-notice.md"
        }
    }

    testOptions {
        unitTests {
            isIncludeAndroidResources = true
        }
        managedDevices {
            allDevices {
                create<com.android.build.api.dsl.ManagedVirtualDevice>("pixel2api33") {
                    device = "Pixel 2"
                    apiLevel = 33
                    systemImageSource = "aosp"
                    testedAbi = "x86_64"
                }
                create<com.android.build.api.dsl.ManagedVirtualDevice>("phonePixel6api34") {
                    device = "Pixel 6"
                    apiLevel = 34
                    systemImageSource = "aosp"
                    testedAbi = "x86_64"
                }
                create<com.android.build.api.dsl.ManagedVirtualDevice>("tablet7Nexus7api34") {
                    device = "Nexus 7"
                    apiLevel = 34
                    systemImageSource = "aosp"
                    testedAbi = "x86_64"
                }
                create<com.android.build.api.dsl.ManagedVirtualDevice>("tablet10PixelTabletApi34") {
                    device = "Pixel Tablet"
                    apiLevel = 34
                    systemImageSource = "aosp"
                    testedAbi = "x86_64"
                }
            }
        }
    }

    lint {
        abortOnError = true
        warningsAsErrors = false
        xmlReport = true
        htmlReport = true
        baseline = file("lint-baseline.xml")
    }
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
    compilerOptions {
        jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
    }
}

detekt {
    config.setFrom(rootProject.file("config/detekt/detekt.yml"))
    buildUponDefaultConfig = true
    autoCorrect = true
}

tasks.named("check") {
    dependsOn("lintDebug")
}

afterEvaluate {
    val requestedTaskNames = gradle.startParameter.taskNames.map { it.substringAfterLast(":") }
    val screenshotDeviceNames = setOf("phonePixel6api34", "tablet7Nexus7api34", "tablet10PixelTabletApi34")
    val isStoreScreenshotRun =
        requestedTaskNames.any { requestedTaskName ->
            requestedTaskName == "generateStoreScreenshots" || requestedTaskName.endsWith("StoreScreenshots")
        }
    val isSmokeCoverageRun = requestedTaskNames.any { it == "generateGmdCoverage" }

                                                                                                
                                                                                                       
                                                                                                   
                                                                                                         
    tasks.withType<com.android.build.gradle.internal.tasks.ManagedDeviceInstrumentationTestTask>().configureEach {
        fun isTaskForDevice(
            taskName: String,
            deviceName: String,
        ): Boolean = taskName.equals("$deviceName${testVariantCap}AndroidTest", ignoreCase = true)

        fun isRequestedForDevice(deviceName: String): Boolean =
            requestedTaskNames.any { requestedTaskName ->
                requestedTaskName.equals("$deviceName${testVariantCap}AndroidTest", ignoreCase = true) ||
                    requestedTaskName.equals("${deviceName}Check", ignoreCase = true)
            } ||
                (deviceName == "pixel2api33" && isSmokeCoverageRun) ||
                (deviceName in screenshotDeviceNames && isStoreScreenshotRun)

        val deviceDirName =
            when {
                isTaskForDevice(name, "phonePixel6api34") -> "phonePixel6api34"
                isTaskForDevice(name, "tablet7Nexus7api34") -> "tablet7Nexus7api34"
                isTaskForDevice(name, "tablet10PixelTabletApi34") -> "tablet10PixelTabletApi34"
                isTaskForDevice(name, "pixel2api33") -> "pixel2api33"
                else -> null
            }
        if (deviceDirName != null) {
                                                                          
            getAdditionalTestOutputDir().set(
                layout.buildDirectory.dir("outputs/managed_device_android_test_additional_output/$testVariant/$deviceDirName"),
            )
        }

                                                                                                       
                                                                                                                     
        val getTestDataMethod = javaClass.getMethod("getTestData")
        val testDataProperty = getTestDataMethod.invoke(this) as org.gradle.api.provider.Property<*>
        val testData = testDataProperty.get()
        val getArgsMethod = testData.javaClass.getMethod("getInstrumentationRunnerArguments")

        @Suppress("UNCHECKED_CAST")
        val argsProperty = getArgsMethod.invoke(testData) as org.gradle.api.provider.MapProperty<String, String>

        val myTask = this
        val taskName = name
                                                                                                 
                                                                                            
        val isExplicitAnnotationRun = providers.gradleProperty("android.testInstrumentationRunnerArguments.annotation").isPresent
        argsProperty.putAll(
            provider {
                val map = mutableMapOf<String, String>()
                                                                                            
                                                                                              
                                                                                             
                                                                                           
                if (!isRequestedForDevice(deviceDirName.orEmpty())) {
                    return@provider map
                }
                when {
                    isTaskForDevice(taskName, "phonePixel6api34") -> {
                        map["package"] = screenshotTestPackage
                        map["additionalTestOutputDir"] = screenshotDeviceOutputDir
                    }
                    isTaskForDevice(taskName, "tablet7Nexus7api34") -> {
                        map["package"] = screenshotTestPackage
                        map["additionalTestOutputDir"] = screenshotDeviceOutputDir
                    }
                    isTaskForDevice(taskName, "tablet10PixelTabletApi34") -> {
                        map["package"] = screenshotTestPackage
                        map["additionalTestOutputDir"] = screenshotDeviceOutputDir
                    }
                    isTaskForDevice(taskName, "pixel2api33") -> {
                                                                                               
                                                                             
                        if (!isExplicitAnnotationRun) {
                            map["annotation"] = smokeAnnotation
                        }
                        map["notPackage"] = screenshotTestPackage
                        map["additionalTestOutputDir"] = flakesDeviceOutputDir
                    }
                }
                map
            },
        )
    }
}

fun registerRegressionDeviceTask(
    taskName: String,
    descriptionText: String,
    runnerArguments: Map<String, String>,
): TaskProvider<GradleBuild> =
    tasks.register<GradleBuild>(taskName) {
        group = "Verification"
        description = descriptionText
        dir = rootProject.projectDir
        buildName = taskName
        tasks = listOf(":app:$regressionDeviceTaskName")
                                                                                          
                                                                                               
        startParameter.projectProperties.putAll(
            gradle.startParameter.projectProperties +
                runnerArguments.mapKeys { (key, _) -> "android.testInstrumentationRunnerArguments.$key" },
        )
    }

val regressionCheck =
    registerRegressionDeviceTask(
        taskName = "regressionCheck",
        descriptionText = "Runs core regression androidTests on the Pixel 2 API 33 managed device.",
        runnerArguments =
            mapOf(
                "annotation" to regressionAnnotation,
                "notAnnotation" to quarantinedAnnotation,
            ),
    )

val regressionFullCoreCheck =
    registerRegressionDeviceTask(
        taskName = "regressionFullCoreCheck",
        descriptionText = "Runs all core regression androidTests, including quarantined tests.",
        runnerArguments = mapOf("annotation" to regressionAnnotation),
    )

val regressionFullExtendedCheck =
    registerRegressionDeviceTask(
        taskName = "regressionFullExtendedCheck",
        descriptionText = "Runs full regression androidTests on the Pixel 2 API 33 managed device.",
        runnerArguments = mapOf("annotation" to regressionFullAnnotation),
    )

                                                                      
                                                                                     
val preserveRegressionCoreResults =
    tasks.register<Copy>("preserveRegressionCoreResults") {
        group = "Verification"
        description = "Copies @Regression XML results to a stable directory so they survive the subsequent regressionFullExtendedCheck run."
        from(
            layout.buildDirectory.dir(
                "outputs/androidTest-results/managedDevice",
            ),
        )
        into(
            layout.buildDirectory.dir(
                "outputs/androidTest-results/regression-core-preserved",
            ),
        )
        duplicatesStrategy = DuplicatesStrategy.INCLUDE
        doFirst {
            project.delete(destinationDir)
        }
    }

regressionFullCoreCheck.configure { finalizedBy(preserveRegressionCoreResults) }
regressionFullExtendedCheck.configure { mustRunAfter(preserveRegressionCoreResults) }

tasks.register("regressionFullCheck") {
    group = "Verification"
    description = "Runs full regression checks, including the macrobenchmark sibling task."
    dependsOn(regressionFullCoreCheck, regressionFullExtendedCheck, macrobenchmarkTaskPath)
}

tasks.register<Copy>("generateGmdCoverage") {
    group = "Verification"
    description = "Runs GMD tests and copies the coverage file to the baseline directory."

                                                                              
                                                           
    val smokeTask = "pixel2api33DebugAndroidTest"
    dependsOn(smokeTask)

    from(layout.buildDirectory.dir("outputs/managed_device_code_coverage/debug/pixel2api33")) {
        include("coverage.ec")
    }
    into(layout.projectDirectory.dir("coverage-baselines"))
    rename { "gmd_smoke.ec" }

    includeEmptyDirs = false
    duplicatesStrategy = DuplicatesStrategy.FAIL

    doLast {
        val destFile = layout.projectDirectory.file("coverage-baselines/gmd_smoke.ec").asFile
        if (!destFile.exists()) {
            throw GradleException("Failed to generate GMD coverage. Expected file not found: $destFile")
        }
    }
}

val storeScreenshotFiles =
    listOf(
        "screen_01_vehicle_list.png",
        "screen_02_vehicle_detail.png",
        "screen_03_add_record.png",
        "screen_04_categories.png",
        "screen_05_settings_privacy.png",
        "screen_06_paywall_dialog.png",
    )
val storeScreenshotsRoot = rootProject.layout.projectDirectory.dir("distribution/store-assets/screenshots")
val storeAdditionalOutputRoot = layout.buildDirectory.dir("outputs/managed_device_android_test_additional_output/$testVariant")

fun registerStoreScreenshotCopy(
    taskPrefix: String,
    deviceTaskName: String,
    outputDirectory: String,
    previousCopyTask: TaskProvider<out Task>? = null,
): TaskProvider<Copy> {
    tasks.named<Task>(deviceTaskName).configure {
        previousCopyTask?.let { mustRunAfter(it) }
                                                                                         
                                                                                           
                                                                                             
        doFirst {
            delete(storeAdditionalOutputRoot)
        }
    }

    return tasks.register<Copy>("copy${taskPrefix}StoreScreenshots") {
        previousCopyTask?.let { dependsOn(it) }
        dependsOn(deviceTaskName)
        into(storeScreenshotsRoot.dir(outputDirectory))
        includeEmptyDirs = false
        duplicatesStrategy = DuplicatesStrategy.FAIL

        doFirst {
            delete(storeScreenshotsRoot.dir(outputDirectory))
        }

        from(storeAdditionalOutputRoot) {
                                                                                
                                                                                            
                                                                                          
            include("**/$outputDirectory/screen_*.png")
            eachFile {
                relativePath = RelativePath(true, name)
            }
        }

        doLast {
            val missing =
                storeScreenshotFiles.mapNotNull { fileName ->
                    val file = storeScreenshotsRoot.file("$outputDirectory/$fileName").asFile
                    if (file.exists()) null else file
                }
            if (missing.isNotEmpty()) {
                throw GradleException(
                    "Missing generated store screenshots for $outputDirectory:\n" +
                        missing.joinToString(separator = "\n") { "- ${it.path}" },
                )
            }
        }
    }
}

afterEvaluate {
    val copyPhoneStoreScreenshots =
        registerStoreScreenshotCopy(
            taskPrefix = "Phone",
            deviceTaskName = "phonePixel6api34${testVariantCap}AndroidTest",
            outputDirectory = "phone",
        )
    val copyTablet7StoreScreenshots =
        registerStoreScreenshotCopy(
            taskPrefix = "Tablet7",
            deviceTaskName = "tablet7Nexus7api34${testVariantCap}AndroidTest",
            outputDirectory = "tablet-7",
            previousCopyTask = copyPhoneStoreScreenshots,
        )
    val copyTablet10StoreScreenshots =
        registerStoreScreenshotCopy(
            taskPrefix = "Tablet10",
            deviceTaskName = "tablet10PixelTabletApi34${testVariantCap}AndroidTest",
            outputDirectory = "tablet-10",
            previousCopyTask = copyTablet7StoreScreenshots,
        )

    tasks.register("generateStoreScreenshots") {
        group = "Verification"
        description = "Runs store screenshot tests on managed devices and copies PNGs into distribution assets."

        dependsOn(copyTablet10StoreScreenshots)
    }
}

tasks.register<JacocoReport>("verifyWithCoverage") {
    group = "Verification"
    description = "Runs JVM unit tests and generates a combined coverage report using JaCoCo with the static GMD baseline."

                                                                                    
    dependsOn("testDebugUnitTest")

    reports {
        xml.required.set(true)
        html.required.set(true)
    }

    val classFilter =
        listOf(
            "**/R.class",
            "**/R$*.class",
            "**/BuildConfig.*",
            "**/Manifest*.*",
            "**/*Test*.*",
            "android/**/*.*",
            "**/*_ViewBinding*.*",
            "**/*_MembersInjector*.*",
            "**/*_Factory*.*",
            "**/*_Provide*Factory*.*",
            "**/*ExtensionsKt*.*",
                                                                                          
                                                                                          
            "**/*ComposableSingletons*.*",
        )

    val debugTree =
        fileTree(layout.buildDirectory.dir("tmp/kotlin-classes/debug")) {
            exclude(classFilter)
        }
    val javaTree =
        fileTree(layout.buildDirectory.dir("intermediates/javac/debug/classes")) {
            exclude(classFilter)
        }

    classDirectories.setFrom(files(debugTree, javaTree))
    sourceDirectories.setFrom(
        files(
            "src/main/java",
            "src/main/kotlin",
            "src/debug/java",
            "src/debug/kotlin",
        ),
    )

                                                                             
                                                                                                             
    val executionDataFiles =
        files(
            fileTree(layout.buildDirectory.dir("outputs/unit_test_code_coverage")) { include("**/*.exec") },
            layout.projectDirectory.file("coverage-baselines/gmd_smoke.ec"),
            fileTree(layout.buildDirectory.dir("jacoco")) { include("**/*.exec") },
        )

    executionData.setFrom(executionDataFiles)

    doFirst {
        val lfsBaseline = layout.projectDirectory.file("coverage-baselines/gmd_smoke.ec").asFile
        if (!lfsBaseline.exists()) {
            throw GradleException(
                "Required coverage baseline is missing: ${lfsBaseline.path}. " +
                    "Run 'git lfs pull' to fetch tracked baselines or './gradlew generateGmdCoverage' to regenerate it.",
            )
        }
        if (lfsBaseline.length() < 1000) {
            val header = lfsBaseline.inputStream().use { it.readNBytes(100) }.toString(Charsets.UTF_8)
            if (header.startsWith("version https://git-lfs.github.com/spec/v1")) {
                throw GradleException("Coverage baseline is a Git LFS pointer. Please run 'git lfs pull' to fetch the actual file.")
            }
        }

        val existingFiles = executionDataFiles.files.filter { it.exists() }
        val dynamicExecutionFiles = existingFiles.filter { it.name != "gmd_smoke.ec" }
        if (dynamicExecutionFiles.isEmpty()) {
            throw GradleException(
                "No dynamic JaCoCo execution data files were found. " +
                    "Ensure tests were executed with coverage enabled (e.g., via -Pcoverage).\n" +
                    "Searched in:\n" +
                    "- ${layout.buildDirectory.dir("outputs/unit_test_code_coverage").get()}\n" +
                    "- ${layout.buildDirectory.dir("jacoco").get()}",
            )
        }
    }
}

val printBillingSandboxSkipReasons =
    tasks.register("printBillingSandboxSkipReasons") {
        group = "Verification"
        description = "Prints billing sandbox assumption skip reasons from connected staging androidTest results."
        onlyIf { isBillingSandboxBuild }

        doLast {
            fun unescapeTextProtoString(value: String): String =
                value
                    .replace("\\n", "\n")
                    .replace("\\\"", "\"")
                    .replace("\\\\", "\\")

            fun extractTextProtoStringFields(
                text: String,
                fieldName: String,
            ): Sequence<String> =
                sequence {
                    var cursor = 0
                    val fieldToken = "$fieldName:"
                    while (cursor < text.length) {
                        val fieldIndex = text.indexOf(fieldToken, cursor)
                        if (fieldIndex == -1) return@sequence
                        var index = fieldIndex + fieldToken.length
                        val chunks = mutableListOf<String>()
                        while (index < text.length) {
                            while (index < text.length && text[index].isWhitespace()) index++
                            if (index >= text.length || text[index] != '"') break
                            index++
                            val chunk = StringBuilder()
                            var escaped = false
                            var closed = false
                            while (index < text.length) {
                                val char = text[index++]
                                if (escaped) {
                                    chunk.append('\\').append(char)
                                    escaped = false
                                } else if (char == '\\') {
                                    escaped = true
                                } else if (char == '"') {
                                    closed = true
                                    break
                                } else {
                                    chunk.append(char)
                                }
                            }
                            if (!closed) {
                                logger.warn("Ignoring malformed unterminated $fieldName string in textproto results.")
                                chunks.clear()
                                break
                            }
                            chunks += chunk.toString()
                        }
                        if (chunks.isNotEmpty()) {
                            yield(unescapeTextProtoString(chunks.joinToString("")))
                        }
                        cursor = index.coerceAtLeast(fieldIndex + fieldToken.length)
                    }
                }

            val resultsDir =
                layout
                    .buildDirectory
                    .dir("outputs/androidTest-results/connected/staging")
                    .get()
                    .asFile
            if (!resultsDir.exists()) {
                logger.lifecycle("Billing sandbox skip reasons: no connected staging results found at ${resultsDir.path}")
                return@doLast
            }

            val reasons =
                resultsDir
                    .walkTopDown()
                    .filter { it.isFile && it.name == "test-result.textproto" }
                    .flatMap { resultFile ->
                        extractTextProtoStringFields(resultFile.readText(), "error_message")
                            .filter { it.contains("AssumptionViolatedException") }
                            .map { message ->
                                message
                                    .lineSequence()
                                    .firstOrNull { it.contains("AssumptionViolatedException") }
                                    .orEmpty()
                                    .substringAfter("AssumptionViolatedException: ")
                                    .trim()
                            }
                    }.filter { it.isNotBlank() }
                    .distinct()
                    .toList()

            if (reasons.isEmpty()) {
                logger.lifecycle("Billing sandbox skip reasons: none found in connected staging results.")
            } else {
                logger.lifecycle("")
                logger.lifecycle("Billing sandbox skipped preconditions:")
                reasons.forEach { reason -> logger.lifecycle("- $reason") }
                logger.lifecycle("")
            }
        }
    }

tasks.matching { it.name == "connectedStagingAndroidTest" }.configureEach {
    finalizedBy(printBillingSandboxSkipReasons)
}

tasks.withType<Test>().configureEach {
    if (isCoverageEnabled) {
        configure<JacocoTaskExtension> {
            isEnabled = true
                                       
            isIncludeNoLocationClasses = true
            excludes = listOf("jdk.internal.*")
        }
    } else {
        configure<JacocoTaskExtension> { isEnabled = false }
    }

    val defaultForks = (Runtime.getRuntime().availableProcessors() / 4).coerceAtLeast(2)
    maxParallelForks = (project.findProperty("testForks") as String?)?.toInt() ?: defaultForks
    forkEvery = 0L

    minHeapSize = "512m"
    maxHeapSize = "1536m"
    jvmArgs =
        listOf(
            "-XX:+UseG1GC",
            "-XX:MaxMetaspaceSize=512m",
            "-Dfile.encoding=UTF-8",
        )

    systemProperty("robolectric.graphicsMode", "NATIVE")
                                                                                   
                                                                
                                                                                      
                                                                                    
                             
    if (name.contains("Release")) {
        enabled = false
    }
    useJUnit {
        excludeCategories("com.yorvana.LocalOnly")
    }

    reports {
        html.required.set(System.getenv("CI") != null)
        junitXml.required.set(true)
    }
}

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(libs.androidx.documentfile)
    implementation(libs.sentry.android)
    implementation(platform(libs.compose.bom))
    implementation(libs.compose.ui)
    implementation(libs.compose.ui.tooling.preview)
    implementation(libs.compose.material3)
    implementation(libs.compose.material.icons.extended)
    implementation(libs.navigation3.runtime)
    implementation(libs.navigation3.ui)
    implementation(libs.lifecycle.viewmodel.compose)
    implementation(libs.lifecycle.viewmodel.savedstate)
    implementation(libs.lifecycle.runtime.compose)
    implementation(libs.datastore.preferences)
    implementation(libs.kotlinx.serialization.json)
    implementation(libs.kotlinx.coroutines.android)
    implementation(libs.coil.compose)
    implementation(libs.google.billing)

    debugImplementation(libs.compose.ui.tooling)
    debugImplementation(libs.compose.ui.test.manifest)

    testImplementation(libs.junit)
    testImplementation(libs.kotlinx.coroutines.test)
    testImplementation(libs.mockk)
    testImplementation(libs.truth)
    testImplementation(libs.turbine)
    testImplementation(libs.robolectric)
    testImplementation(libs.compose.ui.test)
    testImplementation(libs.androidx.test.ext)
    testImplementation(libs.roborazzi)
    testImplementation(libs.roborazzi.compose)

    androidTestImplementation(platform(libs.compose.bom))
    androidTestImplementation(libs.androidx.test.ext)
    androidTestImplementation(libs.compose.ui.test)
    androidTestImplementation(libs.mockk.android)
    androidTestImplementation(libs.truth)
    androidTestImplementation(libs.turbine)
    androidTestImplementation(libs.roborazzi)
    androidTestImplementation(libs.roborazzi.compose)
    androidTestImplementation(libs.uiautomator)
    androidTestImplementation(libs.espresso.intents)
}

val testPythonAggregator =
    tasks.register<Exec>("testPythonAggregator") {
        group = "Verification"
        description = "Runs the Python regression aggregator script unit tests."
        commandLine(
            "sh",
            "-c",
            "if command -v python3 >/dev/null 2>&1; then " +
                "python3 -m unittest $rootDir/tools/test_aggregate_regression.py; " +
                "else " +
                "echo \"WARNING: python3 not found. Skipping Python aggregator unit tests.\"; " +
                "fi",
        )
        workingDir(rootDir)
    }

val aggregateRegression =
    tasks.register<Exec>("aggregateRegression") {
        group = "Verification"
        description = "Runs the Python regression aggregator script to update history and dashboard."
        dependsOn(testPythonAggregator)
        commandLine("python3", "$rootDir/tools/aggregate_regression.py")
        workingDir(rootDir)
    }

                                                                                              
tasks.matching { it.name == "check" || it.name == "testDebugUnitTest" }.configureEach {
    dependsOn(testPythonAggregator)
}