use std::sync::Arc;
use std::time::Duration;
use digdigdig3::connector_manager::ExchangeHub;
use digdigdig3::core::types::{AccountType, ExchangeId};
use serde::{Deserialize, Serialize};
use crate::data::BarPoint;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct GapHealConfig {
pub enabled: bool,
pub default_limit: usize,
pub max_limit: usize,
}
impl Default for GapHealConfig {
fn default() -> Self {
Self {
enabled: false,
default_limit: parse_env_usize("DIG3_HEAL_DEFAULT_LIMIT").unwrap_or(300),
max_limit: parse_env_usize("DIG3_HEAL_MAX_LIMIT").unwrap_or(1000),
}
}
}
fn parse_env_usize(key: &str) -> Option<usize> {
std::env::var(key).ok().and_then(|s| s.parse().ok())
}
impl GapHealConfig {
pub fn on() -> Self {
Self { enabled: true, ..Self::default() }
}
pub fn default_limit(mut self, n: usize) -> Self { self.default_limit = n; self }
pub fn max_limit(mut self, n: usize) -> Self { self.max_limit = n; self }
}
pub fn heal_limit(
cfg: &GapHealConfig,
interval: &str,
last_written_ms: i64,
now_ms: i64,
) -> usize {
let base = cfg.default_limit.max(1);
let capped = base.min(cfg.max_limit.max(1));
if last_written_ms <= 0 || now_ms <= last_written_ms {
return capped;
}
let Some(d) = kline_interval_to_duration(interval) else { return capped };
let interval_ms = d.as_millis().max(1) as u64;
let gap_ms = (now_ms - last_written_ms) as u64;
let need = ((gap_ms + interval_ms - 1) / interval_ms) as usize; need.max(base).min(cfg.max_limit.max(1))
}
pub fn kline_interval_to_duration(s: &str) -> Option<Duration> {
let (n_str, unit) = s.split_at(s.len().saturating_sub(1));
let n: u64 = n_str.parse().ok()?;
let secs = match unit {
"s" => n,
"m" => n * 60,
"h" => n * 3600,
"d" => n * 86400,
"w" => n * 86400 * 7,
_ => return None,
};
Some(Duration::from_secs(secs))
}
pub async fn heal_klines(
hub: &Arc<ExchangeHub>,
exchange: ExchangeId,
account: AccountType,
symbol: &str,
interval: &str,
_last_emitted_ms: i64,
limit: usize,
) -> Vec<BarPoint> {
crate::backfill::klines_recent(hub, exchange, account, symbol, interval, limit).await
}
pub fn select_heal_window<T: crate::series::DataPoint>(
pulled: Vec<T>,
last_seen_ms: i64,
) -> Vec<T> {
let mut filtered: Vec<T> = pulled.into_iter().filter(|p| p.timestamp_ms() > last_seen_ms).collect();
filtered.sort_by_key(|p| p.timestamp_ms());
filtered.dedup_by_key(|p| p.timestamp_ms());
filtered
}