#!/bin/bash
# ═══════════════════════════════════════════════════════════════
# ODS - Session Cleanup Script
# https://github.com/Light-Heart-Labs/ODS
#
# Prevents context overflow crashes by automatically managing
# session file lifecycle. When a session file exceeds the size
# threshold, it's deleted and its reference removed from
# sessions.json, forcing the gateway to create a fresh session.
#
# The agent doesn't notice — it just gets a clean context window.
# ═══════════════════════════════════════════════════════════════

set -euo pipefail

# ── Configuration ──────────────────────────────────────────────
# Strix Halo: OpenClaw runs in Docker, sessions are in data volume
OPENCLAW_DIR="${OPENCLAW_DIR:-$HOME/ods/data/openclaw/home}"
SESSIONS_DIR="${SESSIONS_DIR:-$OPENCLAW_DIR/agents/main/sessions}"
SESSIONS_JSON="$SESSIONS_DIR/sessions.json"
MAX_SIZE="${MAX_SIZE:-256000}"

usage() {
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Prevents context overflow by pruning OpenClaw session files: removes inactive"
    echo "sessions and deletes bloated ones (over size threshold), then updates"
    echo "sessions.json so the gateway creates a fresh session."
    echo ""
    echo "Options:"
    echo "  -h, --help   Show this help and exit."
    echo ""
    echo "Environment:"
    echo "  OPENCLAW_DIR   Base OpenClaw dir (default: \$HOME/ods/data/openclaw/home)"
    echo "  SESSIONS_DIR   Sessions directory (default: \$OPENCLAW_DIR/agents/main/sessions)"
    echo "  MAX_SIZE       Max session file size in bytes (default: 256000)"
    echo ""
    echo "Exit: 0 (always; missing paths are skipped with a log message)."
}

case "${1:-}" in
    -h|--help) usage; exit 0 ;;
esac

# ── Preflight ──────────────────────────────────────────────────
if [ ! -f "$SESSIONS_JSON" ]; then
    echo "[$(date)] No sessions.json found at $SESSIONS_JSON, skipping"
    exit 0
fi

if [ ! -d "$SESSIONS_DIR" ]; then
    echo "[$(date)] Sessions directory not found at $SESSIONS_DIR, skipping"
    exit 0
fi

# ── Extract active session IDs (portable: no grep -P) ─────────
ACTIVE_IDS_EXIT=0
ACTIVE_IDS=$(grep -oE '"sessionId"[[:space:]]*:[[:space:]]*"[^"]+"' "$SESSIONS_JSON" 2>&1 | sed -E 's/.*"sessionId"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') || ACTIVE_IDS_EXIT=$?
if [[ $ACTIVE_IDS_EXIT -ne 0 ]]; then
    ACTIVE_IDS=""
fi

echo "[$(date)] Session cleanup starting"
echo "[$(date)] Sessions dir: $SESSIONS_DIR"
echo "[$(date)] Max size threshold: $MAX_SIZE bytes"
echo "[$(date)] Active sessions found: $(echo "$ACTIVE_IDS" | wc -w)"

# ── Clean up debris ────────────────────────────────────────────
DELETED_EXIT=0
DELETED_COUNT=$(find "$SESSIONS_DIR" -name '*.deleted.*' -delete -print 2>&1 | wc -l) || DELETED_EXIT=$?
if [[ $DELETED_EXIT -ne 0 ]]; then
    DELETED_COUNT=0
fi

BAK_EXIT=0
BAK_COUNT=$(find "$SESSIONS_DIR" -name '*.bak*' -not -name '*.bak-cleanup' -delete -print 2>&1 | wc -l) || BAK_EXIT=$?
if [[ $BAK_EXIT -ne 0 ]]; then
    BAK_COUNT=0
fi

if [ "$DELETED_COUNT" -gt 0 ] || [ "$BAK_COUNT" -gt 0 ]; then
    echo "[$(date)] Cleaned up $DELETED_COUNT .deleted files, $BAK_COUNT .bak files"
fi

# ── Process session files ──────────────────────────────────────
WIPE_IDS=""
REMOVED_INACTIVE=0
REMOVED_BLOATED=0

for f in "$SESSIONS_DIR"/*.jsonl; do
    [ -f "$f" ] || continue
    BASENAME=$(basename "$f" .jsonl)

    # Check if this session is active
    IS_ACTIVE=false
    for ID in $ACTIVE_IDS; do
        if [ "$BASENAME" = "$ID" ]; then
            IS_ACTIVE=true
            break
        fi
    done

    if [ "$IS_ACTIVE" = false ]; then
        SIZE=$(du -h "$f" | cut -f1)
        echo "[$(date)] Removing inactive session: $BASENAME ($SIZE)"
        rm -f "$f"
        REMOVED_INACTIVE=$((REMOVED_INACTIVE + 1))
    else
        # Portable stat: Linux uses -c%s, macOS uses -f%z
        stat_exit=0
        if [ "$(uname -s)" = "Darwin" ]; then
            SIZE_BYTES=$(stat -f%z "$f" 2>&1) || stat_exit=$?
        else
            SIZE_BYTES=$(stat -c%s "$f" 2>&1) || stat_exit=$?
        fi
        if [[ $stat_exit -ne 0 ]]; then
            SIZE_BYTES=0
        fi
        if [ "$SIZE_BYTES" -gt "$MAX_SIZE" ]; then
            SIZE=$(du -h "$f" | cut -f1)
            SIZE_LABEL=$(command -v numfmt >/dev/null 2>&1 && numfmt --to=iec "$MAX_SIZE" || echo "${MAX_SIZE}B")
            echo "[$(date)] Session $BASENAME is bloated ($SIZE > ${SIZE_LABEL}), deleting to force fresh session"
            rm -f "$f"
            WIPE_IDS="$WIPE_IDS $BASENAME"
            REMOVED_BLOATED=$((REMOVED_BLOATED + 1))
        fi
    fi
done

# ── Remove wiped session references from sessions.json ─────────
if [ -n "$WIPE_IDS" ]; then
    echo "[$(date)] Clearing session references from sessions.json for:$WIPE_IDS"
    cp "$SESSIONS_JSON" "$SESSIONS_JSON.bak-cleanup"

    for ID in $WIPE_IDS; do
        PYTHON_CMD="python3"
        if [[ -f "$(dirname "$0")/../lib/python-cmd.sh" ]]; then
            . "$(dirname "$0")/../lib/python-cmd.sh"
            PYTHON_CMD="$(ods_detect_python_cmd)"
        elif command -v python >/dev/null 2>&1; then
            PYTHON_CMD="python"
        fi

        "$PYTHON_CMD" -c "
import json, sys
sessions_file = sys.argv[1]
target_id = sys.argv[2]
with open(sessions_file, 'r') as f:
    data = json.load(f)
to_remove = [k for k, v in data.items() if isinstance(v, dict) and v.get('sessionId') == target_id]
for k in to_remove:
    del data[k]
    print(f'  Removed session key: {k}', file=sys.stderr)
with open(sessions_file, 'w') as f:
    json.dump(data, f, indent=2)
" "$SESSIONS_JSON" "$ID" 2>&1
    done

    # Clean up the backup
    rm -f "$SESSIONS_JSON.bak-cleanup"
fi

# ── Summary ────────────────────────────────────────────────────
echo "[$(date)] Cleanup complete: removed $REMOVED_INACTIVE inactive, $REMOVED_BLOATED bloated"
REMAINING_EXIT=0
REMAINING=$(find "$SESSIONS_DIR" -maxdepth 1 -name '*.jsonl' 2>&1 | wc -l) || REMAINING_EXIT=$?
if [[ $REMAINING_EXIT -ne 0 ]]; then
    REMAINING=0
fi
echo "[$(date)] Remaining session files: $REMAINING"
if [ "$REMAINING" -gt 0 ]; then
    ls_exit=0
    ls -lhS "$SESSIONS_DIR"/*.jsonl 2>&1 | while read -r line; do
        echo "  $line"
    done || ls_exit=$?
fi
