use rustc_hash::FxHashSet as HashSet;

use syntax::ast::Literal;

use super::escape::{equals_target, runtime_bytes};
use super::types::*;

pub enum ScrutineeSignature {
    Constructors {
        type_name: TypeName,
        union: Union,
        seen_tags: HashSet<TagId>,
    },
    WildcardsOrLiterals,
}

pub fn specialize_by_constructor(rows: &[Row], tag_id: &str, arity: usize) -> Vec<Row> {
    rows.iter()
        .filter_map(|row| {
            let first = row.first()?;
            match first {
                NormalizedPattern::Constructor { tag, args, .. } if tag == tag_id => {
                    let mut new_row = args.clone();
                    new_row.extend_from_slice(&row[1..]);
                    Some(new_row)
                }
                NormalizedPattern::Wildcard => {
                    let mut new_row = vec![NormalizedPattern::Wildcard; arity];
                    new_row.extend_from_slice(&row[1..]);
                    Some(new_row)
                }
                _ => None,
            }
        })
        .collect()
}

pub fn specialize_by_wildcard(rows: &[Row]) -> Vec<Row> {
    rows.iter()
        .filter_map(|row| {
            let first = row.first()?;
            if matches!(first, NormalizedPattern::Wildcard) {
                Some(row[1..].to_vec())
            } else {
                None
            }
        })
        .collect()
}

pub fn specialize_by_literal(rows: &[Row], literal: &Literal) -> Vec<Row> {
    let target_bytes = runtime_bytes(literal);
    rows.iter()
        .filter_map(|row| {
            let first = row.first()?;
            match first {
                NormalizedPattern::Literal(lit)
                    if equals_target(lit, literal, target_bytes.as_deref()) =>
                {
                    Some(row[1..].to_vec())
                }
                NormalizedPattern::Wildcard => Some(row[1..].to_vec()),
                _ => None,
            }
        })
        .collect()
}

pub fn get_scrutinee_signature(rows: &[Row], unions: &UnionTable) -> Option<ScrutineeSignature> {
    if rows.is_empty() || rows[0].is_empty() {
        return None;
    }

    let mut type_name: Option<TypeName> = None;
    let mut union: Option<Union> = None;
    let mut seen_tags = HashSet::default();

    for row in rows {
        if let Some(NormalizedPattern::Constructor {
            type_name: tn, tag, ..
        }) = row.first()
        {
            if type_name.is_none() {
                type_name = Some(tn.clone());
                union = unions.get(tn).cloned();
            }
            seen_tags.insert(tag.clone());
        }
    }

    match (type_name, union) {
        (Some(type_name), Some(union)) => Some(ScrutineeSignature::Constructors {
            type_name,
            union,
            seen_tags,
        }),
        _ => Some(ScrutineeSignature::WildcardsOrLiterals),
    }
}

pub fn is_complete(rows: &[Row], unions: &UnionTable) -> bool {
    match get_scrutinee_signature(rows, unions) {
        Some(ScrutineeSignature::Constructors {
            union, seen_tags, ..
        }) => seen_tags.len() == union.len(),
        _ => false,
    }
}
