#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2026 branderson
# SPDX-License-Identifier: GPL-3.0-or-later

#
# linkctl-macro - run a linkctl command as a desktop macro.
#
# Wraps linkctl so it is safe to bind to a global hotkey:
#   - resolves the linkctl binary even in the sparse environment a desktop
#     shortcut is launched with
#   - passes --force, because a deliberate keypress means "do it now" even
#     when no application currently holds the camera open
#   - reports the result as a desktop notification, since tracking state and
#     gimbal position are otherwise invisible
#
# Usage:
#   linkctl-macro tracking on
#   linkctl-macro tracking toggle
#   linkctl-macro preset load home
#   linkctl-macro center
#
# Any linkctl subcommand works; arguments are passed through untouched.
#
# Options (before the linkctl subcommand):
#   --no-notify     suppress the desktop notification
#   --print         echo the resolved command instead of running it
#
# Configuration, from the environment or ~/.config/linkctl-macro.conf:
#   LINKCTL         path to the linkctl binary
#   LINKCTL_DEVICE  video node to target, e.g. /dev/video0 (for multi-camera)
#   LINKCTL_FORCE   1 (default) to pass --force, 0 to respect the idle guard
#   LINKCTL_NOTIFY  always (default) | error | never

set -uo pipefail

readonly CONF="${XDG_CONFIG_HOME:-$HOME/.config}/linkctl-macro.conf"
# shellcheck source=/dev/null
[[ -r $CONF ]] && source "$CONF"

: "${LINKCTL_DEVICE:=}"
: "${LINKCTL_FORCE:=1}"
: "${LINKCTL_NOTIFY:=always}"

# A desktop shortcut does not inherit an interactive shell's PATH, so look in
# the places linkctl is actually installed to before giving up.
#
# Sets LINKCTL_BIN on success and RESOLVE_ERR on failure. Both are globals
# rather than stdout, because a command substitution would run this in a
# subshell and discard the error message.
resolve_linkctl() {
    local c
    LINKCTL_BIN=
    RESOLVE_ERR=
    if [[ -n ${LINKCTL:-} ]]; then
        # An explicit setting is authoritative, so a bad one is an error here
        # rather than a confusing exec failure later.
        if [[ -x $LINKCTL ]]; then
            LINKCTL_BIN=$LINKCTL
            return 0
        fi
        RESOLVE_ERR="LINKCTL is set to '$LINKCTL', which is not executable."
        return 1
    fi
    if c=$(command -v linkctl 2>/dev/null); then
        LINKCTL_BIN=$c
        return 0
    fi
    for c in /usr/local/bin/linkctl /usr/bin/linkctl \
             "$HOME/.local/bin/linkctl" "$HOME/.cargo/bin/linkctl" \
             /var/lib/flatpak/exports/bin/linkctl; do
        [[ -x $c ]] && { LINKCTL_BIN=$c; return 0; }
    done
    RESOLVE_ERR="linkctl not found on PATH or in the usual install locations."
    return 1
}

# Map linkctl's documented exit codes onto something a person can act on.
hint_for() {
    case $1 in
        2)  echo "Invalid arguments." ;;
        3)  echo "Camera not found. Is it plugged in?" ;;
        4)  echo "Several cameras found. Set LINKCTL_DEVICE in $CONF." ;;
        5)  echo "Camera is idle and --force was disabled." ;;
        6)  echo "Permission denied. Add yourself to the 'video' group or install the udev rule." ;;
        7)  echo "This control is not supported on this camera model." ;;
        8)  echo "Device I/O error. Try replugging the camera." ;;
        9)  echo "linkctl configuration error." ;;
        11) echo "Invalid value for this control." ;;
        12) echo "Vendor control failed. AI tracking is experimental." ;;
        13) echo "Camera is busy." ;;
        *)  echo "linkctl exited with status $1." ;;
    esac
}

notify() {
    local urgency=$1 body=$2
    [[ $LINKCTL_NOTIFY == never ]] && return 0
    [[ $LINKCTL_NOTIFY == error && $urgency != critical ]] && return 0
    command -v notify-send >/dev/null 2>&1 || return 0
    # The synchronous hint makes repeated keypresses replace the previous
    # popup instead of stacking a queue of them.
    notify-send \
        --app-name="Insta360 Link" \
        --icon=camera-web \
        --urgency="$urgency" \
        --expire-time=2500 \
        --hint=string:x-canonical-private-synchronous:linkctl-macro \
        "Insta360 Link" "$body"
}

main() {
    local no_notify=0 print_only=0
    while [[ $# -gt 0 ]]; do
        case $1 in
            --no-notify) no_notify=1; shift ;;
            --print)     print_only=1; shift ;;
            --)          shift; break ;;
            *)           break ;;
        esac
    done

    if [[ $# -eq 0 ]]; then
        echo "usage: linkctl-macro [--no-notify] [--print] <linkctl args...>" >&2
        echo "   eg: linkctl-macro tracking toggle" >&2
        return 2
    fi

    (( no_notify )) && LINKCTL_NOTIFY=never

    if ! resolve_linkctl; then
        local msg="${RESOLVE_ERR:-linkctl not found.} Set LINKCTL in $CONF."
        notify critical "$msg"
        echo "linkctl-macro: $msg" >&2
        return 127
    fi

    local -a cmd=("$LINKCTL_BIN")
    [[ $LINKCTL_FORCE == 1 ]] && cmd+=(--force)
    [[ -n $LINKCTL_DEVICE ]] && cmd+=(--device "$LINKCTL_DEVICE")
    cmd+=("$@")

    if (( print_only )); then
        printf '%q ' "${cmd[@]}"; echo
        return 0
    fi

    local out status
    out=$("${cmd[@]}" 2>&1)
    status=$?

    if (( status == 0 )); then
        # linkctl already prints the resulting state, eg "Tracking: On", which
        # is exactly the feedback worth surfacing. Fall back to the request.
        local body=${out%%$'\n'*}
        [[ -z $body ]] && body="$*"
        notify normal "$body"
        [[ -n $out ]] && printf '%s\n' "$out"
    else
        local body=${out%%$'\n'*}
        [[ -n $body ]] && body="$body"$'\n'"$(hint_for "$status")" \
                       || body=$(hint_for "$status")
        notify critical "$body"
        [[ -n $out ]] && printf '%s\n' "$out" >&2
        printf 'linkctl-macro: %s\n' "$(hint_for "$status")" >&2
    fi
    return $status
}

main "$@"
