<#
.SYNOPSIS
    Windows build for the DragonTCP Lite APK (no Gradle, no Android Studio).

.DESCRIPTION
    Port of build_apk.sh. Compiles the Kotlin TUN adapter and the Java UI/service,
    dexes them with d8, packages resources with aapt, aligns with zipalign and
    signs with apksigner.

    Run it from the directory that holds AndroidManifest.xml. The Go core lives
    in <repo>/core; the repo root is located by walking up from this script until
    a core\go.mod is found, so the script keeps working if the tree is nested.

    Everything else is auto-detected where possible:
      * Android SDK   - ANDROID_SDK_ROOT / ANDROID_HOME, or -SdkRoot
      * build-tools   - newest installed version that has aapt/d8/apksigner/zipalign
      * platform      - android-35 if present, else newest installed
      * JDK           - JAVA_HOME, then javac on PATH, then C:\Program Files\Java\*
      * Kotlin        - KOTLIN_HOME, or downloaded once into .\.tools\kotlinc-<version>
      * native lib    - built via <repo>\build_core.ps1 if the .so is missing

.EXAMPLE
    .\build_apk.ps1
    .\build_apk.ps1 -BuildCore                 # rebuild the Go .so first
    .\build_apk.ps1 -BuildTools 35.0.0 -Platform android-35
    .\build_apk.ps1 -Keystore C:\keys\rel.jks -KsPass secret -KeyAlias rel -KeyPass secret
#>
[CmdletBinding()]
param(
    [string]$SdkRoot,
    [string]$BuildTools,
    [string]$Platform,
    [string]$JavaHome,
    [string]$KotlinHome,
    [string]$KotlinVersion = '2.1.21',
    [string]$RepoRoot,
    [string]$Keystore,
    [string]$KsPass   = 'dragontcp',
    [string]$KeyAlias = 'dragontcp',
    [string]$KeyPass  = 'dragontcp',
    # Rebuild the Go native library before packaging.
    [switch]$BuildCore,
    # Fail instead of downloading the Kotlin compiler.
    [switch]$NoDownload
)

$ErrorActionPreference = 'Stop'
$Root = $PSScriptRoot
$ProgressPreference = 'SilentlyContinue'   # Invoke-WebRequest is ~10x faster without it

function Step { param([string]$m) Write-Host "[apk] $m" -ForegroundColor Cyan }
function Die  { param([string]$m) throw $m }

function Invoke-Tool {
    param([string]$Exe, [string[]]$ToolArgs, [string]$What)
    # These tools write harmless warnings to stderr (the JVM's sun.misc.Unsafe
    # notice, apksigner's native-access notice). Under $ErrorActionPreference =
    # 'Stop' with a merged stream those become terminating errors, so judge
    # success by the exit code alone.
    $saved = $ErrorActionPreference
    $ErrorActionPreference = 'Continue'
    try { & $Exe @ToolArgs } finally { $ErrorActionPreference = $saved }
    if ($LASTEXITCODE -ne 0) { Die "$What failed (exit $LASTEXITCODE): $Exe" }
}

if (-not (Test-Path (Join-Path $Root 'AndroidManifest.xml'))) {
    Die "No AndroidManifest.xml next to this script. Run it from the Android app directory."
}

# ------------------------------------------------------------------ repo root
if (-not $RepoRoot) {
    # Walk up collecting every ancestor that looks like a checkout. A tree can
    # contain more than one copy of core\, so prefer the one that also carries
    # build_core.ps1 and only fall back to the nearest bare core\go.mod.
    $fallback = $null
    $probe = $Root
    for ($i = 0; $i -lt 8 -and $probe; $i++) {
        if (Test-Path (Join-Path $probe 'core\go.mod')) {
            if (Test-Path (Join-Path $probe 'build_core.ps1')) { $RepoRoot = $probe; break }
            if (-not $fallback) { $fallback = $probe }
        }
        $parent = Split-Path $probe -Parent
        if ($parent -eq $probe) { break }
        $probe = $parent
    }
    if (-not $RepoRoot) { $RepoRoot = $fallback }
}
if ($RepoRoot) { $RepoRoot = (Resolve-Path $RepoRoot).Path }

# ---------------------------------------------------------------- Android SDK
if (-not $SdkRoot) {
    foreach ($c in $env:ANDROID_SDK_ROOT, $env:ANDROID_HOME,
                   "$env:LOCALAPPDATA\Android\Sdk", 'C:\Android\Sdk') {
        if ($c -and (Test-Path $c)) { $SdkRoot = $c; break }
    }
}
if (-not $SdkRoot -or -not (Test-Path $SdkRoot)) {
    Die "Android SDK not found. Set ANDROID_SDK_ROOT (or pass -SdkRoot 'D:\Android\Sdk')."
}
$SdkRoot = (Resolve-Path $SdkRoot).Path

$needTools = @{ aapt = 'aapt.exe'; d8 = 'd8.bat'; apksigner = 'apksigner.bat'; zipalign = 'zipalign.exe' }

function Test-BuildTools {
    param([string]$Dir)
    foreach ($f in $needTools.Values) { if (-not (Test-Path (Join-Path $Dir $f))) { return $false } }
    return $true
}

$btRoot = Join-Path $SdkRoot 'build-tools'
if (-not (Test-Path $btRoot)) { Die "No build-tools under $SdkRoot. Install them via the SDK Manager." }
if ($BuildTools) {
    $BT = Join-Path $btRoot $BuildTools
    if (-not (Test-BuildTools $BT)) { Die "build-tools $BuildTools is missing one of: $($needTools.Values -join ', ')" }
} else {
    $candidates = Get-ChildItem $btRoot -Directory | Sort-Object {
        try { [version]$_.Name } catch { [version]'0.0.0' }
    } -Descending
    $BT = $null
    foreach ($c in $candidates) { if (Test-BuildTools $c.FullName) { $BT = $c.FullName; $BuildTools = $c.Name; break } }
    if (-not $BT) { Die "No usable build-tools found under $btRoot (need $($needTools.Values -join ', '))." }
}

$platRoot = Join-Path $SdkRoot 'platforms'
if ($Platform) {
    $AJ = Join-Path $platRoot "$Platform\android.jar"
} else {
    $preferred = Join-Path $platRoot 'android-35\android.jar'
    if (Test-Path $preferred) {
        $Platform = 'android-35'; $AJ = $preferred
    } else {
        $p = Get-ChildItem $platRoot -Directory -ErrorAction SilentlyContinue |
             Where-Object { Test-Path (Join-Path $_.FullName 'android.jar') } |
             Sort-Object { [int]($_.Name -replace '\D', '') } -Descending |
             Select-Object -First 1
        if (-not $p) { Die "No android.jar found under $platRoot. Install a platform via the SDK Manager." }
        $Platform = $p.Name; $AJ = Join-Path $p.FullName 'android.jar'
    }
}
if (-not (Test-Path $AJ)) { Die "Android platform jar not found: $AJ" }

# ----------------------------------------------------------------------- JDK
if (-not $JavaHome) {
    if ($env:JAVA_HOME -and (Test-Path "$env:JAVA_HOME\bin\javac.exe")) {
        $JavaHome = $env:JAVA_HOME
    } else {
        $jc = Get-Command javac.exe -ErrorAction SilentlyContinue
        # javapath\javac.exe is a shim: it can compile but has no jar/keytool next to it.
        if ($jc -and (Test-Path (Join-Path (Split-Path $jc.Source) 'jar.exe'))) {
            $JavaHome = Split-Path (Split-Path $jc.Source)
        } else {
            $j = Get-ChildItem 'C:\Program Files\Java', 'C:\Program Files\Eclipse Adoptium',
                               'C:\Program Files\Microsoft', 'C:\Program Files\Android\Android Studio' `
                               -Directory -ErrorAction SilentlyContinue |
                 Where-Object { Test-Path (Join-Path $_.FullName 'bin\javac.exe') } |
                 Sort-Object Name -Descending | Select-Object -First 1
            if ($j) { $JavaHome = $j.FullName }
        }
    }
}
if (-not $JavaHome -or -not (Test-Path "$JavaHome\bin\javac.exe")) {
    Die "No JDK found. Install a JDK (17 or 21 recommended) and set JAVA_HOME, or pass -JavaHome."
}
$JavaHome = (Resolve-Path $JavaHome).Path
$javaExe = "$JavaHome\bin\java.exe"
$javac   = "$JavaHome\bin\javac.exe"
$jar     = "$JavaHome\bin\jar.exe"
$keytool = "$JavaHome\bin\keytool.exe"
foreach ($t in $javaExe, $javac, $jar, $keytool) { if (-not (Test-Path $t)) { Die "Missing $t (a JRE is not enough - a full JDK is required)." } }
# apksigner.bat and d8.bat resolve java through JAVA_HOME.
$env:JAVA_HOME = $JavaHome

# -------------------------------------------------------------------- Kotlin
function Resolve-KotlinHome {
    param([string]$Dir)
    if (-not $Dir) { return $null }
    if (Test-Path (Join-Path $Dir 'bin\kotlinc.bat')) { return (Resolve-Path $Dir).Path }
    # Accept the parent of an extracted kotlin-compiler zip too.
    $inner = Join-Path $Dir 'kotlinc'
    if (Test-Path (Join-Path $inner 'bin\kotlinc.bat')) { return (Resolve-Path $inner).Path }
    return $null
}

$toolsDir = Join-Path $Root '.tools'
if (-not $KotlinHome) {
    foreach ($c in $env:KOTLIN_HOME, (Join-Path $toolsDir "kotlinc-$KotlinVersion")) {
        $r = Resolve-KotlinHome $c
        if ($r) { $KotlinHome = $r; break }
    }
} else {
    $KotlinHome = Resolve-KotlinHome $KotlinHome
    if (-not $KotlinHome) { Die "No bin\kotlinc.bat under the -KotlinHome you passed." }
}

if (-not $KotlinHome) {
    if ($NoDownload) { Die "Kotlin compiler not found. Set KOTLIN_HOME or drop -NoDownload." }
    $dest = Join-Path $toolsDir "kotlinc-$KotlinVersion"
    $zip  = Join-Path $toolsDir "kotlin-compiler-$KotlinVersion.zip"
    $url  = "https://github.com/JetBrains/kotlin/releases/download/v$KotlinVersion/kotlin-compiler-$KotlinVersion.zip"
    New-Item -ItemType Directory -Force -Path $toolsDir | Out-Null
    Step "Downloading Kotlin $KotlinVersion (~85 MB, one time)..."
    try {
        Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing
    } catch {
        Die "Kotlin download failed: $($_.Exception.Message)`nDownload $url manually, extract it, and pass -KotlinHome <dir>\kotlinc."
    }
    Step 'Extracting Kotlin...'
    if (Test-Path $dest) { Remove-Item $dest -Recurse -Force }
    Add-Type -AssemblyName System.IO.Compression.FileSystem
    [System.IO.Compression.ZipFile]::ExtractToDirectory($zip, $dest)
    Remove-Item $zip -Force
    $KotlinHome = Resolve-KotlinHome $dest
    if (-not $KotlinHome) { Die "Extracted Kotlin to $dest but found no bin\kotlinc.bat." }
}

# kotlinc.bat is deliberately NOT used: cmd.exe treats ';' as an argument
# delimiter, which shreds any -classpath we hand it. Drive the compiler jar directly.
$kotlinCompilerJar = Join-Path $KotlinHome 'lib\kotlin-compiler.jar'
if (-not (Test-Path $kotlinCompilerJar)) { Die "Missing $kotlinCompilerJar." }
$coro   = Join-Path $KotlinHome 'lib\kotlinx-coroutines-core-jvm.jar'
$stdlib = @('kotlin-stdlib.jar', 'kotlin-stdlib-jdk7.jar', 'kotlin-stdlib-jdk8.jar') |
          ForEach-Object { Join-Path $KotlinHome "lib\$_" } | Where-Object { Test-Path $_ }
if (-not (Test-Path $coro)) { Die "Missing $coro - use a Kotlin distribution that bundles kotlinx-coroutines-core-jvm.jar." }
if (-not $stdlib) { Die "No kotlin-stdlib jars under $KotlinHome\lib." }

# ----------------------------------------------------------------- native lib
$libDir = Join-Path $Root 'lib\arm64-v8a'
$so     = Join-Path $libDir 'libdragontcp_client.so'
if ($BuildCore -or -not (Test-Path $so)) {
    $why = if ($BuildCore) { '-BuildCore was requested' } else { 'libdragontcp_client.so is missing' }
    if (-not $RepoRoot) { Die "$why but no core\go.mod was found above $Root. Pass -RepoRoot." }
    $coreScript = Join-Path $RepoRoot 'build_core.ps1'
    if (-not (Test-Path $coreScript)) { Die "$why but $coreScript was not found. Pass -RepoRoot." }
    Step 'Native core (Go)...'
    & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $coreScript -ClientOnly -AndroidLibDir $libDir
    if ($LASTEXITCODE -ne 0) { Die 'build_core.ps1 failed.' }
}
if (-not (Test-Path $so)) { Die "Missing $so - build the Go core first." }

Write-Host "app dir    : $Root"
Write-Host "repo root  : $(if ($RepoRoot) { $RepoRoot } else { '<not found>' })"
Write-Host "SDK        : $SdkRoot"
Write-Host "build-tools: $BuildTools"
Write-Host "platform   : $Platform"
Write-Host "JDK        : $JavaHome"
Write-Host "Kotlin     : $KotlinHome"

# -------------------------------------------------------------------- build
$B = Join-Path $Root 'build'
if (Test-Path $B) { Remove-Item $B -Recurse -Force }
New-Item -ItemType Directory -Force -Path "$B\kclasses", "$B\jclasses", "$B\dex" | Out-Null

Step 'Resources...'
Invoke-Tool (Join-Path $BT 'aapt.exe') @(
    'package', '-f',
    '-M', (Join-Path $Root 'AndroidManifest.xml'),
    '-S', (Join-Path $Root 'res'),
    '-A', (Join-Path $Root 'assets'),
    '-I', $AJ,
    '-F', "$B\resources.ap_"
) 'aapt'

Step 'Kotlin TUN adapter...'
$ktSources = Get-ChildItem "$Root\src" -Recurse -Filter *.kt | ForEach-Object { $_.FullName }
if (-not $ktSources) { Die "No .kt sources under $Root\src." }
$CP = (@($AJ, $coro) + $stdlib) -join ';'
Invoke-Tool $javaExe (@(
    '-Xmx2g', '-Dfile.encoding=UTF-8', '-Djava.awt.headless=true',
    '-cp', $kotlinCompilerJar, 'org.jetbrains.kotlin.cli.jvm.K2JVMCompiler',
    '-nowarn', '-no-stdlib', '-jvm-target', '1.8',
    '-kotlin-home', $KotlinHome,
    '-classpath', $CP, '-d', "$B\kclasses"
) + $ktSources) 'kotlinc'

Step 'Java UI/service...'
$javaSources = Get-ChildItem "$Root\src" -Recurse -Filter *.java | ForEach-Object { $_.FullName }
if (-not $javaSources) { Die "No .java sources under $Root\src." }
$JCP = (@($AJ, $coro, "$B\kclasses") + $stdlib) -join ';'
# --release 8 keeps the API surface at Java 8; -bootclasspath is rejected by modern javac.
Invoke-Tool $javac (@('-nowarn', '--release', '8', '-classpath', $JCP, '-d', "$B\jclasses") + $javaSources) 'javac'

Invoke-Tool $jar @('cf', "$B\kclasses.jar", '-C', "$B\kclasses", '.') 'jar (kotlin)'
Invoke-Tool $jar @('cf', "$B\jclasses.jar", '-C', "$B\jclasses", '.') 'jar (java)'

Step 'DEX...'
Invoke-Tool (Join-Path $BT 'd8.bat') (@(
    '--lib', $AJ, '--min-api', '29', '--output', "$B\dex",
    "$B\kclasses.jar", "$B\jclasses.jar", $coro
) + $stdlib) 'd8'

Step 'Packaging...'
$unsigned = "$B\DragonTCP-Hybrid-unsigned.apk"
Copy-Item "$B\resources.ap_" $unsigned -Force

Add-Type -AssemblyName System.IO.Compression.FileSystem
$zipFile = [System.IO.Compression.ZipFile]::Open($unsigned, 'Update')
try {
    $level = [System.IO.Compression.CompressionLevel]::Optimal
    foreach ($dex in Get-ChildItem "$B\dex" -Filter 'classes*.dex') {
        [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zipFile, $dex.FullName, $dex.Name, $level) | Out-Null
    }
    $libRoot = Join-Path $Root 'lib'
    foreach ($f in Get-ChildItem $libRoot -Recurse -File) {
        $entry = 'lib/' + $f.FullName.Substring($libRoot.Length + 1).Replace('\', '/')
        [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zipFile, $f.FullName, $entry, $level) | Out-Null
    }
} finally {
    $zipFile.Dispose()
}

Step 'Aligning...'
$aligned = "$B\DragonTCP-Hybrid-aligned.apk"
Invoke-Tool (Join-Path $BT 'zipalign.exe') @('-p', '-f', '4', $unsigned, $aligned) 'zipalign'

# ------------------------------------------------------------------- signing
if (-not $Keystore) { $Keystore = Join-Path $Root 'dragontcp-lite-debug.jks' }
if (-not (Test-Path $Keystore)) {
    Step 'Generating debug keystore...'
    Invoke-Tool $keytool @(
        '-genkeypair', '-keystore', $Keystore, '-storepass', $KsPass, '-keypass', $KeyPass,
        '-alias', $KeyAlias, '-keyalg', 'RSA', '-keysize', '2048', '-validity', '10000',
        '-dname', 'CN=DragonTCP Lite,O=DragonTCP,C=US'
    ) 'keytool'
}

Step 'Signing...'
$out = "$B\DragonTCP-Hybrid-arm64.apk"
Invoke-Tool (Join-Path $BT 'apksigner.bat') @(
    'sign', '--ks', $Keystore, '--ks-pass', "pass:$KsPass", '--key-pass', "pass:$KeyPass",
    '--out', $out, $aligned
) 'apksigner sign'
Invoke-Tool (Join-Path $BT 'apksigner.bat') @('verify', '--verbose', $out) 'apksigner verify'

Write-Host ''
Write-Host "APK: $out" -ForegroundColor Green
Write-Host ("Size: {0:N1} MB" -f ((Get-Item $out).Length / 1MB))
Write-Host "Install with: adb install -r `"$out`""
