#!/bin/bash
# Proton-GE xzm builder for Porteus
# Version 2026-08-17
# Includes: Proton-GE, Wine symlinks, winetricks, cabextract, locale selection

# Copyright 2023-2030, Blaze, Dankov, Russia

set -u

# ===== CONSTANTS =====
GITHUB_API="https://api.github.com/repos/GloriousEggroll/proton-ge-custom/releases"
WINETRICKS_URL="https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks"
CABEXTRACT_PKGBUILD="https://gitlab.archlinux.org/archlinux/packaging/packages/cabextract/-/raw/main/PKGBUILD?ref_type=heads"
CABEXTRACT_FALLBACK="1.11"
WINE_ICON_URL="https://slackbuilds.org/slackbuilds/15.0/system/wine/wine.png"

PREFIX=/opt/proton-ge
TMPDIR=/tmp/proton-ge-build
OUTPUT=${OUTPUT:-/tmp}

# Colors
BOLD="\e[1m"; CYAN="\e[96m"; GREEN="\e[92m"; RED="\e[31m"; YELLOW="\e[93m"; RESET="\e[0m"

# ===== HELPERS =====
log()  { echo -e "$1"; }
info() { log "${GREEN}OK:${RESET} $1"; }
warn() { log "${YELLOW}WARN:${RESET} $1"; }
err()  { log "${RED}ERROR:${RESET} $1"; }
die()  { err "$1"; cleanup; }

cleanup() { rm -rf "$TMPDIR"; exit "${1:-1}"; }
trap 'cleanup 1' INT TERM

fetch() {
    local url="$1" out="$2"
    if command -v wget >/dev/null; then
        wget -q --show-progress "$url" -O "$out" 2>&1
    else
        curl -fL --progress-bar "$url" -o "$out"
    fi
}

fetch_silent() {
    local url="$1" out="$2"
    if command -v wget >/dev/null; then
        wget -q "$url" -O "$out" 2>/dev/null
    else
        curl -fsL "$url" -o "$out" 2>/dev/null
    fi
}

fetch_stdout() {
    local url="$1"
    if command -v curl >/dev/null; then
        curl -fsL --max-time 10 "$url" 2>/dev/null
    else
        wget -qO- --timeout=10 "$url" 2>/dev/null
    fi
}

# ===== CHECKS =====
check_root() {
    [ "$(id -u)" -eq 0 ] || { err "Must run as root"; exit 1; }
}

check_arch() {
    local arch=$(uname -m)
    [ "$arch" = "x86_64" ] || { err "Unsupported architecture: $arch (only x86_64)"; exit 1; }
}

check_multilib() {
    log "\n${BOLD}Checking multilib...${RESET}"
    local ok=0

    [ -e /lib/ld-linux.so.2 ] && { info "/lib/ld-linux.so.2"; ok=1; }
    [ -e /lib/libc.so.6 ]     && { info "/lib/libc.so.6 (32-bit)"; ok=1; }

    if command -v gcc >/dev/null; then
        if printf 'int main(){return 0;}\n' | gcc -m32 -x c - -o /tmp/.mlc 2>/dev/null; then
            info "gcc -m32 works"; ok=1
            rm -f /tmp/.mlc
        fi
    fi

    if [ $ok -eq 0 ]; then
        warn "multilib not detected — 32-bit Windows apps may fail"
        warn "activate multilib-lite module before running Wine"
    else
        info "multilib ready for 32-bit Windows apps"
    fi
}

# ===== DETECT VERSIONS =====
detect_cabextract_version() {
    log "Detecting latest cabextract version..."
    local content=$(fetch_stdout "$CABEXTRACT_PKGBUILD")
    local ver=$(echo "$content" | grep -E '^pkgver=' | head -1 | sed 's/^pkgver=//')
    CABEXTRACT_VERSION=${ver:-$CABEXTRACT_FALLBACK}
    CABEXTRACT_URL="https://www.cabextract.org.uk/cabextract-${CABEXTRACT_VERSION}.tar.gz"
    [ -n "$ver" ] && info "cabextract $CABEXTRACT_VERSION" || warn "fallback to $CABEXTRACT_VERSION"
}

detect_proton_version() {
    [ -n "${PROTON_GE_VERSION:-}" ] && return
    log "Fetching latest Proton-GE release..."
    local json=$(fetch_stdout "$GITHUB_API/latest")
    [ -z "$json" ] && die "failed to fetch release info"
    PROTON_GE_VERSION=$(echo "$json" | grep -oE '"tag_name": *"[^"]+"' | head -1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/')
    [ -z "$PROTON_GE_VERSION" ] && die "could not detect version"
    info "latest version: $PROTON_GE_VERSION"
}

# ===== FETCH PROTON =====
fetch_proton() {
    if [ -n "${LOCAL_ARCHIVE:-}" ] && [ -f "$LOCAL_ARCHIVE" ]; then
        ARCHIVE_NAME=$(basename "$LOCAL_ARCHIVE")
        log "\nUsing local: ${BOLD}$LOCAL_ARCHIVE${RESET}"
        cp -f "$LOCAL_ARCHIVE" "$TMPDIR/$ARCHIVE_NAME"
        return
    fi

    detect_proton_version

    log "Fetching release info for ${BOLD}$PROTON_GE_VERSION${RESET}..."
    local json=$(fetch_stdout "$GITHUB_API/tags/$PROTON_GE_VERSION")
    [ -z "$json" ] && die "failed to fetch release info for $PROTON_GE_VERSION"

    local url=$(echo "$json" | grep -oE '"browser_download_url": *"[^"]*-x86_64\.tar\.gz"' | \
                head -1 | sed 's/.*"\([^"]*\)".*/\1/')
    [ -z "$url" ] && die "no x86_64 asset found in release $PROTON_GE_VERSION"

    ARCHIVE_NAME=$(basename "$url")
    log "Downloading: ${BOLD}$ARCHIVE_NAME${RESET}"
    fetch "$url" "$TMPDIR/$ARCHIVE_NAME" || die "download failed"
}

# ===== EXTRACT & INSTALL =====
extract_proton() {
    log "\nExtracting..."
    cd "$TMPDIR" || die "cannot cd to $TMPDIR"
    tar xf "$ARCHIVE_NAME" || die "extract failed"

    local rel_dir=$(find . -maxdepth 1 -type d \( -name 'GE-Proton*' -o -name 'Proton*' \) | head -1)
    [ -z "$rel_dir" ] && die "Proton directory not found"

    # Make path absolute to avoid issues with cd in other functions
    PROTON_DIR="$TMPDIR/$(basename "$rel_dir")"

    WINE_DIR="$PROTON_DIR/files"
    [ -d "$WINE_DIR" ] && [ -x "$WINE_DIR/bin/wine" ] || die "Wine not found in $WINE_DIR/bin/"

    WINE_VER_DISPLAY=$(basename "$PROTON_DIR")
    WINE_VER_CLEAN=$(echo "$WINE_VER_DISPLAY" | sed -E 's/-(x86_64|aarch64)$//')
    info "Proton: $WINE_VER_DISPLAY"

    echo
    read -p "Build xzm from $WINE_VER_DISPLAY? [y/n] " -n 1 -r -s
    echo
    [[ $REPLY =~ ^[Yy]$ ]] || cleanup 0
}

install_proton() {
    log "\nInstalling to package..."
    PKG=$TMPDIR/package-proton-ge
    rm -rf "$PKG"
    mkdir -p "$PKG/$PREFIX"
    cp -a "$PROTON_DIR" "$PKG/$PREFIX/" || die "copy failed"
    chown -R root:root "$PKG"

    PROTON_FULL_DIR="$PREFIX/$WINE_VER_DISPLAY"
    WINE_BIN_DIR="$PROTON_FULL_DIR/files/bin"
    WINE_LIB_DIR="$PROTON_FULL_DIR/files/lib"
    WINE_LIB64_DIR="$PROTON_FULL_DIR/files/lib64"
}

# ===== CLEANUP (minimal, safe) =====
cleanup_module() {
    log "Cleaning up unnecessary files..."
    local pdir="$PKG/$PROTON_FULL_DIR"
    local fdir="$pdir/files"
    local before=$(du -sh "$PKG" | cut -f1)

    # Top-level Proton directory
    rm -f "$pdir/user_settings.sample.py"
    rm -f "$pdir/LICENSE" "$pdir/LICENSE.OFL" "$pdir/PATENTS.AV1"

    # Wine files/ directory: docs, man pages
    rm -rf "$fdir/share/doc" "$fdir/share/man" "$fdir/share/info"

    # NOTE: locales are handled separately in setup_locale()

    # Wine's own desktop entries (we have our own)
    rm -rf "$fdir/share/applications"

    # Sample files
    find "$fdir/share" -iname '*.sample' -delete 2>/dev/null
    find "$fdir/share" -iname '*.example' -delete 2>/dev/null

    # Python bytecode cache
    find "$PKG" -name '*.pyc' -delete 2>/dev/null
    find "$PKG" -name '__pycache__' -type d -exec rm -rf {} + 2>/dev/null

    # Empty directories
    find "$PKG" -type d -empty -delete 2>/dev/null

    local after=$(du -sh "$PKG" | cut -f1)
    info "cleanup done: $before → $after"
}

# ===== LOCALE SELECTION =====
setup_locale() {
    local ldir="$PKG/$PROTON_FULL_DIR/files/share/locale"
    [ -d "$ldir" ] || return 0

    log "\n${BOLD}Wine locale setup${RESET}"

    local locales=($(ls "$ldir" 2>/dev/null | sort))
    local count=${#locales[@]}
    [ $count -eq 0 ] && { warn "no locales found"; return 0; }

    # Show numbered list (5 per line)
    log "Available locales ($count):"
    local i=1
    for loc in "${locales[@]}"; do
        printf "  %2d) %-10s" "$i" "$loc"
        [ $((i % 5)) -eq 0 ] && echo
        i=$((i + 1))
    done
    echo

    # Ask until valid input
    local choice=""
    while true; do
        echo
        read -p "Locale number to keep (English always kept) [1-$count, Enter=English only]: " choice

        # Empty → English only
        if [ -z "$choice" ]; then
            cd "$ldir" || return 0
            for loc in *; do
                case "$loc" in en|en_*) ;; *) rm -rf "$loc" ;; esac
            done
            cd "$TMPDIR" || true
            info "only English kept"
            return 0
        fi

        # Must be a number in range
        if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$count" ]; then
            break
        fi

        warn "invalid input, try again"
    done

    # Remove all except English + chosen locale
    local target="${locales[$((choice - 1))]}"
    log "Removing all locales except English and ${BOLD}$target${RESET}..."
    cd "$ldir" || return 0
    for loc in *; do
        case "$loc" in en|en_*|$target) ;; *) rm -rf "$loc" ;; esac
    done
    cd "$TMPDIR" || true
    info "kept: en + $target"
}

# ===== CONFIGURE SYSTEM INTEGRATION =====
setup_ldconf() {
    mkdir -p "$PKG/etc/ld.so.conf.d"
    cat > "$PKG/etc/ld.so.conf.d/proton-ge.conf" <<EOF
$WINE_LIB_DIR
$WINE_LIB64_DIR
$WINE_LIB_DIR/wine
$WINE_LIB64_DIR/wine
EOF
}

setup_profile() {
    mkdir -p "$PKG/etc/profile.d"
    cat > "$PKG/etc/profile.d/proton-ge.sh" <<EOF
# Proton-GE for Porteus + Steam
export LD_LIBRARY_PATH="$WINE_LIB64_DIR:$WINE_LIB_DIR:/usr/lib:/usr/lib64:/lib:\${LD_LIBRARY_PATH:-}"
case ":\$PATH:" in *":$WINE_BIN_DIR:"*) ;; *) PATH="\$PATH:$WINE_BIN_DIR" ;; esac
export PATH
export WINEPREFIX="\${WINEPREFIX:-\$HOME/.wine}"
# Register in Steam for all users on login
if [ -d "$PROTON_FULL_DIR" ] && [ -n "\$HOME" ]; then
    for r in "\$HOME/.steam/root" "\$HOME/.local/share/Steam" "\$HOME/.steam/steam"; do
        if [ -d "\$r" ]; then
            d="\$r/compatibilitytools.d"
            mkdir -p "\$d" 2>/dev/null
            ln -sfn "$PROTON_FULL_DIR" "\$d/$WINE_VER_DISPLAY" 2>/dev/null
        fi
    done
fi
EOF
    chmod 755 "$PKG/etc/profile.d/proton-ge.sh"

    cat > "$PKG/etc/profile.d/proton-ge.csh" <<EOF
if ( -d "$WINE_BIN_DIR" ) setenv PATH "\${PATH}:$WINE_BIN_DIR"
EOF
    chmod 755 "$PKG/etc/profile.d/proton-ge.csh"
}

# ===== WINE WRAPPERS =====
setup_wine_wrappers() {
    log "Setting up Wine symlinks and wrappers..."
    mkdir -p "$PKG/usr/bin"

    # Symlinks for main binaries (needed for `file` detection by winetricks)
    ln -sf "$WINE_BIN_DIR/wine" "$PKG/usr/bin/wine"
    [ -x "$PKG/$WINE_BIN_DIR/wineserver" ] && ln -sf "$WINE_BIN_DIR/wineserver" "$PKG/usr/bin/wineserver"

    # wine64: symlink to wine64 or fallback to wine (WoW64 unified)
    if [ -x "$PKG/$WINE_BIN_DIR/wine64" ]; then
        ln -sf "$WINE_BIN_DIR/wine64" "$PKG/usr/bin/wine64"
    else
        ln -sf "$WINE_BIN_DIR/wine" "$PKG/usr/bin/wine64"
    fi

    # Utilities: symlink if real binary exists, else create wrapper calling `wine <util>`
    for util in winecfg wineboot wineconsole winemine regedit taskmgr uninstaller msidb notepad iexplore control; do
        if [ -x "$PKG/$WINE_BIN_DIR/$util" ]; then
            ln -sf "$WINE_BIN_DIR/$util" "$PKG/usr/bin/$util"
        else
            cat > "$PKG/usr/bin/$util" <<EOF
#!/bin/sh
exec /usr/bin/wine $util "\$@"
EOF
            chmod 755 "$PKG/usr/bin/$util"
        fi
    done
}

# ===== WINETRICKS =====
install_winetricks() {
    log "Installing winetricks..."
    mkdir -p "$PKG/usr/local/bin" "$PKG/usr/share/applications"

    if ! fetch_silent "$WINETRICKS_URL" "$PKG/usr/local/bin/winetricks"; then
        warn "failed to download winetricks"; return
    fi
    chmod 755 "$PKG/usr/local/bin/winetricks"
    info "winetricks installed"

    cat > "$PKG/usr/local/bin/winetricks-wine" <<'EOF'
#!/bin/sh
exec /usr/local/bin/winetricks "$@"
EOF
    chmod 755 "$PKG/usr/local/bin/winetricks-wine"

    cat > "$PKG/usr/share/applications/winetricks.desktop" <<'EOF'
[Desktop Entry]
Name=Winetricks
Name[ru]=Winetricks
GenericName=Wine Package Manager
Comment=Install Windows libraries and components for Wine
Comment[ru]=Установка Windows-библиотек и компонентов для Wine
Keywords=wine;winetricks;install;
Icon=wine
Exec=winetricks --gui
TryExec=winetricks
Terminal=false
Type=Application
Categories=System;PackageManager;Settings;
EOF
    chmod 644 "$PKG/usr/share/applications/winetricks.desktop"
}

# ===== CABEXTRACT =====
build_cabextract() {
    log "Building cabextract ${BOLD}$CABEXTRACT_VERSION${RESET}..."
    local dir="$TMPDIR/cabextract-$CABEXTRACT_VERSION"

    if ! fetch "$CABEXTRACT_URL" "$TMPDIR/cabextract.tar.gz"; then
        warn "failed to download cabextract"; return
    fi

    tar -xzf "$TMPDIR/cabextract.tar.gz" -C "$TMPDIR" 2>/dev/null
    if [ ! -d "$dir" ] || [ ! -f "$dir/configure" ]; then
        warn "cabextract extraction failed"; return
    fi

    (
        cd "$dir" || exit 1
        CFLAGS="-O2 -pipe" ./configure --prefix=/usr >/dev/null 2>&1 || exit 1
        make -j$(nproc 2>/dev/null || echo 2) >/dev/null 2>&1 || exit 1
        [ -x cabextract ] || exit 1
        mkdir -p "$PKG/usr/bin"
        install -m 755 cabextract "$PKG/usr/bin/cabextract"
    )

    if [ -x "$PKG/usr/bin/cabextract" ]; then
        info "cabextract $CABEXTRACT_VERSION built"
    else
        warn "cabextract build failed (some winetricks verbs won't work)"
    fi

    rm -rf "$dir" "$TMPDIR/cabextract.tar.gz"
}

# ===== DESKTOP ENTRIES =====
create_desktop_entries() {
    mkdir -p "$PKG/usr/share/applications" "$PKG/usr/share/pixmaps"

    cat > "$PKG/usr/share/applications/winecfg.desktop" <<'EOF'
[Desktop Entry]
Name=Wine Configuration
Name[ru]=Настройка Wine
GenericName=Windows Compatibility Layer
Comment=Configure the Wine Windows compatibility layer
Comment[ru]=Настройка слоя совместимости Wine
Keywords=wine;windows;configuration;
Icon=wine
Exec=winecfg
TryExec=winecfg
Terminal=false
Type=Application
StartupNotify=false
Categories=System;Emulator;Settings;X-XFCE-SettingsDialog;
EOF
    chmod 644 "$PKG/usr/share/applications/winecfg.desktop"

    # Try to find icon in package, otherwise download
    local icon=""
    for p in "$PKG/$PROTON_FULL_DIR/files/share/icons/hicolor/256x256/apps/wine.png" \
             "$PKG/$PROTON_FULL_DIR/files/share/icons/hicolor/48x48/apps/wine.png"; do
        [ -f "$p" ] && { icon="$p"; break; }
    done
    [ -z "$icon" ] && fetch_silent "$WINE_ICON_URL" "$PKG/usr/share/pixmaps/wine.png" 2>/dev/null
}

# ===== PACKAGE INFO =====
create_pkginfo() {
    local pkginfo_dir="$PKG/var/lib/pkgtools/packages"
    mkdir -p "$pkginfo_dir"

    local safe_ver=${PROTON_GE_VERSION:-$WINE_VER_CLEAN}
    local pkginfo_file="$pkginfo_dir/proton-ge-${safe_ver}-x86_64-1ge"

    cat > "$pkginfo_file" <<EOF
PACKAGE NAME:  proton-ge-${safe_ver}-x86_64-1ge
COMPRESSED PACKAGE SIZE:     $(du -sh "$PKG" | cut -f1)
PACKAGE LOCATION: GloriousEggroll Proton-GE (GitHub)
PACKAGE DESCRIPTION:
proton-ge: Proton-GE (Steam Play compatibility tool + Wine)
proton-ge:
proton-ge: Full Proton-GE by GloriousEggroll for use with Steam,
proton-ge: plus Wine symlinks, winetricks and cabextract for
proton-ge: running Windows games and apps outside Steam.
proton-ge: Automatically registers in Steam on user login.
proton-ge: Requires multilib-lite for 32-bit Windows apps.
proton-ge:
proton-ge: https://github.com/GloriousEggroll/proton-ge-custom
proton-ge:
FILE LIST:
EOF

    (cd "$PKG" && find * | grep -v var/lib/pkgtools >> "$pkginfo_file")
}

# ===== BUILD XZM =====
build_xzm() {
    local safe_ver=${PROTON_GE_VERSION:-$WINE_VER_CLEAN}
    local xzm_file="$OUTPUT/proton-ge-${safe_ver}-x86_64.xzm"

    log "\nBuilding: ${BOLD}$xzm_file${RESET}"
    dir2xzm "$PKG" "$xzm_file" || die "dir2xzm failed"

    if [ -f "$xzm_file" ]; then
        local size=$(du -h "$xzm_file" | cut -f1)
        log "\n${BOLD}Module ready: ${GREEN}${BOLD}$xzm_file${RESET}"
        log "${BOLD}Size: ${CYAN}$size${RESET}"
        log "${BOLD}Copy to /porteus/modules/ to survive reboot.${RESET}\n"

        log "${BOLD}How to use:${RESET}"
        log "  1. Activate this module + multilib-lite + Steam module"
        log "  2. ${CYAN}In Steam:${RESET} game → Properties → Compatibility → Force Proton → ${GREEN}$WINE_VER_DISPLAY${RESET}"
        log "  3. ${CYAN}Outside Steam:${RESET} ${GREEN}wine app.exe${RESET}"
        log "  4. ${CYAN}Install components:${RESET} ${GREEN}winetricks-wine corefonts vcrun2022 dxvk${RESET}"
    else
        die "module not built"
    fi
}

# ===== MAIN =====
main() {
    check_root
    check_arch
    check_multilib
    detect_cabextract_version

    rm -rf "$TMPDIR"
    mkdir -p "$TMPDIR"

    fetch_proton
    extract_proton
    install_proton
    cleanup_module
    setup_locale

    setup_ldconf
    setup_profile
    setup_wine_wrappers
    install_winetricks
    build_cabextract
    create_desktop_entries
    create_pkginfo
    build_xzm

    cleanup 0
}

main "$@"
