@echo off
REM ============================================================================
REM  PDF-Decrypt build  (v1.2.0)  -  run on YOUR Windows machine (internet only)
REM
REM  Downloads a complete portable CPython (python-build-standalone, Tkinter
REM  included), installs pypdf + cryptography, and compiles two exe files:
REM    PDF-Decrypt.exe  -  thin launcher (the drag-and-drop target)
REM    Install.exe      -  installer (copies the app + creates Desktop shortcut)
REM
REM  After it finishes:
REM    - Test: drag an encrypted PDF onto PDF-Decrypt.exe and verify it decrypts.
REM    - Deploy: zip the whole PDF-Decrypt folder and send it to patients.
REM      Patients unzip it, double-click Install.exe, and a Desktop icon appears.
REM
REM  Modes:
REM    build.bat            -> interactive menu
REM    build.bat build      -> build / rebuild
REM    build.bat update     -> refresh Python libraries + recompile exes
REM    build.bat fresh      -> delete runtime\ and build from scratch
REM
REM  Every build now ends with a trim pass that removes files a shipped,
REM  patient-facing build never needs (pip, sqlite, Tcl/Tk demos + locale
REM  catalogs, tz data, test modules, ...). This is lossless for the app and
REM  still lets you re-build: ensure_deps re-bootstraps pip via ensurepip.
REM
REM  Optional deeper trim (saves a further ~10 MB): set SLIM_OPENSSL=1 below.
REM  It drops Python's bundled OpenSSL, which is redundant because the
REM  "cryptography" wheel ships its own. It is OFF by default because it needs
REM  a one-time smoke test (drag an AES-256 PDF onto the built exe and confirm
REM  it decrypts) and because an OpenSSL-stripped runtime can no longer fetch
REM  over HTTPS -- to rebuild after that, use "build.bat fresh".
REM ============================================================================
setlocal enabledelayedexpansion
cd /d "%~dp0"

REM  Set to 1 to also drop Python's redundant OpenSSL (see note above).
set "SLIM_OPENSSL=1"

REM ---- Portable Python runtime -----------------------------------------------
set "PBS_TAG=20260814"
set "PBS_PY=3.12.14"
set "PBS_ARCHIVE=cpython-%PBS_PY%+%PBS_TAG%-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
set "PYURL=https://github.com/astral-sh/python-build-standalone/releases/download/%PBS_TAG%/%PBS_ARCHIVE%"
set "RUNTIME_ID=cpython-%PBS_PY%+%PBS_TAG%"
set "PY_DL=~21 MB"

set "MODE=%~1"
if /i "%MODE%"=="build"   goto :do_build
if /i "%MODE%"=="update"  goto :do_update
if /i "%MODE%"=="fresh"   goto :do_fresh
goto :menu

:menu
echo.
echo ============================================
echo   PDF-Decrypt build  (v1.2.0)
echo ============================================
echo.
echo   [1] Build / rebuild
echo       Download runtime if needed, install libraries, compile exes.
echo.
echo   [2] Update existing install
echo       Refresh Python libraries and recompile the exes.
echo       (Skips the runtime download if the pinned build is present.)
echo.
echo   [Q] Quit
echo.
set "SEL="
set /p "SEL=Choose: "
if /i "%SEL%"=="1" goto :do_build
if /i "%SEL%"=="2" goto :do_update
if /i "%SEL%"=="Q" exit /b 0
goto :menu

:do_fresh
if exist "runtime" rmdir /s /q "runtime"

:do_build
call :setup_runtime || goto :fail
call :ensure_deps   || goto :fail
call :ensure_tk     || goto :fail
call :compile       || goto :fail
call :trim_ship
goto :done

:do_update
if not exist "runtime\python.exe" (
    echo.
    echo No existing runtime found. Use option [1] for a first build.
    goto :fail
)
echo [update] refreshing libraries and exes ...
call :setup_runtime || goto :fail
call :ensure_deps   || goto :fail
call :ensure_tk     || goto :fail
call :compile       || goto :fail
call :trim_ship
goto :done

:done
echo.
echo === Done. ===
echo Test: drag an encrypted PDF onto PDF-Decrypt.exe.
echo Deploy: zip the whole PDF-Decrypt folder and send it to patients.
echo         Patients unzip it, double-click Install.exe, and a Desktop
echo         shortcut is created automatically. No further steps needed.
echo.
pause
exit /b 0

:fail
echo.
echo *** Build failed. See the messages above. ***
echo.
pause
exit /b 1

REM ===========================================================================
REM  Subroutines
REM ===========================================================================

:setup_runtime
REM  Check whether the pinned runtime is already in place.
set "HAVE_ID="
if exist "runtime\RUNTIME.txt" set /p HAVE_ID=<"runtime\RUNTIME.txt"
if exist "runtime\python.exe" if "!HAVE_ID!"=="%RUNTIME_ID%" (
    echo [runtime] %RUNTIME_ID% present - keeping it.
    exit /b 0
)

where tar >nul 2>&1
if errorlevel 1 (
    echo       ERROR: tar.exe was not found. It ships with Windows 10 1803+.
    exit /b 1
)

if exist "runtime" (
    echo [runtime] replacing existing runtime ...
    rmdir /s /q "runtime"
)

echo [runtime] downloading portable Python %PBS_PY% (%PY_DL%, Tkinter included) ...
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
  "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%PYURL%' -OutFile '%TEMP%\pdfdecrypt_py.tar.gz'"
if errorlevel 1 (
    echo       Download failed. Check your internet connection.
    exit /b 1
)

echo [runtime] unpacking ...
mkdir "runtime"
tar -xf "%TEMP%\pdfdecrypt_py.tar.gz" -C "runtime" --strip-components=1
if errorlevel 1 (
    echo       Unpacking failed.
    del "%TEMP%\pdfdecrypt_py.tar.gz" >nul 2>&1
    exit /b 1
)
del "%TEMP%\pdfdecrypt_py.tar.gz" >nul 2>&1

if not exist "runtime\python.exe" (
    echo       ERROR: runtime\python.exe missing after unpacking.
    exit /b 1
)

REM  Trim items a shipped app never needs.
for %%D in ("runtime\include" "runtime\libs" "runtime\Lib\idlelib" "runtime\Lib\turtledemo") do if exist %%D rmdir /s /q %%D

> "runtime\RUNTIME.txt" echo %RUNTIME_ID%
echo [runtime] %RUNTIME_ID% installed.
exit /b 0

:ensure_deps
"runtime\python.exe" -m pip --version >nul 2>&1
if errorlevel 1 (
    echo [deps] bootstrapping pip ...
    "runtime\python.exe" -m ensurepip --default-pip
    if errorlevel 1 exit /b 1
)
echo [deps] installing pypdf + cryptography (wheels only) ...
"runtime\python.exe" -m pip install --no-warn-script-location -q ^
  --only-binary :all: pypdf cryptography
if errorlevel 1 exit /b 1
exit /b 0

:ensure_tk
"runtime\python.exe" -c "import tkinter" >nul 2>&1
if errorlevel 1 (
    echo       ERROR: the bundled runtime cannot import tkinter.
    echo       Delete the runtime folder and run build.bat fresh.
    exit /b 1
)
echo [tk] Tkinter present.
exit /b 0

:compile
echo [build] locating C# compiler ...
set "CSC=%WINDIR%\Microsoft.NET\Framework64\v4.0.30319\csc.exe"
if not exist "%CSC%" set "CSC=%WINDIR%\Microsoft.NET\Framework\v4.0.30319\csc.exe"
if not exist "%CSC%" (
    echo       ERROR: csc.exe not found. The .NET Framework 4.x ships with
    echo       Windows; try installing the .NET Framework 4.8 Developer Pack.
    exit /b 1
)

echo [build] compiling PDF-Decrypt.exe ...
"%CSC%" /nologo /codepage:65001 /target:winexe /out:"PDF-Decrypt.exe" /win32icon:"icon.ico" ^
  /reference:System.Windows.Forms.dll /reference:System.Drawing.dll ^
  PDFDecrypt.cs
if errorlevel 1 exit /b 1

echo [build] compiling Install.exe ...
"%CSC%" /nologo /codepage:65001 /target:winexe /out:"Install.exe" /win32icon:"icon.ico" ^
  /reference:System.Windows.Forms.dll /reference:System.Drawing.dll ^
  Installer.cs
if errorlevel 1 exit /b 1

echo [build] done.
exit /b 0

:trim_ship
REM  Remove files a shipped, patient-facing build never needs. Lossless for
REM  the app: core Tk, tkinter, pypdf, cryptography and the builtin hashes all
REM  stay. Typical effect: ~79 MB -> ~56 MB on disk.
echo [trim] slimming the runtime for shipping ...

REM  pip -- patients never install packages; ensure_deps re-bootstraps it via
REM  ensurepip on the next build/update, so removing it here is safe.
if exist "runtime\Lib\site-packages\pip" rmdir /s /q "runtime\Lib\site-packages\pip"
for /d %%D in ("runtime\Lib\site-packages\pip-*.dist-info") do rmdir /s /q "%%D"

REM  Unused pure-Python stdlib packages.
for %%D in ("runtime\Lib\lib2to3" "runtime\Lib\venv" "runtime\Lib\pydoc_data" "runtime\Lib\sqlite3" "runtime\Lib\turtledemo") do if exist %%D rmdir /s /q %%D
if exist "runtime\Lib\turtle.py" del /q "runtime\Lib\turtle.py"

REM  Unused native modules (sqlite + CPython self-test modules).
del /q "runtime\DLLs\sqlite3.dll"    2>nul
del /q "runtime\DLLs\_sqlite3.pyd"   2>nul
del /q "runtime\DLLs\_test*.pyd"     2>nul
del /q "runtime\DLLs\xxlimited*.pyd" 2>nul

REM  Tcl/Tk extras. Core Tk stays; only demos, locale message catalogs, the
REM  Tcl time-zone database, and the legacy Tix widget set are removed.
for %%D in ("runtime\tcl\tix8.4.3" "runtime\tcl\nmake" "runtime\tcl\tcl8.6\tzdata" "runtime\tcl\tcl8.6\msgs" "runtime\tcl\tk8.6\msgs" "runtime\tcl\tk8.6\demos") do if exist %%D rmdir /s /q %%D

if /i "%SLIM_OPENSSL%"=="1" (
    echo [trim] SLIM_OPENSSL=1 - dropping Python's bundled OpenSSL ^(cryptography ships its own^).
    echo        Smoke-test the built exe before shipping, and rebuild with "fresh".
    del /q "runtime\DLLs\libssl-3-x64.dll"    2>nul
    del /q "runtime\DLLs\_ssl.pyd"            2>nul
    del /q "runtime\Lib\ssl.py"               2>nul
    del /q "runtime\DLLs\libcrypto-3-x64.dll" 2>nul
    del /q "runtime\DLLs\_hashlib.pyd"        2>nul
)

echo [trim] done.
exit /b 0
