#!/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="${VELORAMESH_NETWORK_JOIN_SECRET:-}"
JOIN_SECRET_FILE=""
LISTEN_ADDR="0.0.0.0:42002"
ADVERTISE_ADDR=""
DISCOVERY_SERVERS="edge.veloramesh.com:3478"
FORWARD_BROADCAST_UDP=""
JOIN_SECRET_MIN_LENGTH=24
JOIN_SECRET_MAX_LENGTH=256
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="com.veloramesh.client"
PLIST_PATH="/Library/LaunchDaemons/com.veloramesh.client.plist"
SECRET_DIR=""

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

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

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

Options:
  --install-dir PATH       install directory (default /opt/veloramesh)
  --config PATH            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 SECRET     network join secret; can also use VELORAMESH_NETWORK_JOIN_SECRET
  --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, and config before guided install
  --uninstall              unload and remove the LaunchDaemon
  --purge                  with --uninstall, also remove installed files and config
  --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
}

read_join_secret() {
    if [[ -n "${JOIN_SECRET}" && -n "${JOIN_SECRET_FILE}" ]]; then
        fail "use either --join-secret or --join-secret-file, not both"
    fi
    if [[ -n "${JOIN_SECRET}" ]]; then
        printf '%s' "${JOIN_SECRET}"
        return
    fi
    if [[ -n "${JOIN_SECRET_FILE}" ]]; then
        [[ -f "${JOIN_SECRET_FILE}" ]] || fail "join secret file was not found: ${JOIN_SECRET_FILE}"
        [[ -r "${JOIN_SECRET_FILE}" ]] || fail "join secret file is not readable: ${JOIN_SECRET_FILE}"
        local secret
        secret="$(cat "${JOIN_SECRET_FILE}")"
        secret="${secret//$'\r'/}"
        secret="${secret//$'\n'/}"
        [[ -n "${secret}" ]] || fail "join secret file is empty"
        printf '%s' "${secret}"
        return
    fi
    printf ''
}

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 install and config
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}" || -n "${VELORAMESH_NETWORK_JOIN_SECRET:-}" ]]
}

validate_join_secret() {
    local secret="$1"
    if [[ "${#secret}" -lt "${JOIN_SECRET_MIN_LENGTH}" || "${#secret}" -gt "${JOIN_SECRET_MAX_LENGTH}" ]]; then
        log "network join secret must be ${JOIN_SECRET_MIN_LENGTH}-${JOIN_SECRET_MAX_LENGTH} characters"
        return 1
    fi
}

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
}

join_network_with_retry() {
    while true; do
        local effective_join_secret
        effective_join_secret="$(read_join_secret)"
        if [[ -z "${effective_join_secret}" ]]; then
            fail "--join-secret, --join-secret-file, or VELORAMESH_NETWORK_JOIN_SECRET is required with --network-name"
        fi

        if ! validate_join_secret "${effective_join_secret}"; then
            if ! is_interactive; then
                fail "network join secret must be ${JOIN_SECRET_MIN_LENGTH}-${JOIN_SECRET_MAX_LENGTH} characters"
            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=""
            continue
        fi

        log "joining network ${NETWORK_NAME}"
        if VELORAMESH_NETWORK_JOIN_SECRET="${effective_join_secret}" \
            VELORAMESH_SECRET_DIR="${SECRET_DIR}" \
            "${INSTALL_DIR}/veloramesh-client" network join \
            -server "${SERVER_URL}" \
            -name "${NETWORK_NAME}" \
            -device-name "${DEVICE_NAME}" \
            -config "${CONFIG_PATH}"; then
            return
        fi

        if ! is_interactive; then
            fail "veloramesh-client network join failed"
        fi
        log "network join failed"
        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}" "$@"
}

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 "/var/log/veloramesh-client.log" || log "log rotation skipped for /var/log/veloramesh-client.log"
    rotate_log_file "/var/log/veloramesh-client.err.log" || log "log rotation skipped for /var/log/veloramesh-client.err.log"
}

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
    EXPLICIT_ACTION=1
    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) need_value "$1" "${2:-}"; JOIN_SECRET="$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
configure_interactive_action
if [[ "${FRESH}" -eq 1 && "${UNINSTALL}" -eq 1 ]]; then
    fail "--fresh cannot be combined with --uninstall"
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() {
    launchctl bootout "system/${LAUNCHD_LABEL}" >/dev/null 2>&1 || true
    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() {
    if launchctl print "system/${LAUNCHD_LABEL}" >/dev/null 2>&1; then
        launchctl kickstart -k "system/${LAUNCHD_LABEL}" >/dev/null 2>&1 || true
    else
        launchctl bootstrap system "${PLIST_PATH}"
    fi
    launchctl enable "system/${LAUNCHD_LABEL}"
}

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 [[ "${FRESH}" -eq 1 ]]; then
    if ! confirm_phrase "This removes the macOS LaunchDaemon, installed files, and config." "FRESH"; then
        fail "fresh install cancelled"
    fi
    log "resetting existing macOS client installation"
    remove_launchdaemon_if_present
    remove_installed_files_and_config
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
    else
        log "kept installed files and config; rerun with --purge to remove them"
    fi
    log "uninstall complete"
    exit 0
fi

configure_interactive_install

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 -o root -g wheel -m 0755 "${SOURCE_CLIENT}" "${INSTALL_DIR}/veloramesh-client"

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}"
    fi
done
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)"
    rotate_service_logs
    if [[ "${REPLACE_SERVICE}" -eq 1 ]]; then
        log "replacing existing LaunchDaemon if present"
        launchctl bootout "system/${LAUNCHD_LABEL}" >/dev/null 2>&1 || true
        rm -f "${PLIST_PATH}"
    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"
    {
        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>
    </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>
    <true/>
    <key>StandardOutPath</key>
    <string>/var/log/veloramesh-client.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/veloramesh-client.err.log</string>
</dict>
</plist>
PLIST_TAIL
    } > "${PLIST_PATH}"
    chown root:wheel "${PLIST_PATH}"
    chmod 0644 "${PLIST_PATH}"
    if command -v plutil >/dev/null 2>&1; then
        plutil -lint "${PLIST_PATH}" >/dev/null
    fi

    if [[ "${START_SERVICE}" -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

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
