#!/usr/bin/env bash
set -euo pipefail

INSTALL_DIR="/opt/veloramesh"
CONFIG_PATH="/Library/Application Support/VeloraMesh/client.json"
SERVER_URL="https://api.veloramesh.com:2053"
DEVICE_NAME="$(hostname)"
NETWORK_NAME=""
JOIN_SECRET=""
JOIN_SECRET_FILE=""
LISTEN_ADDR="0.0.0.0:42002"
ADVERTISE_ADDR=""
DISCOVERY_SERVERS="edge.veloramesh.com:3478"
FORWARD_BROADCAST_UDP=""
MIN_MACOS_MAJOR=13
LOG_MAX_BYTES=$((5 * 1024 * 1024))
LOG_BACKUPS=5
INSTALL_SERVICE=0
START_SERVICE=0
SKIP_CONFIG=0
SOURCE_CLIENT=""
REPLACE_SERVICE=0
STATUS=0
FRESH=0
UNINSTALL=0
PURGE=0
NON_INTERACTIVE=0
EXPLICIT_ACTION=0
MENU_ACTION=""
LAUNCHD_LABEL="${VELORAMESH_LAUNCHD_LABEL:-com.veloramesh.client}"
PLIST_PATH="${VELORAMESH_LAUNCHD_PLIST:-/Library/LaunchDaemons/com.veloramesh.client.plist}"
LOG_DIR="${VELORAMESH_LOG_DIR:-/var/log}"
STDOUT_LOG="${LOG_DIR}/veloramesh-client.log"
STDERR_LOG="${LOG_DIR}/veloramesh-client.err.log"
SECRET_DIR=""
SERVICE_WAS_LOADED=0
STAGED_CLIENT=""
PREVIOUS_CLIENT=""
PLIST_STAGING=""
PLIST_PREVIOUS=""
PLIST_HAD_PREVIOUS=0
PLIST_REPLACED=0
CLIENT_UPDATE_PENDING=0
CLIENT_REPLACED=0
PACKAGE_UPDATED_FILES=""

log() {
    printf '[veloramesh] %s\n' "$*" >&2
}

fail() {
    printf '[veloramesh] error: %s\n' "$*" >&2
    exit 1
}

require_supported_macos() {
    local version
    local major

    [[ "$(uname -s)" == "Darwin" ]] || fail "this installer requires macOS"
    command -v sw_vers >/dev/null 2>&1 || fail "unable to determine the macOS version"
    version="$(sw_vers -productVersion 2>/dev/null)" || fail "unable to determine the macOS version"
    major="${version%%.*}"
    case "${major}" in
        ""|*[!0-9]*) fail "unable to parse the macOS version: ${version}" ;;
    esac
    if (( major < MIN_MACOS_MAJOR )); then
        fail "macOS ${MIN_MACOS_MAJOR} Ventura or later is required; found ${version}"
    fi
}

usage() {
    cat <<'USAGE'
Usage: sudo bash ./install-macos-client.sh [options]

Source, path, and endpoint overrides keep the interactive action menu available.
Operation or enrollment options select the direct flow; --non-interactive never prompts for setup.

Options:
  --install-dir PATH       absolute install directory (default /opt/veloramesh)
  --config PATH            absolute client config path (default /Library/Application Support/VeloraMesh/client.json)
  --server URL             API URL override (default https://api.veloramesh.com:2053)
  --device-name NAME       device name used when joining a new config
  --network-name NAME      network to join and/or run in the LaunchDaemon
  --join-secret-file PATH  read network join secret from a local root-readable file
  --listen IP:PORT         UDP listen address for LaunchDaemon tunnel (default 0.0.0.0:42002)
  --advertise IP:PORT      explicit reported UDP endpoint
  --discovery HOST:PORT,... encrypted edge discovery endpoints (default: edge.veloramesh.com:3478)
  --forward-broadcast-udp PORTS
                          extra UDP broadcast ports/ranges to forward, e.g. 27015,2300-2400
  --source-client PATH     source veloramesh-client binary
  --skip-config            do not create or join config
  --background             install and start the LaunchDaemon tunnel service
  --service                install LaunchDaemon
  --start                  start LaunchDaemon after install
  --replace-service        unload and replace the existing LaunchDaemon definition
  --fresh                  remove LaunchDaemon, installed files, config, and service logs before guided install
  --uninstall              unload and remove the LaunchDaemon
  --purge                  with --uninstall, also remove installed files, config, and service logs
  --status                 print LaunchDaemon status after install/start
  --non-interactive        never prompt for missing install values
  -h, --help               show help
USAGE
}

need_value() {
    if [[ $# -lt 2 || -z "${2:-}" || "${2:-}" == --* ]]; then
        fail "$1 requires a value"
    fi
}


is_interactive() {
    [[ "${NON_INTERACTIVE}" -ne 1 && -t 0 && -t 1 ]]
}

prompt_value() {
    local label="$1"
    local default="${2:-}"
    local value
    if [[ -n "${default}" ]]; then
        printf '%s [%s]: ' "${label}" "${default}" >&2
    else
        printf '%s: ' "${label}" >&2
    fi
    IFS= read -r value
    if [[ -z "${value}" ]]; then
        value="${default}"
    fi
    printf '%s' "${value}"
}

prompt_required() {
    local label="$1"
    local default="${2:-}"
    local value
    while true; do
        value="$(prompt_value "${label}" "${default}")"
        if [[ -n "${value}" ]]; then
            printf '%s' "${value}"
            return
        fi
        log "${label} is required"
    done
}

count_char() {
    local value="$1"
    local needle="$2"
    local without="${value//${needle}/}"
    printf '%s' "$(( ${#value} - ${#without} ))"
}

validate_endpoint_host() {
    local value="$1"
    [[ -n "${value}" ]] || return 1
    case "${value}" in
        *[[:space:]]*|*://*|*/*|*\?*|*\#*|*,*) return 1 ;;
    esac
    if [[ "${value}" == \[* || "${value}" == *\] ]]; then
        [[ "${value}" == \[*\] ]] || return 1
        local inner="${value:1:${#value}-2}"
        [[ "${inner}" == *:* ]] || return 1
    fi
    if [[ "${value}" != \[*\] && "$(count_char "${value}" ":")" -eq 1 ]]; then
        return 1
    fi
    return 0
}

validate_endpoint_port() {
    local value="$1"
    case "${value}" in
        ""|*[!0-9]*) return 1 ;;
    esac
    [[ "${value}" -ge 1 && "${value}" -le 65535 ]]
}

format_endpoint_host() {
    local host="$1"
    case "${host}" in
        \[*\]) printf '%s' "${host}" ;;
        *:*) printf '[%s]' "${host}" ;;
        *) printf '%s' "${host}" ;;
    esac
}

server_host_from_url() {
    local value="${1#*://}"
    value="${value%%/*}"
    value="${value%%\?*}"
    value="${value%%\#*}"
    value="${value##*@}"
    if [[ "${value}" == \[*\]* ]]; then
        value="${value#\[}"
        printf '%s' "${value%%\]*}"
        return
    fi
    if [[ "$(count_char "${value}" ":")" -eq 1 ]]; then
        printf '%s' "${value%:*}"
        return
    fi
    printf '%s' "${value}"
}

server_port_from_url() {
    local value="${1#*://}"
    value="${value%%/*}"
    value="${value%%\?*}"
    value="${value%%\#*}"
    value="${value##*@}"
    if [[ "${value}" == \[*\]*:* ]]; then
        printf '%s' "${value##*:}"
        return
    fi
    if [[ "$(count_char "${value}" ":")" -eq 1 ]]; then
        printf '%s' "${value##*:}"
        return
    fi
    printf '443'
}

discovery_host_from_endpoint() {
    local value="${1%%,*}"
    if [[ "${value}" == \[*\]* ]]; then
        value="${value#\[}"
        printf '%s' "${value%%\]*}"
        return
    fi
    if [[ "$(count_char "${value}" ":")" -eq 1 ]]; then
        printf '%s' "${value%:*}"
        return
    fi
    printf '%s' "${value}"
}

discovery_port_from_endpoint() {
    local value="${1%%,*}"
    if [[ "${value}" == \[*\]*:* ]]; then
        printf '%s' "${value##*:}"
        return
    fi
    if [[ "$(count_char "${value}" ":")" -eq 1 ]]; then
        printf '%s' "${value##*:}"
        return
    fi
    printf '3478'
}

prompt_endpoint_host() {
    local label="$1"
    local default="$2"
    local value
    while true; do
        value="$(prompt_required "${label}" "${default}")"
        if validate_endpoint_host "${value}"; then
            printf '%s' "${value}"
            return
        fi
        log "${label} must be a host name or IP address, not a URL"
    done
}

prompt_endpoint_port() {
    local label="$1"
    local default="$2"
    local value
    while true; do
        value="$(prompt_required "${label}" "${default}")"
        if validate_endpoint_port "${value}"; then
            printf '%s' "${value}"
            return
        fi
        log "${label} must be a port between 1 and 65535"
    done
}

prompt_api_endpoint() {
    local host
    local port
    host="$(prompt_endpoint_host "API host/domain" "$(server_host_from_url "${SERVER_URL}")")"
    port="$(prompt_endpoint_port "API HTTPS port" "$(server_port_from_url "${SERVER_URL}")")"
    SERVER_URL="https://$(format_endpoint_host "${host}"):${port}"
}

prompt_discovery_endpoint() {
    local host
    local port
    host="$(prompt_endpoint_host "Edge discovery host/domain" "$(discovery_host_from_endpoint "${DISCOVERY_SERVERS}")")"
    port="$(prompt_endpoint_port "Edge UDP port" "$(discovery_port_from_endpoint "${DISCOVERY_SERVERS}")")"
    DISCOVERY_SERVERS="$(format_endpoint_host "${host}"):${port}"
}

prompt_secret() {
    local label="$1"
    local value
    while true; do
        printf '%s: ' "${label}" >&2
        IFS= read -r -s value
        printf '\n' >&2
        if [[ -n "${value}" ]]; then
            printf '%s' "${value}"
            return
        fi
        log "${label} is required"
    done
}

prompt_yes_no() {
    local label="$1"
    local default="${2:-Y}"
    local value
    local suffix="[Y/n]"
    if [[ "${default}" != "Y" ]]; then
        suffix="[y/N]"
    fi
    while true; do
        printf '%s %s: ' "${label}" "${suffix}" >&2
        IFS= read -r value
        value="$(printf '%s' "${value}" | tr '[:upper:]' '[:lower:]')"
        if [[ -z "${value}" ]]; then
            [[ "${default}" == "Y" ]]
            return
        fi
        case "${value}" in
            y|yes|e|evet) return 0 ;;
            n|no|h|hayir|hayır) return 1 ;;
        esac
    done
}

confirm_phrase() {
    local message="$1"
    local phrase="$2"
    local answer
    printf '%s Type %s to continue: ' "${message}" "${phrase}" >&2
    IFS= read -r answer
    [[ "${answer}" == "${phrase}" ]]
}

configure_interactive_action() {
    if ! is_interactive || [[ "${EXPLICIT_ACTION}" -eq 1 ]]; then
        return
    fi

    local choice
    while true; do
        cat <<MENU

VeloraMesh macOS Client Installer
1) Guided install/update
2) Network operations
3) Service operations
4) Fresh install/reset client
5) Uninstall LaunchDaemon, keep files/config
6) Purge client installation and local data
0) Exit
MENU
        printf 'Select: ' >&2
        IFS= read -r choice
        case "${choice}" in
            1) return ;;
            2) MENU_ACTION="network"; return ;;
            3) MENU_ACTION="service"; return ;;
            4) FRESH=1; return ;;
            5) UNINSTALL=1; return ;;
            6) UNINSTALL=1; PURGE=1; return ;;
            0) exit 0 ;;
            *) log "unknown selection" ;;
        esac
    done
}

has_join_secret_input() {
    [[ -n "${JOIN_SECRET}" || -n "${JOIN_SECRET_FILE}" ]]
}


configure_interactive_install() {
    if ! is_interactive || [[ "${UNINSTALL}" -eq 1 ]]; then
        return
    fi

    log "interactive setup"
    if [[ "${SKIP_CONFIG}" -eq 0 && ! -f "${CONFIG_PATH}" && -z "${NETWORK_NAME}" ]]; then
        prompt_api_endpoint
        NETWORK_NAME="$(prompt_value "Network name (blank to create config only)" "")"
        DEVICE_NAME="$(prompt_required "Device name" "${DEVICE_NAME}")"
        if [[ -n "${NETWORK_NAME}" ]]; then
            JOIN_SECRET="$(prompt_secret "Network join secret")"
            prompt_discovery_endpoint
        fi
    fi

    if [[ "${INSTALL_SERVICE}" -eq 0 && "${START_SERVICE}" -eq 0 && "${SKIP_CONFIG}" -eq 0 && -n "${NETWORK_NAME}" ]]; then
        if prompt_yes_no "Install and start the background tunnel service?" "Y"; then
            INSTALL_SERVICE=1
            START_SERVICE=1
        fi
    fi
}

invoke_network_join_once() (
    local secret_file="${JOIN_SECRET_FILE}"
    local transient_secret_file=""

    if [[ -n "${JOIN_SECRET}" ]]; then
        [[ -z "${secret_file}" ]] || fail "use either the interactive secret or --join-secret-file"
        umask 077
        install -d -o root -g wheel -m 0700 "${SECRET_DIR}" || return 1
        transient_secret_file="$(mktemp "${SECRET_DIR}/.join-secret.XXXXXX")" || return 1
        trap 'rm -f -- "${transient_secret_file}"' EXIT
        chmod 0600 "${transient_secret_file}" || return 1
        printf '%s' "${JOIN_SECRET}" > "${transient_secret_file}" || return 1
        secret_file="${transient_secret_file}"
    fi
    [[ -n "${secret_file}" ]] || fail "--join-secret-file is required with --network-name in non-interactive mode"

    client_env "${INSTALL_DIR}/veloramesh-client" network join \
        -server "${SERVER_URL}" \
        -name "${NETWORK_NAME}" \
        -join-secret-file "${secret_file}" \
        -device-name "${DEVICE_NAME}" \
        -config "${CONFIG_PATH}"
)

join_network_with_retry() {
    while true; do
        log "joining network ${NETWORK_NAME}"
        if invoke_network_join_once; then
            return
        fi
        if ! is_interactive; then
            fail "veloramesh-client network join failed"
        fi
        if ! prompt_yes_no "Try another network name or join secret?" "Y"; then
            fail "veloramesh-client network join failed"
        fi
        NETWORK_NAME="$(prompt_required "Network name" "${NETWORK_NAME}")"
        JOIN_SECRET="$(prompt_secret "Network join secret")"
        JOIN_SECRET_FILE=""
    done
}

configured_network_id() {
    local output
    output="$(client_env "${INSTALL_DIR}/veloramesh-client" network status -config "${CONFIG_PATH}" 2>/dev/null || true)"
    awk -v want="${NETWORK_NAME}" '
        $1 == "network" && $2 == want {
            print $3
            found = 1
            exit
        }
        $1 == "network" && $0 ~ /auto_connect=true/ {
            auto_count++
            auto_id = $3
        }
        END {
            if (!found && auto_count == 1) {
                print auto_id
            }
        }
    ' <<< "${output}"
}

xml_escape() {
    local value="$1"
    value="${value//&/&amp;}"
    value="${value//</&lt;}"
    value="${value//>/&gt;}"
    value="${value//\"/&quot;}"
    value="${value//\'/&apos;}"
    printf '%s' "${value}"
}

write_plist_arg() {
    printf '        <string>'
    xml_escape "$1"
    printf '</string>\n'
}

clear_quarantine() {
    local path="$1"
    if command -v xattr >/dev/null 2>&1; then
        xattr -dr com.apple.quarantine "${path}" >/dev/null 2>&1 || true
    fi
}

client_env() {
    VELORAMESH_SECRET_DIR="${SECRET_DIR}" "$@"
}

rollback_client_update() {
    local status="$?"
    trap - EXIT
    if [[ "${CLIENT_UPDATE_PENDING}" -eq 1 ]]; then
        local optional
        for optional in ${PACKAGE_UPDATED_FILES}; do
            if [[ -f "${INSTALL_DIR}/.${optional}.previous.$$" ]]; then
                mv -f "${INSTALL_DIR}/.${optional}.previous.$$" "${INSTALL_DIR}/${optional}"
            else
                rm -f -- "${INSTALL_DIR}/${optional}"
            fi
        done
        clear_package_transaction_files
        if launchctl print "system/${LAUNCHD_LABEL}" >/dev/null 2>&1; then
            launchctl bootout "system/${LAUNCHD_LABEL}" >/dev/null 2>&1 || true
        fi
        rm -f -- "${STAGED_CLIENT}" "${PLIST_STAGING}"
        if [[ "${PLIST_REPLACED}" -eq 1 ]]; then
            if [[ "${PLIST_HAD_PREVIOUS}" -eq 1 && -f "${PLIST_PREVIOUS}" ]]; then
                mv -f "${PLIST_PREVIOUS}" "${PLIST_PATH}"
            else
                rm -f "${PLIST_PATH}"
            fi
        fi
        rm -f -- "${PLIST_PREVIOUS}"
        if [[ -n "${PREVIOUS_CLIENT}" && -f "${PREVIOUS_CLIENT}" ]]; then
            rm -f -- "${INSTALL_DIR}/veloramesh-client"
            mv "${PREVIOUS_CLIENT}" "${INSTALL_DIR}/veloramesh-client"
        elif [[ "${CLIENT_REPLACED}" -eq 1 ]]; then
            rm -f -- "${INSTALL_DIR}/veloramesh-client"
        fi
        if [[ "${SERVICE_WAS_LOADED}" -eq 1 && -f "${PLIST_PATH}" ]]; then
            launchctl bootstrap system "${PLIST_PATH}" >/dev/null 2>&1 || true
        fi
    fi
    exit "${status}"
}

install_client_binary_atomically() {
    STAGED_CLIENT="${INSTALL_DIR}/.veloramesh-client.new.$$"
    PREVIOUS_CLIENT="${INSTALL_DIR}/.veloramesh-client.previous.$$"
    rm -f -- "${STAGED_CLIENT}" "${PREVIOUS_CLIENT}"
    if ! install -o root -g wheel -m 0755 "${SOURCE_CLIENT}" "${STAGED_CLIENT}"; then
        rm -f -- "${STAGED_CLIENT}"
        fail "could not stage the client binary"
    fi
    clear_quarantine "${STAGED_CLIENT}"
    if ! client_env "${STAGED_CLIENT}" version >/dev/null; then
        rm -f -- "${STAGED_CLIENT}"
        fail "candidate client binary failed validation"
    fi

    if launchctl print "system/${LAUNCHD_LABEL}" >/dev/null 2>&1; then
        SERVICE_WAS_LOADED=1
    fi
    CLIENT_UPDATE_PENDING=1
    trap rollback_client_update EXIT
    if [[ "${SERVICE_WAS_LOADED}" -eq 1 ]]; then
        log "unloading active LaunchDaemon for binary update"
        launchctl bootout "system/${LAUNCHD_LABEL}"
    fi
    if [[ -e "${INSTALL_DIR}/veloramesh-client" ]]; then
        mv "${INSTALL_DIR}/veloramesh-client" "${PREVIOUS_CLIENT}"
    fi
    mv "${STAGED_CLIENT}" "${INSTALL_DIR}/veloramesh-client"
    CLIENT_REPLACED=1
}

commit_client_update() {
    clear_package_transaction_files
    rm -f -- "${STAGED_CLIENT}" "${PREVIOUS_CLIENT}" "${PLIST_STAGING}" "${PLIST_PREVIOUS}"
    CLIENT_UPDATE_PENDING=0
    trap - EXIT
}

clear_package_transaction_files() {
    local optional
    for optional in manifest.json RELEASE-NOTES.md; do
        rm -f -- "${INSTALL_DIR}/.${optional}.new.$$" "${INSTALL_DIR}/.${optional}.previous.$$"
    done
}

install_package_metadata() {
    local optional
    for optional in manifest.json RELEASE-NOTES.md; do
        if [[ -f "${SCRIPT_DIR}/${optional}" ]]; then
            install -o root -g wheel -m 0644 "${SCRIPT_DIR}/${optional}" "${INSTALL_DIR}/.${optional}.new.$$"
            if [[ -f "${INSTALL_DIR}/${optional}" ]]; then
                cp -p "${INSTALL_DIR}/${optional}" "${INSTALL_DIR}/.${optional}.previous.$$"
            fi
            PACKAGE_UPDATED_FILES+=" ${optional}"
            mv -f "${INSTALL_DIR}/.${optional}.new.$$" "${INSTALL_DIR}/${optional}"
        fi
    done
}

rotate_log_file() {
    local path="$1"
    local max_bytes="${2:-$LOG_MAX_BYTES}"
    local backups="${3:-$LOG_BACKUPS}"
    [[ -f "${path}" ]] || return 0
    local size
    size="$(wc -c < "${path}" 2>/dev/null || printf '0')"
    size="${size//[[:space:]]/}"
    [[ "${size}" =~ ^[0-9]+$ ]] || size=0
    [[ "${size}" -ge "${max_bytes}" ]] || return 0
    local i
    for ((i = backups - 1; i >= 1; i--)); do
        if [[ -f "${path}.${i}" ]]; then
            rm -f "${path}.$((i + 1))"
            mv "${path}.${i}" "${path}.$((i + 1))"
        fi
    done
    mv "${path}" "${path}.1"
    log "rotated log ${path}"
}

rotate_service_logs() {
    rotate_log_file "${STDOUT_LOG}" || log "log rotation skipped for ${STDOUT_LOG}"
    rotate_log_file "${STDERR_LOG}" || log "log rotation skipped for ${STDERR_LOG}"
}

secure_service_log_file() {
    local path="$1"
    [[ ! -L "${path}" ]] || fail "refusing to use a symlink as a service log: ${path}"
    if [[ ! -e "${path}" ]]; then
        install -o root -g wheel -m 0600 /dev/null "${path}"
    fi
    [[ -f "${path}" ]] || fail "service log is not a regular file: ${path}"
    chown root:wheel "${path}"
    chmod 0600 "${path}"
}

prepare_service_logs() {
    local path
    local i
    secure_service_log_file "${STDOUT_LOG}"
    secure_service_log_file "${STDERR_LOG}"
    for path in "${STDOUT_LOG}" "${STDERR_LOG}"; do
        for ((i = 1; i <= LOG_BACKUPS; i++)); do
            if [[ -e "${path}.${i}" || -L "${path}.${i}" ]]; then
                secure_service_log_file "${path}.${i}"
            fi
        done
    done
}

remove_service_logs() {
    local path
    local i
    for path in "${STDOUT_LOG}" "${STDERR_LOG}"; do
        rm -f -- "${path}"
        for ((i = 1; i <= LOG_BACKUPS; i++)); do
            rm -f -- "${path}.${i}"
        done
    done
}

run_installer_self_test() {
    [[ -x "${INSTALL_DIR}/veloramesh-client" && -f "${CONFIG_PATH}" ]] || return 0
    log "running installer self-test"
    local args=(doctor -config "${CONFIG_PATH}" -timeout 8s)
    if [[ -n "${NETWORK_NAME}" ]]; then
        local network_id="${NETWORK_ID:-}"
        if [[ -z "${network_id}" ]]; then
            network_id="$(configured_network_id || true)"
        fi
        if [[ -n "${network_id}" ]]; then
            args+=( -id "${network_id}" )
        else
            args+=( -network "${NETWORK_NAME}" )
        fi
        if [[ -n "${DISCOVERY_SERVERS}" ]]; then
            args+=( -discovery "${DISCOVERY_SERVERS}" )
        fi
    else
        args+=( -local-only )
    fi
    if client_env "${INSTALL_DIR}/veloramesh-client" "${args[@]}"; then
        log "self-test passed"
    else
        log "self-test reported issues; run the doctor command above for details"
    fi
}

while [[ $# -gt 0 ]]; do
    case "$1" in
        --network-name|--join-secret-file|--skip-config|--background|--service|--start|--replace-service|--fresh|--uninstall|--purge|--status)
            EXPLICIT_ACTION=1 ;;
    esac
    case "$1" in
        --install-dir) need_value "$1" "${2:-}"; INSTALL_DIR="$2"; shift 2 ;;
        --config) need_value "$1" "${2:-}"; CONFIG_PATH="$2"; shift 2 ;;
        --server) need_value "$1" "${2:-}"; SERVER_URL="$2"; shift 2 ;;
        --device-name) need_value "$1" "${2:-}"; DEVICE_NAME="$2"; shift 2 ;;
        --network-name) need_value "$1" "${2:-}"; NETWORK_NAME="$2"; shift 2 ;;
        --join-secret-file) need_value "$1" "${2:-}"; JOIN_SECRET_FILE="$2"; shift 2 ;;
        --listen) need_value "$1" "${2:-}"; LISTEN_ADDR="$2"; shift 2 ;;
        --advertise) need_value "$1" "${2:-}"; ADVERTISE_ADDR="$2"; shift 2 ;;
        --discovery) need_value "$1" "${2:-}"; DISCOVERY_SERVERS="$2"; shift 2 ;;
        --forward-broadcast-udp) need_value "$1" "${2:-}"; FORWARD_BROADCAST_UDP="$2"; shift 2 ;;
        --source-client) need_value "$1" "${2:-}"; SOURCE_CLIENT="$2"; shift 2 ;;
        --skip-config) SKIP_CONFIG=1; shift ;;
        --background) INSTALL_SERVICE=1; START_SERVICE=1; shift ;;
        --service) INSTALL_SERVICE=1; shift ;;
        --start) START_SERVICE=1; shift ;;
        --replace-service) REPLACE_SERVICE=1; shift ;;
        --fresh) FRESH=1; shift ;;
        --uninstall) UNINSTALL=1; shift ;;
        --purge) PURGE=1; shift ;;
        --status) STATUS=1; shift ;;
        --non-interactive) NON_INTERACTIVE=1; shift ;;
        -h|--help) usage; exit 0 ;;
        *) usage >&2; fail "unknown option: $1" ;;
    esac
done

if [[ "${EUID}" -ne 0 ]]; then
    fail "install-macos-client.sh must run as root"
fi
[[ "${INSTALL_DIR}" == /* ]] || fail "--install-dir must be an absolute path"
[[ "${CONFIG_PATH}" == /* ]] || fail "--config must be an absolute path"
[[ "${PLIST_PATH}" == /* ]] || fail "VELORAMESH_LAUNCHD_PLIST must be an absolute path"
[[ "${LOG_DIR}" == /* ]] || fail "VELORAMESH_LOG_DIR must be an absolute path"
[[ "${LAUNCHD_LABEL}" =~ ^[A-Za-z0-9.-]+$ ]] || fail "invalid LaunchDaemon label: ${LAUNCHD_LABEL}"
configure_interactive_action
if [[ "${FRESH}" -eq 1 && "${UNINSTALL}" -eq 1 ]]; then
    fail "--fresh cannot be combined with --uninstall"
fi
if [[ "${FRESH}" -eq 1 && "${START_SERVICE}" -eq 1 && "${INSTALL_SERVICE}" -eq 0 ]]; then
    fail "--fresh --start requires --service to create the replacement LaunchDaemon"
fi
if [[ "${PURGE}" -eq 1 && "${UNINSTALL}" -ne 1 ]]; then
    fail "--purge can only be used with --uninstall"
fi
SECRET_DIR="$(dirname "${CONFIG_PATH}")/secrets"

safe_purge_dir() {
    local path="$1"
    [[ -n "${path}" && "${path}" != "/" ]] || fail "refusing to purge unsafe path: ${path}"
    [[ "$(basename "${path}")" == "veloramesh" ]] || fail "refusing to purge unexpected path: ${path}"
    rm -rf "${path}"
}

safe_purge_config() {
    local path="$1"
    [[ "$(basename "${path}")" == "client.json" ]] || fail "refusing to purge unexpected config path: ${path}"
    rm -f "${path}"
    rmdir "$(dirname "${path}")" >/dev/null 2>&1 || true
}

safe_purge_secret_dir() {
    local path="$1"
    [[ -n "${path}" && "${path}" != "/" ]] || fail "refusing to purge unsafe secret path: ${path}"
    [[ "$(basename "${path}")" == "secrets" ]] || fail "refusing to purge unexpected secret path: ${path}"
    rm -rf "${path}"
}

remove_launchdaemon_if_present() {
    if launchctl print "system/${LAUNCHD_LABEL}" >/dev/null 2>&1; then
        launchctl bootout "system/${LAUNCHD_LABEL}" || fail "could not unload ${LAUNCHD_LABEL}; installed files were kept"
        if launchctl print "system/${LAUNCHD_LABEL}" >/dev/null 2>&1; then
            fail "${LAUNCHD_LABEL} is still loaded; installed files were kept"
        fi
    fi
    rm -f "${PLIST_PATH}"
}

remove_installed_files_and_config() {
    safe_purge_dir "${INSTALL_DIR}"
    safe_purge_secret_dir "${SECRET_DIR}"
    safe_purge_config "${CONFIG_PATH}"
    log "removed installed files and config"
}

require_installed_client() {
    [[ -x "${INSTALL_DIR}/veloramesh-client" ]] || fail "veloramesh-client was not found at ${INSTALL_DIR}/veloramesh-client; run Guided install/update first"
}

client_cmd() {
    require_installed_client
    client_env "${INSTALL_DIR}/veloramesh-client" "$@"
}

start_launchdaemon() {
    launchctl enable "system/${LAUNCHD_LABEL}"
    if launchctl print "system/${LAUNCHD_LABEL}" >/dev/null 2>&1; then
        launchctl kickstart -k "system/${LAUNCHD_LABEL}"
    else
        launchctl bootstrap system "${PLIST_PATH}"
    fi
}

network_operations_menu() {
    require_installed_client
    local choice
    local name
    while true; do
        cat <<MENU

VeloraMesh macOS Client - Network
1) Status
2) Join network
3) List peers
4) Disconnect network
5) Connect network
6) Leave network
7) Rename device
8) Doctor
0) Back
MENU
        printf 'Select: ' >&2
        IFS= read -r choice
        case "${choice}" in
            1) client_cmd network status -config "${CONFIG_PATH}" ;;
            2)
                prompt_api_endpoint
                NETWORK_NAME="$(prompt_required "Network name" "${NETWORK_NAME}")"
                DEVICE_NAME="$(prompt_required "Device name" "${DEVICE_NAME}")"
                JOIN_SECRET="$(prompt_secret "Network join secret")"
                JOIN_SECRET_FILE=""
                join_network_with_retry
                ;;
            3)
                name="$(prompt_required "Network name" "${NETWORK_NAME}")"
                client_cmd network peers -name "${name}" -config "${CONFIG_PATH}"
                ;;
            4)
                name="$(prompt_required "Network name" "${NETWORK_NAME}")"
                client_cmd network disconnect -name "${name}" -config "${CONFIG_PATH}"
                ;;
            5)
                name="$(prompt_required "Network name" "${NETWORK_NAME}")"
                client_cmd network connect -name "${name}" -config "${CONFIG_PATH}"
                ;;
            6)
                name="$(prompt_required "Network name" "${NETWORK_NAME}")"
                if prompt_yes_no "Leave network '${name}'?" "N"; then
                    client_cmd network leave -name "${name}" -config "${CONFIG_PATH}"
                fi
                ;;
            7)
                name="$(prompt_required "New device name" "${DEVICE_NAME}")"
                client_cmd device rename -name "${name}" -config "${CONFIG_PATH}"
                ;;
            8)
                name="$(prompt_value "Network name (blank for local checks)" "")"
                if [[ -n "${name}" ]]; then
                    client_cmd doctor -config "${CONFIG_PATH}" -network "${name}" -discovery "${DISCOVERY_SERVERS}"
                else
                    client_cmd doctor -config "${CONFIG_PATH}"
                fi
                ;;
            0) return ;;
            *) log "unknown selection" ;;
        esac
    done
}

service_operations_menu() {
    require_installed_client
    local choice
    while true; do
        cat <<MENU

VeloraMesh macOS Client - Service
1) Status
2) Start
3) Stop
4) Restart
5) Doctor
0) Back
MENU
        printf 'Select: ' >&2
        IFS= read -r choice
        case "${choice}" in
            1) launchctl print "system/${LAUNCHD_LABEL}" || true ;;
            2) start_launchdaemon ;;
            3) launchctl bootout "system/${LAUNCHD_LABEL}" >/dev/null 2>&1 || true ;;
            4)
                launchctl bootout "system/${LAUNCHD_LABEL}" >/dev/null 2>&1 || true
                start_launchdaemon
                ;;
            5) client_cmd doctor -config "${CONFIG_PATH}" -local-only ;;
            0) return ;;
            *) log "unknown selection" ;;
        esac
    done
}

while [[ -n "${MENU_ACTION}" ]]; do
    case "${MENU_ACTION}" in
        network)
            MENU_ACTION=""
            network_operations_menu
            configure_interactive_action
            ;;
        service)
            MENU_ACTION=""
            service_operations_menu
            configure_interactive_action
            ;;
        *) break ;;
    esac
done

if [[ "${UNINSTALL}" -ne 1 ]]; then
    require_supported_macos
fi

if [[ "${FRESH}" -eq 1 ]]; then
    if ! confirm_phrase "This removes the macOS LaunchDaemon, installed files, config, and service logs." "FRESH"; then
        fail "fresh install cancelled"
    fi
    log "resetting existing macOS client installation"
    remove_launchdaemon_if_present
    remove_installed_files_and_config
    remove_service_logs
fi

if [[ "${UNINSTALL}" -eq 1 ]]; then
    log "unloading and removing LaunchDaemon if present"
    remove_launchdaemon_if_present
    if [[ "${PURGE}" -eq 1 ]]; then
        remove_installed_files_and_config
        remove_service_logs
    else
        log "kept installed files and local data; rerun with --purge to remove them"
    fi
    log "uninstall complete"
    exit 0
fi

configure_interactive_install

if [[ "${START_SERVICE}" -eq 1 && "${INSTALL_SERVICE}" -eq 0 && ! -f "${PLIST_PATH}" ]]; then
    fail "--start requires an installed LaunchDaemon; use --service --start for first installation"
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -z "${SOURCE_CLIENT}" ]]; then
    if [[ -x "${SCRIPT_DIR}/veloramesh-client" ]]; then
        SOURCE_CLIENT="${SCRIPT_DIR}/veloramesh-client"
    else
        fail "veloramesh-client was not found; run from the macOS client package or pass --source-client"
    fi
fi

install -d -m 0755 "${INSTALL_DIR}"
install -d -m 0700 "$(dirname "${CONFIG_PATH}")"
install -d -m 0700 "${SECRET_DIR}"
chown root:wheel "$(dirname "${CONFIG_PATH}")" "${SECRET_DIR}"
chmod 0700 "$(dirname "${CONFIG_PATH}")" "${SECRET_DIR}"

log "installing client binary"
install_client_binary_atomically

install_package_metadata
clear_quarantine "${INSTALL_DIR}"

log "installed version"
client_env "${INSTALL_DIR}/veloramesh-client" version

if [[ "${SKIP_CONFIG}" -eq 1 ]]; then
    log "skipping config initialization"
elif [[ -f "${CONFIG_PATH}" ]]; then
    log "keeping existing config ${CONFIG_PATH}"
    if [[ -n "${NETWORK_NAME}" ]] && has_join_secret_input; then
        join_network_with_retry
    fi
elif [[ -n "${NETWORK_NAME}" ]]; then
    join_network_with_retry
else
    log "creating config ${CONFIG_PATH}"
    client_env "${INSTALL_DIR}/veloramesh-client" config init \
        -server "${SERVER_URL}" \
        -name "${DEVICE_NAME}" \
        -config "${CONFIG_PATH}"
fi

if [[ -f "${CONFIG_PATH}" ]]; then
    chown root:wheel "${CONFIG_PATH}"
    chmod 0600 "${CONFIG_PATH}"
fi

if [[ "${INSTALL_SERVICE}" -eq 1 ]]; then
    if [[ -z "${NETWORK_NAME}" ]]; then
        fail "--network-name is required with --service"
    fi
    NETWORK_ID="$(configured_network_id)"
    install -d -o root -g wheel -m 0755 "${LOG_DIR}"
    rotate_service_logs
    prepare_service_logs
    if [[ "${REPLACE_SERVICE}" -eq 1 ]]; then
        log "replacing the LaunchDaemon transactionally"
    fi

    SERVICE_ARGS=(
        "${INSTALL_DIR}/veloramesh-client" run-network
        -config "${CONFIG_PATH}"
        -listen "${LISTEN_ADDR}"
    )
    if [[ -n "${NETWORK_ID}" ]]; then
        SERVICE_ARGS+=( -id "${NETWORK_ID}" )
        log "service network_id=${NETWORK_ID}"
    else
        SERVICE_ARGS+=( -name "${NETWORK_NAME}" )
    fi
    if [[ -n "${ADVERTISE_ADDR}" ]]; then
        SERVICE_ARGS+=( -advertise "${ADVERTISE_ADDR}" )
    fi
    if [[ -n "${DISCOVERY_SERVERS}" ]]; then
        SERVICE_ARGS+=( -discovery "${DISCOVERY_SERVERS}" )
    fi
    if [[ -n "${FORWARD_BROADCAST_UDP}" ]]; then
        SERVICE_ARGS+=( -forward-broadcast-udp "${FORWARD_BROADCAST_UDP}" )
    fi

    log "installing LaunchDaemon"
    install -d -o root -g wheel -m 0755 "$(dirname "${PLIST_PATH}")"
    PLIST_STAGING="${PLIST_PATH}.new.$$"
    PLIST_PREVIOUS="${PLIST_PATH}.previous.$$"
    {
        cat <<PLIST_HEAD
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>${LAUNCHD_LABEL}</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>VELORAMESH_SECRET_DIR</key>
        <string>$(xml_escape "${SECRET_DIR}")</string>
        <key>VELORAMESH_SERVICE_LOG_PATH</key>
        <string>$(xml_escape "${STDOUT_LOG}")</string>
    </dict>
    <key>ProgramArguments</key>
    <array>
PLIST_HEAD
        for arg in "${SERVICE_ARGS[@]}"; do
            write_plist_arg "${arg}"
        done
        cat <<PLIST_TAIL
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <dict>
        <key>SuccessfulExit</key>
        <false/>
    </dict>
    <key>ThrottleInterval</key>
    <integer>10</integer>
    <key>StandardOutPath</key>
    <string>$(xml_escape "${STDOUT_LOG}")</string>
    <key>StandardErrorPath</key>
    <string>$(xml_escape "${STDERR_LOG}")</string>
</dict>
</plist>
PLIST_TAIL
    } > "${PLIST_STAGING}"
    chown root:wheel "${PLIST_STAGING}"
    chmod 0644 "${PLIST_STAGING}"
    if command -v plutil >/dev/null 2>&1; then
        plutil -lint "${PLIST_STAGING}" >/dev/null
    fi
    if [[ -f "${PLIST_PATH}" ]]; then
        cp -p "${PLIST_PATH}" "${PLIST_PREVIOUS}"
        PLIST_HAD_PREVIOUS=1
    fi
    mv "${PLIST_STAGING}" "${PLIST_PATH}"
    PLIST_REPLACED=1

    if [[ "${START_SERVICE}" -eq 1 || "${SERVICE_WAS_LOADED}" -eq 1 ]]; then
        log "starting LaunchDaemon"
        start_launchdaemon
    else
        log "installed without starting LaunchDaemon"
        log "next: sudo launchctl bootstrap system ${PLIST_PATH}"
    fi
    if [[ "${START_SERVICE}" -eq 1 || "${STATUS}" -eq 1 ]]; then
        if launchctl print "system/${LAUNCHD_LABEL}" >/dev/null 2>&1; then
            log "service=loaded"
        else
            log "service=not-loaded"
        fi
    fi
fi

if [[ "${INSTALL_SERVICE}" -eq 0 && ( "${START_SERVICE}" -eq 1 || "${SERVICE_WAS_LOADED}" -eq 1 ) ]]; then
    log "reloading existing LaunchDaemon"
    start_launchdaemon
fi

log "install_dir=${INSTALL_DIR}"
log "client=${INSTALL_DIR}/veloramesh-client"
log "config=${CONFIG_PATH}"
if [[ -n "${NETWORK_NAME}" ]]; then
    if [[ -n "${NETWORK_ID:-}" ]]; then
        log "doctor=sudo \"${INSTALL_DIR}/veloramesh-client\" doctor -config \"${CONFIG_PATH}\" -id \"${NETWORK_ID}\" -discovery \"${DISCOVERY_SERVERS}\""
    else
        log "doctor=sudo \"${INSTALL_DIR}/veloramesh-client\" doctor -config \"${CONFIG_PATH}\" -network \"${NETWORK_NAME}\" -discovery \"${DISCOVERY_SERVERS}\""
    fi
else
    log "doctor=sudo \"${INSTALL_DIR}/veloramesh-client\" doctor -config \"${CONFIG_PATH}\""
fi
run_installer_self_test
commit_client_update
