//! Nickel Schema Intermediate Representation
//!
//! Defines the normalized representation of a Nickel schema extracted from
//! `nickel query` output, independent of the Nickel AST.

use serde::{Deserialize, Serialize};

/// Intermediate representation of a Nickel schema
///
/// Contains the parsed structure of a Nickel configuration schema including
/// field definitions, types, and metadata.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NickelSchemaIR {
    /// Schema name/identifier
    pub name: String,

    /// Optional description from | doc
    pub description: Option<String>,

    /// All fields in the schema
    pub fields: Vec<NickelFieldIR>,
}

/// Encryption metadata extracted from Sensitive contract annotation
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EncryptionMetadata {
    /// Field is sensitive (must be encrypted/redacted)
    pub sensitive: bool,

    /// Encryption backend (age, rustyvault, sops)
    pub backend: Option<String>,

    /// Encryption key identifier/configuration
    pub key: Option<String>,
}

impl EncryptionMetadata {
    /// Create a new encryption metadata
    pub fn new(sensitive: bool) -> Self {
        Self {
            sensitive,
            backend: None,
            key: None,
        }
    }

    /// Set encryption backend
    pub fn with_backend(mut self, backend: String) -> Self {
        self.backend = Some(backend);
        self
    }

    /// Set encryption key
    pub fn with_key(mut self, key: String) -> Self {
        self.key = Some(key);
        self
    }
}

/// Represents a contract call: module.function(args)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ContractCall {
    /// Module name (e.g., "validators", "validators_common", "std.string")
    pub module: String,

    /// Function/validator name (e.g., "ValidPort", "NonEmpty")
    pub function: String,

    /// Arguments passed to the validator (e.g., "22", "\"dev\"")
    pub args: Option<String>,

    /// Full expression string (e.g., "validators.ValidPort 22")
    pub expr: String,
}

/// Intermediate representation of a single field in a Nickel schema
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NickelFieldIR {
    /// Path to field: ["user", "name"] for user.name
    pub path: Vec<String>,

    /// Flattened name: "user_name" for user.name
    pub flat_name: String,

    /// Semantic alias for field name (short, human-readable)
    pub alias: Option<String>,

    /// Nickel type information
    pub nickel_type: NickelType,

    /// Documentation from | doc
    pub doc: Option<String>,

    /// Default value from | default
    pub default: Option<serde_json::Value>,

    /// Whether field is optional (| optional)
    pub optional: bool,

    /// Nickel contract/predicate (e.g., "std.string.NonEmpty")
    pub contract: Option<String>,

    /// Parsed contract call information (module.function args)
    pub contract_call: Option<ContractCall>,

    /// Semantic grouping for form UI
    pub group: Option<String>,

    /// Fragment marker from # @fragment: comment
    pub fragment_marker: Option<String>,

    /// Flag indicating this is Array(Record(...)) pattern
    pub is_array_of_records: bool,

    /// Fields of the record element type (if is_array_of_records)
    pub array_element_fields: Option<Vec<NickelFieldIR>>,

    /// Encryption metadata from Sensitive contract
    pub encryption_metadata: Option<EncryptionMetadata>,
}

/// Nickel type information for a field
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum NickelType {
    /// String type
    String,

    /// Number type
    Number,

    /// Boolean type
    Bool,

    /// Array type with element type
    Array(Box<NickelType>),

    /// Nested record with fields
    Record(Vec<NickelFieldIR>),

    /// Custom/unknown type
    Custom(String),
}

impl NickelSchemaIR {
    /// Create a new schema with given name and fields
    pub fn new(name: String, fields: Vec<NickelFieldIR>) -> Self {
        Self {
            name,
            description: None,
            fields,
        }
    }

    /// Find a field by its flat name
    pub fn find_field(&self, flat_name: &str) -> Option<&NickelFieldIR> {
        self.fields.iter().find(|f| f.flat_name == flat_name)
    }

    /// Get all fields in a specific group
    pub fn fields_by_group(&self, group: &str) -> Vec<&NickelFieldIR> {
        self.fields
            .iter()
            .filter(|f| f.group.as_deref() == Some(group))
            .collect()
    }

    /// Get all unique groups in the schema
    pub fn groups(&self) -> Vec<String> {
        let mut groups: Vec<_> = self.fields.iter().filter_map(|f| f.group.clone()).collect();
        groups.sort();
        groups.dedup();
        groups
    }

    /// Get all fields in a specific fragment
    pub fn fields_by_fragment(&self, fragment: &str) -> Vec<&NickelFieldIR> {
        self.fields
            .iter()
            .filter(|f| f.fragment_marker.as_deref() == Some(fragment))
            .collect()
    }

    /// Get all unique fragments in the schema
    pub fn fragments(&self) -> Vec<String> {
        let mut fragments: Vec<_> = self
            .fields
            .iter()
            .filter_map(|f| f.fragment_marker.clone())
            .collect();
        fragments.sort();
        fragments.dedup();
        fragments
    }

    /// Get fields without a fragment marker (ungrouped)
    pub fn fields_without_fragment(&self) -> Vec<&NickelFieldIR> {
        self.fields
            .iter()
            .filter(|f| f.fragment_marker.is_none())
            .collect()
    }
}
