//! Configuration file rules for multi-file configuration discovery.

use crate::core::{config_tier::ConfigTier, pattern::FilePattern};

/// Rule for finding a single configuration file.
///
/// Defines how to search for a specific configuration file across
/// different tiers (User, Local, System) and what to do when
/// multiple instances are found.
///
/// # Example
///
/// ```
/// use cfgmatic_paths::{ConfigFileRule, TierSearchMode};
///
/// let rule = ConfigFileRule::extensions("config", &["toml", "yaml"])
///     .tiers(TierSearchMode::All)
///     .required(true);
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfigFileRule {
    /// File pattern to match.
    pub pattern: FilePattern,

    /// Which tiers to search for this file.
    pub tiers: TierSearchMode,

    /// Whether the file is required (error if not found).
    pub required: bool,
}

impl ConfigFileRule {
    /// Create a new rule for a file with a single extension.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic_paths::ConfigFileRule;
    ///
    /// let rule = ConfigFileRule::toml("config");
    /// ```
    #[must_use]
    pub fn toml(base: impl Into<String>) -> Self {
        Self {
            pattern: FilePattern::extensions(base.into(), &["toml"]),
            tiers: TierSearchMode::default(),
            required: false,
        }
    }

    /// Create a new rule for a file with multiple extensions.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic_paths::ConfigFileRule;
    ///
    /// let rule = ConfigFileRule::extensions("config", &["toml", "yaml", "json"]);
    /// ```
    #[must_use]
    pub fn extensions(base: impl Into<String>, extensions: &[&str]) -> Self {
        Self {
            pattern: FilePattern::extensions(base.into(), extensions),
            tiers: TierSearchMode::default(),
            required: false,
        }
    }

    /// Create a new rule for an exact filename match.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic_paths::ConfigFileRule;
    ///
    /// let rule = ConfigFileRule::exact("main.conf");
    /// ```
    #[must_use]
    pub fn exact(name: impl Into<String>) -> Self {
        Self {
            pattern: FilePattern::exact(name.into()),
            tiers: TierSearchMode::default(),
            required: false,
        }
    }

    /// Create a new rule for glob pattern matching.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic_paths::ConfigFileRule;
    ///
    /// let rule = ConfigFileRule::glob("*.conf");
    /// ```
    #[must_use]
    pub fn glob(pattern: impl Into<String>) -> Self {
        Self {
            pattern: FilePattern::glob(pattern.into()),
            tiers: TierSearchMode::default(),
            required: false,
        }
    }

    /// Set which tiers to search.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic_paths::{ConfigFileRule, TierSearchMode, ConfigTier};
    ///
    /// let rule = ConfigFileRule::toml("config")
    ///     .tiers(TierSearchMode::FromTier(ConfigTier::System));
    /// ```
    #[must_use]
    pub const fn tiers(mut self, tiers: TierSearchMode) -> Self {
        self.tiers = tiers;
        self
    }

    /// Mark the file as required.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic_paths::ConfigFileRule;
    ///
    /// let rule = ConfigFileRule::toml("config")
    ///     .required(true);
    /// ```
    #[must_use]
    pub const fn required(mut self, required: bool) -> Self {
        self.required = required;
        self
    }
}

/// How to search tiers for configuration files.
///
/// Defines which configuration tiers (User, Local, System) should be searched
/// and in what order.
///
/// # Example
///
/// ```
/// use cfgmatic_paths::{TierSearchMode, ConfigTier};
///
/// // Search all tiers (default)
/// let mode = TierSearchMode::All;
///
/// // Only user tier
/// let mode = TierSearchMode::UserOnly;
///
/// // User and Local tiers
/// let mode = TierSearchMode::UserAndLocal;
///
/// // From a specific tier upward
/// let mode = TierSearchMode::FromTier(ConfigTier::System);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TierSearchMode {
    /// Only User tier.
    UserOnly,

    /// User and Local tiers.
    UserAndLocal,

    /// All tiers: User, Local, System (default).
    #[default]
    All,

    /// From a specific tier upward (e.g., FromTier(System) means System, Local, User).
    FromTier(ConfigTier),

    /// From a specific tier downward (e.g., FromTier(User) means User only).
    FromTierDownward(ConfigTier),

    /// Custom selection of tiers.
    Custom {
        /// Include User tier.
        user: bool,
        /// Include Local tier.
        local: bool,
        /// Include System tier.
        system: bool,
    },
}

impl TierSearchMode {
    /// Returns true if the User tier should be searched.
    #[must_use]
    pub const fn includes_user(&self) -> bool {
        matches!(
            self,
            Self::UserOnly | Self::UserAndLocal | Self::All | Self::FromTierDownward(_)
        ) || matches!(self, Self::Custom { user: true, .. })
            || matches!(self, Self::FromTier(ConfigTier::User))
    }

    /// Returns true if the Local tier should be searched.
    #[must_use]
    pub const fn includes_local(&self) -> bool {
        matches!(self, Self::All | Self::FromTier(ConfigTier::Local))
            || matches!(self, Self::Custom { local: true, .. })
            || matches!(self, Self::FromTier(ConfigTier::System | ConfigTier::User))
    }

    /// Returns true if the System tier should be searched.
    #[must_use]
    pub const fn includes_system(&self) -> bool {
        matches!(self, Self::All)
            || matches!(self, Self::Custom { system: true, .. })
            || matches!(self, Self::FromTier(ConfigTier::System))
    }

    /// Returns an iterator of tiers to search in priority order (User → Local → System).
    #[must_use]
    pub const fn tiers(&self) -> TierIterator {
        TierIterator::new(*self)
    }

    /// Returns the default tier for this mode.
    #[must_use]
    pub const fn default_tier(&self) -> ConfigTier {
        match self {
            Self::FromTier(tier) => *tier,
            _ => ConfigTier::User,
        }
    }
}

/// Iterator over configuration tiers in priority order.
///
/// Yields tiers in the order they should be searched and merged
/// (highest priority first).
#[derive(Debug, Clone)]
pub struct TierIterator {
    /// Search mode determining which tiers to iterate.
    mode: TierSearchMode,
    /// Current iteration state.
    state: u8,
}

impl TierIterator {
    /// Create a new iterator for the given mode.
    #[must_use]
    pub const fn new(mode: TierSearchMode) -> Self {
        Self { mode, state: 0 }
    }
}

impl Iterator for TierIterator {
    type Item = ConfigTier;

    fn next(&mut self) -> Option<Self::Item> {
        let mode = self.mode;
        let state = self.state;
        self.state = state.wrapping_add(1);

        match mode {
            // AllTiers: User → Local → System
            TierSearchMode::All => match state {
                0 => Some(ConfigTier::User),
                1 => Some(ConfigTier::Local),
                2 => Some(ConfigTier::System),
                _ => None,
            },

            // UserOnly
            TierSearchMode::UserOnly => match state {
                0 => Some(ConfigTier::User),
                _ => None,
            },

            // UserAndLocal: User → Local
            TierSearchMode::UserAndLocal => match state {
                0 => Some(ConfigTier::User),
                1 => Some(ConfigTier::Local),
                _ => None,
            },

            // FromTier: tier → tiers above (in priority order: User → Local → System)
            TierSearchMode::FromTier(start) => match state {
                0 => Some(start),
                1 if start < ConfigTier::User => Some(ConfigTier::User),
                1 if start < ConfigTier::Local => Some(ConfigTier::Local),
                _ => None,
            },

            // FromTierDownward: tier → tiers below (in reverse priority: System → Local → User)
            TierSearchMode::FromTierDownward(start) => match state {
                0 => Some(start),
                1 if start > ConfigTier::System => Some(ConfigTier::System),
                1 if start > ConfigTier::Local => Some(ConfigTier::Local),
                _ => None,
            },

            // Custom
            TierSearchMode::Custom {
                user,
                local,
                system,
            } => match state {
                0 if user => Some(ConfigTier::User),
                1 if local => Some(ConfigTier::Local),
                2 if system => Some(ConfigTier::System),
                _ => None,
            },
        }
    }
}

/// Rule for configuration fragment directories (conf.d style).
///
/// Fragment directories contain multiple small configuration files
/// that are merged together.
///
/// # Example
///
/// ```
/// use cfgmatic_paths::{FragmentRule, TierSearchMode};
///
/// let rule = FragmentRule::new("conf.d", "*.conf")
///     .tiers(TierSearchMode::All);
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FragmentRule {
    /// Name of the fragment directory (e.g., "conf.d").
    pub dir_name: String,

    /// Pattern for files in the fragment directory.
    pub pattern: FilePattern,

    /// Which tiers to search for fragments.
    pub tiers: TierSearchMode,
}

impl FragmentRule {
    /// Create a new fragment rule.
    ///
    /// # Example
    ///
    /// ```
    /// use cfgmatic_paths::FragmentRule;
    ///
    /// let rule = FragmentRule::new("conf.d", "*.conf");
    /// ```
    #[must_use]
    pub fn new(dir_name: impl Into<String>, pattern: impl Into<String>) -> Self {
        Self {
            dir_name: dir_name.into(),
            pattern: FilePattern::glob(pattern),
            tiers: TierSearchMode::default(),
        }
    }

    /// Set which tiers to search.
    #[must_use]
    pub const fn tiers(mut self, tiers: TierSearchMode) -> Self {
        self.tiers = tiers;
        self
    }
}

/// Set of configuration file rules.
///
/// Defines all configuration files for an application, including
/// main files, fragments, and legacy files.
///
/// # Example
///
/// ```
/// use cfgmatic_paths::{ConfigRuleSet, ConfigFileRule, FragmentRule};
///
/// let rules = ConfigRuleSet::builder()
///     .main_file(ConfigFileRule::toml("config").required(true))
///     .main_file(ConfigFileRule::extensions("config", &["yaml"]))
///     .fragments(FragmentRule::new("conf.d", "*.conf"))
///     .build();
/// ```
#[derive(Debug, Clone, Default)]
pub struct ConfigRuleSet {
    /// Main configuration files.
    pub main_files: Vec<ConfigFileRule>,

    /// Fragment directory rule (optional).
    pub fragments: Option<FragmentRule>,
}

impl ConfigRuleSet {
    /// Create a new empty rule set.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Create a new builder for rule sets.
    #[must_use]
    pub fn builder() -> ConfigRuleSetBuilder {
        ConfigRuleSetBuilder::new()
    }

    /// Add a main file rule.
    pub fn add_main_file(&mut self, rule: ConfigFileRule) {
        self.main_files.push(rule);
    }

    /// Set the fragment rule.
    pub fn set_fragments(&mut self, fragments: FragmentRule) {
        self.fragments = Some(fragments);
    }

    /// Get all main file rules.
    #[must_use]
    pub fn main_files(&self) -> &[ConfigFileRule] {
        &self.main_files
    }

    /// Get the fragment rule if set.
    #[must_use]
    pub const fn fragments(&self) -> Option<&FragmentRule> {
        self.fragments.as_ref()
    }
}

/// Builder for creating configuration rule sets.
#[derive(Debug, Clone, Default)]
pub struct ConfigRuleSetBuilder {
    /// Rules being built.
    rules: ConfigRuleSet,
}

impl ConfigRuleSetBuilder {
    /// Create a new builder.
    #[must_use]
    pub fn new() -> Self {
        Self {
            rules: ConfigRuleSet::new(),
        }
    }

    /// Add a main file rule.
    #[must_use]
    pub fn main_file(mut self, rule: ConfigFileRule) -> Self {
        self.rules.add_main_file(rule);
        self
    }

    /// Set the fragment rule.
    #[must_use]
    pub fn fragments(mut self, fragments: FragmentRule) -> Self {
        self.rules.set_fragments(fragments);
        self
    }

    /// Build the rule set.
    #[must_use]
    pub fn build(self) -> ConfigRuleSet {
        self.rules
    }
}

/// Result of rule-based configuration discovery.
///
/// Contains all discovered configuration files grouped by rule,
/// along with fragment information.
///
/// # Example
///
/// ```
/// use cfgmatic_paths::{PathsBuilder, ConfigRuleSet, ConfigFileRule, FragmentRule};
///
/// let finder = PathsBuilder::new("myapp").build();
///
/// let rules = ConfigRuleSet::builder()
///     .main_file(ConfigFileRule::toml("config"))
///     .fragments(FragmentRule::new("conf.d", "*.conf"))
///     .build();
///
/// let discovery = finder.discover_with_rules(&rules);
///
/// // Get all file paths for loading
/// for path in discovery.all_paths() {
///     println!("Found: {}", path.display());
/// }
///
/// // Check if required files are present
/// if let Some(missing) = discovery.missing_required() {
///     eprintln!("Missing required file: {:?}", missing);
/// }
/// ```
#[derive(Debug, Clone)]
pub struct RuleBasedDiscovery {
    /// The rule set that was used for discovery.
    pub rules: ConfigRuleSet,

    /// Discovered main files grouped by rule.
    pub main_files: Vec<RuleMatchResult>,

    /// Discovered fragment files.
    pub fragments: Vec<RuleMatchResult>,
}

impl RuleBasedDiscovery {
    /// Check if any files were found.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.main_files.iter().all(|r| r.matches.is_empty())
            && self.fragments.iter().all(|r| r.matches.is_empty())
    }

    /// Get the total count of all discovered files.
    #[must_use]
    pub fn file_count(&self) -> usize {
        let main_count: usize = self.main_files.iter().map(|r| r.matches.len()).sum();
        let fragment_count: usize = self.fragments.iter().map(|r| r.matches.len()).sum();
        main_count + fragment_count
    }

    /// Get all file paths from main files, sorted by priority (highest first).
    ///
    /// Returns paths in merge order: lowest priority first, highest priority last.
    /// This allows sequential merging where later files override earlier ones.
    #[must_use]
    pub fn main_paths(&self) -> Vec<std::path::PathBuf> {
        let mut paths = Vec::new();
        for result in &self.main_files {
            for candidate in &result.matches {
                paths.push(candidate.path.clone());
            }
        }
        // Sort by tier priority (lowest first for merge order)
        paths.sort_by_key(|p| {
            self.main_files
                .iter()
                .flat_map(|r| &r.matches)
                .find(|c| &c.path == p)
                .map_or(std::cmp::Reverse(0), |c| {
                    std::cmp::Reverse(u8::from(c.tier))
                })
        });
        paths
    }

    /// Get all file paths from fragments, sorted by priority (highest first).
    ///
    /// Returns paths in merge order: lowest priority first, highest priority last.
    #[must_use]
    pub fn fragment_paths(&self) -> Vec<std::path::PathBuf> {
        let mut paths = Vec::new();
        for result in &self.fragments {
            for candidate in &result.matches {
                paths.push(candidate.path.clone());
            }
        }
        // Sort by tier priority (lowest first for merge order)
        paths.sort_by_key(|p| {
            self.fragments
                .iter()
                .flat_map(|r| &r.matches)
                .find(|c| &c.path == p)
                .map_or(std::cmp::Reverse(0), |c| {
                    std::cmp::Reverse(u8::from(c.tier))
                })
        });
        paths
    }

    /// Get all discovered file paths (both main and fragments).
    ///
    /// Returns paths in merge order: main files first (by tier), then fragments.
    #[must_use]
    pub fn all_paths(&self) -> Vec<std::path::PathBuf> {
        let mut paths = self.main_paths();
        paths.extend(self.fragment_paths());
        paths
    }

    /// Get candidates for main files sorted by merge priority.
    #[must_use]
    pub fn main_candidates(&self) -> Vec<&ConfigCandidate> {
        let mut candidates: Vec<&ConfigCandidate> = self
            .main_files
            .iter()
            .flat_map(|r| r.matches.iter())
            .collect();
        candidates.sort_by_key(|c| std::cmp::Reverse(u8::from(c.tier)));
        candidates
    }

    /// Get candidates for fragments sorted by merge priority.
    #[must_use]
    pub fn fragment_candidates(&self) -> Vec<&ConfigCandidate> {
        let mut candidates: Vec<&ConfigCandidate> = self
            .fragments
            .iter()
            .flat_map(|r| r.matches.iter())
            .collect();
        candidates.sort_by_key(|c| std::cmp::Reverse(u8::from(c.tier)));
        candidates
    }

    /// Check if a required rule has no matching files.
    ///
    /// Returns the first required rule that has no matches.
    #[must_use]
    pub fn missing_required(&self) -> Option<&ConfigFileRule> {
        self.rules.main_files.iter().find(|rule| {
            rule.required && {
                !self
                    .main_files
                    .iter()
                    .any(|r| &r.rule == *rule && !r.matches.is_empty())
            }
        })
    }

    /// Get all existing files (filter out non-existent candidates).
    #[must_use]
    pub fn existing_files(&self) -> Vec<&ConfigCandidate> {
        self.main_candidates()
            .into_iter()
            .chain(self.fragment_candidates())
            .filter(|c| c.status.exists())
            .collect()
    }
}

/// Result of searching for files by a single rule.
///
/// Contains the rule that was used and all matching files found.
#[derive(Debug, Clone)]
pub struct RuleMatchResult {
    /// The rule that was used for matching.
    pub rule: ConfigFileRule,

    /// Files that matched the rule.
    pub matches: Vec<ConfigCandidate>,
}

/// Re-export for convenience at the crate level.
pub use crate::core::discovery::ConfigCandidate;
