#!/usr/bin/env bash
# ── Timmy Development Loop v2 ──────────────────────────────────────────
# Runs forever. Each cycle: Timmy triages → Hermes picks work → execute
# → Timmy reviews → merge. State in .loop/state.json.
# ───────────────────────────────────────────────────────────────────────

set -uo pipefail

export PATH="$HOME/.local/bin:$HOME/.hermes/bin:/usr/local/bin:$PATH"

REPO="$HOME/Timmy-Time-dashboard"
STATE="$REPO/.loop/state.json"
LOG_DIR="$REPO/.loop/logs"
CLAIMS="$REPO/.loop/claims.json"
PROMPT_FILE="$HOME/.hermes/bin/timmy-loop-prompt.md"
LOCKFILE="/tmp/timmy-loop.lock"
COOLDOWN=3
MAX_CYCLE_TIME=1200          # 20 min — enough for complex issues
CLAIM_TTL_SECONDS=3600       # 1 hour — stale claims auto-expire
# macOS doesn't have timeout; use perl fallback
if ! command -v timeout &>/dev/null; then
    timeout() {
        local duration="$1"; shift
        perl -e "alarm $duration; exec @ARGV" -- "$@"
    }
fi

mkdir -p "$LOG_DIR"

# ── Single-instance lock ──────────────────────────────────────────────
if [ -f "$LOCKFILE" ]; then
    PID=$(cat "$LOCKFILE" 2>/dev/null)
    if kill -0 "$PID" 2>/dev/null; then
        echo "[loop] Already running (PID $PID). Exiting."
        exit 0
    fi
    rm -f "$LOCKFILE"
fi
echo $$ > "$LOCKFILE"
trap 'rm -f "$LOCKFILE"' EXIT

# ── Helpers ───────────────────────────────────────────────────────────
update_state() {
    local key="$1" value="$2"
    python3 -c "
import json
with open('$STATE') as f: s = json.load(f)
s['$key'] = $value
with open('$STATE', 'w') as f: json.dump(s, f, indent=2)
"
}

log() {
    echo "[$(date '+%H:%M:%S')] $*"
}

# ── Expire stale claims ──────────────────────────────────────────────
expire_claims() {
    python3 -c "
import json
from datetime import datetime, timezone, timedelta

TTL = timedelta(seconds=$CLAIM_TTL_SECONDS)
now = datetime.now(timezone.utc)

try:
    with open('$CLAIMS') as f:
        claims = json.load(f)
except:
    claims = {}

expired = []
active = {}
for issue, info in claims.items():
    if isinstance(info, dict):
        claimed_at = info.get('at', '')
        try:
            dt = datetime.fromisoformat(claimed_at.replace('Z', '+00:00'))
            if now - dt > TTL:
                expired.append(issue)
                continue
        except:
            pass
    active[issue] = info

if expired:
    print(f'[claims] Expired {len(expired)} stale claims: {expired}')
    with open('$CLAIMS', 'w') as f:
        json.dump(active, f, indent=2)
else:
    print('[claims] No stale claims')
" 2>&1
}

# ── Cleanup after timeout/failure ─────────────────────────────────────
cleanup_cycle() {
    local cycle_num="$1"
    local worktree="/tmp/timmy-cycle-${cycle_num}"

    # Remove worktree if it exists
    if [ -d "$worktree" ]; then
        log "Cleaning up worktree $worktree"
        cd "$REPO" && git worktree remove "$worktree" --force 2>/dev/null || true
    fi

    # Release any claims held by this cycle
    python3 -c "
import json
try:
    with open('$CLAIMS') as f:
        claims = json.load(f)
    released = []
    active = {}
    for issue, info in claims.items():
        agent = info.get('by', '') if isinstance(info, dict) else ''
        if 'loop' in agent or 'hermes' in agent.lower():
            released.append(issue)
        else:
            active[issue] = info
    if released:
        with open('$CLAIMS', 'w') as f:
            json.dump(active, f, indent=2)
        print(f'[cleanup] Released claims: {released}')
except Exception as e:
    print(f'[cleanup] Claim cleanup failed: {e}')
" 2>&1
}



# ── Main Loop ─────────────────────────────────────────────────────────
log "Timmy development loop v2 starting. PID $$"
log "Timeout: ${MAX_CYCLE_TIME}s | Cooldown: ${COOLDOWN}s | Claim TTL: ${CLAIM_TTL_SECONDS}s"
log "Repo:  $REPO"
update_state "started_at" "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
update_state "status" '"running"'

CYCLE=$(python3 -c "import json; print(json.load(open('$STATE'))['cycle'])")

while true; do
    # Check for stop file
    if [ -f "$REPO/.loop/STOP" ]; then
        log "STOP file found. Halting loop."
        update_state "status" '"stopped"'
        break
    fi

    CYCLE=$((CYCLE + 1))
    CYCLE_LOG="$LOG_DIR/cycle-$(printf '%04d' $CYCLE).log"

    log "━━━ Cycle $CYCLE ━━━"
    update_state "cycle" "$CYCLE"
    update_state "status" '"working"'

    # ── Pre-cycle housekeeping ────────────────────────────────────────
    expire_claims
    
    # ── Build the prompt with time budget ──────────────────────────────
    PROMPT=$(cat "$PROMPT_FILE")
    PROMPT="TIME BUDGET: You have $((MAX_CYCLE_TIME / 60)) minutes for this cycle. Plan accordingly — do not start work you cannot finish.

$PROMPT"

    log "Spawning hermes for cycle $CYCLE..."

    # Run hermes with timeout — tee to log AND stdout for live visibility
    if timeout "$MAX_CYCLE_TIME" hermes chat --yolo -q "$PROMPT" 2>&1 | tee "$CYCLE_LOG"; then
        log "Cycle $CYCLE completed successfully"
        update_state "status" '"idle"'
        update_state "last_completed" "\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""


    else
        EXIT_CODE=$?
        log "Cycle $CYCLE exited with code $EXIT_CODE"
        update_state "status" '"error"'
        python3 -c "
import json
with open('$STATE') as f: s = json.load(f)
errs = s.get('errors', [])
errs.append({'cycle': $CYCLE, 'code': $EXIT_CODE, 'time': '$(date -u +%Y-%m-%dT%H:%M:%SZ)'})
s['errors'] = errs[-10:]
with open('$STATE', 'w') as f: json.dump(s, f, indent=2)
"
        # ── Cleanup on failure ────────────────────────────────────────
        cleanup_cycle "$CYCLE"
    fi

    log "Cooling down ${COOLDOWN}s before next cycle..."
    sleep "$COOLDOWN"
done
