# reuse-mode.sh — Container reuse mode: idle watchdog and sleep-infinity PID 1 loop.
# shellcheck shell=bash
# Requires: _entrypoint_log (from helpers.sh).
# When DEVCAGE_REUSE=1, the entrypoint stays alive as PID 1 instead of exec-ing.
# Subsequent sessions attach via `docker exec` running /usr/local/bin/devcage-session.
# Copyright (c) 2026 Progralink. Licensed under the DevCage License — see LICENSE.

# _enter_reuse_mode: start idle watchdog, then sleep infinity as PID 1.
# NEVER returns — this function either waits indefinitely or exits the process.
#
# Signal semantics (DO NOT CHANGE — tested by tests/test-reuse.sh):
#   SIGUSR1: sent by idle watchdog → exit 0 so on-failure policy does NOT restart
#   SIGTERM: sent by docker stop / daemon shutdown → exit 143 so on-failure
#            restarts the container after a daemon/machine restart
_enter_reuse_mode() {
    _entrypoint_log "Container ready for exec sessions (reuse mode)"
    mkdir -p /tmp/.devcage-sessions

    # ── Idle watchdog (background) ────────────────────────────────
    (
        local _IDLE_TIMEOUT="${DEVCAGE_IDLE_TIMEOUT:-300}"  # 5 minutes default
        # Immediate-stop mode (idle-timeout=0): poll every 5s as crash/stale-file safety net.
        # For short non-zero timeouts, cap poll interval at the timeout value so the container
        # stops within ~2× the configured timeout (predictable for tests and short-lived jobs).
        local _POLL_INTERVAL=30
        [ "$_IDLE_TIMEOUT" = "0" ] && _POLL_INTERVAL=5
        if [ "$_IDLE_TIMEOUT" != "0" ] && [ "$_IDLE_TIMEOUT" -lt 30 ] 2>/dev/null; then
            _POLL_INTERVAL="$_IDLE_TIMEOUT"
        fi
        local _idle_since="" _had_sessions=0 _sf _pid _live
        while true; do
            sleep "$_POLL_INTERVAL"

            # Detect first session: devcage-session creates .had-sessions on startup.
            # This distinguishes "no sessions yet" (don't fire idle-timeout=0) from
            # "all sessions ended" (do fire).
            if [ "$_had_sessions" = "0" ] && [ -f /tmp/.devcage-sessions/.had-sessions ]; then
                _had_sessions=1
                _idle_since=""  # Reset any startup-timeout countdown on first session
            fi

            # Count live sessions via /proc/$PID liveness; prune stale files.
            # Session files are named by the devcage-session bash PID. That PID
            # persists through the exec chain (setpriv → agent), so /proc/$PID
            # correctly reflects whether the agent is still running. Using kill -0
            # would require CAP_KILL to signal unprivileged (UID 1000) processes
            # from root — /proc is capability-free. Hidden files (like .had-sessions)
            # are not matched by the glob and are never pruned.
            _live=0
            for _sf in /tmp/.devcage-sessions/*; do
                [ -f "$_sf" ] || continue
                _pid="${_sf##*/}"
                if [ -d "/proc/$_pid" ]; then
                    _live=$(( _live + 1 ))
                else
                    rm -f "$_sf"
                fi
            done

            if [ "$_live" -gt 0 ]; then
                _idle_since=""  # Sessions active — reset idle timer
            elif [ "$_had_sessions" = "1" ]; then
                # All sessions have ended
                if [ "$_IDLE_TIMEOUT" = "0" ]; then
                    _entrypoint_log "No active sessions — stopping container (idle-timeout=0)"
                    kill -USR1 1 2>/dev/null || true  # Ask PID 1 (bash) to exit cleanly
                    exit 0
                fi
                if [ "$_idle_since" = "" ]; then
                    _idle_since="$(date +%s)"
                elif [ $(( $(date +%s) - _idle_since )) -ge "$_IDLE_TIMEOUT" ]; then
                    _entrypoint_log "Idle timeout (${_IDLE_TIMEOUT}s) — stopping container"
                    kill -USR1 1 2>/dev/null || true  # Ask PID 1 (bash) to exit cleanly
                    exit 0
                fi
            else
                # No sessions have connected yet — startup timeout (zombie container prevention).
                # Not applied for idle-timeout=0; those containers wait indefinitely for the
                # first session so the launcher has time to docker exec into them.
                if [ "$_IDLE_TIMEOUT" != "0" ]; then
                    if [ "$_idle_since" = "" ]; then
                        _idle_since="$(date +%s)"
                    elif [ $(( $(date +%s) - _idle_since )) -ge "$_IDLE_TIMEOUT" ]; then
                        _entrypoint_log "Startup timeout (${_IDLE_TIMEOUT}s, no sessions connected) — stopping container"
                        kill -USR1 1 2>/dev/null || true
                        exit 0
                    fi
                fi
            fi
        done
    ) &

    # Keep container alive; trap signals for graceful shutdown.
    # Bash must stay as PID 1 (no exec) so the traps below are honoured.
    trap 'exit 143' TERM
    trap 'exit 0' USR1 INT
    sleep infinity &
    wait
    # wait returns when sleep infinity is killed by a signal; the trap then fires.
    # This function never returns normally.
}
