//! 四个 MentionResolver 实现(核心设计2)
//!
//! - [`ProjectResolver`]:持 `Arc<Database>`,经 `ProjectRepo::get_by_id` 取项目记录,
//!   path 经 [`sanitize_for`] 脱敏(Local 全路径 / Remote basename)。
//! - [`TaskResolver`]:经 `TaskRepo::get_by_id` 拿 task,再 join `project_id` 取 project_name。
//! - [`IdeaResolver`]:经 `IdeaRepo::get_by_id`(IdeaRecord 无 project_id,注入无 path)。
//! - [`SkillResolver`]:调 [`super::super::skills::read_skill_content_stripped`] 取剥 frontmatter 正文。
//!
//! Resolver 持 `Arc<Database>` 而非 `Arc<AppState>` —— 避免循环依赖(AppState 持
//! `Arc<ResolverRegistry>`,Resolver 若持 AppState 则成环)。Repo 在 resolve 内即时
//! `::new(&db)` 构造(Arc clone 廉价),与 tool_registry.rs 闭包内建 Repo 同模式。

use std::sync::Arc;

use async_trait::async_trait;
use df_storage::crud::{IdeaRepo, ProjectRepo, TaskRepo};
use df_storage::db::Database;
use df_types::augmentation::{Augmentation, MentionRef, ResolveError, SanitizedPath};
use df_types::types::{IdeaStatus, ProjectStatus, TaskStatus};
use df_storage::crud::{IdeaQuery, TaskQuery};

use crate::commands::ai::augmentation::registry::ResolverRegistry;
use crate::commands::ai::augmentation::sanitize::{sanitize_for, ProviderLocality};
use crate::commands::ai::augmentation::MentionResolver;
use crate::commands::ai::skills::read_skill_content_stripped;

/// 启动期构建 ResolverRegistry,注册四个 resolver(Project/Task/Idea/Skill)。
///
/// resolver 持 `Arc<Database>` 访问 repo(非 AppState,避免循环依赖)。
/// Skill resolver 不持 db(调 skills::read_skill_content_stripped 读缓存 + 单文件)。
/// 在 [`crate::state::AppState::init`] 与 `build_ai_tool_registry` 同侧调用,产
/// `Arc<ResolverRegistry>` 注入 `AppState.resolvers`。
pub fn build_resolver_registry(db: Arc<Database>) -> ResolverRegistry {
    let mut reg = ResolverRegistry::new();
    reg.register(Arc::new(ProjectResolver::new(db.clone())));
    reg.register(Arc::new(TaskResolver::new(db.clone())));
    reg.register(Arc::new(IdeaResolver::new(db.clone())));
    reg.register(Arc::new(SkillResolver::new()));
    reg
}

/// @项目 Resolver:取项目记录 + path 脱敏。
///
/// path 脱敏(Local 全路径 / Remote basename)由 `loc` 参数决定,经 [`sanitize_for`]
/// 包成 [`SanitizedPath`] newtype 防裸 String 误用。项目未绑定目录(path=None)时
/// Augmentation.path=None(与现状一致,不堵未绑定项目)。
pub struct ProjectResolver {
    db: Arc<Database>,
}

impl ProjectResolver {
    pub fn new(db: Arc<Database>) -> Self {
        Self { db }
    }
}

#[async_trait]
impl MentionResolver for ProjectResolver {
    fn kind(&self) -> &'static str {
        "project"
    }

    async fn resolve(
        &self,
        rf: &MentionRef,
        loc: ProviderLocality,
    ) -> Result<Augmentation, ResolveError> {
        let id = match rf {
            MentionRef::Project { id, .. } => id.clone(),
            _ => {
                return Err(ResolveError::KindMismatch {
                    expected: "project".to_string(),
                    actual: rf.kind_str().to_string(),
                });
            }
        };
        let repo = ProjectRepo::new(&self.db);
        let record = repo
            .get_by_id(&id)
            .await
            .map_err(|e| ResolveError::Skip {
                kind: "project".to_string(),
                ref_id: id.clone(),
                reason: format!("查询失败: {}", e),
            })?
            .ok_or_else(|| ResolveError::NotFound {
                kind: "project".to_string(),
                ref_id: id.clone(),
            })?;
        // status: String → ProjectStatus(未知字符串按 Planning 兜底,不阻断注入)
        let status = ProjectStatus::from_db_str(record.status.as_str()).unwrap_or(ProjectStatus::Planning);
        // path 脱敏:None(未绑定)保持 None;Some 则按 locality 脱敏包 newtype
        let path = record
            .path
            .as_deref()
            .filter(|s| !s.is_empty())
            .map(|raw| SanitizedPath::new(sanitize_for(raw, loc)));
        Ok(Augmentation::Project {
            id: record.id,
            name: record.name,
            status,
            description: record.description,
            path,
            extra: {
                let mut lines: Vec<String> = Vec::new();
                // Phase 4:查询该项目的前 5 条未完成任务
                let task_repo = TaskRepo::new(&self.db);
                if let Ok(tasks) = task_repo.list_by_query(&TaskQuery {
                    project_id: Some(id.clone()),
                    status: Some("in_progress".into()),
                    limit: Some(5),
                    ..Default::default()
                }).await {
                    if !tasks.is_empty() {
                        let mut task_lines: Vec<String> = tasks.iter().map(|t| {
                            format!("  - {} ({})", t.title, t.status.as_str())
                        }).collect();
                        task_lines.insert(0, format!("进行中任务({}):", tasks.len()));
                        lines.push(task_lines.join("\n"));
                    }
                }
                // 待评估灵感
                let idea_repo = IdeaRepo::new(&self.db);
                if let Ok(ideas) = idea_repo.list_by_query(&IdeaQuery {
                    limit: Some(3),
                    ..Default::default()
                }).await {
                    let related: Vec<_> = ideas.iter()
                        .filter(|i| i.status.as_str() == "pending_review")
                        .take(3)
                        .map(|i| format!("  - {} ({})", i.title, i.status.as_str()))
                        .collect();
                    if !related.is_empty() {
                        let mut idea_lines = vec![format!("待评估灵感({}):", related.len())];
                        idea_lines.extend(related);
                        lines.push(idea_lines.join("\n"));
                    }
                }
                lines
            },
        })
    }
}

/// @任务 Resolver:取任务记录 + join 项目名(project_name)。
///
/// TaskRepo::get_by_id 拿 task,再经 ProjectRepo::get_by_id(task.project_id) 取项目名;
/// project_id 无对应项目(游离任务/项目已删)时 project_name=None(非错误,不阻断注入)。
pub struct TaskResolver {
    db: Arc<Database>,
}

impl TaskResolver {
    pub fn new(db: Arc<Database>) -> Self {
        Self { db }
    }
}

#[async_trait]
impl MentionResolver for TaskResolver {
    fn kind(&self) -> &'static str {
        "task"
    }

    async fn resolve(
        &self,
        rf: &MentionRef,
        _loc: ProviderLocality,
    ) -> Result<Augmentation, ResolveError> {
        let (id, _name) = match rf {
            MentionRef::Task { id, name } => (id.clone(), name.clone()),
            _ => {
                return Err(ResolveError::KindMismatch {
                    expected: "task".to_string(),
                    actual: rf.kind_str().to_string(),
                });
            }
        };
        let task_repo = TaskRepo::new(&self.db);
        let task = task_repo
            .get_by_id(&id)
            .await
            .map_err(|e| ResolveError::Skip {
                kind: "task".to_string(),
                ref_id: id.clone(),
                reason: format!("查询失败: {}", e),
            })?
            .ok_or_else(|| ResolveError::NotFound {
                kind: "task".to_string(),
                ref_id: id.clone(),
            })?;
        let status = TaskStatus::from_db_str(task.status.as_str()).unwrap_or(TaskStatus::Todo);
        // join project_name:project_id 取 ProjectRecord.name;失败/无对应 None(非错误)
        let project_name = {
            let project_repo = ProjectRepo::new(&self.db);
            match project_repo.get_by_id(&task.project_id).await {
                Ok(Some(p)) => Some(p.name),
                _ => None,
            }
        };
        Ok(Augmentation::Task {
            id: task.id,
            title: task.title,
            status,
            description: task.description,
            project_name,
            extra: vec![],
        })
    }
}

/// @灵感 Resolver:取灵感记录(IdeaRecord 无 project_id,注入无 path)。
pub struct IdeaResolver {
    db: Arc<Database>,
}

impl IdeaResolver {
    pub fn new(db: Arc<Database>) -> Self {
        Self { db }
    }
}

#[async_trait]
impl MentionResolver for IdeaResolver {
    fn kind(&self) -> &'static str {
        "idea"
    }

    async fn resolve(
        &self,
        rf: &MentionRef,
        _loc: ProviderLocality,
    ) -> Result<Augmentation, ResolveError> {
        let (id, _name) = match rf {
            MentionRef::Idea { id, name } => (id.clone(), name.clone()),
            _ => {
                return Err(ResolveError::KindMismatch {
                    expected: "idea".to_string(),
                    actual: rf.kind_str().to_string(),
                });
            }
        };
        let repo = IdeaRepo::new(&self.db);
        let record = repo
            .get_by_id(&id)
            .await
            .map_err(|e| ResolveError::Skip {
                kind: "idea".to_string(),
                ref_id: id.clone(),
                reason: format!("查询失败: {}", e),
            })?
            .ok_or_else(|| ResolveError::NotFound {
                kind: "idea".to_string(),
                ref_id: id.clone(),
            })?;
        let status = IdeaStatus::from_db_str(record.status.as_str()).unwrap_or(IdeaStatus::Draft);
        Ok(Augmentation::Idea {
            id: record.id,
            title: record.title,
            status,
            description: record.description,
            extra: vec![],
        })
    }
}

/// /技能 Resolver:调 [`read_skill_content_stripped`] 取剥 frontmatter 正文。
///
/// skill 以 name 为唯一键(无全局 id),MentionRef::Skill 仅携带 name。
/// 缓存未命中 / 文件读失败 → NotFound(skill 已卸载或名称错)。
/// source 取缓存中 SkillInfo.source(skill/command/plugin),默认 "skill" 兜底。
pub struct SkillResolver;

impl SkillResolver {
    pub fn new() -> Self {
        Self
    }
}

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

#[async_trait]
impl MentionResolver for SkillResolver {
    fn kind(&self) -> &'static str {
        "skill"
    }

    async fn resolve(
        &self,
        rf: &MentionRef,
        _loc: ProviderLocality,
    ) -> Result<Augmentation, ResolveError> {
        let name = match rf {
            MentionRef::Skill { name } => name.clone(),
            _ => {
                return Err(ResolveError::KindMismatch {
                    expected: "skill".to_string(),
                    actual: rf.kind_str().to_string(),
                });
            }
        };
        // 取剥 frontmatter 正文(注入用);None = 缓存未命中或文件读失败
        let body = read_skill_content_stripped(name.clone())
            .await
            .ok_or_else(|| {
                tracing::warn!(
                    skill = %name,
                    "[SkillResolver] 未命中缓存/读失败 → aug 空,skill 未注入(用户感知:发送后未真使用)"
                );
                ResolveError::NotFound {
                    kind: "skill".to_string(),
                    ref_id: name.clone(),
                }
            })?;
        tracing::info!(
            skill = %name,
            body_len = body.len(),
            "[SkillResolver] resolve 成功,正文将注入 system prompt"
        );
        // source 从缓存取 SkillInfo.source;失败(缓存不一致)默认 "skill"
        let source = crate::commands::ai::skills::skills_cached()
            .await
            .into_iter()
            .find(|s| s.name == name)
            .map(|s| s.source)
            .unwrap_or_else(|| "skill".to_string());
        Ok(Augmentation::Skill {
            name,
            source,
            body,
        })
    }
}
