#!/usr/bin/env bash
# Copyright 2026, Jiri Eischmann <jiri@eischmann.cz> and the meshy contributors
# SPDX-License-Identifier: GPL-3.0-or-later
set -euo pipefail

APP_ID="page.codeberg.sesivany.Meshy"
APP_NAME="Meshy"
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)"
BUILD_DIR="$ROOT_DIR/builddir-appimage"
DIST_DIR="$ROOT_DIR/dist"
APPDIR="$DIST_DIR/AppDir"
TOOLS_DIR="$ROOT_DIR/.cache/appimage"
ARCH="${ARCH:-$(uname -m)}"
WITH_QR=1
DOWNLOAD_TOOLS=0
APPDIR_ONLY=0

log_step() {
    printf '\n==> %s\n' "$1"
}

usage() {
    cat <<'EOF'
Usage: ./build-aux/appimage/build-appimage.sh [options]

Options:
  --appdir-only      Build the staged AppDir but skip the final AppImage step
  --build-dir PATH   Meson build directory to use
  --dist-dir PATH    Output directory for AppDir and final AppImage
  --download-tools   Download linuxdeploy and appimagetool into .cache/appimage
  --no-qr            Build without QR scanner support
  -h, --help         Show this help text
EOF
}

while [ "$#" -gt 0 ]; do
    case "$1" in
        --appdir-only)
            APPDIR_ONLY=1
            ;;
        --build-dir)
            BUILD_DIR="$2"
            shift
            ;;
        --dist-dir)
            DIST_DIR="$2"
            APPDIR="$DIST_DIR/AppDir"
            shift
            ;;
        --download-tools)
            DOWNLOAD_TOOLS=1
            ;;
        --no-qr)
            WITH_QR=0
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            echo "Unknown option: $1" >&2
            usage >&2
            exit 1
            ;;
    esac
    shift
done

if [ "$ARCH" != "x86_64" ]; then
    echo "Only x86_64 AppImage builds are supported by this script right now." >&2
    exit 1
fi

mkdir -p "$DIST_DIR" "$TOOLS_DIR"

require_tool() {
    if ! command -v "$1" >/dev/null 2>&1; then
        echo "Missing required tool: $1" >&2
        exit 1
    fi
}

download_tool() {
    local target="$1"
    local url="$2"
    if [ ! -x "$target" ]; then
        if command -v curl >/dev/null 2>&1; then
            curl -L "$url" -o "$target"
        elif command -v wget >/dev/null 2>&1; then
            wget -O "$target" "$url"
        else
            echo "Missing curl or wget; cannot download $url" >&2
            exit 1
        fi
        chmod +x "$target"
    fi
}

resolve_appimage_tool() {
    local binary_name="$1"
    local appimage_name="$2"
    local url="$3"
    local target="$TOOLS_DIR/$appimage_name"
    if command -v "$binary_name" >/dev/null 2>&1; then
        command -v "$binary_name"
        return
    fi
    if command -v "$appimage_name" >/dev/null 2>&1; then
        command -v "$appimage_name"
        return
    fi
    if [ -x "$target" ]; then
        echo "$target"
        return
    fi
    if [ "$DOWNLOAD_TOOLS" -eq 1 ]; then
        download_tool "$target" "$url"
        echo "$target"
        return
    fi
    echo ""
}

run_appimage_tool() {
    local tool="$1"
    shift
    if [[ "$tool" == *.AppImage ]]; then
        APPIMAGE_EXTRACT_AND_RUN=1 "$tool" "$@"
    else
        "$tool" "$@"
    fi
}

LINUXDEPLOY_BIN="$(resolve_appimage_tool "linuxdeploy" "linuxdeploy-x86_64.AppImage" "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage")"
APPIMAGETOOL_BIN="$(resolve_appimage_tool "appimagetool" "appimagetool-x86_64.AppImage" "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage")"

require_tool python3
require_tool meson
require_tool pkg-config
require_tool glib-compile-schemas

BUILD_PYTHON="$(command -v python3)"
SYSTEM_PYTHON="/usr/bin/python3"
if [ ! -x "$SYSTEM_PYTHON" ]; then
    echo "Missing required system interpreter: $SYSTEM_PYTHON" >&2
    exit 1
fi

TYPELIB_DIR="$(pkg-config --variable=typelibdir gobject-introspection-1.0)"

LDCONFIG_BIN="$(command -v ldconfig || true)"
if [ -z "$LDCONFIG_BIN" ]; then
    for candidate in /sbin/ldconfig /usr/sbin/ldconfig; do
        if [ -x "$candidate" ]; then
            LDCONFIG_BIN="$candidate"
            break
        fi
    done
fi
if [ -z "$LDCONFIG_BIN" ]; then
    echo "Missing required tool: ldconfig" >&2
    exit 1
fi

if [ -z "$LINUXDEPLOY_BIN" ]; then
    echo "Missing linuxdeploy. Install it or rerun with --download-tools." >&2
    exit 1
fi
if [ -z "$APPIMAGETOOL_BIN" ]; then
    echo "Missing appimagetool. Install it or rerun with --download-tools." >&2
    exit 1
fi

QROPT="-Dqr_scanner=true"
if [ "$WITH_QR" -ne 1 ]; then
    QROPT="-Dqr_scanner=false"
fi

log_step "Preparing build directories"
rm -rf "$APPDIR" "$BUILD_DIR"

log_step "Configuring Meson"
meson setup "$BUILD_DIR" --prefix=/usr --buildtype=release "$QROPT"
log_step "Compiling project"
meson compile -C "$BUILD_DIR"
log_step "Installing into AppDir"
DESTDIR="$APPDIR" meson install -C "$BUILD_DIR"

log_step "Installing AppImage launcher and metadata"
cp "$SCRIPT_DIR/AppRun" "$APPDIR/AppRun"
chmod +x "$APPDIR/AppRun"

ln -srf "$APPDIR/usr/share/applications/$APP_ID.desktop" "$APPDIR/$APP_ID.desktop"
ln -srf "$APPDIR/usr/share/icons/hicolor/256x256/apps/$APP_ID.png" "$APPDIR/$APP_ID.png"
ln -srf "$APPDIR/usr/share/icons/hicolor/256x256/apps/$APP_ID.png" "$APPDIR/.DirIcon"

SCHEMA_DIR="$APPDIR/usr/share/glib-2.0/schemas"
log_step "Compiling GSettings schemas"
glib-compile-schemas "$SCHEMA_DIR"

PYTHON_VERSION="$("$SYSTEM_PYTHON" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
PYTHON_STDLIB="$("$SYSTEM_PYTHON" -c 'import sysconfig; print(sysconfig.get_path("stdlib"))')"
PYTHON_LIBDIR_NAME="$("$SYSTEM_PYTHON" -c 'import sysconfig; import os; print(os.path.basename(sysconfig.get_config_var("LIBDIR")))')"
TARGET_PYTHON_DIR="$APPDIR/usr/lib/python$PYTHON_VERSION"
TARGET_SITE_PACKAGES="$TARGET_PYTHON_DIR/site-packages"

mkdir -p "$APPDIR/usr/bin" "$TARGET_SITE_PACKAGES" "$APPDIR/usr/lib/girepository-1.0"
log_step "Copying Python runtime"
cp -aL "$SYSTEM_PYTHON" "$APPDIR/usr/bin/python3"
cp -a "$PYTHON_STDLIB/." "$TARGET_PYTHON_DIR/"
if [ "$PYTHON_LIBDIR_NAME" != "lib" ] && [ ! -e "$APPDIR/usr/$PYTHON_LIBDIR_NAME" ]; then
    ln -s "lib" "$APPDIR/usr/$PYTHON_LIBDIR_NAME"
fi

copy_python_module() {
    local interpreter="$1"
    local module="$2"
    "$interpreter" - "$module" "$TARGET_SITE_PACKAGES" <<'PY'
import importlib.util
import pathlib
import shutil
import sys

module = sys.argv[1]
target_site = pathlib.Path(sys.argv[2])
spec = importlib.util.find_spec(module)
if spec is None or spec.origin is None:
    print(f"Missing Python module {module}", file=sys.stderr)
    sys.exit(1)
origin = pathlib.Path(spec.origin)
if origin.name == "__init__.py":
    dest = target_site / module
    shutil.copytree(origin.parent, dest, dirs_exist_ok=True)
else:
    dest = target_site / origin.name
    dest.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(origin, dest)
PY
}

copy_python_module "$BUILD_PYTHON" "Crypto"
copy_python_module "$BUILD_PYTHON" "serial"
copy_python_module "$BUILD_PYTHON" "segno"
copy_python_module "$SYSTEM_PYTHON" "gi"
copy_python_module "$SYSTEM_PYTHON" "cairo"
if [ "$WITH_QR" -eq 1 ]; then
    copy_python_module "$BUILD_PYTHON" "pyzbar"
fi

log_step "Collecting GObject introspection data"
copy_typelib() {
    local typelib="$1"
    local src="$TYPELIB_DIR/$typelib.typelib"
    if [ -f "$src" ]; then
        cp -a "$src" "$APPDIR/usr/lib/girepository-1.0/"
    fi
}

copy_typelib "Gtk-4.0"
copy_typelib "Adw-1"
copy_typelib "Gio-2.0"
copy_typelib "GLib-2.0"
copy_typelib "GdkPixbuf-2.0"
copy_typelib "Shumate-1.0"
copy_typelib "Geoclue-2.0"
copy_typelib "Gdk-4.0"
copy_typelib "GObject-2.0"

log_step "Resolving GTK stack libraries for bundling"
resolve_system_lib() {
    "$LDCONFIG_BIN" -p | grep "$1" | head -1 | awk '{print $NF}' || true
}

BUNDLE_LIBS=()
for lib_name in libgtk-4.so libadwaita-1.so libshumate-1.0.so; do
    lib_path="$(resolve_system_lib "$lib_name")"
    if [ -n "$lib_path" ] && [ -f "$lib_path" ]; then
        BUNDLE_LIBS+=(--library "$lib_path")
        echo "  Will bundle: $lib_path"
    else
        echo "  Warning: $lib_name not found on system" >&2
    fi
done

log_step "Bundling shared libraries with linuxdeploy (patchelf disabled)"
NOOP_PATCHELF_DIR="$(mktemp -d)"
cat > "$NOOP_PATCHELF_DIR/patchelf" << 'NOOP'
#!/bin/sh
exit 0
NOOP
chmod +x "$NOOP_PATCHELF_DIR/patchelf"
if [[ "$LINUXDEPLOY_BIN" == *.AppImage ]]; then
    LINUXDEPLOY_EXTRACTED="$(mktemp -d)"
    cd "$LINUXDEPLOY_EXTRACTED"
    "$LINUXDEPLOY_BIN" --appimage-extract >/dev/null 2>&1
    cp "$NOOP_PATCHELF_DIR/patchelf" "$LINUXDEPLOY_EXTRACTED/squashfs-root/usr/bin/patchelf"
    NO_STRIP=true ARCH="$ARCH" "$LINUXDEPLOY_EXTRACTED/squashfs-root/AppRun" \
        --appdir "$APPDIR" \
        --executable "$APPDIR/usr/bin/python3" \
        "${BUNDLE_LIBS[@]}" \
        --desktop-file "$APPDIR/usr/share/applications/$APP_ID.desktop" \
        --icon-file "$APPDIR/usr/share/icons/hicolor/256x256/apps/$APP_ID.png"
    rm -rf "$LINUXDEPLOY_EXTRACTED"
    cd "$ROOT_DIR"
else
    PATH="$NOOP_PATCHELF_DIR:$PATH" NO_STRIP=true ARCH="$ARCH" "$LINUXDEPLOY_BIN" \
        --appdir "$APPDIR" \
        --executable "$APPDIR/usr/bin/python3" \
        "${BUNDLE_LIBS[@]}" \
        --desktop-file "$APPDIR/usr/share/applications/$APP_ID.desktop" \
        --icon-file "$APPDIR/usr/share/icons/hicolor/256x256/apps/$APP_ID.png"
fi
rm -rf "$NOOP_PATCHELF_DIR"

log_step "Bundling gdk-pixbuf loaders"
GDK_PIXBUF_MODULEDIR_SYS="$(pkg-config --variable=gdk_pixbuf_moduledir gdk-pixbuf-2.0 2>/dev/null || true)"
if [ -n "$GDK_PIXBUF_MODULEDIR_SYS" ] && [ -d "$GDK_PIXBUF_MODULEDIR_SYS" ]; then
    shopt -s nullglob
    LOADER_FILES=("$GDK_PIXBUF_MODULEDIR_SYS"/*.so)
    shopt -u nullglob
    if [ "${#LOADER_FILES[@]}" -gt 0 ]; then
        APPDIR_LOADERS="$APPDIR/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders"
        mkdir -p "$APPDIR_LOADERS"
        cp -a "${LOADER_FILES[@]}" "$APPDIR_LOADERS/"
        if command -v gdk-pixbuf-query-loaders >/dev/null 2>&1; then
            gdk-pixbuf-query-loaders "$APPDIR_LOADERS/"*.so > "$APPDIR/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" 2>/dev/null || true
        fi
    else
        echo "  No gdk-pixbuf loader modules found (likely built-in), skipping"
    fi
else
    echo "  gdk-pixbuf module dir not found, skipping"
fi

APPIMAGE_PATH="$DIST_DIR/${APP_NAME}.AppImage"
if [ "$APPDIR_ONLY" -eq 1 ]; then
    exit 0
fi

log_step "Building final AppImage"
ARCH="$ARCH" run_appimage_tool "$APPIMAGETOOL_BIN" "$APPDIR" "$APPIMAGE_PATH"

log_step "Done"
echo "AppImage written to $APPIMAGE_PATH"
