/// ops_heartbeat.mbt —— 活动信号/心跳看护(M4 运维:AO ActivityState / no_signal watchdog)
///
/// 语义参考 agent-orchestrator 的 activity lifecycle:
///   pre-execution -> active(执行前上报) -> submit(提交后 idle)
/// 单进程 MCP server 场景,进程内登记即可;心跳超时触发 heal 回滚重派(见 ops_heal.mbt)。
/// 持久化说明:心跳为易失活动态,不落 SQLite(重启即重新派发,符合 watchdog 语义)。

/// 心跳登记表:task_id -> (signal, last_seen)。
pub struct Heartbeat {
  signals : Map[String, String]
  last_seen : Map[String, String]
}

pub fn Heartbeat::new() -> Heartbeat {
  { signals: Map([]), last_seen: Map([]) }
}

/// 上报心跳:登记任务信号(pre-execution / active / submit)与时间戳。
pub fn Heartbeat::beat(
  self : Heartbeat,
  task_id : String,
  signal~ : String = "active",
  now~ : String,
) -> Unit {
  self.signals.set(task_id, signal)
  self.last_seen.set(task_id, now)
}

/// 查询某任务最近一次心跳信号;无记录返回空串。
pub fn Heartbeat::signal_of(self : Heartbeat, task_id : String) -> String {
  match self.signals.get(task_id) {
    Some(s) => s
    None => ""
  }
}

/// 查询某任务最近心跳时间;无记录返回空串。
pub fn Heartbeat::last_seen_of(self : Heartbeat, task_id : String) -> String {
  match self.last_seen.get(task_id) {
    Some(s) => s
    None => ""
  }
}

/// 清除某任务的心跳记录(heal 回滚后调用)。
pub fn Heartbeat::clear(self : Heartbeat, task_id : String) -> Unit {
  self.signals.remove(task_id)
  self.last_seen.remove(task_id)
}

/// 是否"超时静默"(no_signal):
///   - 无任何心跳记录 -> 视作从未活动(stale,交由 watchdog 处理);
///   - 有记录但距上次心跳超过 timeout_sec -> stale。
/// 时间戳解析失败时保守返回 false(不误伤)。
pub fn Heartbeat::is_stale(
  self : Heartbeat,
  task_id : String,
  now : String,
  timeout_sec~ : Int = 600,
) -> Bool {
  let now_s = iso_to_secs(now)
  if now_s < 0 {
    return false
  }
  match self.last_seen.get(task_id) {
    None => true
    Some(ts) => {
      let ts_s = iso_to_secs(ts)
      if ts_s < 0 { false } else { now_s - ts_s > timeout_sec }
    }
  }
}