/*
 * Copyright (c) 2026 Meshtastic LLC
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import dev.detekt.gradle.Detekt
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.meshtastic.buildlogic.configureGraphTasks
import org.meshtastic.buildlogic.resolveVersionInfo

plugins {
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.compose.compiler)
    alias(libs.plugins.compose.multiplatform)
    alias(libs.plugins.meshtastic.detekt)
    alias(libs.plugins.meshtastic.spotless)
    alias(libs.plugins.meshtastic.koin)
    id("meshtastic.kover")
    id("meshtastic.aboutlibraries")
}

configureGraphTasks()

// ── Version resolution (shared with app/build.gradle.kts via build-logic) ────
val versionInfo = resolveVersionInfo()
val resolvedIsDebug: Boolean = providers.gradleProperty("desktop.release").map { !it.toBoolean() }.getOrElse(true)

// ── Generate DesktopBuildConfig ──────────────────────────────────────────────
// Mirrors AGP's BuildConfig for Android so the desktop runtime has access to the
// same version metadata without hardcoding.
// Uses an abstract task with typed properties so the configuration cache can
// serialise it without capturing build-script object references.
@CacheableTask
abstract class GenerateBuildConfigTask : DefaultTask() {
    @get:Input abstract val content: Property<String>

    @get:OutputDirectory abstract val outputDir: DirectoryProperty

    @TaskAction
    fun generate() {
        val dir = outputDir.get().asFile
        dir.mkdirs()
        dir.resolve("DesktopBuildConfig.kt").writeText(content.get())
    }
}

val buildConfigOutputDir = layout.buildDirectory.dir("generated/buildconfig")

val generateBuildConfig =
    tasks.register<GenerateBuildConfigTask>("generateDesktopBuildConfig") {
        content.set(
            """
            |package org.meshtastic.desktop
            |
            |/**
            | * Auto-generated build configuration for Meshtastic Desktop.
            | * Do not edit — values are derived from config.properties and git at build time.
            | */
            |object DesktopBuildConfig {
            |    const val VERSION_CODE: Int = ${versionInfo.versionCode}
            |    const val VERSION_NAME: String = "${versionInfo.versionName}"
            |    const val IS_DEBUG: Boolean = $resolvedIsDebug
            |    const val APPLICATION_ID: String = "org.meshtastic.desktop"
            |    const val MIN_FW_VERSION: String = "${versionInfo.minFwVersion}"
            |    const val ABS_MIN_FW_VERSION: String = "${versionInfo.absMinFwVersion}"
            |}
            """
                .trimMargin(),
        )
        outputDir.set(buildConfigOutputDir.map { it.dir("org/meshtastic/desktop") })
    }

sourceSets.main { kotlin.srcDir(generateBuildConfig.map { buildConfigOutputDir }) }

// ── ProGuard configuration ───────────────────────────────────────────────────
// compose-jb's standalone ProGuard 7.7 task does NOT auto-include
// `META-INF/proguard/*.pro` consumer rules from dependency jars (only R8 on
// Android does). We therefore inline every keep rule we need into the two
// static .pro files referenced below.

kotlin {
    jvmToolchain {
        languageVersion.set(JavaLanguageVersion.of(21))
        vendor.set(JvmVendorSpec.JETBRAINS)
    }
    compilerOptions {
        jvmTarget.set(JvmTarget.JVM_21)
        freeCompilerArgs.add("-jvm-default=no-compatibility")
    }
}

// Exclude generated Compose resource files from detekt analysis
tasks.withType<Detekt>().configureEach { exclude("**/generated/**") }

compose.desktop {
    application {
        mainClass = "org.meshtastic.desktop.MainKt"

        val desktopJvmArgs =
            listOf(
                "-Xmx2G",
                "-Dapple.awt.application.name=Meshtastic Desktop",
                "-Dcom.apple.mrj.application.apple.menu.about.name=Meshtastic Desktop",
                "-Dcom.apple.bundle.identifier=org.meshtastic.desktop",
            )
        jvmArgs(*desktopJvmArgs.toTypedArray())

        buildTypes.release.proguard {
            isEnabled.set(true)
            obfuscate.set(false) // Open-source project — obfuscation adds no value
            optimize.set(true)
            configurationFiles.from(
                rootProject.file("config/proguard/shared-rules.pro"),
                project.file("proguard-rules.pro"),
            )
        }

        nativeDistributions {
            packageName = "Meshtastic Desktop"

            // Ensure critical JVM modules are included in the custom JRE bundled with the app.
            // jdeps might miss some of these if they are loaded via reflection or JNI.
            modules(
                "java.net.http", // Ktor Java client
                "jdk.accessibility", // Java Access Bridge for screen readers (JAWS, NVDA, VoiceOver)
                "jdk.crypto.ec", // Required for SSL/TLS HTTPS requests
                "jdk.unsupported", // sun.misc.Unsafe used by Coroutines & Okio
                "java.sql", // Sometimes required by SQLite JNI
                "java.naming", // Required by Ktor for DNS resolution
            )

            // Default JVM arguments for the packaged application
            // Increase max heap size to prevent OOM issues on complex maps/data
            jvmArgs(*desktopJvmArgs.toTypedArray())

            // App Icon & OS Specific Configurations
            macOS {
                iconFile.set(project.file("src/main/resources/icon.icns"))
                minimumSystemVersion = "12.0"
                bundleID = "org.meshtastic.desktop"
                appCategory = "public.app-category.utilities"
                entitlementsFile.set(project.file("entitlements.plist"))
                infoPlist {
                    extraKeysRawXml =
                        """
                        <key>NSBluetoothAlwaysUsageDescription</key>
                        <string>Meshtastic uses Bluetooth to communicate with your Meshtastic radio device.</string>
                        <key>NSLocalNetworkUsageDescription</key>
                        <string>Meshtastic uses your local network to discover Meshtastic devices connected via WiFi.</string>
                        <key>NSUserNotificationAlertStyle</key>
                        <string>alert</string>
                        <key>CFBundleURLTypes</key>
                        <array>
                          <dict>
                            <key>CFBundleURLName</key>
                            <string>Meshtastic deep link</string>
                            <key>CFBundleURLSchemes</key>
                            <array>
                              <string>meshtastic</string>
                            </array>
                          </dict>
                        </array>
                        """
                            .trimIndent()
                }
                // macOS code signing and notarization.
                // Required CI secrets:
                //   APPLE_SIGNING_IDENTITY      – e.g. "Developer ID Application: Meshtastic LLC (TEAMID)"
                //   APPLE_ID                    – Apple ID email used for notarization
                //   APPLE_APP_SPECIFIC_PASSWORD – App-specific password from appleid.apple.com
                //   APPLE_TEAM_ID               – 10-character Apple Developer Team ID
                val signMacOs = providers.environmentVariable("SIGN_MACOS").map { it.toBoolean() }.getOrElse(false)
                if (signMacOs) {
                    signing {
                        sign.set(true)
                        identity.set(providers.environmentVariable("APPLE_SIGNING_IDENTITY"))
                    }
                    notarization {
                        appleID.set(providers.environmentVariable("APPLE_ID"))
                        password.set(providers.environmentVariable("APPLE_APP_SPECIFIC_PASSWORD"))
                        teamID.set(providers.environmentVariable("APPLE_TEAM_ID"))
                    }
                }
            }
            windows {
                iconFile.set(project.file("src/main/resources/icon.ico"))
                menuGroup = "Meshtastic"
                shortcut = true
                menu = true
                dirChooser = true
                // Stable UUID ensures MSI upgrades replace the previous installation
                // rather than installing side-by-side. NEVER change this value.
                upgradeUuid = "4974EA87-98AA-470E-B590-0BD5CF9FAE8E"
            }
            linux {
                iconFile.set(project.file("src/main/resources/icon.png"))
                menuGroup = "Network"
                debMaintainer = "developers@meshtastic.org"
                appCategory = "Network"
                rpmLicenseType = "GPLv3+"
            }

            // Define target formats based on the current host OS to avoid configuration errors
            // (e.g., trying to configure Linux AppImage notarization on macOS).
            val currentOs = providers.systemProperty("os.name").get().lowercase()
            when {
                currentOs.contains("mac") -> targetFormats(TargetFormat.Dmg)
                currentOs.contains("win") -> targetFormats(TargetFormat.Msi, TargetFormat.Exe)
                else -> targetFormats(TargetFormat.Deb, TargetFormat.Rpm, TargetFormat.AppImage)
            }

            // Reuse the resolved version from the top of this script (mirrors app/build.gradle.kts).
            // Native installers require strict numeric semantic versions (X.Y.Z) without suffixes.
            val sanitizedVersion = Regex("^\\d+\\.\\d+\\.\\d+").find(versionInfo.versionName)?.value ?: "1.0.0"
            packageVersion = sanitizedVersion

            description = "Meshtastic Desktop Application"
            vendor = "Meshtastic LLC"
        }
    }
}

dependencies {
    implementation(libs.aboutlibraries.core)
    implementation(libs.aboutlibraries.compose.m3)

    // Coil image loading (network + SVG decoding for device hardware images)
    implementation(libs.coil)
    implementation(libs.coil.network.ktor3)
    implementation(libs.coil.svg)

    // Core KMP modules (JVM variants)
    implementation(projects.core.common)
    implementation(projects.core.di)
    implementation(projects.core.model)
    implementation(projects.core.navigation)
    implementation(libs.jetbrains.lifecycle.viewmodel.navigation3)
    implementation(projects.core.repository)
    implementation(projects.core.domain)
    implementation(projects.core.data)
    implementation(projects.core.database)
    implementation(projects.core.datastore)
    implementation(projects.core.prefs)
    implementation(projects.core.network)
    implementation(projects.core.takserver)
    implementation(projects.core.resources)
    implementation(projects.core.service)
    implementation(projects.core.ui)
    implementation(libs.meshtastic.protobufs)
    implementation(projects.core.ble)

    // Feature modules (JVM variants for real composable wiring)
    implementation(projects.feature.settings)
    implementation(projects.feature.docs)
    implementation(projects.feature.node)
    implementation(projects.feature.messaging)
    implementation(projects.feature.connections)
    implementation(projects.feature.map)
    implementation(projects.feature.discovery)
    implementation(projects.feature.firmware)
    implementation(projects.feature.wifiProvision)
    implementation(projects.feature.intro)

    // Compose Desktop
    implementation(compose.desktop.currentOs)
    implementation(libs.compose.multiplatform.animation)
    implementation(libs.compose.multiplatform.material3)
    implementation(libs.compose.multiplatform.runtime)
    implementation(libs.compose.multiplatform.foundation)
    implementation(libs.compose.multiplatform.resources)

    // JetBrains Material 3 Adaptive (multiplatform ListDetailPaneScaffold)
    implementation(libs.jetbrains.compose.material3.adaptive)
    implementation(libs.jetbrains.compose.material3.adaptive.layout)
    implementation(libs.jetbrains.compose.material3.adaptive.navigation)

    // Navigation 3 (JetBrains fork — multiplatform)
    implementation(libs.jetbrains.navigation3.ui)
    implementation(libs.jetbrains.lifecycle.viewmodel.navigation3)
    implementation(libs.jetbrains.lifecycle.viewmodel.compose)
    implementation(libs.jetbrains.lifecycle.runtime.compose)

    // Koin DI
    implementation(libs.koin.core)
    implementation(libs.koin.compose.viewmodel)

    implementation(libs.kotlinx.coroutines.core)
    implementation(libs.kotlinx.coroutines.swing)
    implementation(libs.kotlinx.serialization.core)
    implementation(libs.kermit)
    implementation(libs.okio)

    // Ktor HttpClient (Java engine for JVM/Desktop)
    implementation(libs.ktor.client.java)
    implementation(libs.ktor.client.content.negotiation)
    implementation(libs.ktor.client.logging)
    implementation(libs.ktor.serialization.kotlinx.json)

    implementation(libs.androidx.paging.common)
    implementation(libs.androidx.datastore.preferences)
    implementation(libs.androidx.datastore)
    implementation(libs.androidx.room.runtime)
    implementation(libs.androidx.sqlite.bundled)
    implementation(libs.koin.annotations)
    implementation(libs.kotlinx.collections.immutable)

    implementation(libs.jna)

    testRuntimeOnly(libs.junit.vintage.engine)
    testImplementation(libs.koin.test)
    testImplementation(libs.kotlinx.coroutines.test)
    testImplementation(kotlin("test"))
}
