#!/bin/bash
set -Eeuo pipefail

PHP_MANAGER_VENDOR="AlphaGNU"
PHP_MANAGER_BUILD="AlphaGNU-2026-php85-latest"


# Standalone CWP PHP Build Manager
# No CWP php-*.sh / php-fpm-*.sh / php-rebuild.sh / CWP configure files.
# PHP sources: official php.net distributions.
# Source-built common dependencies: libzip, PCRE2, libaom, libavif.

PREFIX_DEPS="${PHP_BUILD_DEPS_PREFIX:-/opt/php-build-deps}"
BUILD_ROOT="/usr/local/src/php-build"
DEPS_SCRIPT=""
for candidate in \
    "$(dirname "$(readlink -f "$0")")/build-php-deps.sh" \
    "/usr/local/bin/build-php-deps.sh" \
    "/usr/local/php-cli-manager/bin/build-php-deps.sh"
do
    if [[ -x "$candidate" ]]; then DEPS_SCRIPT="$candidate"; break; fi
done
JOBS="${JOBS:-$(nproc)}"

SUPPORTED_BRANCHES=(7.4 8.0 8.1 8.2 8.3 8.4 8.5)

die(){ echo "ERROR: $*" >&2; exit 1; }
msg(){ echo; echo "==> $*"; }

[[ $EUID -eq 0 ]] || die "Run as root."

detect_os() {
    [[ -r /etc/os-release ]] || die "Cannot detect operating system."
    . /etc/os-release
    [[ "${ID:-}" == "almalinux" || "${ID:-}" == "rocky" || "${ID:-}" == "centos" || "${ID:-}" == "rhel" || "${ID:-}" == "cloudlinux" ]] ||
        die "This manager supports EL8/EL9 compatible systems."
    case "${VERSION_ID%%.*}" in
        8) EL_MAJOR=8 ;;
        9) EL_MAJOR=9 ;;
        *) die "Only EL8 and EL9 are supported." ;;
    esac
    echo "Detected EL${EL_MAJOR} (${PRETTY_NAME:-unknown})"
}

install_toolsets() {
    msg "Installing EL${EL_MAJOR} GCC toolsets"
    dnf -y install dnf-plugins-core >/dev/null || die "Unable to install dnf-plugins-core."
    command -v scl >/dev/null 2>&1 || dnf -y install scl-utils >/dev/null || die "Unable to install scl-utils."

    # Both toolsets are installed on EL8/EL9 so the manager can select the
    # compiler per PHP branch without changing the host's default gcc.
    dnf -y install \
        gcc-toolset-12-gcc gcc-toolset-12-gcc-c++ gcc-toolset-12-binutils \
        gcc-toolset-13-gcc gcc-toolset-13-gcc-c++ gcc-toolset-13-binutils \
        || die "GCC Toolset 12/13 packages are not available on this EL${EL_MAJOR} system."
}

select_toolchain() {
    local version="$1" branch
    branch="$(version_branch "$version")"
    TOOLCHAIN=()

    if [[ "$branch" == "7.4" ]]; then
        if (( EL_MAJOR == 8 || EL_MAJOR == 9 )); then
            TOOLCHAIN=(scl enable gcc-toolset-12 --)
            COMPILER_LABEL="GCC Toolset 12"
        else
            die "Unsupported EL major: $EL_MAJOR"
        fi
    else
        if (( EL_MAJOR == 8 || EL_MAJOR == 9 )); then
            TOOLCHAIN=(scl enable gcc-toolset-13 --)
            COMPILER_LABEL="GCC Toolset 13"
        else
            die "Unsupported EL major: $EL_MAJOR"
        fi
    fi

    local gcc_version
    gcc_version="$(${TOOLCHAIN[@]} gcc -dumpfullversion -dumpversion 2>/dev/null)" ||
        die "Unable to execute selected compiler for PHP $version."
    case "$branch" in
        7.4)
            [[ "$gcc_version" == 12.* ]] || die "PHP $version requires GCC 12, but selected compiler is $gcc_version."
            ;;
        *)
            [[ "$gcc_version" == 13.* ]] || die "PHP $version requires GCC 13, but selected compiler is $gcc_version."
            ;;
    esac

    echo "Compiler: $COMPILER_LABEL ($gcc_version)"
}

base_deps() {
    msg "Installing PHP build prerequisites"
    dnf -y install \
        autoconf automake bison re2c \
        bzip2 bzip2-devel \
        curl ca-certificates git python3 tar gzip \
        file \
        freetype-devel \
        gcc gcc-c++ \
        gettext-devel \
        gmp-devel \
        krb5-devel \
        libcurl-devel \
        libedit-devel libargon2-devel \
        libicu-devel \
        libjpeg-turbo-devel \
        libpng-devel \
        libstdc++-devel \
        libtool libtool-ltdl-devel \
        libwebp-devel \
        libX11-devel libXpm-devel \
        libxml2-devel \
        libxslt-devel \
        make \
        ncurses-devel \
        oniguruma-devel \
        openssl-devel \
        pcre2-devel \
        pkgconf-pkg-config \
        postgresql-devel \
        readline-devel \
        sqlite-devel \
        systemd-devel \
        tidy-devel \
        zlib-devel \
        || die "Base build prerequisites could not be installed."

    # Optional extensions used by the baseline.
    dnf -y install \
        aspell-devel \
        libargon2-devel \
        uw-imap-devel \
        2>/dev/null || true
}

ensure_source_deps() {
    if [[ -z "$DEPS_SCRIPT" ]]; then
        die "build-php-deps.sh not found."
    fi

    if [[ ! -f "$PREFIX_DEPS/etc/php-build-deps.env" ]]; then
        msg "Building source dependencies"
        chmod +x "$DEPS_SCRIPT"
        "$DEPS_SCRIPT"
    else
        source "$PREFIX_DEPS/etc/php-build-deps.env"
        if ! pkg-config --exists libzip || \
           ! pkg-config --exists libpcre2-8 || \
           ! pkg-config --exists 'libavif >= 0.8.2'; then
            msg "Existing dependency stack is incomplete; rebuilding missing pieces"
            chmod +x "$DEPS_SCRIPT"
            "$DEPS_SCRIPT"
        fi
    fi

    source "$PREFIX_DEPS/etc/php-build-deps.env"
    pkg-config --exists libzip || die "libzip is not available through pkg-config."
    pkg-config --exists libpcre2-8 || die "PCRE2 is not available through pkg-config."
    pkg-config --exists 'libavif >= 0.8.2' || die "libavif >= 0.8.2 is not available through pkg-config."
}

version_branch() {
    local v="$1"
    echo "$v" | awk -F. '{print $1"."$2}'
}

validate_version() {
    [[ "$1" =~ ^(7|8)\.[0-9]+\.[0-9]+$ ]] || die "Enter an exact PHP version such as 8.3.8."
    local b
    b="$(version_branch "$1")"
    case "$b" in
        7.4|8.0|8.1|8.2|8.3|8.4|8.5) ;;
        *) die "Unsupported PHP branch: $b. Supported: ${SUPPORTED_BRANCHES[*]}" ;;
    esac
}

# Baseline derived from the enabled option entries in the supplied last_build.ini.
# Version-specific filtering below removes options no longer supported by newer PHP.
baseline_flags() {
    local version="$1" kind="$2" prefix="$3"
    local b
    b="$(version_branch "$version")"

    case "$kind" in
        switcher)
            # Switcher layout is fixed and must always use these paths.
            config_path="/usr/local/php"
            scan_path="/usr/local/php/php.d"
            ;;
        selector)
            config_path="$prefix/php"
            scan_path="$prefix/php/php.d"
            ;;
        fpm)
            config_path="$prefix/php"
            scan_path="$prefix/php/php.d"
            ;;
        *) die "Unknown build kind: $kind" ;;
    esac

    FLAGS=(
        "--with-config-file-path=$config_path"
        "--enable-cgi"
        "--with-config-file-scan-dir=$scan_path"
        "--with-zlib=/usr"
        "--with-zip"
        "--enable-bcmath"
        "--enable-mbstring"
        "--enable-pcntl"
        "--enable-ftp"
        "--enable-exif"
        "--enable-calendar"
        "--enable-sysvmsg"
        "--enable-sysvsem"
        "--enable-sysvshm"
        "--with-tidy"
        "--with-curl"
        "--with-iconv"
        "--with-gmp"
        "--enable-gd"
        "--with-jpeg"
        "--with-freetype"
        "--enable-gd-jis-conv"
        "--with-webp"
        "--with-avif"
        "--with-xpm"
        "--with-openssl"
        "--with-pdo-mysql=mysqlnd"
        "--with-gettext=/usr"
        "--with-bz2=/usr"
        "--with-mysqli"
        "--enable-soap"
        "--enable-phar"
        "--with-xsl"
        "--with-kerberos"
        "--enable-posix"
        "--enable-sockets"
        "--with-external-pcre"
        "--with-libdir=lib64"
        "--with-mysql-sock=/var/lib/mysql/mysql.sock"
        "--enable-intl"
        "--with-password-argon2"
        "--with-pdo-pgsql"
        "--with-pgsql"
        "--with-pdo-sqlite"
    )

    # FPM OPcache is enabled for all supported PHP versions, including PHP 8.5.
    if [[ "$kind" == "fpm" ]]; then
        FLAGS+=( "--enable-opcache" )
    fi

    # PHP < 8.4 keeps these bundled extensions. PHP 8.4+ moved them to PECL.
    if [[ "$b" != "8.4" && "$b" != "8.5" ]]; then
        FLAGS+=("--with-pspell" "--with-imap" "--with-imap-ssl")
    fi

    # PHP 7.4+ must never receive the obsolete --with-zlib-dir option.
}

validate_flags_for_version() {
    local version="$1" kind="$2" prefix="$3" b f
    b="$(version_branch "$version")"
    baseline_flags "$version" "$kind" "$prefix"

    for f in "${FLAGS[@]}"; do
        [[ "$f" != --with-zlib-dir* ]] || die "Internal flag validation failed for PHP $version: $f"
    done

    if [[ "$b" == "8.4" || "$b" == "8.5" ]]; then
        for f in "${FLAGS[@]}"; do
            [[ "$f" != --with-imap* && "$f" != --with-pspell* ]] ||
                die "Internal flag validation failed for PHP $version: obsolete flag $f"
        done
    else
        printf '%s\n' "${FLAGS[@]}" | grep -qx -- '--with-imap' || die "PHP $version IMAP flag missing"
        printf '%s\n' "${FLAGS[@]}" | grep -qx -- '--with-pspell' || die "PHP $version PSpell flag missing"
    fi

    # Required layout checks.
    case "$kind" in
        switcher)
            [[ "${FLAGS[0]}" == "--with-config-file-path=/usr/local/php" ]] || die "Switcher config path validation failed"
            [[ "${FLAGS[2]}" == "--with-config-file-scan-dir=/usr/local/php/php.d" ]] || die "Switcher scan path validation failed"
            ;;
        selector)
            [[ "${FLAGS[0]}" == "--with-config-file-path=$prefix/php" ]] || die "Selector config path validation failed"
            [[ "${FLAGS[2]}" == "--with-config-file-scan-dir=$prefix/php/php.d" ]] || die "Selector scan path validation failed"
            ;;
        fpm)
            [[ "${FLAGS[0]}" == "--with-config-file-path=$prefix/php" ]] || die "FPM config path validation failed"
            [[ "${FLAGS[2]}" == "--with-config-file-scan-dir=$prefix/php/php.d" ]] || die "FPM scan path validation failed"
            ;;
    esac
}

self_check_supported_layouts() {
    msg "Checking PHP version/layout configuration matrix"
    local b v kind prefix
    for b in 7.4 8.0 8.1 8.2 8.3 8.4 8.5; do
        v="$b.0"
        for kind in selector switcher fpm; do
            case "$kind" in
                selector) prefix="/opt/alt/php${b/./}/usr" ;;
                switcher) prefix="/usr/local/php" ;;
                fpm) prefix="/opt/alt/php-fpm${b/./}/usr" ;;
            esac
            validate_flags_for_version "$v" "$kind" "$prefix"
        done
    done
    echo "Version/layout matrix: OK (PHP 7.4.x, 8.0.x, 8.1.x, 8.2.x, 8.3.x, 8.4.x, 8.5.x; selector/switcher/FPM)"
}

install_php() {
    local version="$1" kind="$2" prefix="$3" srcdir="$4"
    msg "Installing PHP $version ($kind)"
    make -C "$srcdir" install

    case "$kind" in
        fpm)
            mkdir -p "$prefix/php/php.d"
            if [[ ! -f "$prefix/php/php.ini" ]]; then
                if [[ -f "$srcdir/php.ini-production" ]]; then
                    cp -f "$srcdir/php.ini-production" "$prefix/php/php.ini"
                elif [[ -f "$srcdir/php.ini-development" ]]; then
                    cp -f "$srcdir/php.ini-development" "$prefix/php/php.ini"
                fi
            fi
            ;;
        *)
            mkdir -p "$prefix/etc/php.d"
            if [[ ! -f "$prefix/etc/php.ini" ]]; then
                if [[ -f "$srcdir/php.ini-production" ]]; then
                    cp -f "$srcdir/php.ini-production" "$prefix/etc/php.ini"
                elif [[ -f "$srcdir/php.ini-development" ]]; then
                    cp -f "$srcdir/php.ini-development" "$prefix/etc/php.ini"
                fi
            fi
            ;;
    esac
}


apply_php74_php80_openssl3_compat() {
    local version="$1" srcdir="$2"
    local openssl_version openssl_major

    [[ "$version" == 7.4.* || "$version" == 8.0.* ]] || return 0
    (( EL_MAJOR == 9 )) || return 0

    command -v openssl >/dev/null 2>&1 || return 0
    openssl_version="$(openssl version 2>/dev/null | awk '{print $2}')"
    openssl_major="${openssl_version%%.*}"
    [[ "$openssl_major" == "3" ]] || return 0

    msg "Applying PHP $version/OpenSSL 3 compatibility for EL9"

    # PHP 7.4.33 uses legacy RSA accessors that return const RSA * with
    # OpenSSL 3, while the legacy RSA_* APIs expect RSA *.
    export CFLAGS="${CFLAGS:-} -Wno-error=deprecated-declarations -Wno-error=discarded-qualifiers"

    # OpenSSL 3 removed RSA_SSLV23_PADDING. Guard the legacy PHP constant
    # registration when that macro is unavailable.
    if grep -q 'REGISTER_LONG_CONSTANT("OPENSSL_SSLV23_PADDING"' "$srcdir/ext/openssl/openssl.c"; then
        python3 - "$srcdir/ext/openssl/openssl.c" <<'PY2'
from pathlib import Path
import sys
p = Path(sys.argv[1])
s = p.read_text()
needle = '\tREGISTER_LONG_CONSTANT("OPENSSL_SSLV23_PADDING", RSA_SSLV23_PADDING, CONST_CS|CONST_PERSISTENT);'
if needle in s and '#ifdef RSA_SSLV23_PADDING\n' not in s:
    s = s.replace(needle, '#ifdef RSA_SSLV23_PADDING\n' + needle + '\n#endif', 1)
p.write_text(s)
PY2
    fi

    echo "PHP $version/OpenSSL 3 compatibility: enabled"
    echo "OpenSSL: $openssl_version"
}

configure_and_build() {
    local version="$1" kind="$2" prefix="$3" srcdir="$4" builddir="$5" extra="${6:-}"
    local -a cfg extra_arr

    baseline_flags "$version" "$kind" "$prefix"
    source "$PREFIX_DEPS/etc/php-build-deps.env"
    validate_flags_for_version "$version" "$kind" "$prefix"

    msg "Checking configure dependencies"
    if [[ "$(version_branch "$version")" == "8.5" ]]; then
        pkg-config --exists 'libcurl >= 7.61.0' || die "PHP 8.5 requires libcurl >= 7.61.0."
        pkg-config --exists 'libxml-2.0 >= 2.9.4' || die "PHP 8.5 requires libxml2 >= 2.9.4."
    fi
    pkg-config --exists libzip || die "libzip pkg-config check failed."
    pkg-config --exists libpcre2-8 || die "PCRE2 pkg-config check failed."
    pkg-config --exists 'libavif >= 0.8.2' || die "libavif pkg-config check failed."

    export PKG_CONFIG_PATH="$PREFIX_DEPS/lib64/pkgconfig:$PREFIX_DEPS/lib/pkgconfig:${PKG_CONFIG_PATH:-}"
    export CPPFLAGS="-I$PREFIX_DEPS/include ${CPPFLAGS:-}"
    export LDFLAGS="-L$PREFIX_DEPS/lib64 -L$PREFIX_DEPS/lib ${LDFLAGS:-}"
    export LD_LIBRARY_PATH="$PREFIX_DEPS/lib64:$PREFIX_DEPS/lib:${LD_LIBRARY_PATH:-}"

    # The first three Switcher options are intentionally fixed:
    # --with-config-file-path=/usr/local/php
    # --enable-cgi
    # --with-config-file-scan-dir=/usr/local/php/php.d
    cfg=("./configure" "${FLAGS[@]}" "--prefix=$prefix")
    if [[ "$kind" == "fpm" ]]; then
        cfg+=("--enable-fpm" "--with-fpm-systemd" "--with-fpm-user=apache" "--with-fpm-group=apache")
    fi

    if [[ -n "$extra" ]]; then
        # shellcheck disable=SC2206
        extra_arr=( $extra )
        cfg+=("${extra_arr[@]}")
    fi

    echo "Compiler: ${COMPILER_LABEL:-unknown}"
    echo "PHP: $version | Build type: $kind | Prefix: $prefix"
    msg "Running PHP configure in source directory"
    printf 'Configure command:\n  '
    printf '%q ' "${cfg[@]}"
    echo

    # Configure MUST execute from the extracted PHP source directory.
    if (cd "$srcdir" && "${TOOLCHAIN[@]}" "${cfg[@]}"); then
        :
    else
        die "PHP $version configure FAILED. Build stopped; make was NOT run."
    fi

    msg "Building PHP $version"
    (cd "$srcdir" && "${TOOLCHAIN[@]}" make -j"$JOBS") ||
        die "PHP $version make FAILED."

    msg "Installing PHP $version"
    (cd "$srcdir" && "${TOOLCHAIN[@]}" make install) ||
        die "PHP $version make install FAILED."

    case "$kind" in
        switcher)
            mkdir -p "$prefix/php.d"
            if [[ ! -f "$prefix/php.ini" && -f "$srcdir/php.ini-production" ]]; then
                cp -f "$srcdir/php.ini-production" "$prefix/php.ini"
            fi
            ;;
        selector)
            mkdir -p "$prefix/php/php.d"
            if [[ ! -f "$prefix/php/php.ini" && -f "$srcdir/php.ini-production" ]]; then
                cp -f "$srcdir/php.ini-production" "$prefix/php/php.ini"
            fi
            ;;
        fpm)
            mkdir -p "$prefix/php/php.d"
            if [[ ! -f "$prefix/php/php.ini" && -f "$srcdir/php.ini-production" ]]; then
                cp -f "$srcdir/php.ini-production" "$prefix/php/php.ini"
            fi
            ;;
    esac
}

setup_fpm() {
    local version="$1" prefix="$2"
    local branch service service_file fpm_bin fpm_conf pool_dir socket_dir

    branch="$(version_branch "$version")"
    service="php-fpm${branch/./}.service"
    service_file="/usr/lib/systemd/system/$service"
    fpm_bin="$prefix/sbin/php-fpm"
    fpm_conf="$prefix/etc/php-fpm.conf"
    pool_dir="$prefix/etc/php-fpm.d"
    socket_dir="$prefix/var/sockets"

    [[ -x "$fpm_bin" ]] || die "PHP-FPM binary missing: $fpm_bin"

    mkdir -p "$prefix/etc" "$pool_dir/users" "$socket_dir" "$prefix/var/log" "$prefix/var/run"

    # CWP-style php-fpm.conf includes normal pools and per-user pools.
    if [[ ! -s "$fpm_conf" ]]; then
    cat > "$fpm_conf" <<EOF
[global]
pid = $prefix/var/run/php-fpm.pid
error_log = $prefix/var/log/php-fpm.log
include = $pool_dir/*.conf
EOF
    fi

    if [[ ! -s "$pool_dir/users.conf" ]]; then
    cat > "$pool_dir/users.conf" <<EOF
include=$pool_dir/users/*.conf
EOF
    fi

    # CWP-style cwpsvc pool. Keep it version-specific and do not overwrite
    # an existing administrator-created pool with the same name.
    if [[ ! -s "$pool_dir/cwpsvc.conf" ]]; then
        cat > "$pool_dir/cwpsvc.conf" <<EOF
[cwpsvc]
listen = $socket_dir/cwpsvc.sock
listen.owner = cwpsvc
listen.group = cwpsvc
listen.mode = 0640
user = cwpsvc
group = cwpsvc
listen.allowed_clients = 127.0.0.1
pm = ondemand
pm.max_children = 25
pm.process_idle_timeout = 15s
request_terminate_timeout = 0s
rlimit_files = 131073
rlimit_core = unlimited
catch_workers_output = yes
env[HOSTNAME] = \$HOSTNAME
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp
env[PATH] = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
EOF
    fi

    # Ensure the service account exists when the host does not already have it.
    if ! id cwpsvc >/dev/null 2>&1; then
        useradd --system --no-create-home --shell /sbin/nologin cwpsvc ||
            die "Unable to create cwpsvc system user."
    fi
    if getent group cwpsvc >/dev/null 2>&1; then
        usermod -a -G cwpsvc cwpsvc >/dev/null 2>&1 || true
    fi

    # Test the complete configuration before creating/enabling/starting systemd.
    "$fpm_bin" -t -y "$fpm_conf" ||
        die "PHP-FPM configuration test failed for PHP $version. Service was NOT created/started."

    # Use the PHP source's service template, like CWP, when available; replace
    # prefix placeholders with this version's actual installation path.
    local template=""
    if [[ -f "$BUILD_ROOT/fpm/$version/php-$version/sapi/fpm/php-fpm.service" ]]; then
        template="$BUILD_ROOT/fpm/$version/php-$version/sapi/fpm/php-fpm.service"
    fi

    if [[ -s "$service_file" || -s "/etc/systemd/system/$service" ]]; then
        echo "Preserving existing $service unit."
    elif [[ -n "$template" ]]; then
        cp -f "$template" "$service_file"
        sed -i "s#\${exec_prefix}#$prefix#g; s#\${prefix}#$prefix#g" "$service_file"
    else
        cat > "$service_file" <<EOF
[Unit]
Description=PHP $version FPM
After=network.target

[Service]
Type=notify
PIDFile=$prefix/var/run/php-fpm.pid
ExecStart=$fpm_bin --nodaemonize --fpm-config $fpm_conf
ExecReload=/bin/kill -USR2 \$MAINPID
Restart=on-failure
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF
    fi

    systemctl daemon-reload
    systemctl enable "$service" >/dev/null ||
        die "Unable to enable $service"
    systemctl restart "$service" ||
        die "Unable to start/restart $service"

    systemctl is-active --quiet "$service" ||
        die "$service is not active after start."

    "$fpm_bin" -t -y "$fpm_conf" ||
        die "PHP-FPM post-start configuration verification failed for PHP $version."

    echo "PHP-FPM service created and active: $service"
    echo "PHP-FPM config: $fpm_conf"
    echo "PHP-FPM pool: $pool_dir/cwpsvc.conf"
    echo "PHP-FPM socket: $socket_dir/cwpsvc.sock"
}
enable_fpm_opcache() {
    local version="$1" prefix="$2"
    local ini_dir="$prefix/php/php.d"
    local ini="$ini_dir/opcache.ini"

    mkdir -p "$ini_dir"

    if [[ "$(version_branch "$version")" == "8.5" ]]; then
        cat > "$ini" <<'EOF'
; PHP Manager - FPM OPcache
opcache.enable=1
opcache.enable_cli=0
EOF
    else
        cat > "$ini" <<'EOF'
; PHP Manager - FPM OPcache
zend_extension=opcache
opcache.enable=1
opcache.enable_cli=0
EOF
    fi

    echo "FPM OPcache enabled in: $ini"
}

post_install_selector() {
    local version="$1" prefix="$2"
    mkdir -p "$prefix/php/php.d"
    [[ -x "$prefix/bin/php" ]] || die "Selector PHP binary missing: $prefix/bin/php"
    "$prefix/bin/php" -v | head -1
    "$prefix/bin/php" -m >/dev/null || die "Selector PHP module check failed."
    echo "Selector PHP installed at: $prefix"
    echo "CLI: $prefix/bin/php"
}


activate_switcher() {
    local prefix="$1" version="$2"
    local target="$prefix/bin/php"
    [[ -x "$target" ]] || die "Switcher PHP binary missing: $target"

    # Preserve an existing non-symlink CLI once, then make /usr/local/bin/php
    # point at the newly installed switcher PHP.
    if [[ -e /usr/local/bin/php && ! -L /usr/local/bin/php ]]; then
        cp -a /usr/local/bin/php "/usr/local/bin/php.cwp-manager-backup" ||
            die "Unable to back up existing /usr/local/bin/php"
    fi
    ln -sfn "$target" /usr/local/bin/php

    # Keep all active Switcher PHP executables in sync with /usr/local/php.
    # php-cgi is included because Switcher is built with --enable-cgi.
    for tool in php php-cgi phpize php-config phpdbg phar; do
        if [[ -x "$prefix/bin/$tool" ]]; then
            if [[ -e "/usr/local/bin/$tool" && ! -L "/usr/local/bin/$tool" ]]; then
                cp -a "/usr/local/bin/$tool" "/usr/local/bin/$tool.cwp-manager-backup" ||
                    die "Unable to back up existing /usr/local/bin/$tool"
            fi
            ln -sfn "$prefix/bin/$tool" "/usr/local/bin/$tool"
        fi
    done

    hash -r 2>/dev/null || true
    local got
    got="$($target -r 'echo PHP_VERSION;')"
    [[ "$got" == "$version" ]] || die "Switcher activation verification failed: expected $version, got $got"
    [[ "$(readlink -f /usr/local/bin/php)" == "$(readlink -f "$target")" ]] ||
        die "Switcher /usr/local/bin/php symlink verification failed."
    [[ -L /usr/local/bin/php-cgi && "$(readlink -f /usr/local/bin/php-cgi)" == "$(readlink -f "$prefix/bin/php-cgi")" ]] ||
        die "Switcher /usr/local/bin/php-cgi symlink verification failed."

    echo "Active CLI PHP: $got"
    echo "Active CLI path: $(readlink -f /usr/local/bin/php)"
}

post_install_switcher() {
    local version="$1" prefix="$2"
    mkdir -p "$prefix/php.d"
    [[ -x "$prefix/bin/php" ]] || die "Switcher PHP binary missing: $prefix/bin/php"
    echo "Preserving Switcher INI files; rebuild incompatible extensions before production use."
    activate_switcher "$prefix" "$version"
    systemctl try-restart httpd.service 2>/dev/null || true
    echo "Switcher PHP installed and activated at: $prefix"
}


install_pie_system_dependencies() {
    local target_version="$1"
    local package="$2"
    local package_lc="${package,,}"
    local os_major="${OS_MAJOR:-}"

    # Imagick requires the ImageMagick runtime and development headers.
    if [[ "$package_lc" == "imagick/imagick" || "$package_lc" == "imagick" ]]; then
        echo "Installing ImageMagick dependencies..."
        if command -v dnf >/dev/null 2>&1; then
            dnf -y install ImageMagick ImageMagick-devel ||
                die "Failed to install ImageMagick dependencies."
        else
            yum -y install ImageMagick ImageMagick-devel ||
                die "Failed to install ImageMagick dependencies."
        fi
        command -v magick >/dev/null 2>&1 || command -v convert >/dev/null 2>&1 ||
            die "ImageMagick installation could not be verified."
    fi


    # PIE can install extension build dependencies itself in non-interactive
    # mode. This is always enabled for our standalone manager.
    echo "PIE will auto-install required extension build dependencies."

    # Memcached PHP extension != Memcached server. When the user installs
    # php-memcached through PIE, also install/enable the memcached daemon so
    # the extension has the service it is normally used with.
    if [[ "$package_lc" == "php-memcached/php-memcached" || "$package_lc" == "memcached/memcached" || "$package_lc" == "memcached" ]]; then
        echo "Installing Memcached server..."

        if ! command -v memcached >/dev/null 2>&1; then
            if command -v dnf >/dev/null 2>&1; then
                dnf -y install memcached ||
                    die "Failed to install Memcached server."
            else
                yum -y install memcached ||
                    die "Failed to install Memcached server."
            fi
        else
            echo "Memcached server already installed: $(command -v memcached)"
        fi

        command -v memcached >/dev/null 2>&1 ||
            die "Memcached server installation could not be verified."

        local memcached_service=""
        local memcached_unit_state=""
        for candidate in memcached; do
            memcached_unit_state="$(systemctl show "${candidate}.service" -p LoadState --value 2>/dev/null || true)"
            if [[ "$memcached_unit_state" == "loaded" ]]; then
                memcached_service="$candidate"
                break
            fi
        done

        if [[ -z "$memcached_service" ]]; then
            if pgrep -x memcached >/dev/null 2>&1; then
                echo "Memcached server is running, but no systemd unit was found; leaving it untouched."
            else
                die "Memcached server binary exists, but no Memcached systemd service was found."
            fi
        else
            systemctl enable "$memcached_service" >/dev/null ||
                die "Unable to enable $memcached_service."
            systemctl start "$memcached_service" ||
                die "Unable to start $memcached_service."
            systemctl is-active --quiet "$memcached_service" ||
                die "$memcached_service is not active."
            echo "Memcached server is installed and active: $memcached_service"
        fi
    fi

    # Redis PHP extension != Redis server. For the Redis extension, install the
    # Redis server only when it is not already present. This is intentionally
    # package-specific so unrelated PHP extensions never install services.
    if [[ "$package_lc" == "redis/redis" || "$package_lc" == "phpredis/phpredis" || "$package_lc" == "redis" ]]; then
        if command -v redis-server >/dev/null 2>&1; then
            echo "Redis server already installed: $(command -v redis-server)"
        else
            echo "Redis server is required for the Redis PHP extension."
            echo "Installing Redis server on EL${os_major:-8/9}..."

            if command -v dnf >/dev/null 2>&1; then
                dnf -y install redis || true
            else
                yum -y install redis || true
            fi

            # If the normal EL repository does not provide Redis, use Redis'
            # official RPM repository for EL8/EL9.
            if ! command -v redis-server >/dev/null 2>&1; then
                [[ "$os_major" == "8" || "$os_major" == "9" ]] ||
                    die "Redis server package is unavailable for this OS."

                local repo_base
                if [[ "$os_major" == "8" ]]; then
                    repo_base="http://packages.redis.io/rpm/rockylinux8"
                else
                    repo_base="http://packages.redis.io/rpm/rockylinux9"
                fi

                cat > /etc/yum.repos.d/redis.repo <<EOF
[Redis]
name=Redis
baseurl=$repo_base
enabled=1
gpgcheck=1
EOF

                curl -fsSL https://packages.redis.io/gpg -o /tmp/redis.key ||
                    die "Failed to download Redis repository GPG key."
                rpm --import /tmp/redis.key ||
                    die "Failed to import Redis repository GPG key."

                if command -v dnf >/dev/null 2>&1; then
                    dnf -y install redis ||
                        die "Failed to install Redis server."
                else
                    yum -y install redis ||
                        die "Failed to install Redis server."
                fi
            fi
        fi

        local redis_service=""
        local unit_state=""

        # Redis' official EL8/EL9 RPM uses redis.service.
        # Older/vendor packages may use redis-server.service, so inspect
        # systemd's actual LoadState instead of parsing list-unit-files output.
        for candidate in redis redis-server; do
            unit_state="$(systemctl show "${candidate}.service" -p LoadState --value 2>/dev/null || true)"
            if [[ "$unit_state" == "loaded" ]]; then
                redis_service="$candidate"
                break
            fi
        done

        # A custom Redis installation may have no systemd unit. If it is
        # already running, do not incorrectly fail the PHP extension install.
        if [[ -z "$redis_service" ]]; then
            if pgrep -x redis-server >/dev/null 2>&1; then
                echo "Redis server is running, but no systemd unit was found; leaving it untouched."
            else
                die "Redis server binary exists, but no Redis systemd service was found."
            fi
        else
            systemctl enable "$redis_service" >/dev/null ||
                die "Unable to enable $redis_service."
            systemctl start "$redis_service" ||
                die "Unable to start $redis_service."
            systemctl is-active --quiet "$redis_service" ||
                die "$redis_service is not active."

            echo "Redis server is installed and active: $redis_service"
        fi
    fi
}

reload_pie_target_fpm() {
    local target_kind="$1"
    local target_version="$2"
    local branch service
    case "$target_kind" in
        fpm|php-fpm|php-fpm-selector) ;;
        *) return 0 ;;
    esac
    branch="$(version_branch "$target_version")"
    service="php-fpm${branch/./}.service"
    if systemctl list-unit-files "$service" >/dev/null 2>&1; then
        echo "Reloading $service after PIE extension installation..."
        if ! systemctl reload "$service" 2>/dev/null; then
            echo "FPM reload unsupported; restarting $service..."
            systemctl restart "$service" || die "Unable to restart $service after PIE installation."
        fi
        systemctl is-active --quiet "$service" || die "$service is not active after PIE installation."
        echo "$service is active."
    else
        echo "FPM service $service not found; extension installed but no service was reloaded."
    fi
}


PIE_RUNTIME_PREFIX="/usr/local/php-pie"
# Resolve once per command; never silently fall back to an old release.
PIE_RUNTIME_VERSION=""
PIE_RUNTIME_SHA256=""

resolve_latest_pie_runtime() {
    [[ -n "$PIE_RUNTIME_VERSION" && -n "$PIE_RUNTIME_SHA256" ]] && return 0
    local metadata resolved
    metadata="$(curl -fsSL --connect-timeout 15 --max-time 60 --retry 3 \
        'https://www.php.net/releases/index.php?json&version=8.5')" ||
        die "Cannot query php.net for the latest stable PHP 8.5 release."
    resolved="$(printf '%s' "$metadata" | python3 -c '
import json, re, sys
try:
    data = json.load(sys.stdin)
    version = data["version"]
    if not re.fullmatch(r"8\.5\.[0-9]+", version):
        raise ValueError("Expected a stable PHP 8.5.x release")
    source = next(x for x in data["source"] if x["filename"] == "php-" + version + ".tar.gz")
    checksum = source["sha256"]
    if not re.fullmatch(r"[a-fA-F0-9]{64}", checksum):
        raise ValueError("Invalid SHA-256")
    print(version, checksum.lower())
except (KeyError, ValueError, TypeError, StopIteration) as exc:
    sys.exit("Invalid php.net release metadata: " + str(exc))
')" || die "Cannot resolve a verified stable PHP 8.5 release."
    read -r PIE_RUNTIME_VERSION PIE_RUNTIME_SHA256 <<< "$resolved"
}

verify_pie_runtime() {
    local binary="$1" version="$2"
    [[ -x "$binary" ]] || return 1
    [[ "$("$binary" -n -r 'echo PHP_VERSION;' 2>/dev/null)" == "$version" ]] || return 1
    "$binary" -n -r 'foreach (["Phar", "openssl", "curl", "mbstring", "filter", "tokenizer", "ctype", "fileinfo", "iconv", "zlib"] as $e) { if (!extension_loaded($e)) exit(1); }' >/dev/null 2>&1
}

build_pie_runtime() (
    local version="${1:-$PIE_RUNTIME_VERSION}"
    local builddir srcdir archive url prefix
    validate_version "$version"
    [[ "$(version_branch "$version")" == "8.5" ]] || die "PIE runtime must be PHP 8.5.x. Requested: $version"

    prefix="$PIE_RUNTIME_PREFIX"
    builddir="$BUILD_ROOT/pie-runtime/$version"
    srcdir="$builddir/php-$version"
    archive="$builddir/php-$version.tar.gz"
    url="https://www.php.net/distributions/php-$version.tar.gz"

    if [[ -x "$prefix/bin/php" ]]; then
        local installed
        installed="$($prefix/bin/php -r 'echo PHP_VERSION;' 2>/dev/null || true)"
        if verify_pie_runtime "$prefix/bin/php" "$version"; then
            echo "PIE runtime already installed: $prefix/bin/php ($installed)"
            return 0
        fi
        echo "Existing PIE runtime is $installed; rebuilding PHP $version."
    fi

    select_toolchain "$version"
    mkdir -p "$builddir"
    if [[ ! -s "$archive" ]]; then
        msg "Downloading PIE runtime PHP $version from php.net"
        curl -fL --retry 5 --retry-delay 2 "$url" -o "$archive" ||
            die "Unable to download PIE runtime PHP $version from php.net."
    fi

    [[ -n "$PIE_RUNTIME_SHA256" ]] || die "Missing runtime source checksum."
    if ! printf '%s  %s\n' "$PIE_RUNTIME_SHA256" "$archive" | sha256sum -c -; then
        rm -f "$archive"
        die "PIE runtime source checksum failed; removed cached archive. Retry the command."
    fi

    rm -rf "$srcdir"
    mkdir -p "$srcdir"
    tar -xzf "$archive" -C "$srcdir" --strip-components=1
    [[ -x "$srcdir/configure" ]] || die "PIE runtime source does not contain configure: $srcdir/configure"

    # Keep the working runtime until compilation has succeeded.
    mkdir -p "$prefix"

    local -a flags=(
        "--prefix=$prefix"
        "--with-config-file-path=$prefix/etc"
        "--with-config-file-scan-dir=$prefix/etc/php.d"
        "--disable-all"
        "--enable-cli"
        "--enable-phar"
        "--with-openssl"
        "--with-curl"
        "--with-zlib"
        "--with-libxml"
        "--with-iconv"
        "--enable-mbstring"
        "--enable-filter"
        "--enable-tokenizer"
        "--enable-session"
        "--enable-ctype"
        "--enable-fileinfo"
        "--enable-pcntl"
        "--enable-posix"
        "--enable-sockets"
    )

    echo "PIE runtime compiler: $COMPILER_LABEL"
    echo "PIE runtime source: $srcdir"
    echo "PIE runtime configure: ${flags[*]}"

    # PHP's configure script lives in the extracted source directory.
    # Run configure and make from that directory, while keeping logs/build
    # artifacts under the version-specific PIE runtime build directory.
    cd "$srcdir" || {
        echo "ERROR: Unable to enter PIE runtime source directory: $srcdir" >&2
        return 1
    }

    pkg-config --exists 'libcurl >= 7.61.0' || die "PHP 8.5 requires libcurl >= 7.61.0."
    pkg-config --exists 'libxml-2.0 >= 2.9.4' || die "PHP 8.5 requires libxml2 >= 2.9.4."

    "${TOOLCHAIN[@]}" ./configure "${flags[@]}" >"$builddir/configure.log" 2>&1 || {
        echo "ERROR: PIE runtime configure failed. See $builddir/configure.log" >&2
        return 1
    }

    "${TOOLCHAIN[@]}" make -j"$JOBS" >"$builddir/make.log" 2>&1 || {
        echo "ERROR: PIE runtime make failed. See $builddir/make.log" >&2
        return 1
    }
    "${TOOLCHAIN[@]}" make install >"$builddir/install.log" 2>&1 || {
        echo "ERROR: PIE runtime install failed. See $builddir/install.log" >&2
        return 1
    }

    mkdir -p "$prefix/etc/php.d"
    [[ -f "$srcdir/php.ini-production" ]] && cp -f "$srcdir/php.ini-production" "$prefix/etc/php.ini"
    verify_pie_runtime "$prefix/bin/php" "$version" || die "Installed PIE runtime failed version/module validation."
    local actual
    actual="$($prefix/bin/php -r 'echo PHP_VERSION;' 2>/dev/null || true)"
    [[ "$actual" == "$version" ]] || die "PIE runtime version mismatch: expected $version, got $actual"
    echo "PIE runtime installed: $prefix/bin/php ($actual)"
)

ensure_pie_runtime() {
    resolve_latest_pie_runtime
    if verify_pie_runtime "$PIE_RUNTIME_PREFIX/bin/php" "$PIE_RUNTIME_VERSION"; then
        echo "PIE runtime verified: $PIE_RUNTIME_PREFIX/bin/php ($PIE_RUNTIME_VERSION)"
        return 0
    fi
    build_pie_runtime "$PIE_RUNTIME_VERSION"
}

resolve_pie_version() {
    local requested="$1" target_kind="${2:-auto}" branch prefix binary actual

    if [[ "$requested" =~ ^(7|8)\.[0-9]+\.[0-9]+$ ]]; then
        validate_version "$requested"
        echo "$requested"
        return 0
    fi

    [[ "$requested" =~ ^(7|8)\.[0-9]+$ ]] ||
        die "Enter a PHP branch such as 8.3 or an exact PHP version such as 8.3.8."

    branch="$requested"
    case "$target_kind" in
        selector|php-selector)
            prefix="/opt/alt/php${branch/./}/usr"
            ;;
        fpm|php-fpm|php-fpm-selector)
            prefix="/opt/alt/php-fpm${branch/./}/usr"
            ;;
        switcher)
            prefix="/usr/local/php"
            ;;
        auto|"")
            if [[ -x "/opt/alt/php-fpm${branch/./}/usr/bin/php" ]]; then
                prefix="/opt/alt/php-fpm${branch/./}/usr"
            elif [[ -x "/opt/alt/php${branch/./}/usr/bin/php" ]]; then
                prefix="/opt/alt/php${branch/./}/usr"
            elif [[ -x "/usr/local/php/bin/php" ]]; then
                prefix="/usr/local/php"
            else
                die "Installed PHP $requested was not found in Selector, FPM, or Switcher layout."
            fi
            ;;
        *)
            die "Unknown PIE target type '$target_kind'. Use selector, fpm, switcher, or auto."
            ;;
    esac

    binary="$prefix/bin/php"
    [[ -x "$binary" ]] || die "Target PHP binary not found: $binary"
    actual="$($binary -n -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION.".".PHP_RELEASE_VERSION;' 2>/dev/null || true)"
    [[ "$actual" =~ ^(7|8)\.[0-9]+\.[0-9]+$ ]] ||
        die "Unable to determine installed PHP version for $requested: $binary"

    validate_version "$actual"
    [[ "$(version_branch "$actual")" == "$branch" ]] || die "Requested PHP $branch but found $actual at $binary."
    echo "$actual"
}

run_pie_command() {
    local target_kind="auto" package="" arg resolved kind branch binary
    local versions=()
    local targets=()
    local all_requested=0

    (( $# >= 2 )) || die "Usage: php-manager pie <PHP-version|all> [PHP-version ...] <vendor/package> [selector|fpm|switcher]"

    while (( $# > 0 )); do
        arg="$1"
        if [[ "$arg" == "all" ]]; then
            all_requested=1
            shift
        elif [[ "$arg" =~ ^(7|8)\.[0-9]+(\.[0-9]+)?$ ]]; then
            versions+=("$arg")
            shift
        else
            package="$arg"
            shift
            break
        fi
    done

    (( all_requested || ${#versions[@]} > 0 )) || die "No valid PHP version supplied."
    [[ -n "$package" ]] || die "PIE package is required."

    if (( $# > 0 )); then
        target_kind="$1"
        shift
    fi
    (( $# == 0 )) || die "Unexpected PIE argument '$1'."

    [[ "$package" == */* ]] || die "PIE package must use vendor/package format."

    if (( all_requested )); then
        (( ${#versions[@]} == 0 )) || die "Do not combine 'all' with individual PHP versions."

        case "$target_kind" in
            selector|php-selector|fpm|php-fpm|php-fpm-selector|switcher|auto|"")
                ;;
            *)
                die "Unknown PIE target type '$target_kind'. Use selector, fpm, switcher, or auto."
                ;;
        esac

        # 'all' means every installed PHP target when no manager type is
        # specified, or every installed target of the requested manager type.
        if [[ "$target_kind" == "auto" || -z "$target_kind" ]]; then
            for kind in selector fpm switcher; do
                case "$kind" in
                    selector)
                        for binary in /opt/alt/php[0-9][0-9]/usr/bin/php; do
                            [[ -x "$binary" ]] || continue
                            branch="$(basename "$(dirname "$(dirname "$(dirname "$binary")")")")"
                            branch="${branch#php}"
                            targets+=("$kind:$branch")
                        done
                        ;;
                    fpm)
                        for binary in /opt/alt/php-fpm[0-9][0-9]/usr/bin/php; do
                            [[ -x "$binary" ]] || continue
                            branch="$(basename "$(dirname "$(dirname "$(dirname "$binary")")")")"
                            branch="${branch#php-fpm}"
                            targets+=("$kind:$branch")
                        done
                        ;;
                    switcher)
                        [[ -x /usr/local/php/bin/php ]] && targets+=("$kind:switcher")
                        ;;
                esac
            done
        else
            case "$target_kind" in
                selector|php-selector) kind=selector ;;
                fpm|php-fpm|php-fpm-selector) kind=fpm ;;
                switcher) kind=switcher ;;
            esac

            case "$kind" in
                selector)
                    for binary in /opt/alt/php[0-9][0-9]/usr/bin/php; do
                        [[ -x "$binary" ]] || continue
                        branch="$(basename "$(dirname "$(dirname "$(dirname "$binary")")")")"
                        branch="${branch#php}"
                        targets+=("$kind:$branch")
                    done
                    ;;
                fpm)
                    for binary in /opt/alt/php-fpm[0-9][0-9]/usr/bin/php; do
                        [[ -x "$binary" ]] || continue
                        branch="$(basename "$(dirname "$(dirname "$(dirname "$binary")")")")"
                        branch="${branch#php-fpm}"
                        targets+=("$kind:$branch")
                    done
                    ;;
                switcher)
                    [[ -x /usr/local/php/bin/php ]] && targets+=("$kind:switcher")
                    ;;
            esac
        fi

        ((${#targets[@]} > 0)) || die "No installed PHP targets found for '$target_kind'."

        for arg in "${targets[@]}"; do
            kind="${arg%%:*}"
            branch="${arg#*:}"
            if [[ "$kind" == "switcher" ]]; then
                resolved="$(resolve_pie_version "$(/usr/local/php/bin/php -n -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION.".".PHP_RELEASE_VERSION;' 2>/dev/null || true)" "$kind")"
            else
                resolved="$(resolve_pie_version "${branch:0:1}.${branch:1}" "$kind")"
            fi
            install_pie_extension "$resolved" "$package" "$kind"
        done
        return 0
    fi

    for arg in "${versions[@]}"; do
        resolved="$(resolve_pie_version "$arg" "$target_kind")"
        install_pie_extension "$resolved" "$package" "$target_kind"
    done
}

install_pie_extension() {
    local target_version="${1:-}"
    local package="${2:-}"
    local target_kind="${3:-auto}"
    local branch target_prefix php_target php_config phpize pie runner runner_version
    local candidate cv

    [[ -n "$target_version" ]] || read -r -p "Target PHP exact version (e.g. 8.5.10): " target_version
    validate_version "$target_version"

    [[ -n "$package" ]] || read -r -p "PIE package (e.g. apcu/apcu): " package
    [[ -n "$package" ]] || die "PIE package is required."

    # PIE itself is always executed by the dedicated PHP 8.5 runtime.
    # The requested target PHP is never used as PIE's interpreter.
    if [[ -z "${EL_MAJOR:-}" ]]; then
        detect_os
        install_toolsets
        base_deps
    fi
    ensure_pie_runtime
    install_pie_system_dependencies "$target_version" "$package"

    branch="$(version_branch "$target_version")"
    case "$target_kind" in
        selector|php-selector)
            target_prefix="/opt/alt/php${branch/./}/usr"
            ;;
        fpm|php-fpm|php-fpm-selector)
            target_prefix="/opt/alt/php-fpm${branch/./}/usr"
            ;;
        switcher)
            target_prefix="/usr/local/php"
            ;;
        auto|"")
            if [[ -x "/opt/alt/php-fpm${branch/./}/usr/bin/php" ]]; then
                target_prefix="/opt/alt/php-fpm${branch/./}/usr"
            elif [[ -x "/opt/alt/php${branch/./}/usr/bin/php" ]]; then
                target_prefix="/opt/alt/php${branch/./}/usr"
            elif [[ -x "/usr/local/php/bin/php" ]]; then
                target_prefix="/usr/local/php"
            else
                die "Installed PHP $target_version was not found in Selector, FPM, or Switcher layout."
            fi
            ;;
        *)
            die "Unknown PIE target type '$target_kind'. Use selector, fpm, switcher, or auto."
            ;;
    esac

    php_target="$target_prefix/bin/php"
    php_config="$target_prefix/bin/php-config"
    phpize="$target_prefix/bin/phpize"

    [[ -x "$php_target" ]] || die "Target PHP binary not found: $php_target"
    [[ -x "$php_config" ]] || die "Target php-config not found: $php_config"
    [[ -x "$phpize" ]] || die "Target phpize not found: $phpize"
    local actual_target
    actual_target="$("$php_target" -n -r 'echo PHP_VERSION;' 2>/dev/null)" || die "Cannot execute target PHP."
    [[ "$actual_target" == "$target_version" ]] ||
        die "Target version mismatch: requested $target_version, found $actual_target at $php_target."
    [[ "$("$php_config" --version)" == "$target_version" ]] || die "Target php-config version mismatch."
    if [[ "$target_prefix" == /opt/alt/php-fpm*/usr ]]; then target_kind=fpm; fi


    # Always execute PIE itself with the dedicated PHP 8.5 runtime. The
    # target PHP is used only through php-config/phpize so extensions are
    # compiled for the requested target installation.
    ensure_pie_runtime
    runner="$PIE_RUNTIME_PREFIX/bin/php"
    runner_version="$($runner -r 'echo PHP_VERSION;' 2>/dev/null || true)"
    [[ "$runner_version" =~ ^8\.5\. ]] || die "Dedicated PIE runtime is not PHP 8.5.x: $runner_version"

    pie="/usr/local/bin/pie"
    if [[ ! -x "$pie" ]]; then
        echo "Installing PIE to $pie..."
        curl -fsSL https://github.com/php/pie/releases/latest/download/pie.phar -o "$pie" ||
            die "Failed to download PIE."
        chmod +x "$pie"
    fi

    echo
    echo "========================================"
    echo " PIE Extension Installation"
    echo "========================================"
    echo "Target PHP : $target_version"
    echo "Target path: $target_prefix"
    echo "PIE runner : $runner ($runner_version)"
    echo "Package    : $package"
    echo

    "$runner" "$pie" install \
        --auto-install-system-dependencies \
        --with-php-config="$php_config" \
        --with-phpize-path="$phpize" \
        "$package" ||
        die "PIE extension installation failed for $package (target PHP $target_version)."

    echo
    reload_pie_target_fpm "$target_kind" "$target_version"

    echo "PIE extension installation completed for PHP $target_version."
}

clear_build_files() {
    local target="${1:-}"
    local root="/usr/local/src/php-build"

    echo
    echo "========================================"
    echo " Clear PHP Build Files"
    echo "========================================"
    echo "1) Clear all PHP source/build files"
    echo "2) Clear one PHP version"
    echo "3) Clear one build type + PHP version"
    echo "4) Back"
    echo
    read -r -p "Select [1-4]: " c

    case "$c" in
        1)
            read -r -p "Type DELETE to remove all PHP build sources: " confirm
            [[ "$confirm" == "DELETE" ]] || { echo "Cancelled."; return; }
            rm -rf -- "$root"
            mkdir -p "$root"
            echo "All PHP build source files removed."
            ;;
        2)
            read -r -p "Enter exact PHP version (e.g. 8.5.3): " v
            validate_version "$v"
            local found=0
            for kind in selector switcher fpm; do
                if [[ -d "$root/$kind/$v" ]]; then
                    rm -rf -- "$root/$kind/$v"
                    echo "Removed: $root/$kind/$v"
                    found=1
                fi
            done
            (( found )) || echo "No build files found for PHP $v."
            ;;
        3)
            echo "1) selector"
            echo "2) switcher"
            echo "3) fpm"
            read -r -p "Build type [1-3]: " t
            case "$t" in
                1) kind=selector ;;
                2) kind=switcher ;;
                3) kind=fpm ;;
                *) echo "Cancelled."; return ;;
            esac
            read -r -p "Enter exact PHP version (e.g. 8.5.3): " v
            validate_version "$v"
            if [[ -d "$root/$kind/$v" ]]; then
                rm -rf -- "$root/$kind/$v"
                echo "Removed: $root/$kind/$v"
            else
                echo "No build files found: $root/$kind/$v"
            fi
            ;;
        4) return ;;
        *) echo "Invalid selection." ;;
    esac
}


cleanup_builtin_mbstring_ini() {
    local prefix="$1" php_binary builtin scanned
    php_binary="$prefix/bin/php"
    builtin="$("$php_binary" -n -r 'echo extension_loaded("mbstring") ? "1" : "0";')" ||
        die "Unable to check built-in mbstring: $php_binary"
    [[ "$builtin" == "1" ]] || return 0

    # Ask this PHP installation which INI files it actually scanned.
    scanned="$("$php_binary" -r 'echo json_encode(php_ini_scanned_files() ?: "");' 2>/dev/null)" ||
        die "Unable to determine scanned PHP INI files: $php_binary"
    python3 - "$scanned" <<'PY'
import json
import re
import sys
from pathlib import Path

scanned = json.loads(sys.argv[1])
directive = re.compile(r'''^\s*extension\s*=\s*("[^"]*"|'[^']*'|[^;#\s]+)\s*(?:[;#].*)?$''', re.I)
for filename in scanned.split(','):
    path = Path(filename.strip())
    if path.name != 'mbstring.ini' or not path.is_file():
        continue
    lines = path.read_text().splitlines(keepends=True)
    redundant = []
    for index, line in enumerate(lines):
        match = directive.fullmatch(line.rstrip('\r\n'))
        if match and Path(match.group(1).strip('"\'')).name.lower() in ('mbstring', 'mbstring.so'):
            redundant.append(index)
    if not redundant:
        continue
    remaining = [line for index, line in enumerate(lines) if index not in redundant]
    if all(not line.strip() or line.lstrip().startswith((';', '#')) for line in remaining):
        path.unlink()
        print(f'Removed redundant built-in mbstring INI: {path}')
    else:
        for index in redundant:
            lines[index] = '; PHP Manager: mbstring is built in; ' + lines[index]
        path.write_text(''.join(lines))
        print(f'Disabled redundant mbstring directive; preserved settings: {path}')
PY
    "$php_binary" -r 'exit(extension_loaded("mbstring") ? 0 : 1);' >/dev/null ||
        die "PHP startup verification failed after mbstring INI cleanup: $php_binary"
}

build_one() {
    local version="$1" kind="$2" extra="${3:-}"
    local b prefix srcdir builddir archive url

    validate_version "$version"
    select_toolchain "$version"
    b="$(version_branch "$version")"

    case "$kind" in
        selector) prefix="/opt/alt/php${b/./}/usr" ;;
        fpm)      prefix="/opt/alt/php-fpm${b/./}/usr" ;;
        switcher) prefix="/usr/local/php" ;;
        *) die "Invalid build type: $kind" ;;
    esac

    builddir="$BUILD_ROOT/$kind/$version"
    srcdir="$builddir/php-$version"
    archive="$builddir/php-$version.tar.gz"
    url="https://www.php.net/distributions/php-$version.tar.gz"

    mkdir -p "$builddir"
    if [[ ! -s "$archive" ]]; then
        msg "Downloading PHP $version from php.net"
        curl -fL --retry 5 --retry-delay 2 "$url" -o "$archive" ||
            die "Unable to download PHP $version from php.net."
    fi

    rm -rf "$srcdir"
    mkdir -p "$srcdir"
    tar -xzf "$archive" -C "$srcdir" --strip-components=1
    [[ -x "$srcdir/configure" ]] || die "PHP $version source does not contain executable configure: $srcdir/configure"

    apply_php74_php80_openssl3_compat "$version" "$srcdir"
    configure_and_build "$version" "$kind" "$prefix" "$srcdir" "$builddir" "$extra"
    cleanup_builtin_mbstring_ini "$prefix"

    case "$kind" in
        fpm) enable_fpm_opcache "$version" "$prefix"
 setup_fpm "$version" "$prefix" ;;
        selector) post_install_selector "$version" "$prefix" ;;
        switcher) post_install_switcher "$version" "$prefix" ;;
    esac

    echo
    echo "PHP $version ($kind) build completed successfully."
}

usage() {
    cat <<EOF
Usage:
  php-manager
  php-manager switcher <PHP-version> [additional-configure-flags]
  php-manager selector <PHP-version> [PHP-version ...] [additional-configure-flags]
  php-manager fpm <PHP-version> [PHP-version ...] [additional-configure-flags]
  php-manager pie <PHP-version|all> [PHP-version ...] <vendor/package> [selector|fpm|switcher]
  php-manager pie-runtime

Examples:
  php-manager switcher 7.4.19
  php-manager selector 7.4.33 8.0.30 8.1.33 8.2.29 8.3.8 8.4.25 8.5.10
  php-manager fpm 7.4.33 8.2.29 8.3.8
  php-manager pie 8.5.10 apcu/apcu fpm
  php-manager pie 8.3 imagick/imagick fpm
  php-manager pie 8.0 8.1 8.2 8.3 8.4 8.5 imagick/imagick fpm
  php-manager pie all imagick/imagick
  php-manager pie all imagick/imagick fpm
  php-manager pie 7.4.33 apcu/apcu fpm
  php-manager selector 8.3.8 --enable-debug

Notes:
  Switcher accepts exactly one PHP version because /usr/local/php is the active installation.
  Selector and FPM accept multiple PHP versions and install them side-by-side.
  Configure flags are optional and should be supplied after the version list.
  In interactive mode, Selector and FPM also accept multiple space-separated versions.
  PIE always runs on the latest stable PHP 8.5.x resolved from php.net per command.
  Extension targets remain the PHP versions you request. No background auto-update is installed.

Supported PHP branches: 7.4.x, 8.0.x, 8.1.x, 8.2.x, 8.3.x, 8.4.x, 8.5.x
EOF
}

run_one_liner() {
    local requested_kind="$1" kind
    shift

    case "$requested_kind" in
        selector|php-selector) kind=selector ;;
        switcher|php-switcher) kind=switcher ;;
        fpm|php-fpm|php-fpm-selector) kind=fpm ;;
        pie|pecl|extension) run_pie_command "$@"; return 0 ;;
        pie-runtime) (( $# == 0 )) || die "pie-runtime always uses latest stable PHP 8.5.x; omit the version."; detect_os; install_toolsets; base_deps; ensure_pie_runtime; return 0 ;;
        -h|--help|help) usage; exit 0 ;;
        *) die "Unknown command '$requested_kind'. Use selector, switcher, fpm, or pie." ;;
    esac

    (( $# >= 1 )) || die "At least one PHP version is required."

    local versions=()
    local extras=()
    local arg

    # Versions are consecutive arguments matching the exact X.Y.Z form.
    # Once the first non-version argument is seen, the remainder is treated
    # as additional configure flags.
    while (( $# > 0 )); do
        arg="$1"
        if [[ "$arg" =~ ^(7|8)\.[0-9]+\.[0-9]+$ ]] && (( ${#extras[@]} == 0 )); then
            versions+=("$arg")
            shift
        else
            extras+=("$@")
            break
        fi
    done

    (( ${#versions[@]} > 0 )) || die "No valid PHP version supplied."
    if [[ "$kind" == "switcher" && ${#versions[@]} -ne 1 ]]; then
        die "PHP Switcher accepts exactly one PHP version. /usr/local/php can contain only one active Switcher build."
    fi

    detect_os
    install_toolsets
    base_deps
    ensure_source_deps
    self_check_supported_layouts

    local extra=""
    if (( ${#extras[@]} > 0 )); then
        printf -v extra '%s ' "${extras[@]}"
        extra="${extra% }"
    fi

    for arg in "${versions[@]}"; do
        echo
        echo "=================================================="
        echo "Building PHP $arg ($kind)"
        echo "=================================================="
        build_one "$arg" "$kind" "$extra"
    done
}

menu() {
    detect_os
    install_toolsets
    base_deps
    ensure_source_deps
    self_check_supported_layouts

    while true; do
        echo
        echo "========================================"
        echo " Standalone CWP PHP Build Manager"
        echo "========================================"
        echo "Supported PHP branches: 7.4.x, 8.0.x, 8.1.x, 8.2.x, 8.3.x, 8.4.x, 8.5.x"
        echo "Selector/FPM: enter one or more exact patch releases, space-separated"
        echo "Switcher: enter one exact patch release"
        echo "1) PHP Selector"
        echo "2) PHP Switcher"
        echo "3) PHP-FPM Selector"
        echo "4) PIE Extension Installer"
        echo "5) Build/verify dedicated PIE PHP 8.5 runtime"
        echo "6) Clear PHP build files"
        echo "7) Exit"
        echo
        read -r -p "Select [1-7]: " choice

        case "$choice" in
            1) kind=selector ;;
            2) kind=switcher ;;
            3) kind=fpm ;;
            4)
                read -r -p "Target PHP exact version (e.g. 8.5.10): " pie_version
                validate_version "$pie_version"
                read -r -p "PIE package (e.g. apcu/apcu): " pie_package
                read -r -p "Target type [auto/selector/fpm/switcher] (default: auto): " pie_type
                pie_type="${pie_type:-auto}"
                install_pie_extension "$pie_version" "$pie_package" "$pie_type"
                continue
                ;;
            5) ensure_pie_runtime; continue ;;
            6) clear_build_files; continue ;;
            7) exit 0 ;;
            *) echo "Invalid selection."; continue ;;
        esac

        if [[ "$kind" == "switcher" ]]; then
            read -r -p "Enter exact PHP version (e.g. 8.3.8): " version
            validate_version "$version"
            versions=("$version")
        else
            read -r -p "Enter exact PHP version(s), space-separated (e.g. 7.4.33 8.2.29 8.5.10): " version_line
            read -r -a versions <<< "$version_line"
            (( ${#versions[@]} > 0 )) || { echo "No PHP versions supplied."; continue; }
            for version in "${versions[@]}"; do
                validate_version "$version"
            done
        fi

        read -r -p "Additional configure flags (optional): " extra
        for version in "${versions[@]}"; do
            echo
            echo "=================================================="
            echo "Building PHP $version ($kind)"
            echo "=================================================="
            build_one "$version" "$kind" "$extra"
        done
    done
}

if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then return 0; fi

if (( $# > 0 )); then
    if [[ "$1" == "-h" || "$1" == "--help" || "$1" == "help" ]]; then
        usage
        exit 0
    fi
    [[ "$1" == "pie-runtime" ]] || (( $# >= 2 )) || { usage; exit 1; }
    run_one_liner "$@"
else
    menu
fi
