apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
apply plugin: "org.jlleitschuh.gradle.ktlint"
apply plugin: "io.gitlab.arturbosch.detekt"

def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()

/**
 * This is the configuration block to customize your React Native Android app.
 * By default you don't need to apply any configuration, just uncomment the lines you need.
 */
react {
    entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
    reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
    hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
    codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()

    // Use Expo CLI to bundle the app, this ensures the Metro config
    // works correctly with Expo projects.
    cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
    bundleCommand = "export:embed"

    /* Folders */
     //   The root of your project, i.e. where "package.json" lives. Default is '../..'
    // root = file("../../")
    //   The folder where the react-native NPM package is. Default is ../../node_modules/react-native
    // reactNativeDir = file("../../node_modules/react-native")
    //   The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
    // codegenDir = file("../../node_modules/@react-native/codegen")

    /* Variants */
    //   The list of variants to that are debuggable. For those we're going to
    //   skip the bundling of the JS bundle and the assets. By default is just 'debug'.
    //   If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
    // debuggableVariants = ["liteDebug", "prodDebug"]

    /* Bundling */
    //   A list containing the node command and its flags. Default is just 'node'.
    // nodeExecutableAndArgs = ["node"]

    //
    //   The path to the CLI configuration file. Default is empty.
    // bundleConfig = file(../rn-cli.config.js)
    //
    //   The name of the generated asset file containing your JS bundle
    // bundleAssetName = "MyApplication.android.bundle"
    //
    //   The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
    // entryFile = file("../js/MyApplication.android.js")
    //
    //   A list of extra flags to pass to the 'bundle' commands.
    //   See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
    // extraPackagerArgs = []

    /* Hermes Commands */
    //   The hermes compiler command to run. By default it is 'hermesc'
    // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
    //
    //   The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
    // hermesFlags = ["-O", "-output-source-map"]

    /* Autolinking */
    autolinkLibrariesWithApp()
}

/**
 * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
 */
def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean()

/**
 * The preferred build flavor of JavaScriptCore (JSC)
 *
 * For example, to use the international variant, you can use:
 * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`
 *
 * The international variant includes ICU i18n library and necessary data
 * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
 * give correct results when using with locales other than en-US. Note that
 * this variant is about 6MiB larger per architecture than default.
 */
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'

android {
    ndkVersion rootProject.ext.ndkVersion

    buildToolsVersion rootProject.ext.buildToolsVersion
    compileSdk rootProject.ext.compileSdkVersion

    namespace 'net.aliasvault.app'
    defaultConfig {
        applicationId 'net.aliasvault.app'
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 2700100
        versionName "0.27.0-alpha"

        // Instrumented test configuration
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    signingConfigs {
        debug {
            storeFile file('debug.keystore')
            storePassword 'android'
            keyAlias 'androiddebugkey'
            keyPassword 'android'
        }
        release {
            if (project.hasProperty('ALIASVAULT_UPLOAD_STORE_FILE')) {
                storeFile file(ALIASVAULT_UPLOAD_STORE_FILE)
                storePassword ALIASVAULT_UPLOAD_STORE_PASSWORD
                keyAlias ALIASVAULT_UPLOAD_KEY_ALIAS
                keyPassword ALIASVAULT_UPLOAD_KEY_PASSWORD
            }
        }
    }
    buildTypes {
        debug {
            signingConfig signingConfigs.debug
        }
        release {
            // Caution! In production, you need to generate your own keystore file.
            // see https://reactnative.dev/docs/signed-apk-android.
            signingConfig signingConfigs.debug
            signingConfig signingConfigs.release
            shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
            crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true)
        }
    }
    packagingOptions {
        jniLibs {
            useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false)
        }
    }
    androidResources {
        ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~'
    }
    lintOptions {
        checkReleaseBuilds true
        abortOnError true
        xmlReport true
        htmlReport true
        lintConfig file("lint.xml")
        // Focus only on app/src and exclude expo modules
        disable 'InvalidPackage'
    }
    testOptions {
        unitTests.all {
            useJUnitPlatform()
            testLogging {
                events "passed", "failed", "skipped"
                exceptionFormat "full"
                showStandardStreams true
            }
        }
    }
}

// Apply static values from `gradle.properties` to the `android.packagingOptions`
// Accepts values in comma delimited lists, example:
// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini
["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop ->
    // Split option: 'foo,bar' -> ['foo', 'bar']
    def options = (findProperty("android.packagingOptions.$prop") ?: "").split(",");
    // Trim all elements in place.
    for (i in 0..<options.size()) options[i] = options[i].trim();
    // `[] - ""` is essentially `[""].filter(Boolean)` removing all empty strings.
    options -= ""

    if (options.length > 0) {
        println "android.packagingOptions.$prop += $options ($options.length)"
        // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**'
        options.each {
            android.packagingOptions[prop] += it
        }
    }
}

dependencies {
    // The version of react-native is set by the React Native Gradle Plugin
    implementation("com.facebook.react:react-android")

    // Add biometric dependency for credential management
    implementation("androidx.biometric:biometric:1.1.0")

    // Add Credential Manager dependencies for passkey support (Android 14+)
    implementation("androidx.credentials:credentials:1.6.0-beta02")

    // Add Material Design components for UI
    implementation("com.google.android.material:material:1.11.0")

    // Add Fragment and ViewModel dependencies for fragment-based UI
    implementation("androidx.fragment:fragment-ktx:1.6.2")
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")

    // Add vector drawable support for SVG
    implementation("com.caverock:androidsvg-aar:1.4")

    // Add Argon2 library for password key derivation
    implementation("com.lambdapioneer.argon2kt:argon2kt:1.6.0")

    // Add modern SQLite library with VACUUM INTO and backup API support
    implementation("com.github.requery:sqlite-android:3.49.0")

    // Add ZXing library for QR code scanning (F-Droid compatible)
    implementation("com.journeyapps:zxing-android-embedded:4.3.0")

    // Add JNA library for UniFFI native bindings (Android-specific AAR with native libs)
    // Version 5.17.0+ required for Android 15 16KB page size support
    // See: https://github.com/java-native-access/jna/issues/1647
    implementation("net.java.dev.jna:jna:5.17.0") {
        artifact {
            type = "aar"
        }
    }

    // Test dependencies (unit tests)
    testImplementation 'junit:junit:4.13.2'
    testImplementation 'org.mockito:mockito-core:4.0.0'
    testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0'
    testImplementation 'androidx.test:core:1.5.0'
    testImplementation 'androidx.test:runner:1.5.2'
    testImplementation 'androidx.test:rules:1.5.0'
    testImplementation 'androidx.test.ext:junit:1.1.5'
    testImplementation 'org.robolectric:robolectric:4.9'
    testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
    testImplementation 'org.junit.vintage:junit-vintage-engine:5.9.2'

    // Instrumented test dependencies (androidTest)
    androidTestImplementation 'androidx.test:core:1.5.0'
    androidTestImplementation 'androidx.test:runner:1.5.2'
    androidTestImplementation 'androidx.test:rules:1.5.0'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
    androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'

    def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
    def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
    def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";

    if (isGifEnabled) {
        // For animated gif support
        implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}")
    }

    if (isWebpEnabled) {
        // For webp support
        implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}")
        if (isWebpAnimatedEnabled) {
            // Animated webp support
            implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}")
        }
    }

    if (hermesEnabled.toBoolean()) {
        implementation("com.facebook.react:hermes-android")
    } else {
        implementation jscFlavor
    }
    implementation "org.jetbrains.kotlin:kotlin-test:1.9.25"
}

ktlint {
    android = true
    verbose = true
    outputToConsole = true
    ignoreFailures = false
    enableExperimentalRules = true
    filter {
        // Include all Kotlin and Java files first
        include("**/kotlin/**")
        include("**/java/**")
        // Then exclude generated files
        exclude("**/generated/**")
        exclude("**/rustcore/uniffi/**")  // Exclude UniFFI-generated Rust bindings
        exclude("**/uniffi/**")  // Exclude any uniffi directory
    }

    // Configure max line length
    kotlinScriptAdditionalPaths {
        include(fileTree("scripts/"))
    }

    // Set max line length to 160
    version.set("0.50.0")
}

task lintFormat {
    description = "Run all auto-fixers (ktlint and Android lint)"
    group = "formatting"
    dependsOn 'ktlintFormat'
    dependsOn 'detekt'
}

task lintCheck {
    description = "Run all linting checks (ktlint, Detekt and Android lint)"
    group = "formatting"
    dependsOn 'ktlintCheck'
    dependsOn 'detekt'
}

// Add a task dependency to make ktlint run before build
tasks.named("preBuild") {
    dependsOn("lintFormat")
    dependsOn("lintCheck")
}

// Ensure codegen completes before ktlint tasks run
afterEvaluate {
    tasks.withType(org.jlleitschuh.gradle.ktlint.tasks.KtLintCheckTask).configureEach {
        dependsOn("generateCodegenArtifactsFromSchema")
        // Exclude UniFFI-generated Rust bindings
        exclude("**/rustcore/uniffi/**/*.kt")
        exclude("**/uniffi/**/*.kt")
    }
    tasks.withType(org.jlleitschuh.gradle.ktlint.tasks.KtLintFormatTask).configureEach {
        dependsOn("generateCodegenArtifactsFromSchema")
        // Exclude UniFFI-generated Rust bindings
        exclude("**/rustcore/uniffi/**/*.kt")
        exclude("**/uniffi/**/*.kt")
    }
}

detekt {
    buildUponDefaultConfig = true
    config = files("$rootProject.projectDir/detekt.yml")
    baseline = file("$rootProject.projectDir/baseline.xml")

    source.setFrom(
        fileTree("src/main/java") {
            exclude "**/uniffi/**"
        }
    )
    reports {
        html.required.set(true)
        xml.required.set(true)
        txt.required.set(false)
        sarif.required.set(false)
    }
}

// Rust Core build task - ensures native libraries and Kotlin bindings are up to date
// Uses checksum-based incremental builds to skip if sources haven't changed
task buildRustCore(type: Exec) {
    description = "Build Rust Core native libraries and Kotlin bindings"
    group = "build"

    workingDir = file("${rootProject.projectDir}/scripts")

    // Set up environment for cargo
    environment "PATH", "${System.getProperty('user.home')}/.cargo/bin:/usr/local/bin:/opt/homebrew/bin:${System.getenv('PATH')}"

    // Determine build mode based on variant
    def isRelease = gradle.startParameter.taskNames.any { it.toLowerCase().contains('release') }
    def buildArgs = isRelease ? ['./build-rust-core.sh', '--release'] : ['./build-rust-core.sh', '--debug']

    commandLine buildArgs

    // Only log if there are actual changes
    ignoreExitValue = false
}

// Make Rust Core build run before Kotlin compilation
tasks.matching { it.name.startsWith('compile') && it.name.contains('Kotlin') }.configureEach {
    dependsOn buildRustCore
}
