claudy 0.2.0

Modern multi-provider launcher for Claude CLI
use std::time::Duration;

use crate::domain::channel_events::{OutboundMessage, Platform};
use crate::ports::channel_ports::ChannelPort;

#[derive(Debug, Clone)]
pub struct RetryPolicy {
    pub max_attempts: u32,
    pub base_delay: Duration,
    pub max_delay: Duration,
    pub jitter: bool,
}

impl RetryPolicy {
    pub fn for_platform(platform: Platform) -> Self {
        match platform {
            Platform::Telegram => Self {
                max_attempts: 3,
                base_delay: Duration::from_secs(1),
                max_delay: Duration::from_secs(30),
                jitter: true,
            },
            Platform::Slack => Self {
                max_attempts: 3,
                base_delay: Duration::from_secs(2),
                max_delay: Duration::from_secs(60),
                jitter: true,
            },
            Platform::Discord => Self {
                max_attempts: 3,
                base_delay: Duration::from_secs(5),
                max_delay: Duration::from_secs(120),
                jitter: true,
            },
        }
    }

    pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
        let base = self.base_delay.as_millis() as u64 * 2u64.pow(attempt);
        let capped = base.min(self.max_delay.as_millis() as u64);
        Duration::from_millis(capped)
    }
}

pub async fn retry_send(
    channel: &dyn ChannelPort,
    msg: &OutboundMessage,
    policy: &RetryPolicy,
) -> anyhow::Result<crate::domain::channel_events::MessageDelivery> {
    let mut attempt = 0;
    loop {
        match channel.send_message(msg).await {
            Ok(delivery) => return Ok(delivery),
            Err(e) if attempt + 1 < policy.max_attempts => {
                let delay = policy.delay_for_attempt(attempt);
                tracing::warn!(attempt, error = %e, "Retrying send after delay");
                tokio::time::sleep(delay).await;
                attempt += 1;
            }
            Err(e) => return Err(e),
        }
    }
}