//! Skill perception system for the AI agent.
//!
//! Provides three perception modes for injecting relevant skills into the agent's context:
//!
//! - **Auto (自动感知)**: Background awareness that scans conversation content for skill
//!   relevance based on keyword matching and semantic similarity.
//!
//! - **Active (主动感知)**: Proactive skill retrieval triggered by explicit user intent,
//!   such as mentioning a skill slug directly in the message. Both keyword and vector-based.
//!
//! - **Passive (被动感知)**: Reactive skill retrieval triggered by tool-call events,
//!   such as when the agent mentions a specific skill in its reasoning. Both keyword and
//!   vector-based.

pub mod active;
pub mod auto;
pub mod passive;
pub mod vector;

pub use active::ActiveSkillAwareness;
pub use auto::AutoSkillAwareness;
pub use passive::PassiveSkillAwareness;
pub use vector::{VectorActiveAwareness, VectorPassiveAwareness};

use crate::client::ChatRequestMessage;

/// A chunk of skill context ready to be injected into the message list.
#[derive(Debug, Clone)]
pub struct SkillContext {
    /// Human-readable label shown to the AI, e.g. "Active skill: code-review"
    pub label: String,
    /// The actual skill content to inject.
    pub content: String,
}

/// Converts skill context into a system message for injection.
impl SkillContext {
    pub fn to_system_message(self) -> ChatRequestMessage {
        ChatRequestMessage::system(format!("[{}]\n{}", self.label, self.content))
    }
}

/// Unified perception service combining all three modes.
#[derive(Debug, Clone)]
pub struct PerceptionService {
    pub auto: AutoSkillAwareness,
    pub active: ActiveSkillAwareness,
    pub passive: PassiveSkillAwareness,
}

impl Default for PerceptionService {
    fn default() -> Self {
        Self {
            auto: AutoSkillAwareness::default(),
            active: ActiveSkillAwareness::default(),
            passive: PassiveSkillAwareness::default(),
        }
    }
}

impl PerceptionService {
    /// Inject relevant skill context into the message list based on current conversation state.
    ///
    /// - **auto**: Scans the current input and conversation history for skill-relevant keywords
    ///   and injects matching skills that are enabled.
    /// - **active**: Checks if the user explicitly invoked a skill by slug (e.g. "用 code-review")
    ///   and injects it.
    /// - **passive**: Checks if any tool-call events or prior observations mention a skill
    ///   slug and injects the matching skill.
    ///
    /// Returns a list of system messages to prepend to the conversation.
    pub async fn inject_skills(
        &self,
        input: &str,
        history: &[String],
        tool_calls: &[ToolCallEvent],
        enabled_skills: &[SkillEntry],
    ) -> Vec<SkillContext> {
        let mut results = Vec::new();

        // Active: explicit skill invocation (highest priority)
        if let Some(skill) = self.active.detect(input, enabled_skills) {
            results.push(skill);
        }

        // Passive: triggered by tool-call events
        for tc in tool_calls {
            if let Some(skill) = self.passive.detect(tc, enabled_skills) {
                if !results.iter().any(|r: &SkillContext| r.label == skill.label) {
                    results.push(skill);
                }
            }
        }

        // Auto: keyword-based relevance matching
        let auto_results = self.auto.detect(input, history, enabled_skills).await;
        for skill in auto_results {
            if !results.iter().any(|r: &SkillContext| r.label == skill.label) {
                results.push(skill);
            }
        }

        results
    }
}

/// A tool-call event used for passive skill detection.
#[derive(Debug, Clone)]
pub struct ToolCallEvent {
    pub tool_name: String,
    pub arguments: String,
}

/// A skill entry from the database, used for matching.
#[derive(Debug, Clone)]
pub struct SkillEntry {
    pub slug: String,
    pub name: String,
    pub description: Option<String>,
    pub content: String,
}
