#!/bin/sh
# shellcheck shell=dash
# shellcheck disable=SC3043  # local is non-POSIX but supported by all target shells
# Firefox installer script
# Usage: curl --proto '=https' --tlsv1.2 -sSf <url>/install-firefox.sh -o install-firefox.sh && sh install-firefox.sh [OPTIONS]
#    or: sh install-firefox.sh [OPTIONS]
#
# Options:
#   --channel <release|beta|devedition|nightly>   Choose Firefox channel (default: release)
#   --install-method <method>          Force install method: apt, rpm, flatpak, tarball, snap
#   --lang <locale>                    Force locale (e.g. fr, de, ja)
#   -v, --verbose                      Enable verbose output
#   -h, --help                         Show this help

set -eu

CHANNEL="release"
INSTALL_METHOD=""
LANG_OVERRIDE=""
VERBOSE=false
ARCH=""
DISTRO=""
DISTRO_VERSION=""
DETECTED_LOCALE=""

info()  { printf "[info] %s\n" "$*"; }
ok()    { printf "[ok] %s\n" "$*"; }
warn()  { printf "[warn] %s\n" "$*" >&2; }
error() { printf "[error] %s\n" "$*" >&2; exit 1; }

need_cmd() {
    if ! command -v "$1" >/dev/null 2>&1; then
        error "Required command not found: $1"
    fi
}

check_cmd() {
    command -v "$1" >/dev/null 2>&1
}

run_sudo() {
    if [ "$(id -u)" -eq 0 ]; then
        "$@"
    else
        [ "$VERBOSE" = true ] && info "Running with sudo: $*"
        sudo "$@"
    fi
}

retry() {
    local _n=3 _delay=5 _i=0
    while true; do
        if "$@"; then
            return 0
        fi
        _i=$((_i + 1))
        if [ "$_i" -ge "$_n" ]; then
            error "Command failed after $_n attempts: $*"
        fi
        warn "Attempt $_i/$_n failed; retrying in ${_delay}s..."
        sleep "$_delay"
        _delay=$((_delay * 2))
    done
}

# Finds the l10n package for $DETECTED_LOCALE given a base package name and a
# package existence check command. Tries the full locale code first (fr-FR),
# then the language-only code (fr). Sets _l10n_pkg to the found name or "".
find_l10n_package() {
    local _check_cmd="$1"
    local _base_pkg="$2"
    _l10n_pkg=""
    if [ "$DETECTED_LOCALE" = "en-US" ]; then
        return
    fi
    local _full _lang _cand
    _full=$(echo "$DETECTED_LOCALE" | tr '[:upper:]_' '[:lower:]-')
    _lang="${_full%%-*}"
    for _cand in "$_full" "$_lang"; do
        if "$_check_cmd" "${_base_pkg}-l10n-${_cand}" >/dev/null 2>&1; then
            _l10n_pkg="${_base_pkg}-l10n-${_cand}"
            return
        fi
    done
    warn "No language pack available for $DETECTED_LOCALE; installing en-US"
}

# Downloader abstraction: prefer curl, fall back to wget
download() {
    local _url="$1"
    local _output="$2"

    if check_cmd curl; then
        curl --proto '=https' --tlsv1.2 -sSf --retry 3 -C - -L -o "$_output" "$_url"
    elif check_cmd wget; then
        wget --https-only --quiet --continue -O "$_output" "$_url"
    else
        error "Neither curl nor wget found. Install one and retry."
    fi
}

# Downloader for piping to stdout (e.g. GPG keys)
download_to_stdout() {
    local _url="$1"

    if check_cmd curl; then
        curl --proto '=https' --tlsv1.2 -sSf --retry 3 "$_url"
    elif check_cmd wget; then
        wget --https-only --quiet -O - "$_url"
    else
        error "Neither curl nor wget found. Install one and retry."
    fi
}

rpm_pkg_exists() {
    local _mgr="$1"
    local _name="$2"
    case "$_mgr" in
        dnf|yum)  "$_mgr" info "$_name" >/dev/null 2>&1 ;;
        zypper)   zypper --non-interactive search --match-exact --type package "$_name" >/dev/null 2>&1 ;;
        *)        return 1 ;;
    esac
}

# Check that a URL is reachable before relying on it
check_url() {
    local _url="$1"

    if check_cmd curl; then
        curl --proto '=https' --tlsv1.2 -sSf --head --retry 2 "$_url" >/dev/null 2>&1
    elif check_cmd wget; then
        wget --https-only --spider --quiet "$_url" 2>/dev/null
    else
        # Unreachable in practice: main() checks for curl/wget before calling
        # any install function that invokes check_url.
        return 1
    fi
}

usage() {
    cat <<'EOF'
Firefox installer script
Usage: curl --proto '=https' --tlsv1.2 -sSf <url>/install-firefox.sh -o install-firefox.sh && sh install-firefox.sh [OPTIONS]
   or: sh install-firefox.sh [OPTIONS]

Options:
  --channel <release|beta|devedition|nightly>   Choose Firefox channel (default: release)
  --install-method <method>          Force install method: apt, rpm, flatpak, tarball, snap
  --lang <locale>                    Force locale (e.g. fr, de, ja)
  -v, --verbose                      Enable verbose output (shell trace + sudo command logging)
  -h, --help                         Show this help
EOF
    exit 0
}

parse_args() {
    while [ $# -gt 0 ]; do
        case "$1" in
            --channel)
                shift
                CHANNEL="${1:?Missing channel value}"
                case "$CHANNEL" in
                    release|beta|devedition|nightly) ;;
                    *) error "Invalid channel '$CHANNEL'. Must be: release, beta, devedition, nightly" ;;
                esac
                ;;
            --install-method)
                shift
                INSTALL_METHOD="${1:?Missing install method value}"
                case "$INSTALL_METHOD" in
                    apt|rpm|flatpak|snap|tarball) ;;
                    *) error "Invalid install method '$INSTALL_METHOD'. Must be: apt, rpm, flatpak, snap, tarball" ;;
                esac
                ;;
            --lang)
                shift
                LANG_OVERRIDE="${1:?Missing lang value}"
                ;;
            -v|--verbose)
                VERBOSE=true
                ;;
            -h|--help)
                usage
                ;;
            *)
                error "Unknown option: $1"
                ;;
        esac
        shift
    done
}

detect_os() {
    need_cmd uname

    case "$(uname -s)" in
        Linux) ;;
        *)
            warn "Diagnostic info for bug report:"
            warn "  uname -a: $(uname -a)"
            [ -f /etc/os-release ] && warn "  os-release: $(cat /etc/os-release)"
            error "Unsupported operating system: $(uname -s) (this script only supports Linux)"
            ;;
    esac

    case "$(uname -m)" in
        x86_64|amd64)  ARCH="x86_64" ;;
        aarch64|arm64) ARCH="aarch64" ;;
        *)
            warn "Diagnostic info for bug report:"
            warn "  uname -a: $(uname -a)"
            error "Unsupported architecture: $(uname -m)"
            ;;
    esac

    local _os_release="${_OS_RELEASE:-/etc/os-release}"
    if [ -f "$_os_release" ]; then
        # shellcheck source=/dev/null
        . "$_os_release"
        DISTRO="${ID:-unknown}"
        DISTRO_VERSION="${VERSION_ID:-}"
        info "Detected: ${PRETTY_NAME:-$DISTRO} ($ARCH)"
    else
        DISTRO="unknown"
        warn "Could not detect Linux distribution"
    fi
}

detect_locale() {
    if [ -n "$LANG_OVERRIDE" ]; then
        DETECTED_LOCALE="$LANG_OVERRIDE"
        info "Using forced locale: $DETECTED_LOCALE"
        return
    fi

    local _raw_locale=""
    _raw_locale="${LC_ALL:-${LC_MESSAGES:-${LANG:-en_US.UTF-8}}}"
    _raw_locale="${_raw_locale%%.*}"  # strip .UTF-8
    _raw_locale="${_raw_locale%%@*}"  # strip @modifier

    case "$_raw_locale" in
        C|POSIX|"") DETECTED_LOCALE="en-US" ;;
        *_*)
            local _lang="${_raw_locale%%_*}"
            local _region="${_raw_locale##*_}"
            DETECTED_LOCALE="${_lang}-${_region}"
            ;;
        *)
            DETECTED_LOCALE="$_raw_locale"
            ;;
    esac
    info "Detected locale: $DETECTED_LOCALE"
}

channel_to_package() {
    case "$CHANNEL" in
        release)    echo "firefox" ;;
        beta)       echo "firefox-beta" ;;
        devedition) echo "firefox-devedition" ;;
        nightly)    echo "firefox-nightly" ;;
        *)          error "Unsupported channel: $CHANNEL" ;;
    esac
}

apt_source_format() {
    case "$DISTRO" in
        debian)
            if [ -n "$DISTRO_VERSION" ] && \
               dpkg --compare-versions "$DISTRO_VERSION" ge "13" 2>/dev/null; then
                echo "deb822"; return
            fi
            ;;
        ubuntu)
            if [ -n "$DISTRO_VERSION" ] && \
               dpkg --compare-versions "$DISTRO_VERSION" ge "25.10" 2>/dev/null; then
                echo "deb822"; return
            fi
            ;;
    esac
    echo "list"
}

# Returns 0 if the system supports the modern signed-by keyring (Debian 11+,
# Ubuntu 22.04+), 1 if it requires the legacy apt-key path.
apt_use_modern_keyring() {
    case "$DISTRO" in
        debian)
            if [ -n "$DISTRO_VERSION" ] && \
               dpkg --compare-versions "$DISTRO_VERSION" lt "11" 2>/dev/null; then
                return 1
            fi
            ;;
        ubuntu)
            if [ -n "$DISTRO_VERSION" ] && \
               dpkg --compare-versions "$DISTRO_VERSION" lt "22.04" 2>/dev/null; then
                return 1
            fi
            ;;
    esac
    return 0
}

apt_verify_gpg_fingerprint() {
    local _keyring="$1"
    local _expected="35BAA0B33E9EB396F59CA838C0BA5CE6DC6315A3"
    local _actual
    _actual="$(gpg --with-colons --show-keys "$_keyring" 2>/dev/null \
        | awk -F: '/^fpr:/{print $10; exit}')"
    if [ "$_actual" != "$_expected" ]; then
        run_sudo rm -f "$_keyring"
        error "GPG key fingerprint mismatch: expected $_expected, got $_actual"
    fi
    info "GPG key fingerprint verified: $_actual"
}

apt_write_source_entry() {
    local _keyring="$1"
    local _repo_url="$2"
    if [ "$(apt_source_format)" = "deb822" ]; then
        # ${_repo_url} and ${_keyring} are interpolated — safe because both are
        # hardcoded Mozilla URLs set by the caller, never user-controlled input.
        run_sudo tee /etc/apt/sources.list.d/mozilla.sources >/dev/null <<SRCEOF
Types: deb
URIs: ${_repo_url}
Suites: mozilla
Components: main
Signed-By: ${_keyring}
SRCEOF
    else
        echo "deb [signed-by=$_keyring] $_repo_url mozilla main" \
            | run_sudo tee /etc/apt/sources.list.d/mozilla.list >/dev/null
    fi
}

# Removes any conflicting legacy Mozilla apt sources that would prevent the
# modern signed-by keyring from working correctly.
# Path variables are overrideable for testing.
apt_cleanup_legacy_sources() {
    local _legacy_key="${_APT_LEGACY_KEY:-/etc/apt/trusted.gpg.d/packages.mozilla.org.gpg}"
    local _sources_dir="${_APT_SOURCES_DIR:-/etc/apt/sources.list.d}"
    local _sources_list="${_APT_SOURCES_LIST:-/etc/apt/sources.list}"
    if [ -e "$_legacy_key" ]; then
        info "Removing legacy Mozilla keyring: $_legacy_key"
        run_sudo rm -f "$_legacy_key"
    fi
    local _f
    for _f in "$_sources_dir"/*.list "$_sources_dir"/*.sources; do
        [ -e "$_f" ] || continue
        if grep -q 'packages\.mozilla\.org' "$_f" 2>/dev/null; then
            info "Removing existing Mozilla source: $_f"
            run_sudo rm -f "$_f"
        fi
    done
    if [ -e "$_sources_list" ] \
        && grep -q 'packages\.mozilla\.org' "$_sources_list" 2>/dev/null; then
        info "Stripping Mozilla entries from $_sources_list"
        run_sudo sed -i '/packages\.mozilla\.org/d' "$_sources_list"
    fi
}

apt_setup_modern_keyring() {
    local _key_url="$1"
    local _repo_url="$2"
    apt_cleanup_legacy_sources
    run_sudo install -d -m 0755 /etc/apt/keyrings
    local _keyring="/etc/apt/keyrings/packages.mozilla.org.asc"
    if [ ! -f "$_keyring" ]; then
        local _tmpkey
        _tmpkey="$(mktemp)"
        download "$_key_url" "$_tmpkey"
        run_sudo install -m 644 "$_tmpkey" "$_keyring"
        rm -f "$_tmpkey"
        if check_cmd gpg; then
            apt_verify_gpg_fingerprint "$_keyring"
        else
            warn "gpg not found, skipping key fingerprint verification"
        fi
    fi
    apt_write_source_entry "$_keyring" "$_repo_url"
}

apt_setup_legacy_keyring() {
    local _key_url="$1"
    local _repo_url="$2"
    info "Using legacy apt-key for older system"
    local _tmpkey
    _tmpkey="$(mktemp)"
    download "$_key_url" "$_tmpkey"
    if check_cmd gpg; then
        apt_verify_gpg_fingerprint "$_tmpkey"
    else
        warn "gpg not found, skipping key fingerprint verification"
    fi
    run_sudo apt-key add "$_tmpkey"
    rm -f "$_tmpkey"
    echo "deb $_repo_url mozilla main" \
        | run_sudo tee /etc/apt/sources.list.d/mozilla.list >/dev/null
}

apt_add_priority_pin() {
    # Pin Mozilla repo higher than distro packages
    run_sudo tee /etc/apt/preferences.d/mozilla >/dev/null <<'PINEOF'
Package: *
Pin: origin packages.mozilla.org
Pin-Priority: 1000
PINEOF
}

apt_install_packages() {
    local _pkg="$1"
    local _l10n_pkg
    retry run_sudo apt-get update -qq
    # Mozilla packages most languages without a region (firefox-l10n-fr),
    # but some require it (firefox-l10n-pt-br, firefox-l10n-en-gb).
    _apt_l10n_check() { apt-cache show "$1"; }
    find_l10n_package _apt_l10n_check "$_pkg"
    if [ -n "$_l10n_pkg" ]; then
        retry run_sudo apt-get install -y "$_pkg" "$_l10n_pkg"
    else
        retry run_sudo apt-get install -y "$_pkg"
    fi
}

install_apt() {
    info "Installing Firefox ($CHANNEL) via apt..."
    need_cmd apt-get
    export DEBIAN_FRONTEND=noninteractive
    local _repo_url="https://packages.mozilla.org/apt"
    local _key_url="https://packages.mozilla.org/apt/repo-signing-key.gpg"
    if apt_use_modern_keyring; then
        apt_setup_modern_keyring "$_key_url" "$_repo_url"
    else
        apt_setup_legacy_keyring "$_key_url" "$_repo_url"
    fi
    check_url "$_repo_url/dists/mozilla/Release" || \
        error "Mozilla APT repository is not reachable at $_repo_url"
    apt_add_priority_pin
    apt_install_packages "$(channel_to_package)"
    ok "Firefox ($CHANNEL, $ARCH, $DETECTED_LOCALE) installed via apt"
}

rpm_detect_pkg_manager() {
    if check_cmd dnf; then
        echo "dnf"
    elif check_cmd yum; then
        error "yum is not supported: verifying Mozilla's package signatures requires RPM 4.18+ (GPG subkey support), but yum-based systems ship older RPM versions that cannot verify the signing key. Use --install-method flatpak or --install-method tarball instead, or upgrade to a distribution with dnf (RHEL 8+, Fedora, etc.)."
    elif check_cmd zypper; then
        echo "zypper"
    else
        error "No supported RPM package manager found (dnf, yum, zypper)"
    fi
}

rpm_add_repository() {
    local _pkg_manager="$1"
    local _repo_url="$2"
    local _gpg_key="$3"
    if [ "$_pkg_manager" = "zypper" ]; then
        # --gpgcheck-allow-unsigned-repo: Mozilla's repo metadata is not GPG-signed;
        # individual .rpm packages are verified via gpgcheck=1 at install time.
        zypper lr mozilla-firefox >/dev/null 2>&1 || \
            run_sudo zypper ar -f -p 10 --gpgcheck-allow-unsigned-repo \
                "$_repo_url" mozilla-firefox
        # Note: unlike the apt path, the downloaded key is not verified against a
        # hardcoded fingerprint — packages are verified by gpgcheck=1 instead.
        retry run_sudo zypper --gpg-auto-import-keys refresh mozilla-firefox
    else
        local _repo_file="/etc/yum.repos.d/mozilla-firefox.repo"
        if [ ! -f "$_repo_file" ]; then
            run_sudo tee "$_repo_file" >/dev/null <<REPOEOF
[mozilla-firefox]
name=Mozilla Firefox
baseurl=${_repo_url}
enabled=1
gpgcheck=1
repo_gpgcheck=0
gpgkey=${_gpg_key}
priority=10
REPOEOF
        fi
    fi
}

rpm_install_packages() {
    local _pkg_manager="$1"
    local _pkg="$2"
    local _l10n_pkg="$3"
    if [ "$_pkg_manager" = "zypper" ]; then
        # --replacefiles: on openSUSE the distro ships MozillaFirefox which owns
        # /usr/bin/firefox; zypper blocks the install without this flag.
        retry run_sudo zypper install -y --replacefiles "$_pkg"
    else
        retry run_sudo "$_pkg_manager" install -y "$_pkg"
    fi
    if [ -n "$_l10n_pkg" ]; then
        # Soft fallback: Mozilla RPM repos have fewer l10n packages than apt.
        # One attempt only — retry would exit 1 on failure, defeating the fallback.
        run_sudo "$_pkg_manager" install -y "$_l10n_pkg" 2>/dev/null || \
            warn "Could not install language pack $_l10n_pkg; Firefox is installed without it"
    fi
}

install_rpm() {
    local _pkg_manager _pkg _repo_url _gpg_key _l10n_pkg
    _pkg_manager="$(rpm_detect_pkg_manager)"
    info "Installing Firefox ($CHANNEL) via $_pkg_manager..."
    _pkg="$(channel_to_package)"
    _repo_url="https://packages.mozilla.org/rpm/firefox"
    _gpg_key="https://packages.mozilla.org/rpm/firefox/signing-key.gpg"
    check_url "$_repo_url" || error "Mozilla RPM repository is not reachable at $_repo_url"
    rpm_add_repository "$_pkg_manager" "$_repo_url" "$_gpg_key"
    _rpm_l10n_check() { rpm_pkg_exists "$_pkg_manager" "$1"; }
    find_l10n_package _rpm_l10n_check "$_pkg"
    rpm_install_packages "$_pkg_manager" "$_pkg" "$_l10n_pkg"
    ok "Firefox ($CHANNEL, $ARCH, $DETECTED_LOCALE) installed via $_pkg_manager"
}

channel_to_flatpak_branch() {
    case "$CHANNEL" in
        release)    echo "" ;;
        beta)       echo "beta" ;;
        devedition) error "Firefox Developer Edition is not available as a flatpak" ;;
        nightly)    error "Firefox Nightly is not available as a flatpak" ;;
        *)          error "Unsupported channel: $CHANNEL" ;;
    esac
}

channel_to_snap_channel() {
    case "$CHANNEL" in
        release)    echo "stable" ;;
        beta)       echo "beta" ;;
        nightly)    echo "edge" ;;
        devedition) error "Firefox Developer Edition is not available as a snap" ;;
        *)          error "Unsupported channel: $CHANNEL" ;;
    esac
}

# flathub is always added: runtimes (e.g. org.freedesktop.Platform) live only
# on flathub, not flathub-beta.
flatpak_add_remotes() {
    local _branch="$1"
    retry run_sudo flatpak remote-add --if-not-exists --system flathub \
        "https://flathub.org/repo/flathub.flatpakrepo"
    if [ -n "$_branch" ]; then
        retry run_sudo flatpak remote-add --if-not-exists --system flathub-beta \
            "https://flathub.org/beta-repo/flathub-beta.flatpakrepo"
    fi
}

flatpak_install_or_update() {
    local _branch="$1"
    local _remote _ref
    _remote="${_branch:+flathub-beta}"
    _remote="${_remote:-flathub}"
    _ref="org.mozilla.firefox${_branch:+//$_branch}"
    if flatpak info --system "$_ref" >/dev/null 2>&1; then
        retry run_sudo flatpak update --system -y "$_ref"
    else
        retry run_sudo flatpak install --system -y "$_remote" "$_ref"
    fi
}

install_flatpak() {
    info "Installing Firefox ($CHANNEL) via flatpak..."
    need_cmd flatpak
    local _branch
    _branch="$(channel_to_flatpak_branch)"
    flatpak_add_remotes "$_branch"
    flatpak_install_or_update "$_branch"
    ok "Firefox ($CHANNEL, $ARCH, $DETECTED_LOCALE) installed via flatpak"
}

install_snap() {
    info "Installing Firefox ($CHANNEL) via snap..."
    need_cmd snap
    local _channel
    _channel="$(channel_to_snap_channel)"
    if snap list firefox >/dev/null 2>&1; then
        retry run_sudo snap refresh firefox --channel="$_channel"
    else
        retry run_sudo snap install firefox --channel="$_channel"
    fi
    ok "Firefox ($CHANNEL, $ARCH, $DETECTED_LOCALE) installed via snap"
}

tarball_product_name() {
    case "$CHANNEL" in
        release)    echo "firefox-latest-ssl" ;;
        beta)       echo "firefox-beta-latest-ssl" ;;
        devedition) echo "firefox-devedition-latest-ssl" ;;
        nightly)    echo "firefox-nightly-latest-ssl" ;;
        *)          error "Unsupported channel: $CHANNEL" ;;
    esac
}

tarball_build_url() {
    local _product _os
    _product="$(tarball_product_name)"
    case "$ARCH" in
        x86_64)  _os="linux64" ;;
        aarch64) _os="linux64-aarch64" ;;
        *)       error "Unsupported architecture for tarball: $ARCH" ;;
    esac
    echo "https://download.mozilla.org/?product=${_product}&os=${_os}&lang=${DETECTED_LOCALE}"
}

tarball_resolve_final_url() {
    local _url="$1"
    if check_cmd curl; then
        curl --proto '=https' --tlsv1.2 -sSfIL -o /dev/null -w '%{url_effective}' "$_url"
    elif check_cmd wget; then
        wget --https-only --server-response --spider "$_url" 2>&1 \
            | awk '/^  Location: /{url=$2} END{print url}'
    fi
    # No else: if neither downloader is available, returns empty string.
    # Caller uses ${_final_url:-$_url} and skips GPG verification with a warning.
}

tarball_key_url() {
    local _final_url="$1"
    case "$_final_url" in
        */pub/firefox/releases/*)
            echo "$_final_url" | sed 's|\(/pub/firefox/releases/[^/]*\)/.*|\1/KEY|'
            ;;
        */pub/firefox/nightly/*)
            echo "${_final_url%/*}/KEY"
            ;;
    esac
    # Unknown path: return empty; caller will skip GPG verification with a warning.
}

tarball_verify_gpg() {
    local _tarball="$1"
    local _sig_url="$2"
    local _key_url="$3"
    local _gpgdir="$4"  # Created and cleaned up by install_tarball's EXIT trap
    local _expected="14F26682D0916CDD81E37B6D61B7B526D98F0353"
    local _actual
    chmod 700 "$_gpgdir"
    download_to_stdout "$_key_url" \
        | gpg --homedir "$_gpgdir" --import >/dev/null 2>&1
    _actual="$(gpg --homedir "$_gpgdir" --with-colons --list-keys 2>/dev/null \
        | awk -F: '/^fpr:/{print $10; exit}')"
    if [ "$_actual" != "$_expected" ]; then
        error "GPG key fingerprint mismatch for tarball: expected $_expected, got ${_actual:-<none>}"
    fi
    download "$_sig_url" "$_gpgdir/firefox.tar.asc"
    if ! gpg --homedir "$_gpgdir" --verify "$_gpgdir/firefox.tar.asc" "$_tarball" >/dev/null 2>&1; then
        error "GPG signature verification of tarball failed"
    fi
    info "Tarball GPG signature verified"
}

tarball_download_and_extract() {
    local _url="$1"
    local _tmpdir="$2"
    local _final_url
    _final_url="$(tarball_resolve_final_url "$_url")"
    download "${_final_url:-$_url}" "$_tmpdir/firefox.tar"
    if [ -n "$_final_url" ] && check_cmd gpg; then
        local _key_url
        _key_url="$(tarball_key_url "$_final_url")"
        if [ -n "$_key_url" ]; then
            local _gpgdir="$_tmpdir/gpg"
            mkdir -p "$_gpgdir"
            tarball_verify_gpg "$_tmpdir/firefox.tar" "${_final_url}.asc" "$_key_url" "$_gpgdir"
        else
            warn "Skipping GPG verification (unrecognized download URL path: $_final_url)"
        fi
    else
        warn "Skipping GPG verification ($([ -z "$_final_url" ] && echo 'could not resolve download URL' || echo 'gpg not found'))"
    fi
    tar -xf "$_tmpdir/firefox.tar" -C "$_tmpdir"
    rm -f "$_tmpdir/firefox.tar"
}

tarball_install_to() {
    local _tmpdir="$1"
    local _install_dir="$2"
    local _pkg_bin="$3"
    local _extracted_dir
    _extracted_dir="$(find "$_tmpdir" -maxdepth 1 -mindepth 1 -type d | head -1)"
    if [ -z "$_extracted_dir" ]; then
        error "Tarball extraction produced no directory in $_tmpdir"
    fi
    # Non-atomic: backup old dir, move new dir in, then remove backup.
    # A crash mid-upgrade leaves no Firefox; acceptable for an installer script.
    run_sudo mv "$_install_dir" "${_install_dir}.old" 2>/dev/null || true
    run_sudo mv "$_extracted_dir" "$_install_dir"
    run_sudo rm -rf "${_install_dir}.old"
    run_sudo ln -sf "$_install_dir/firefox" "/usr/local/bin/${_pkg_bin}"
}

install_tarball() {
    info "Installing Firefox ($CHANNEL) via tarball..."
    need_cmd tar
    local _tmpdir _pkg_bin _install_dir
    _tmpdir="$(mktemp -d)"
    # shellcheck disable=SC2064
    trap "rm -rf '$_tmpdir'" EXIT
    _pkg_bin="$(channel_to_package)"
    _install_dir="/opt/${_pkg_bin}"
    info "Downloading Firefox ($CHANNEL)..."
    tarball_download_and_extract "$(tarball_build_url)" "$_tmpdir"
    tarball_install_to "$_tmpdir" "$_install_dir" "$_pkg_bin"
    rm -rf "$_tmpdir"
    trap - EXIT
    ok "Firefox ($CHANNEL, $ARCH, $DETECTED_LOCALE) installed via tarball"
}

detect_best_method() {
    if [ -n "$INSTALL_METHOD" ]; then
        info "Using forced install method: $INSTALL_METHOD"
        return
    fi

    if check_cmd apt-get; then
        INSTALL_METHOD="apt"
    elif check_cmd dnf || check_cmd zypper; then
        INSTALL_METHOD="rpm"
    elif check_cmd snap; then
        # snap preferred over flatpak: better system integration on Ubuntu/Debian.
        INSTALL_METHOD="snap"
    elif check_cmd flatpak; then
        INSTALL_METHOD="flatpak"
    else
        INSTALL_METHOD="tarball"
    fi

    info "Selected install method: $INSTALL_METHOD"
}

install_firefox() {
    if [ "$VERBOSE" = true ]; then set -x; fi
    case "$INSTALL_METHOD" in
        apt)     install_apt ;;
        rpm)     install_rpm ;;
        flatpak) install_flatpak ;;
        snap)    install_snap ;;
        tarball) install_tarball ;;
        *)       error "Unknown install method: $INSTALL_METHOD" ;;
    esac
    if [ "$VERBOSE" = true ]; then set +x; fi
}

verify_install() {
    info "Verifying installation..."
    if [ "$INSTALL_METHOD" = "flatpak" ]; then
        local _branch _ref
        _branch="$(channel_to_flatpak_branch)"
        _ref="org.mozilla.firefox${_branch:+//$_branch}"
        if flatpak info --system "$_ref" >/dev/null 2>&1; then
            ok "Verified: Firefox flatpak ($CHANNEL) is installed"
        else
            warn "Could not verify Firefox flatpak installation"
        fi
        return
    fi
    local _pkg_bin _bin _seen _candidate
    _pkg_bin="$(channel_to_package)"
    _seen=""
    for _candidate in "firefox" "$_pkg_bin"; do
        [ "$_candidate" = "$_seen" ] && continue
        _seen="$_candidate"
        _bin="$(command -v "$_candidate" 2>/dev/null || true)"
        if [ -n "$_bin" ]; then
            local _version
            _version="$("$_bin" --version 2>/dev/null || echo "unknown version")"
            ok "$_version (at $_bin)"
            return
        fi
    done
    warn "Could not verify Firefox installation"
}

main() {
    parse_args "$@"
    info "Firefox installer - channel: $CHANNEL"

    # Verify we have a downloader before doing anything
    if ! check_cmd curl && ! check_cmd wget; then
        error "Either curl or wget is required. Install one and retry."
    fi
    need_cmd uname
    need_cmd mktemp

    detect_os
    detect_locale
    detect_best_method
    install_firefox
    verify_install
}

if [ "${INSTALL_FIREFOX_SOURCED:-}" != "1" ]; then
    main "$@"
fi
