#!/usr/bin/env bash
#
# deploy.sh — sync this project to the LattePanda and build/run it there.
# The LattePanda is the machine wired to the gimbal + camera, so the actual
# cmake build (and the live run) happen remotely over ssh. We edit locally and
# mirror. Mirrors ../firmware/deploy.sh, but for the CMake/C++ side.
#
# Usage:
#   ./deploy.sh                 rsync + remote configure + build
#   ./deploy.sh --run           ... then run the app over ssh (RUN_ARGS, ctrl-c to stop)
#   ./deploy.sh --run --home    ... and run the endstop-finding home sequence at startup
#   ./deploy.sh --clean         wipe the remote build dir first (fresh configure)
#   ./deploy.sh --check-deps     only check the remote build dependencies
#   ./deploy.sh --run --force    skip the placeholder-config guard before running
#
# Flags combine, e.g.  ./deploy.sh --clean --run
#
# Config comes from .deploy.env (see .deploy.env.example).
#
set -euo pipefail

cd "$(dirname "$0")"

ENV_FILE=".deploy.env"
if [[ ! -f "$ENV_FILE" ]]; then
    echo "error: $ENV_FILE not found (copy .deploy.env.example and edit it)" >&2
    exit 1
fi
# shellcheck disable=SC1090
source "$ENV_FILE"

: "${REMOTE_HOST:?set REMOTE_HOST in .deploy.env}"
: "${REMOTE_DIR:?set REMOTE_DIR in .deploy.env}"
CMAKE_ARGS="${CMAKE_ARGS:--DWITH_MQTT=ON -DWITH_VIMBA=ON}"
BUILD_JOBS="${BUILD_JOBS:-}"
RUN_ARGS="${RUN_ARGS:---start}"

CLEAN=0
RUN=0
CHECK_DEPS=0
FORCE=0
HOME_INIT=0
for arg in "$@"; do
    case "$arg" in
        --clean)      CLEAN=1 ;;
        --run|--start) RUN=1 ;;
        --home|--init) HOME_INIT=1 ;;
        --check-deps) CHECK_DEPS=1 ;;
        --force)      FORCE=1 ;;
        *) echo "unknown option: $arg" >&2; exit 1 ;;
    esac
done

# Homing is opt-in: append --init only when --home is passed, so a plain run
# brings up the TUI without driving the endstop-finding sequence.
if [[ $HOME_INIT -eq 1 && "$RUN_ARGS" != *--init* ]]; then
    RUN_ARGS="--init $RUN_ARGS"
fi

# Locate cmake on the remote. Non-interactive ssh shells don't source .bashrc,
# so allow an explicit REMOTE_CMAKE override in .deploy.env; otherwise search
# the usual locations.
RESOLVE_CMAKE="
CMAKE='${REMOTE_CMAKE:-}'
if [ -z \"\$CMAKE\" ]; then
    for c in \"\$(command -v cmake 2>/dev/null)\" /usr/bin/cmake /usr/local/bin/cmake; do
        if [ -x \"\$c\" ]; then CMAKE=\"\$c\"; break; fi
    done
fi
if [ -z \"\$CMAKE\" ]; then
    echo 'error: cmake not found on remote; set REMOTE_CMAKE in .deploy.env' >&2
    exit 127
fi
"

# Vimba X needs the GenTL transport-layer dir on GENICAM_GENTL64_PATH at runtime,
# or VmbStartup() fails with "Could not start Vimba X API". The SDK installs that
# via /etc/profile.d, which non-interactive ssh shells don't source — so set it
# ourselves before launching. Optional VIMBA_CTI_PATH override in .deploy.env.
RESOLVE_VIMBA_ENV="
for s in /etc/profile.d/*Vimba*GenTL*.sh; do
    [ -f \"\$s\" ] && . \"\$s\"
done
if [ -z \"\${GENICAM_GENTL64_PATH:-}\" ]; then
    for d in '${VIMBA_CTI_PATH:-}' /opt/VimbaX/cti /opt/VimbaX_*/cti; do
        if [ -d \"\$d\" ]; then export GENICAM_GENTL64_PATH=\"\$d\"; break; fi
    done
fi
"

# Preflight: the two heavyweight deps that aren't auto-fetched (Paho is). OpenCV
# and Boost.program_options must be present or the configure step fails opaquely.
CHECK_DEPS_CMD="
miss=''
pkg-config --exists opencv4 2>/dev/null || miss=\"\$miss opencv4\"
pkg-config --exists libjxl libjxl_threads 2>/dev/null || miss=\"\$miss libjxl\"
[ -f /usr/include/boost/version.hpp ] || ls /usr/include/boost*/boost/version.hpp >/dev/null 2>&1 || miss=\"\$miss boost\"
if [ -n \"\$miss\" ]; then
    echo \"error: missing remote build deps:\$miss\" >&2
    echo 'install on the LattePanda (Ubuntu):' >&2
    echo '  sudo apt-get install -y build-essential cmake git libopencv-dev libboost-program-options-dev libjxl-dev' >&2
    exit 1
fi
echo 'remote deps OK (OpenCV, Boost, libjxl; Vimba via cmake/FindVmb; Paho fetched)'
"

if [[ "$CHECK_DEPS" == "1" ]]; then
    echo ">> checking remote build deps on $REMOTE_HOST"
    # shellcheck disable=SC2029
    ssh "$REMOTE_HOST" "$CHECK_DEPS_CMD"
    exit 0
fi

# .git is excluded from rsync, so capture build identity locally and pass it on.
if git rev-parse --git-dir >/dev/null 2>&1; then
    GIT_REV="$(git describe --always --dirty 2>/dev/null || echo nogit)"
else
    GIT_REV="nogit"
fi
echo ">> build id: $GIT_REV"

echo ">> ensuring remote dir exists"
ssh "$REMOTE_HOST" "mkdir -p '$REMOTE_DIR'"

# config.ini and images/ are device-local (runtime config + captured output) and
# are deliberately NOT mirrored, so a deploy never clobbers them. build/ is
# machine-specific and regenerated remotely.
echo ">> syncing to $REMOTE_HOST:$REMOTE_DIR"
rsync -avz --delete \
    --exclude 'build/' \
    --exclude '.git' \
    --exclude '.venv' \
    --exclude '.vscode' \
    --exclude '__pycache__' \
    --exclude '.deploy.env' \
    --exclude 'config.ini' \
    --exclude 'images/' \
    ./ "$REMOTE_HOST:$REMOTE_DIR/"

# Seed a config.ini on the device from the example if it doesn't have one yet,
# so the first run finds a config. Never overwrites an existing device config.
echo ">> ensuring remote config.ini exists"
ssh "$REMOTE_HOST" "cd '$REMOTE_DIR' && \
    if [ ! -f config.ini ]; then \
        cp config/config.example.ini config.ini && \
        echo '   seeded config.ini from config/config.example.ini — edit it on the device'; \
    else echo '   keeping existing device config.ini'; fi"

if [[ "$CLEAN" == "1" ]]; then
    echo ">> wiping remote build dir"
    ssh "$REMOTE_HOST" "rm -rf '$REMOTE_DIR/build'"
fi

echo ">> remote preflight + configure + build ($CMAKE_ARGS)"
JOBS_FLAG=""
[[ -n "$BUILD_JOBS" ]] && JOBS_FLAG="-j$BUILD_JOBS" || JOBS_FLAG="-j\$(nproc)"
# shellcheck disable=SC2029
ssh "$REMOTE_HOST" "$RESOLVE_CMAKE $CHECK_DEPS_CMD cd '$REMOTE_DIR' && \
    GIT_REV='$GIT_REV' \"\$CMAKE\" -S . -B build $CMAKE_ARGS && \
    \"\$CMAKE\" --build build $JOBS_FLAG"
echo ">> build complete: $REMOTE_DIR/build/fire_gimbal_control"

if [[ "$RUN" == "1" ]]; then
    # Guard: a freshly seeded config.ini is the example template, with placeholders
    # that connect nowhere and silently drop to degraded mode. Refuse to launch on
    # placeholders (unless --force) so it's an explicit failure, not a confusing
    # half-run. Only enforce placeholders for subsystems this run actually uses,
    # mirroring Application.cpp's resolution: MQTT off if --no-mqtt, a WITH_MQTT=OFF
    # build, OR [Features] enable_mqtt is falsey; camera mocked if --mock-camera OR
    # [Features] mock_camera true / enable_camera false. CLI/build signals are
    # computed here; the config's own flags are parsed remotely where the file is.
    if [[ "$FORCE" != "1" ]]; then
        MQTT_OFF_CLI=0; CAM_MOCK_CLI=0
        [[ "$RUN_ARGS" == *--no-mqtt* || "$CMAKE_ARGS" == *WITH_MQTT=OFF* ]] && MQTT_OFF_CLI=1
        [[ "$RUN_ARGS" == *--mock-camera* ]] && CAM_MOCK_CLI=1
        echo ">> checking device config.ini for unedited placeholders"
        # shellcheck disable=SC2029
        if ! ssh "$REMOTE_HOST" "
            cfg='$REMOTE_DIR/config.ini'
            [ -f \"\$cfg\" ] || { echo \"error: \$cfg missing on device\" >&2; exit 2; }
            # Read an INI value (last assignment wins), strip inline ';' comment + ws, lowercase.
            ini_val() { grep -iE \"^[[:space:]]*\$1[[:space:]]*=\" \"\$cfg\" | tail -1 \
                | sed -E 's/^[^=]*=[[:space:]]*//; s/[[:space:]]*(;.*)?\$//' | tr 'A-Z' 'a-z'; }
            is_false() { case \"\$(ini_val \"\$1\")\" in 0|false|no|off) return 0;; *) return 1;; esac; }
            is_true()  { case \"\$(ini_val \"\$1\")\" in 1|true|yes|on)  return 0;; *) return 1;; esac; }

            mqtt_on=1; [ '$MQTT_OFF_CLI' = 1 ] && mqtt_on=0; is_false enable_mqtt && mqtt_on=0
            cam_real=1; [ '$CAM_MOCK_CLI' = 1 ] && cam_real=0
            is_true mock_camera && cam_real=0; is_false enable_camera && cam_real=0

            pat='ExampleTower'                                  # tower identity: always
            [ \"\$mqtt_on\"  = 1 ] && pat=\"\$pat|CHANGE_ME\"        # MQTT creds: only if MQTT on
            [ \"\$cam_real\" = 1 ] && pat=\"\$pat|DEV_0000000000\"   # camera id: only if real cam

            if grep -Eq \"\$pat\" \"\$cfg\"; then
                echo 'error: device config.ini still has example placeholders:' >&2
                grep -En \"\$pat\" \"\$cfg\" | sed 's/^/    /' >&2
                echo 'edit them on the device, or pass --force to run anyway.' >&2
                exit 3
            fi
        "; then
            echo ">> aborting run; fix the config or re-run with --force" >&2
            exit 1
        fi
    fi

    echo ">> running on $REMOTE_HOST (ctrl-c to stop)"
    # -t for a real tty so the capture loop's logs stream and ctrl-c propagates.
    # cwd = REMOTE_DIR so ./config.ini and the output dir resolve.
    # shellcheck disable=SC2029
    ssh -t "$REMOTE_HOST" "$RESOLVE_VIMBA_ENV cd '$REMOTE_DIR' && exec ./build/fire_gimbal_control $RUN_ARGS"
fi
