use crate::ast::{
    Block, ClassItem, Expression, FunctionDecl, Identifier, MatchItem, Postfix, Prefix, Spanned,
    Statement, StatementBranch, TopLevel, TopLevelKind, VarDeclStmt,
};

pub trait GetMutability {
    fn get_mutability(&self) -> Vec<Identifier>;
}

#[derive(Debug, Clone)]
pub struct SemanticError {
    pub line: usize,
    pub column: usize,
    pub error_message: String,
}

/// Class semantics - Ensuring fields are initialized

pub fn find_function_call<'a>(
    items: &'a Vec<ClassItem>,
    name: &Identifier,
) -> Option<&'a Spanned<FunctionDecl>> {
    items.iter().find_map(|v| {
        if let ClassItem::Method(m) = v {
            if m.item.is_using_self() && &m.item.name == name {
                Some(m)
            } else {
                None
            }
        } else {
            None
        }
    })
}

pub fn check_class_semantics<'a>(top_level: &TopLevel) -> Result<(), Vec<SemanticError>> {
    if let TopLevelKind::ClassDecl {
        fields,
        constructor,
        items,
        name,
        inherits,
        ..
    } = &top_level.0.item
    {
        if let Some(constructor) = constructor {
            let mut fields = fields
                .iter()
                .map(|field| Spanned {
                    item: field.item.decl.name.clone(),
                    line: field.line,
                    column: field.column,
                })
                .collect::<Vec<_>>();

            if inherits.is_some() {
                fields.push(Spanned {
                    line: top_level.0.line,
                    column: top_level.0.column,
                    item: Identifier("_super".to_string()),
                });
            }

            let mut mutability = constructor.item.body.get_mutability();

            let mut i = 0;
            while i < mutability.len() {
                let name = mutability[i].clone();
                if let Some(method) = find_function_call(items, &name) {
                    if let Some(body) = &method.item.body {
                        for entry in body.get_mutability() {
                            if !mutability.contains(&entry) {
                                mutability.push(entry);
                            }
                        }
                    }
                }
                i += 1;
            }

            let mut errs = Vec::new();

            for field in &fields {
                if !mutability.contains(&field.item) {
                    errs.push(SemanticError {
                        line: field.line,
                        column: field.column,
                        error_message: format!(
                            "class field `{}.{}` is uninitialized",
                            name.0, field.item.0
                        ),
                    });
                }
            }

            if errs.len() > 0 {
                return Err(errs);
            }
        }

        Ok(())
    } else {
        Ok(())
    }
}

impl<T: GetMutability> GetMutability for Vec<T> {
    fn get_mutability(&self) -> Vec<Identifier> {
        self.iter().flat_map(T::get_mutability).collect()
    }
}

impl GetMutability for Block {
    fn get_mutability(&self) -> Vec<Identifier> {
        let mut items: Vec<Identifier> = self
            .statements
            .iter()
            .flat_map(|v| v.item.get_mutability())
            .collect();

        if let Some(v) = &self.soft_return {
            items.append(&mut v.item.get_mutability());
        }

        items
    }
}

impl GetMutability for Statement {
    fn get_mutability(&self) -> Vec<Identifier> {
        match self {
            Statement::Block(body) | Statement::UnsafeBlock(body) => body.get_mutability(),
            Statement::If {
                initial,
                else_if,
                else_branch,
            } => {
                let mut all_sets: Vec<Vec<Identifier>> = Vec::new();
                all_sets.push(initial.body.get_mutability());

                for branch in else_if {
                    all_sets.push(branch.body.get_mutability());
                }

                if let Some(else_block) = else_branch {
                    all_sets.push(else_block.get_mutability());
                } else {
                    return Vec::new();
                }

                let mut result = all_sets.remove(0);
                for set in all_sets {
                    result.retain(|id| set.contains(id));
                }
                result
            }
            Statement::Loop(body) => body.get_mutability(),
            Statement::While(branch) => branch.body.get_mutability(),
            Statement::CStyleFor {
                body,
                init,
                condition,
                update,
                ..
            } => {
                let mut a = init.get_mutability();
                a.append(&mut condition.get_mutability());
                a.append(&mut update.get_mutability());
                a.append(&mut body.get_mutability());
                a
            }
            Statement::For { body, iterator, .. } => {
                let mut a = iterator.get_mutability();
                a.append(&mut body.get_mutability());
                a
            }
            Statement::Match(_, items) => {
                if items.is_empty() {
                    return Vec::new();
                }
                let mut all_sets: Vec<Vec<Identifier>> = items
                    .iter()
                    .map(|item| item.item.get_mutability())
                    .collect();
                let mut result = all_sets.remove(0);
                for set in all_sets {
                    result.retain(|id| set.contains(id));
                }
                result
            }
            Statement::VarDecl(stmt) => stmt
                .init
                .as_ref()
                .map(|e| e.get_mutability())
                .unwrap_or_default(),
            Statement::Return(expr) => expr
                .as_ref()
                .map(|e| e.get_mutability())
                .unwrap_or_default(),
            Statement::Break | Statement::Continue => Vec::new(),
        }
    }
}

impl GetMutability for StatementBranch {
    fn get_mutability(&self) -> Vec<Identifier> {
        let mut a = self.condition.get_mutability();
        a.append(&mut self.body.get_mutability());
        a
    }
}

impl GetMutability for MatchItem {
    fn get_mutability(&self) -> Vec<Identifier> {
        self.1.get_mutability()
    }
}

impl GetMutability for VarDeclStmt {
    fn get_mutability(&self) -> Vec<Identifier> {
        self.init
            .as_ref()
            .map(|e| e.get_mutability())
            .unwrap_or_default()
    }
}

fn get_self_ref(initial: &Expression, postfixes: &[Postfix]) -> Option<Identifier> {
    match initial {
        Expression::Path(v) => {
            let ident = v.0.first()?.ident.0.as_str();
            if ident == "self" || ident == "super" {
                if let Some(Postfix::FieldAccess(field, _)) = postfixes.first() {
                    if !matches!(postfixes.get(1), Some(Postfix::Call(_))) {
                        return Some(field.clone());
                    }
                }
            }
        }
        _ => {}
    }
    None
}

fn get_self_method_call(initial: &Expression, postfixes: &[Postfix]) -> Option<Identifier> {
    match initial {
        Expression::Path(v) => {
            let ident = v.0.first()?.ident.0.as_str();
            if ident == "self" || ident == "super" {
                if let Some(Postfix::FieldAccess(method, _)) = postfixes.first() {
                    if matches!(postfixes.get(1), Some(Postfix::Call(_))) {
                        return Some(method.clone());
                    }
                }
            }
        }
        _ => {}
    }
    None
}

impl Expression {
    fn get_self_ref(&self) -> Option<Identifier> {
        match self {
            Self::Fix {
                initial, postfixes, ..
            } => get_self_ref(initial, postfixes),
            _ => None,
        }
    }
}

impl GetMutability for Expression {
    fn get_mutability(&self) -> Vec<Identifier> {
        match self {
            Self::Array(v) => v.get_mutability(),
            Self::ArrayRepeat(v, i) => {
                let mut a = v.get_mutability();
                a.append(&mut i.get_mutability());
                a
            }
            Self::Binary { lhs, op, rhs } => {
                if mutates_lhs(op) {
                    let mut a = if let Some(self_ref) = lhs.get_self_ref() {
                        vec![self_ref]
                    } else {
                        lhs.get_mutability()
                    };

                    a.append(&mut rhs.get_mutability());
                    a
                } else {
                    let mut a = lhs.get_mutability();
                    a.append(&mut rhs.get_mutability());
                    a
                }
            }
            Self::Statement(stmt) => stmt.get_mutability(),
            Self::Fix {
                initial,
                prefixes,
                postfixes,
            } => {
                let mut result = Vec::new();

                // &mut self.field / &mut super.field / &mut super pattern
                if prefixes.iter().any(|p| matches!(p, Prefix::RefMut)) {
                    if let Some(self_ref) = get_self_ref(initial, postfixes) {
                        result.push(self_ref);
                    } else if let Expression::Path(v) = &**initial {
                        if v.0.first().map_or(false, |s| s.ident.0 == "super") {
                            result.push(Identifier("_super".to_string()));
                        }
                    }
                }

                // Recurse into all postfix sub-expressions
                for postfix in postfixes {
                    match postfix {
                        Postfix::Assign(_, rhs) => {
                            if let Some(self_ref) = get_self_ref(initial, postfixes) {
                                result.push(self_ref);
                            }
                            result.append(&mut rhs.get_mutability());
                        }
                        Postfix::Call(args) => {
                            for arg in args {
                                result.append(&mut arg.get_mutability());
                            }
                        }
                        Postfix::StructCall(fields) => {
                            for (_, expr) in fields {
                                if let Some(expr) = expr {
                                    result.append(&mut expr.get_mutability());
                                }
                            }
                        }
                        Postfix::Index(expr) => {
                            result.append(&mut expr.get_mutability());
                        }
                        _ => {}
                    }
                }

                // Method calls on self: self.method_name(args)
                if let Some(method) = get_self_method_call(initial, postfixes) {
                    result.push(method);
                }

                result
            }
            Self::Closure { body, .. } => body.get_mutability(),
            Self::Literal(_) => Vec::new(),
            Self::Path(v) => {
                if v.0.first().map_or(false, |s| s.ident.0 == "super") {
                    vec![Identifier("_super".to_string())]
                } else {
                    Vec::new()
                }
            }
        }
    }
}

pub fn mutates_lhs(op: &str) -> bool {
    matches!(op, "=" | "->")
}
