#!/usr/bin/env ruby
# frozen_string_literal: true

# SPDX-License-Identifier: GPL-3.0-only
# Copyright (C) 2026 Hugo Antonio Sepulveda Manriquez / NeatNerds
#
# varunad-lifecycle — Varuna orchestrator lifecycle manager.
#
# Loops on a fixed interval, invoking Varuna::Lifecycle::Manager#tick.
# When the orchestrator pane is AWOL and the recovery budget allows,
# fires the ROLLOVER-FORCED.flag for cc-auto-rollover-spawner to
# consume.
#
# Subsumes the four core behaviors of the legacy
# `duct-tape-watchdog.service`:
#
#   * alive-detection (S00-ORCHESTRATOR pane probe)
#   * rollover-trigger (ROLLOVER-FORCED.flag write)
#   * cooldown/throttle (max recoveries per hour)
#   * hourly budget tracking (persistent YAML ledger)
#
# This is Phase 4 Task 4.1 of BOIP () — the foundation. Future
# tasks layer state authority, gRPC API, recovery state machine, and
# bulkhead architecture on top.
#
# ## Environment variables
#
# | Var                            | Default | Purpose                                       |
# | ------------------------------ | ------- | --------------------------------------------- |
# | `VARUNA_LIFECYCLE_INTERVAL`    | `60`    | seconds between ticks                         |
# | `VARUNA_LIFECYCLE_WINDOW`      | unset   | orchestrator byobu window name override       |
# | `VARUNA_LIFECYCLE_MAX_PER_HOUR`| `3`     | rolling-hour recovery cap                     |
# | `VARUNA_LIFECYCLE_BUDGET_PATH` | unset   | override path for varuna-budget.yml           |
# | `VARUNA_LIFECYCLE_ONCE`        | unset   | when truthy, run a single tick and exit 0     |
#
# ## Exit codes
#
# * `0` clean SIGINT / SIGTERM shutdown
# * `0` `--once` mode completed one tick
# * `64` invalid CLI invocation

require_relative '../lib/varuna'
require 'varuna/lifecycle'

@running = true
['INT', 'TERM'].each do |sig|
  Signal.trap(sig) { @running = false }
end

interval = Integer(ENV.fetch('VARUNA_LIFECYCLE_INTERVAL', '60'))
window   = ENV['VARUNA_LIFECYCLE_WINDOW'] || Varuna::Lifecycle::ORCHESTRATOR_WINDOW
max_per_hour = Integer(ENV.fetch('VARUNA_LIFECYCLE_MAX_PER_HOUR', '3'))
budget_path  = ENV['VARUNA_LIFECYCLE_BUDGET_PATH'] ||
               File.join(Varuna::Lifecycle::STATE_DIR,
                         Varuna::Lifecycle::Throttle::DEFAULT_BUDGET_FILENAME)
run_once = !ENV['VARUNA_LIFECYCLE_ONCE'].to_s.strip.empty?

probe    = Varuna::Lifecycle::AliveProbe.new(window_name: window)
trigger  = Varuna::Lifecycle::RolloverTrigger.new
throttle = Varuna::Lifecycle::Throttle.new(max_per_hour: max_per_hour,
                                           budget_path: budget_path)
manager  = Varuna::Lifecycle::Manager.new(alive_probe: probe,
                                          rollover_trigger: trigger,
                                          throttle: throttle)

warn "[varunad-lifecycle] started — interval=#{interval}s window=#{window} " \
     "max_per_hour=#{max_per_hour} budget=#{budget_path}"

loop do
  result = manager.tick
  warn "[varunad-lifecycle] #{result[:action]}: #{result[:evidence]}" if result[:action] != :no_action

  break if run_once
  break unless @running

  sleep interval
end

warn '[varunad-lifecycle] shutting down cleanly'
