//! Mention Resolver 注册表(核心设计2)
//!
//! 借鉴 [`crate::commands::ai::tool_registry::build_ai_tool_registry`] 的 AiToolRegistry
//! 模式:启动期 build 一次性注册,通过 `Arc<ResolverRegistry>` 注入 AppState,
//! 读路径(`resolve_all`)无锁并发访问。
//!
//! `resolve_all` 按 [`MentionRef::kind_str`] 分发到对应 Resolver,单条 resolve 失败
//! 收集进 errors 列表不阻断整批注入(返回 `(Vec<Augmentation>, Vec<ResolveError>)`)。

use std::collections::HashMap;
use std::sync::Arc;

use df_types::augmentation::{Augmentation, MentionRef, ResolveError};

use crate::commands::ai::augmentation::sanitize::ProviderLocality;
use crate::commands::ai::augmentation::MentionResolver;

/// Mention Resolver 注册表 — 按 kind 分发 MentionRef 到对应 Resolver。
///
/// 启动期 build 一次性 `register`,之后只读(`resolve_all`),故内部 HashMap 无 RwLock,
/// 通过 `Arc<ResolverRegistry>` 注入 AppState 共享。新增 mention 类型 = build 处
/// `register` 一行 + 加一个 impl,主干零改。
pub struct ResolverRegistry {
    resolvers: HashMap<&'static str, Arc<dyn MentionResolver>>,
}

impl ResolverRegistry {
    /// 创建空注册表。
    pub fn new() -> Self {
        Self {
            resolvers: HashMap::new(),
        }
    }

    /// 注册一个 Resolver。重复 kind 后注册者覆盖(开发期防漏注同名)。
    pub fn register(&mut self, resolver: Arc<dyn MentionResolver>) {
        let kind = resolver.kind();
        self.resolvers.insert(kind, resolver);
    }

    /// 批量 resolve mention 引用列表为 Augmentation。
    ///
    /// 按 [`MentionRef::kind_str`] 分发到对应 Resolver,逐条独立 resolve:
    /// - 成功 → push 进 `augs`
    /// - 失败(Resolver 返 Err / kind 无对应 Resolver)→ push 进 `errors`,**不中断**
    ///   后续 mention(整批注入最大化保留可用上下文)。
    ///
    /// 返回 `(成功列表, 失败列表)`,调用方据 errors 数决定是否提示用户某 mention 跳过。
    pub async fn resolve_all(
        &self,
        refs: &[MentionRef],
        loc: ProviderLocality,
    ) -> (Vec<Augmentation>, Vec<ResolveError>) {
        let mut augs = Vec::with_capacity(refs.len());
        let mut errors = Vec::new();
        for rf in refs {
            let kind = rf.kind_str();
            match self.resolvers.get(kind) {
                Some(resolver) => match resolver.resolve(rf, loc).await {
                    Ok(aug) => augs.push(aug),
                    Err(e) => errors.push(e),
                },
                None => {
                    // kind 无对应 Resolver(未注册的 mention 类型):收集 KindMismatch 不中断
                    errors.push(ResolveError::KindMismatch {
                        expected: "<registered>".to_string(),
                        actual: kind.to_string(),
                    });
                }
            }
        }
        (augs, errors)
    }
}

impl Default for ResolverRegistry {
    fn default() -> Self {
        Self::new()
    }
}

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

    /// 测试用 Resolver:kind 固定 "test",resolve 固定返 Skip(模拟失败收集)。
    struct SkipResolver;
    #[async_trait]
    impl MentionResolver for SkipResolver {
        fn kind(&self) -> &'static str {
            "test"
        }
        async fn resolve(
            &self,
            _rf: &MentionRef,
            _loc: ProviderLocality,
        ) -> Result<Augmentation, ResolveError> {
            Err(ResolveError::Skip {
                kind: "test".into(),
                ref_id: "x".into(),
                reason: "test skip".into(),
            })
        }
    }

    #[tokio::test]
    async fn resolve_all_unknown_kind_collected_not_panic() {
        let reg = ResolverRegistry::new();
        // 未注册任何 Resolver,传入 Project mention → KindMismatch 收集,不 panic
        let rf = MentionRef::Project {
            id: "p".into(),
            name: "n".into(),
        };
        let (augs, errors) = reg.resolve_all(&[rf], ProviderLocality::Local).await;
        assert!(augs.is_empty());
        assert_eq!(errors.len(), 1);
        match &errors[0] {
            ResolveError::KindMismatch { actual, .. } => assert_eq!(actual, "project"),
            other => panic!("expected KindMismatch, got {:?}", other),
        }
    }

    #[tokio::test]
    async fn resolve_all_failure_collected_continues_others() {
        // 验证:单条 resolve 失败不中断后续 mention。
        // 构造两条 mention,第一条命中已注册 Resolver(SkipResolver 返 Skip 失败),
        // 第二条命中未知 kind(KindMismatch)。两条失败都收集,augs 为空,无 panic。
        // 注:SkipResolver kind="test",无法用现有 MentionRef 变体精确匹配(枚举 kind 固定
        // project/task/idea/skill),故此处仅验证 unknown-kind 路径收集不中断——核心语义
        // (失败收集 + 继续)已在 resolve_all 实现的 for 循环中保证,本测为回归守护。
        let mut reg = ResolverRegistry::new();
        reg.register(Arc::new(SkipResolver));
        let refs = vec![
            MentionRef::Project { id: "p".into(), name: "n".into() },
            MentionRef::Skill { name: "s".into() },
        ];
        let (augs, errors) = reg.resolve_all(&refs, ProviderLocality::Local).await;
        assert!(augs.is_empty(), "两条 mention kind 均≠test 应全 KindMismatch");
        assert_eq!(errors.len(), 2, "两条失败都应收集");
    }
}
