//! Knowledge scope hierarchy for pattern and policy resolution.
//!
//! Implements hierarchical scope levels (Organization, Team, Project) with
//! inheritance semantics. More specific scopes override inherited patterns,
//! with explicit override tracking for audit and governance.

mod config;
mod override_record;
mod resolver;
mod store;

pub use config::ScopeConfig;
pub use override_record::{OverrideValue, ScopeOverride};
pub use resolver::{OverridePolicy, ScopeResolver};
pub use store::{override_store_dir, OverrideStore};

use serde::{Deserialize, Serialize};

/// Hierarchical scope level for knowledge organization.
///
/// Scopes form an inheritance hierarchy where more specific scopes
/// can override patterns from broader scopes:
///
/// ```text
/// Organization (broadest)
///     └── Team
///           └── Project (most specific)
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ScopeLevel {
    /// Organization-wide standards.
    ///
    /// Applied across all teams and projects. Typically contains
    /// security baselines and compliance requirements.
    Organization,

    /// Team-level patterns.
    ///
    /// Shared across projects within a team. May override org
    /// standards for team-specific needs.
    Team,

    /// Project-specific configurations.
    ///
    /// Most specific scope level. Overrides inherited patterns
    /// for this particular project.
    #[default]
    Project,
}

impl ScopeLevel {
    /// Get precedence weight (higher = more specific).
    ///
    /// Used for sorting and override resolution.
    pub fn precedence(&self) -> u8 {
        match self {
            ScopeLevel::Organization => 1,
            ScopeLevel::Team => 2,
            ScopeLevel::Project => 3,
        }
    }

    /// Returns true if this scope is more specific than another.
    pub fn is_more_specific_than(&self, other: &ScopeLevel) -> bool {
        self.precedence() > other.precedence()
    }
}

impl std::fmt::Display for ScopeLevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ScopeLevel::Organization => write!(f, "organization"),
            ScopeLevel::Team => write!(f, "team"),
            ScopeLevel::Project => write!(f, "project"),
        }
    }
}

impl std::str::FromStr for ScopeLevel {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "organization" | "org" => Ok(ScopeLevel::Organization),
            "team" => Ok(ScopeLevel::Team),
            "project" | "proj" => Ok(ScopeLevel::Project),
            _ => Err(format!(
                "Invalid scope level '{}'. Valid values: organization, team, project",
                s
            )),
        }
    }
}

/// Unique identifier for a scope.
///
/// Combines the scope level with a name to create a globally unique reference.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ScopeId {
    /// The scope level (organization, team, or project).
    pub level: ScopeLevel,

    /// The scope name (e.g., "acme-corp", "platform-team", "api-gateway").
    pub name: String,
}

/// Characters not allowed in scope names (path separators and special chars).
const INVALID_SCOPE_CHARS: &[char] = &['/', '\\', ':', '<', '>', '|', '*', '?', '"'];

impl ScopeId {
    /// Create a new scope ID with validation.
    ///
    /// # Errors
    ///
    /// Returns an error if the name is empty, whitespace-only, or contains
    /// invalid characters (path separators, special characters).
    pub fn try_new(level: ScopeLevel, name: impl Into<String>) -> Result<Self, String> {
        let name = name.into();

        if name.is_empty() {
            return Err("Scope name cannot be empty".to_string());
        }

        if name.trim().is_empty() {
            return Err("Scope name cannot be whitespace-only".to_string());
        }

        if let Some(c) = name.chars().find(|c| INVALID_SCOPE_CHARS.contains(c)) {
            return Err(format!(
                "Scope name cannot contain '{}' (invalid characters: {:?})",
                c, INVALID_SCOPE_CHARS
            ));
        }

        Ok(Self { level, name })
    }

    /// Create a new scope ID without validation.
    ///
    /// Use this for internal code where the name is known to be valid,
    /// or for deserialization where validation happened earlier.
    pub fn new(level: ScopeLevel, name: impl Into<String>) -> Self {
        Self { level, name: name.into() }
    }

    /// Create an organization scope.
    pub fn organization(name: impl Into<String>) -> Self {
        Self::new(ScopeLevel::Organization, name)
    }

    /// Create a team scope.
    pub fn team(name: impl Into<String>) -> Self {
        Self::new(ScopeLevel::Team, name)
    }

    /// Create a project scope.
    pub fn project(name: impl Into<String>) -> Self {
        Self::new(ScopeLevel::Project, name)
    }

    /// Validate a scope name.
    ///
    /// Returns Ok(()) if valid, Err with message if invalid.
    pub fn validate_name(name: &str) -> Result<(), String> {
        if name.is_empty() {
            return Err("Scope name cannot be empty".to_string());
        }

        if name.trim().is_empty() {
            return Err("Scope name cannot be whitespace-only".to_string());
        }

        if let Some(c) = name.chars().find(|c| INVALID_SCOPE_CHARS.contains(c)) {
            return Err(format!(
                "Scope name cannot contain '{}' (invalid characters: {:?})",
                c, INVALID_SCOPE_CHARS
            ));
        }

        Ok(())
    }
}

impl std::fmt::Display for ScopeId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}", self.level, self.name)
    }
}

/// Context for scope resolution and inheritance.
///
/// Represents the current scope hierarchy for pattern resolution.
/// A project typically has all three levels set, while a team-level
/// scan might only have organization and team.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ScopeContext {
    /// The current project name.
    pub project: Option<String>,

    /// The owning team name.
    pub team: Option<String>,

    /// The organization name.
    pub organization: Option<String>,
}

impl ScopeContext {
    /// Create a new scope context with all levels.
    pub fn new(
        project: Option<String>,
        team: Option<String>,
        organization: Option<String>,
    ) -> Self {
        Self { project, team, organization }
    }

    /// Create a project-level context.
    pub fn project_only(name: impl Into<String>) -> Self {
        Self { project: Some(name.into()), team: None, organization: None }
    }

    /// Get the scope chain from most specific to least specific.
    ///
    /// The chain always starts with the most specific scope (project)
    /// and proceeds to broader scopes. This ordering is used for
    /// override resolution where the first match wins.
    pub fn inheritance_chain(&self) -> Vec<ScopeId> {
        let mut chain = Vec::with_capacity(3);

        if let Some(ref name) = self.project {
            chain.push(ScopeId::project(name.clone()));
        }
        if let Some(ref name) = self.team {
            chain.push(ScopeId::team(name.clone()));
        }
        if let Some(ref name) = self.organization {
            chain.push(ScopeId::organization(name.clone()));
        }

        chain
    }

    /// Get the current (most specific) scope ID, if any.
    pub fn current_scope(&self) -> Option<ScopeId> {
        self.inheritance_chain().into_iter().next()
    }

    /// Check if this context is empty (no scopes defined).
    pub fn is_empty(&self) -> bool {
        self.project.is_none() && self.team.is_none() && self.organization.is_none()
    }
}

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

    #[test]
    fn test_scope_level_precedence() {
        assert!(ScopeLevel::Project.is_more_specific_than(&ScopeLevel::Team));
        assert!(ScopeLevel::Team.is_more_specific_than(&ScopeLevel::Organization));
        assert!(!ScopeLevel::Organization.is_more_specific_than(&ScopeLevel::Project));
    }

    #[test]
    fn test_scope_level_default() {
        assert_eq!(ScopeLevel::default(), ScopeLevel::Project);
    }

    #[test]
    fn test_scope_level_from_str() {
        assert_eq!("organization".parse::<ScopeLevel>().ok(), Some(ScopeLevel::Organization));
        assert_eq!("org".parse::<ScopeLevel>().ok(), Some(ScopeLevel::Organization));
        assert_eq!("team".parse::<ScopeLevel>().ok(), Some(ScopeLevel::Team));
        assert_eq!("project".parse::<ScopeLevel>().ok(), Some(ScopeLevel::Project));
        assert_eq!("proj".parse::<ScopeLevel>().ok(), Some(ScopeLevel::Project));
        assert!("invalid".parse::<ScopeLevel>().is_err());
    }

    #[test]
    fn test_scope_id_display() {
        let id = ScopeId::organization("acme-corp");
        assert_eq!(id.to_string(), "organization:acme-corp");
    }

    #[test]
    fn test_scope_context_inheritance_chain() {
        let ctx = ScopeContext::new(
            Some("api-gateway".to_string()),
            Some("platform-team".to_string()),
            Some("acme-corp".to_string()),
        );

        let chain = ctx.inheritance_chain();
        assert_eq!(chain.len(), 3);
        assert_eq!(chain[0], ScopeId::project("api-gateway"));
        assert_eq!(chain[1], ScopeId::team("platform-team"));
        assert_eq!(chain[2], ScopeId::organization("acme-corp"));
    }

    #[test]
    fn test_scope_context_partial() {
        let ctx = ScopeContext::new(Some("my-project".to_string()), None, None);

        let chain = ctx.inheritance_chain();
        assert_eq!(chain.len(), 1);
        assert_eq!(chain[0], ScopeId::project("my-project"));
    }

    #[test]
    fn test_scope_context_empty() {
        let ctx = ScopeContext::default();
        assert!(ctx.is_empty());
        assert!(ctx.inheritance_chain().is_empty());
        assert!(ctx.current_scope().is_none());
    }

    #[test]
    fn test_scope_level_serde() {
        let level = ScopeLevel::Team;
        let json = serde_json::to_string(&level).expect("serialize");
        assert_eq!(json, "\"team\"");

        let parsed: ScopeLevel = serde_json::from_str("\"organization\"").expect("deserialize");
        assert_eq!(parsed, ScopeLevel::Organization);
    }

    #[test]
    fn test_scope_id_validation() {
        // Valid names
        assert!(ScopeId::try_new(ScopeLevel::Project, "my-project").is_ok());
        assert!(ScopeId::try_new(ScopeLevel::Team, "platform_team").is_ok());
        assert!(ScopeId::try_new(ScopeLevel::Organization, "Acme Corp").is_ok());

        // Empty name
        assert!(ScopeId::try_new(ScopeLevel::Project, "").is_err());

        // Whitespace only
        assert!(ScopeId::try_new(ScopeLevel::Project, "   ").is_err());

        // Invalid characters
        assert!(ScopeId::try_new(ScopeLevel::Project, "my/project").is_err());
        assert!(ScopeId::try_new(ScopeLevel::Project, "my\\project").is_err());
        assert!(ScopeId::try_new(ScopeLevel::Project, "my:project").is_err());
        assert!(ScopeId::try_new(ScopeLevel::Project, "my<project").is_err());
    }

    #[test]
    fn test_validate_name() {
        assert!(ScopeId::validate_name("valid-name").is_ok());
        assert!(ScopeId::validate_name("").is_err());
        assert!(ScopeId::validate_name("path/sep").is_err());
    }
}
