#!/bin/bash
# ─────────────────────────────────────────────────────────────────────────────
# YUMI GUI — Linux Universal Standalone Binary Script
# Builds directly on your host Ubuntu desktop, against the HOST'S SYSTEM
# Python + Tk (not a portable/managed Python), so the frozen binary picks up
# the system Tk build, which is compiled with Xft/FreeType support.
#
# WHY: python-build-standalone / other "portable" Python builds bundle their
# own Tcl/Tk, usually compiled *without* Xft/FreeType (same known issue as
# Anaconda's bundled Tk) so that they don't need fontconfig/Xft as a runtime
# dependency on ancient hosts. Without Xft, Tk silently falls back to old
# X11 core bitmap fonts — jagged, no anti-aliasing — no matter which font
# family name the app requests. Debian/Ubuntu's system `python3-tk` package
# is built against the system Tk, which does have Xft, so switching to it
# is what actually fixes font quality.
#
# TRADE-OFF: this drops the "runs on glibc 2.17 (RHEL7-era, 2014)" portability
# claim. The resulting binary will need a glibc roughly as new as this build
# host's (Ubuntu 20.04+/22.04+/24.04 => glibc 2.31/2.35/2.39). That covers
# essentially every Linux desktop in active use in 2026; it just won't run
# on genuinely ancient distros anymore.
#
# Requirements: python3-tk and python3-venv installed on the host (the
# script will apt-install them if missing — needs sudo the first time).
# Usage: bash build-onefile.sh
# ─────────────────────────────────────────────────────────────────────────────
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE}")" && pwd)"
OUTPUT="$SCRIPT_DIR/dist/yumi_py"

if [ ! -f "$SCRIPT_DIR/yumi_gui.py" ]; then
    echo "ERROR: yumi_gui.py not found in $SCRIPT_DIR"
    exit 1
fi

# Clean up any old broken virtual environments or temporary staging structures
rm -rf "$SCRIPT_DIR/.venv" "$SCRIPT_DIR/build" "$SCRIPT_DIR/dist" "$SCRIPT_DIR/yumi_py.spec"

echo "Step 1: Instantly fetching official 'uv' binary using host tools..."
if ! command -v uv &>/dev/null; then
    echo "  Installing uv via host Python pip subsystem..."
    python3 -m pip install --quiet --user uv || sudo apt install -y uv || snap install uv --classic
fi

export PATH="$HOME/.local/bin:$PATH"
echo "  uv version: $(uv --version)"

echo "Step 2: Locating a SYSTEM Python 3.8-3.12 with Tk (Xft/FreeType)..."
# PyInstaller >=6.3 needs Python 3.8-3.13. Old hosts (e.g. Ubuntu 18.04)
# default to python3.6, which won't work at all — so we search newest-first
# for anything already installed before resorting to installing one.
SYS_PY=""
for v in 3.12 3.11 3.10 3.9 3.8; do
    if command -v "python$v" &>/dev/null && "python$v" -c "import tkinter" &>/dev/null; then
        SYS_PY="python$v"
        break
    fi
done

if [ -z "$SYS_PY" ]; then
    echo "  No system Python 3.8-3.12 with Tk found (found: $(python3 --version 2>&1))."
    echo "  Trying Python 3.11 + Tk via the deadsnakes PPA (needs sudo)..."
    echo "  (This still links against your system's libtk8.6, so it keeps Xft/anti-aliasing —"
    echo "   unlike a portable/managed Python, which bundles its own non-Xft Tk.)"
    set +e
    sudo apt update -qq
    sudo apt install -y software-properties-common
    sudo add-apt-repository -y ppa:deadsnakes/ppa
    sudo apt update -qq
    sudo apt install -y python3.11 python3.11-venv python3.11-tk python3.11-dev
    set -e
    if command -v python3.11 &>/dev/null && python3.11 -c "import tkinter" &>/dev/null; then
        SYS_PY="python3.11"
    fi
fi

if [ -z "$SYS_PY" ]; then
    # deadsnakes has dropped support for EOL Ubuntu releases (e.g. bionic/18.04)
    # entirely, so there's no packaged option left. Fall back to compiling a
    # Python from source — it still links against this host's existing
    # libtk8.6, so it keeps the same Xft/anti-aliasing, it just isn't a
    # pre-built .deb.
    echo "  deadsnakes has no packages for this Ubuntu release (it drops EOL"
    echo "  releases like 18.04 entirely). Building Python 3.11 from source"
    echo "  instead, linked against your system's existing Tk (needs sudo, and"
    echo "  will take a few minutes)..."
    PY_SRC_VERSION="3.11.9"
    BUILD_DIR="$SCRIPT_DIR/.pybuild"
    PREFIX="$BUILD_DIR/opt"
    sudo apt update -qq
    sudo apt install -y build-essential wget \
        libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
        libffi-dev liblzma-dev libncurses5-dev libgdbm-dev uuid-dev \
        tk-dev tcl-dev
    mkdir -p "$BUILD_DIR"
    cd "$BUILD_DIR"
    [ -f "Python-$PY_SRC_VERSION.tar.xz" ] || wget -q "https://www.python.org/ftp/python/$PY_SRC_VERSION/Python-$PY_SRC_VERSION.tar.xz"
    rm -rf "Python-$PY_SRC_VERSION"
    tar xf "Python-$PY_SRC_VERSION.tar.xz"
    cd "Python-$PY_SRC_VERSION"

    # Python 3.11's configure detects Tcl/Tk via pkg-config, but Ubuntu
    # 18.04's tk-dev/tcl-dev packages don't ship .pc files, so autodetection
    # silently finds nothing and _tkinter just gets skipped (the overall
    # build still "succeeds" — CPython treats it as an optional module).
    # Point configure at the real paths directly instead.
    TK_H="$(find /usr/include -name 'tk.h' 2>/dev/null | head -1)"
    TK_LIB="$(find /usr/lib -name 'libtk8.6.so*' 2>/dev/null | head -1)"
    if [ -z "$TK_H" ] || [ -z "$TK_LIB" ]; then
        echo "ERROR: tk.h or libtk8.6.so not found even though tk-dev was installed."
        echo "       Run: dpkg -L tk-dev tk8.6-dev | grep -E 'tk\.h|libtk'"
        exit 1
    fi
    TCLTK_INC_DIR="$(dirname "$TK_H")"
    TCLTK_LIB_DIR="$(dirname "$TK_LIB")"
    echo "  Using Tk headers from $TCLTK_INC_DIR, libs from $TCLTK_LIB_DIR"

    BUILD_LOG="$BUILD_DIR/build.log"
    {
        TCLTK_CFLAGS="-I$TCLTK_INC_DIR" TCLTK_LIBS="-L$TCLTK_LIB_DIR -ltcl8.6 -ltk8.6" \
            ./configure --prefix="$PREFIX" --enable-shared --with-ensurepip=install
        make -j"$(nproc)"
        make altinstall
    } >"$BUILD_LOG" 2>&1
    cd "$SCRIPT_DIR"
    if [ -x "$PREFIX/bin/python3.11" ] && LD_LIBRARY_PATH="$PREFIX/lib:$LD_LIBRARY_PATH" "$PREFIX/bin/python3.11" -c "import tkinter" &>/dev/null; then
        SYS_PY="$PREFIX/bin/python3.11"
        export LD_LIBRARY_PATH="$PREFIX/lib:$LD_LIBRARY_PATH"
    else
        echo "ERROR: source build of Python 3.11 + Tk failed. Relevant build log lines:"
        grep -iE 'tkinter|tcl|tk\.h|failed to build' "$BUILD_LOG" | tail -30
        echo ""
        echo "Full log: $BUILD_LOG"
        echo "Easiest fallback: build on a currently-supported Ubuntu LTS (22.04/24.04) instead."
        exit 1
    fi
fi
echo "  Using: $SYS_PY ($("$SYS_PY" --version))"

# Sanity-check that Tk here actually has FreeType/Xft (best-effort; informational only)
if command -v ldconfig &>/dev/null; then
    TK_SO=$(ldconfig -p | grep -m1 'libtk8\.6\.so\|libtk\.so' | awk '{print $NF}')
    if [ -n "$TK_SO" ] && command -v ldd &>/dev/null; then
        if ldd "$TK_SO" 2>/dev/null | grep -qi 'libxft'; then
            echo "  OK: system Tk is linked against libXft (anti-aliased fonts available)."
        else
            echo "  WARNING: could not confirm libXft linkage for system Tk — fonts may still look crude."
        fi
    fi
fi

echo "Step 3: Provisioning a venv from that SYSTEM Python (not a managed/portable one)..."
# --python-preference only-system forces uv to use the system interpreter
# we just found/installed, instead of downloading its own portable/managed
# CPython+Tk build (which is what caused the crude bitmap fonts originally).
uv venv "$SCRIPT_DIR/.venv" --python "$SYS_PY" --python-preference only-system
source "$SCRIPT_DIR/.venv/bin/activate"
echo "  Using: $(python3 --version) at $(command -v python3)"

echo "Step 4: Upgrading toolchain dependencies inside the clean workspace..."
uv pip install "setuptools<82.0.0" "pyinstaller>=6.3" tkinterdnd2

# Pre-compute data flags as individual array elements for safe PyInstaller flag mapping
DATA_FLAGS=("--add-data=distros.json:.")
[ -f "$SCRIPT_DIR/yumi_icon.png" ] && DATA_FLAGS+=("--add-data=yumi_icon.png:.")
[ -d "$SCRIPT_DIR/YUMI4Linux" ]   && DATA_FLAGS+=("--add-data=YUMI4Linux:YUMI4Linux")
[ -f "$SCRIPT_DIR/YUMI-Copying.txt" ] && DATA_FLAGS+=("--add-data=YUMI-Copying.txt:.")

echo "Step 4b: Locating the Linux tkdnd shared library to bundle explicitly..."
TKDND_SO=$(python3 -c "import tkinterdnd2, os; print(os.path.join(os.path.dirname(tkinterdnd2.__file__), 'tkdnd', 'linux-x64', 'libtkdnd2.10.1.so'))")
if [ ! -f "$TKDND_SO" ]; then
    echo "ERROR: expected tkdnd .so not found at $TKDND_SO"
    echo "       Check tkinterdnd2's actual install layout with: pip show -f tkinterdnd2"
    exit 1
fi
echo "  Found: $TKDND_SO"

echo "Step 5: Generating PyInstaller spec via pyi-makespec (no build yet)..."
# We generate the spec first, then patch it, then build from the patched
# spec. Reason: --collect-all=tkinter pulls in libBLT (a Tcl/Tk graphing
# extension shipped alongside system Tcl/Tk), and libBLT in turn drags in
# its own optional-rendering dependency chain — GTK2/GTK3, Qt5, and ICU —
# none of which yumi_gui.py ever calls into. There's no CLI flag to exclude
# a transitively-pulled binary, so we filter a.binaries directly in the spec.
pyi-makespec \
    --onefile \
    --name=yumi_py \
    --hidden-import=tkinter \
    --hidden-import=tkinter.ttk \
    --hidden-import=tkinter.filedialog \
    --hidden-import=tkinter.messagebox \
    --hidden-import=tkinter.simpledialog \
    --hidden-import=ipaddress \
    --collect-all=tkinter \
    --collect-submodules=tkinterdnd2 \
    --add-binary="${TKDND_SO}:tkinterdnd2/tkdnd/linux-x64" \
    "${DATA_FLAGS[@]}" \
    --specpath "$SCRIPT_DIR" \
    yumi_gui.py

SPEC_FILE="$SCRIPT_DIR/yumi_py.spec"
if [ ! -f "$SPEC_FILE" ]; then
    echo "ERROR: spec file was not generated at $SPEC_FILE"
    exit 1
fi

echo "Step 5b: Patching spec to strip GTK/Qt/ICU libs pulled in transitively via libBLT..."
python3 - "$SPEC_FILE" << 'PYEOF'
import sys

spec_path = sys.argv[1]
with open(spec_path, "r") as f:
    content = f.read()

marker = "pyz = PYZ(a.pure)"
if marker not in content:
    print(f"ERROR: could not find '{marker}' in {spec_path} to patch — spec format may have changed.")
    sys.exit(1)

filter_block = '''
# Strip GTK2/GTK3, Qt5, and ICU libraries. These are not used by yumi_gui.py
# directly - they're transitively pulled in by libBLT.so (a Tcl/Tk graphing
# extension bundled alongside the system Tcl/Tk install) because --collect-all
# sweeps in every shared library that sits alongside the Tcl/Tk libs it's
# actually after, including optional-rendering dependencies of unrelated
# extensions. Safe to drop: yumi_gui.py never imports BLT/GTK/Qt.
_EXCLUDE_BINARY_SUBSTRINGS = (
    'libBLT', 'libicudata', 'libicui18n', 'libicuuc',
    'libQt5', 'libgtk-3', 'libgtk-x11-2.0',
)
a.binaries = [b for b in a.binaries if not any(x in b[0] for x in _EXCLUDE_BINARY_SUBSTRINGS)]

'''

content = content.replace(marker, filter_block + marker)
with open(spec_path, "w") as f:
    f.write(content)

print(f"  Patched {spec_path}")
PYEOF

echo "Step 5c: Building from the patched spec..."
rm -rf "$SCRIPT_DIR/build" "$SCRIPT_DIR/dist"
python3 -m PyInstaller --noconfirm --clean "$SPEC_FILE"

if [ -f "$OUTPUT" ]; then
    SIZE=$(du -h "$OUTPUT" | cut -f1)
    echo ""
    echo "Done: $OUTPUT ($SIZE)"
    echo "Run: ./dist/yumi_py"
    echo ""
    echo "Note: this binary was built against this host's system Tk (for"
    echo "anti-aliased fonts) and needs a glibc roughly as new as this host's."
    echo "Build on the oldest LTS release you still need to support."
    echo ""
    echo "IMPORTANT: this build strips libBLT/GTK/Qt/ICU libraries that were"
    echo "transitively pulled in but are not actually used by the app. Test"
    echo "drag-and-drop, ISO loading, and file dialogs after this build to"
    echo "confirm nothing silently depended on them."
else
    echo ""
    echo "ERROR: Build failed — standalone binary was not created."
    exit 1
fi
