//! Event-to-notification mapping.
//!
//! Pure functions that classify [`WatcherEvent`] variants into notification
//! actions, deciding which events produce user-visible messages and which
//! are suppressed or logged server-side only.

use crate::io::watcher::WatcherEvent;

/// The notification action to take in response to a [`WatcherEvent`].
#[derive(Debug, PartialEq)]
pub enum EventAction {
    /// Post a merge-failure error notification.
    MergeFailure,
    /// Post a rate-limit warning (subject to config/debounce suppression).
    RateLimitWarning,
    /// Post a story-blocked notification.
    StoryBlocked,
    /// Post an OAuth account-swap notification naming the new account.
    OAuthAccountSwapped,
    /// Post an OAuth accounts-exhausted notification with the earliest reset time.
    OAuthAccountsExhausted,
    /// Post an agent-started (running) notification.
    AgentStarted,
    /// Post an agent-completed notification with pass/fail result.
    AgentCompleted {
        /// `true` if acceptance gates passed.
        success: bool,
    },
    /// Post a new-item-created notification.
    NewItemCreated,
    /// Post a merge-auto-retry notification naming the attempt and budget.
    MergeAutoRetry,
    /// Post a low-disk-space warning notification (story 1200).
    DiskWarning {
        /// Severity level: `"warn"` or `"critical"`.
        level: String,
    },
    /// Post a disk-space recovery notification (story 1200).
    DiskRecovery,
    /// Log server-side only; do not post to chat (e.g. hard rate-limit blocks).
    LogOnly,
    /// Reload the project configuration.
    ReloadConfig,
    /// Skip silently (synthetic events, unknown variants).
    Skip,
}

/// Classify a [`WatcherEvent`] into the action the notification listener should take.
pub fn classify(event: &WatcherEvent) -> EventAction {
    match event {
        // Stage-change notifications are now handled by the TransitionFired subscriber
        // (story 995). WorkItem events are skipped regardless of from_stage.
        WatcherEvent::WorkItem { .. } => EventAction::Skip,
        WatcherEvent::MergeFailure { .. } => EventAction::MergeFailure,
        WatcherEvent::RateLimitWarning { .. } => EventAction::RateLimitWarning,
        WatcherEvent::StoryBlocked { .. } => EventAction::StoryBlocked,
        WatcherEvent::RateLimitHardBlock { .. } => EventAction::LogOnly,
        WatcherEvent::ConfigChanged => EventAction::ReloadConfig,
        WatcherEvent::OAuthAccountSwapped { .. } => EventAction::OAuthAccountSwapped,
        WatcherEvent::OAuthAccountsExhausted { .. } => EventAction::OAuthAccountsExhausted,
        WatcherEvent::AgentStarted { .. } => EventAction::AgentStarted,
        WatcherEvent::AgentCompleted { success, .. } => {
            EventAction::AgentCompleted { success: *success }
        }
        WatcherEvent::NewItemCreated { .. } => EventAction::NewItemCreated,
        WatcherEvent::MergeAutoRetry { .. } => EventAction::MergeAutoRetry,
        WatcherEvent::DiskSpaceWarning { level, .. } => EventAction::DiskWarning {
            level: level.clone(),
        },
        WatcherEvent::DiskSpaceRecovered { .. } => EventAction::DiskRecovery,
        _ => EventAction::Skip,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn work_item(from_stage: Option<&str>) -> WatcherEvent {
        WatcherEvent::WorkItem {
            stage: "3_qa".to_string(),
            item_id: "1_story_foo".to_string(),
            action: "qa".to_string(),
            commit_msg: String::new(),
            from_stage: from_stage.map(str::to_string),
        }
    }

    // Stage-change notifications moved to TransitionFired subscriber (story 995).
    // All WorkItem events are now classified as Skip regardless of from_stage.
    #[test]
    fn work_item_with_from_stage_is_skip() {
        let event = work_item(Some("2_current"));
        assert_eq!(classify(&event), EventAction::Skip);
    }

    #[test]
    fn work_item_without_from_stage_is_skip() {
        let event = work_item(None);
        assert_eq!(classify(&event), EventAction::Skip);
    }

    #[test]
    fn merge_failure_is_classified_correctly() {
        let event = WatcherEvent::MergeFailure {
            story_id: "1_story_foo".to_string(),
            reason: "conflict".to_string(),
        };
        assert_eq!(classify(&event), EventAction::MergeFailure);
    }

    #[test]
    fn rate_limit_warning_is_classified_correctly() {
        let event = WatcherEvent::RateLimitWarning {
            story_id: "1_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
        };
        assert_eq!(classify(&event), EventAction::RateLimitWarning);
    }

    #[test]
    fn story_blocked_is_classified_correctly() {
        let event = WatcherEvent::StoryBlocked {
            story_id: "1_story_foo".to_string(),
            reason: "empty diff".to_string(),
        };
        assert_eq!(classify(&event), EventAction::StoryBlocked);
    }

    #[test]
    fn rate_limit_hard_block_is_log_only() {
        let event = WatcherEvent::RateLimitHardBlock {
            story_id: "1_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            reset_at: chrono::Utc::now(),
        };
        assert_eq!(classify(&event), EventAction::LogOnly);
    }

    #[test]
    fn config_changed_triggers_reload() {
        assert_eq!(
            classify(&WatcherEvent::ConfigChanged),
            EventAction::ReloadConfig
        );
    }

    #[test]
    fn oauth_account_swapped_is_classified_correctly() {
        let event = WatcherEvent::OAuthAccountSwapped {
            new_email: "new@example.com".to_string(),
        };
        assert_eq!(classify(&event), EventAction::OAuthAccountSwapped);
    }

    #[test]
    fn oauth_accounts_exhausted_is_classified_correctly() {
        let event = WatcherEvent::OAuthAccountsExhausted {
            earliest_reset_msg: "All accounts rate-limited; earliest reset in 2h".to_string(),
        };
        assert_eq!(classify(&event), EventAction::OAuthAccountsExhausted);
    }

    #[test]
    fn agent_started_is_classified_correctly() {
        let event = WatcherEvent::AgentStarted {
            story_id: "1_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
        };
        assert_eq!(classify(&event), EventAction::AgentStarted);
    }

    #[test]
    fn agent_completed_success_is_classified_correctly() {
        let event = WatcherEvent::AgentCompleted {
            story_id: "1_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            success: true,
        };
        assert_eq!(
            classify(&event),
            EventAction::AgentCompleted { success: true }
        );
    }

    #[test]
    fn agent_completed_failure_is_classified_correctly() {
        let event = WatcherEvent::AgentCompleted {
            story_id: "1_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            success: false,
        };
        assert_eq!(
            classify(&event),
            EventAction::AgentCompleted { success: false }
        );
    }

    #[test]
    fn new_item_created_is_classified_correctly() {
        let event = WatcherEvent::NewItemCreated {
            item_id: "1075_refactor_split_stage".to_string(),
            item_type: "refactor".to_string(),
            name: "Split Stage enum".to_string(),
            origin: "user alice".to_string(),
        };
        assert_eq!(classify(&event), EventAction::NewItemCreated);
    }

    #[test]
    fn merge_auto_retry_is_classified_correctly() {
        let event = WatcherEvent::MergeAutoRetry {
            story_id: "1_story_foo".to_string(),
            attempt: 1,
            budget: 3,
        };
        assert_eq!(classify(&event), EventAction::MergeAutoRetry);
    }
}
