//! Backlog promotion: scan items in `Pipeline::Backlog` and promote stories whose `depends_on` are all met.

use crate::pipeline_state::Pipeline;
use crate::slog;
use crate::slog_warn;

use super::super::AgentPool;
use super::story_checks::{check_archived_dependencies, has_unmet_dependencies};

impl AgentPool {
    /// Scan items in `Pipeline::Backlog` and promote any story whose `depends_on` are all met.
    ///
    /// A story is only promoted if it explicitly lists `depends_on` AND every
    /// listed dependency has reached `Pipeline::Done` or `Pipeline::Archived`.
    /// Stories with no `depends_on` are left in the backlog for human scheduling.
    ///
    /// **Archived dep semantics:** a dep in `Pipeline::Archived` counts as satisfied
    /// (since stories auto-sweep from `Done` to `Archived` after 4 hours, and the
    /// dependent story would normally already be promoted by then).  However, if a
    /// dep was already archived when the dependent story was created (e.g. it
    /// was abandoned/superseded before the dependent existed), a prominent warning is
    /// logged so the user can see the promotion was triggered by an archived dep, not
    /// a clean completion.
    pub(super) fn promote_ready_backlog_stories(&self) {
        // Story 1086: scan by Pipeline column, not Stage variant. Pipeline::Backlog
        // covers Stage::Upcoming and Stage::Backlog uniformly.
        let items: Vec<String> = {
            use std::collections::BTreeSet;
            let mut ids = BTreeSet::new();
            for item in crate::pipeline_state::read_all_typed() {
                if item.stage.pipeline() == Pipeline::Backlog {
                    ids.insert(item.story_id.0.clone());
                }
            }
            ids.into_iter().collect()
        };
        for story_id in &items {
            // Only promote stories that explicitly declare dependencies
            // (story 929: read from the CRDT register, not YAML).
            let has_deps = crate::crdt_state::read_item(story_id)
                .map(|w| !w.depends_on().is_empty())
                .unwrap_or(false);
            if !has_deps {
                continue;
            }
            // Check whether any dependencies are still unmet.
            if has_unmet_dependencies(story_id) {
                continue;
            }
            // Warn if any deps were satisfied via archive rather than via clean done.
            let archived_deps = check_archived_dependencies(story_id);
            if !archived_deps.is_empty() {
                slog_warn!(
                    "[auto-assign] Story '{story_id}' is being promoted because deps \
                     {archived_deps:?} are in 6_archived (not cleanly completed via 5_done). \
                     These deps may have been abandoned or superseded. If this promotion is \
                     unintentional, remove the depends_on or manually move the story back to \
                     1_backlog."
                );
            }
            // All deps met — promote from backlog to current.
            slog!("[auto-assign] Story '{story_id}' deps met; promoting from backlog to current.");
            if let Err(e) = crate::agents::lifecycle::move_story_to_current(story_id) {
                slog!("[auto-assign] Failed to promote '{story_id}' to current: {e}");
            }
        }
    }
}
