//! Pattern match exhaustiveness checking
//!
//! Implements the "usefulness" algorithm to check if pattern matches
//! cover all possible cases.

use crate::ast::{Literal, LiteralKind, MatchArm, Pattern};
use crate::types::{Type, TypeDef, TypeEnv, VariantDef};
use std::collections::HashSet;

/// Result of exhaustiveness checking
#[derive(Debug, Clone)]
pub struct ExhaustivenessResult {
    pub is_exhaustive: bool,
    pub missing_patterns: Vec<String>,
    pub redundant_arms: Vec<usize>,
}

/// Check if a match expression is exhaustive
pub fn check_exhaustiveness(
    scrutinee_type: &Type,
    arms: &[MatchArm],
    env: &TypeEnv,
) -> ExhaustivenessResult {
    let patterns: Vec<&Pattern> = arms.iter().map(|arm| &arm.pattern).collect();

    // Check for guards - patterns with guards don't guarantee coverage
    let has_guards = arms.iter().any(|arm| arm.guard.is_some());

    // Get missing patterns
    let missing = find_missing_patterns(scrutinee_type, &patterns, env);

    // Find redundant arms (patterns that can never match)
    let redundant = find_redundant_arms(&patterns);

    // If any pattern has a guard, we can't guarantee exhaustiveness
    // unless there's an unconditional wildcard at the end
    let is_exhaustive = if has_guards {
        // Check if last pattern is an unconditional catch-all
        arms.last()
            .map(|arm| arm.guard.is_none() && is_catch_all(&arm.pattern))
            .unwrap_or(false)
    } else {
        missing.is_empty()
    };

    ExhaustivenessResult {
        is_exhaustive,
        missing_patterns: missing,
        redundant_arms: redundant,
    }
}

/// Check if a pattern is a catch-all (matches anything)
fn is_catch_all(pattern: &Pattern) -> bool {
    match pattern {
        Pattern::Wildcard(_) => true,
        Pattern::Var(_) => true,
        _ => false,
    }
}

/// Find patterns that are missing from coverage
fn find_missing_patterns(
    scrutinee_type: &Type,
    patterns: &[&Pattern],
    env: &TypeEnv,
) -> Vec<String> {
    // If any pattern is a catch-all, nothing is missing
    if patterns.iter().any(|p| is_catch_all(p)) {
        return Vec::new();
    }

    match scrutinee_type {
        Type::Bool => check_bool_exhaustiveness(patterns),
        Type::Option(inner) => check_option_exhaustiveness(patterns, inner, env),
        Type::Named(name) => check_named_type_exhaustiveness(patterns, name, env),
        Type::Tuple(elements) => check_tuple_exhaustiveness(patterns, elements, env),
        // For other types (Int, String, etc.), we can't enumerate all values
        // So we need a wildcard pattern
        Type::Int | Type::Float | Type::String | Type::Char => {
            vec!["_".to_string()]
        }
        // Unit type has exactly one value
        Type::Unit => Vec::new(),
        // For type variables and other complex types, assume exhaustive if there's any pattern
        _ => {
            if patterns.is_empty() {
                vec!["_".to_string()]
            } else {
                Vec::new()
            }
        }
    }
}

/// Check Bool exhaustiveness
fn check_bool_exhaustiveness(patterns: &[&Pattern]) -> Vec<String> {
    let mut has_true = false;
    let mut has_false = false;

    for pattern in patterns {
        match pattern {
            Pattern::Literal(Literal {
                kind: LiteralKind::Bool(true),
                ..
            }) => has_true = true,
            Pattern::Literal(Literal {
                kind: LiteralKind::Bool(false),
                ..
            }) => has_false = true,
            _ => {}
        }
    }

    let mut missing = Vec::new();
    if !has_true {
        missing.push("true".to_string());
    }
    if !has_false {
        missing.push("false".to_string());
    }
    missing
}

/// Check Option exhaustiveness
fn check_option_exhaustiveness(
    patterns: &[&Pattern],
    _inner: &Type,
    _env: &TypeEnv,
) -> Vec<String> {
    let mut has_none = false;
    let mut has_some = false;

    for pattern in patterns {
        if let Pattern::Constructor { name, .. } = pattern {
            match name.name.as_str() {
                "None" => has_none = true,
                "Some" => has_some = true,
                _ => {}
            }
        }
    }

    let mut missing = Vec::new();
    if !has_none {
        missing.push("None".to_string());
    }
    if !has_some {
        missing.push("Some(_)".to_string());
    }
    missing
}

/// Check named type (enum) exhaustiveness
fn check_named_type_exhaustiveness(
    patterns: &[&Pattern],
    type_name: &str,
    env: &TypeEnv,
) -> Vec<String> {
    // Look up the type definition
    let typedef = match env.types.get(type_name) {
        Some(td) => td,
        None => return Vec::new(), // Unknown type, assume exhaustive
    };

    // Handle Result specially since it's common
    if type_name == "Result" {
        return check_result_exhaustiveness(patterns);
    }

    // Get all constructors for enum types
    let constructors: Vec<&VariantDef> = match typedef {
        TypeDef::Enum(variants) => variants.iter().collect(),
        _ => return Vec::new(), // Not an enum, assume exhaustive
    };

    // Find which constructors are covered
    let mut covered: HashSet<&str> = HashSet::new();
    for pattern in patterns {
        if let Pattern::Constructor { name, .. } = pattern {
            covered.insert(&name.name);
        }
    }

    // Find missing constructors
    constructors
        .iter()
        .filter(|v| !covered.contains(v.name.as_str()))
        .map(|v| format_constructor_pattern(v))
        .collect()
}

/// Check Result exhaustiveness
fn check_result_exhaustiveness(patterns: &[&Pattern]) -> Vec<String> {
    let mut has_ok = false;
    let mut has_err = false;

    for pattern in patterns {
        if let Pattern::Constructor { name, .. } = pattern {
            match name.name.as_str() {
                "Ok" => has_ok = true,
                "Err" => has_err = true,
                _ => {}
            }
        }
    }

    let mut missing = Vec::new();
    if !has_ok {
        missing.push("Ok(_)".to_string());
    }
    if !has_err {
        missing.push("Err(_)".to_string());
    }
    missing
}

/// Check tuple exhaustiveness
fn check_tuple_exhaustiveness(
    patterns: &[&Pattern],
    _elements: &[Type],
    _env: &TypeEnv,
) -> Vec<String> {
    // Tuples need a pattern that matches the whole tuple
    // For simplicity, we just check if there's any tuple pattern
    let has_tuple_pattern = patterns.iter().any(|p| matches!(p, Pattern::Tuple { .. }));

    if has_tuple_pattern {
        Vec::new()
    } else {
        vec!["(_, ...)".to_string()]
    }
}

/// Format a variant as a pattern suggestion
fn format_constructor_pattern(variant: &VariantDef) -> String {
    use crate::types::VariantFieldsDef;

    match &variant.fields {
        VariantFieldsDef::Unit => variant.name.clone(),
        VariantFieldsDef::Tuple(fields) => {
            let wildcards: Vec<&str> = fields.iter().map(|_| "_").collect();
            format!("{}({})", variant.name, wildcards.join(", "))
        }
        VariantFieldsDef::Record(fields) => {
            let wildcards: Vec<String> = fields.iter().map(|(n, _)| format!("{}: _", n)).collect();
            format!("{} {{ {} }}", variant.name, wildcards.join(", "))
        }
    }
}

/// Find redundant arms that can never match
fn find_redundant_arms(patterns: &[&Pattern]) -> Vec<usize> {
    let mut redundant = Vec::new();
    let mut seen_catch_all = false;

    for (i, pattern) in patterns.iter().enumerate() {
        if seen_catch_all {
            // Any pattern after a catch-all is redundant
            redundant.push(i);
        } else if is_catch_all(pattern) {
            seen_catch_all = true;
        }
    }

    redundant
}

/// Generate a hint message for missing patterns
pub fn missing_patterns_hint(missing: &[String]) -> String {
    if missing.is_empty() {
        return String::new();
    }

    if missing.len() == 1 {
        format!("Pattern '{}' is not covered.", missing[0])
    } else if missing.len() <= 3 {
        format!("Patterns {} are not covered.", missing.join(", "))
    } else {
        format!(
            "Patterns {}, and {} more are not covered.",
            missing[..2].join(", "),
            missing.len() - 2
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::{Ident, Span};

    fn span() -> Span {
        Span::default()
    }

    fn make_ident(name: &str) -> Ident {
        Ident {
            name: name.to_string(),
            span: span(),
        }
    }

    #[test]
    fn test_bool_exhaustive() {
        let patterns = vec![
            Pattern::Literal(Literal {
                kind: LiteralKind::Bool(true),
                span: span(),
            }),
            Pattern::Literal(Literal {
                kind: LiteralKind::Bool(false),
                span: span(),
            }),
        ];
        let refs: Vec<&Pattern> = patterns.iter().collect();
        let missing = check_bool_exhaustiveness(&refs);
        assert!(missing.is_empty());
    }

    #[test]
    fn test_bool_missing_true() {
        let patterns = vec![Pattern::Literal(Literal {
            kind: LiteralKind::Bool(false),
            span: span(),
        })];
        let refs: Vec<&Pattern> = patterns.iter().collect();
        let missing = check_bool_exhaustiveness(&refs);
        assert_eq!(missing, vec!["true"]);
    }

    #[test]
    fn test_option_exhaustive() {
        let patterns = vec![
            Pattern::Constructor {
                name: make_ident("None"),
                fields: vec![],
                span: span(),
            },
            Pattern::Constructor {
                name: make_ident("Some"),
                fields: vec![Pattern::Wildcard(span())],
                span: span(),
            },
        ];
        let refs: Vec<&Pattern> = patterns.iter().collect();
        let env = TypeEnv::new();
        let missing = check_option_exhaustiveness(&refs, &Type::Int, &env);
        assert!(missing.is_empty());
    }

    #[test]
    fn test_option_missing_none() {
        let patterns = vec![Pattern::Constructor {
            name: make_ident("Some"),
            fields: vec![Pattern::Wildcard(span())],
            span: span(),
        }];
        let refs: Vec<&Pattern> = patterns.iter().collect();
        let env = TypeEnv::new();
        let missing = check_option_exhaustiveness(&refs, &Type::Int, &env);
        assert!(missing.contains(&"None".to_string()));
    }

    #[test]
    fn test_wildcard_covers_all() {
        let patterns = vec![Pattern::Wildcard(span())];
        let refs: Vec<&Pattern> = patterns.iter().collect();
        assert!(is_catch_all(&patterns[0]));

        let env = TypeEnv::new();
        let missing = find_missing_patterns(&Type::Bool, &refs, &env);
        assert!(missing.is_empty());
    }

    #[test]
    fn test_redundant_after_wildcard() {
        let patterns = vec![
            Pattern::Wildcard(span()),
            Pattern::Literal(Literal {
                kind: LiteralKind::Bool(true),
                span: span(),
            }),
        ];
        let refs: Vec<&Pattern> = patterns.iter().collect();
        let redundant = find_redundant_arms(&refs);
        assert_eq!(redundant, vec![1]);
    }

    #[test]
    fn test_result_exhaustive() {
        let patterns = vec![
            Pattern::Constructor {
                name: make_ident("Ok"),
                fields: vec![Pattern::Wildcard(span())],
                span: span(),
            },
            Pattern::Constructor {
                name: make_ident("Err"),
                fields: vec![Pattern::Wildcard(span())],
                span: span(),
            },
        ];
        let refs: Vec<&Pattern> = patterns.iter().collect();
        let missing = check_result_exhaustiveness(&refs);
        assert!(missing.is_empty());
    }
}
