use std::collections::{HashMap, HashSet};

use ty_ree::declaration as ty;
use ty_ree::expression::{self as ty_expression, ExpressionKind, Statement};

const MAX_NESTING_DEPTH: usize = 4;

pub fn check_unused_bindings(module: &ty::Module) -> Vec<String> {
    let mut errors = Vec::new();
    for declaration in module {
        if let ty::Declaration::Impl(impl_block) = declaration {
            if impl_block.const_fn {
                continue;
            }
            let name = if let ty_ree::types::Type::Named(name, _) = &impl_block.target_type {
                name.as_str()
            } else {
                "<impl>"
            };
            check_block_unused(&impl_block.callable.body, name, &mut errors);
            check_nesting(&impl_block.callable.body, 0, &mut errors);
            check_trailing_return(&impl_block.callable.body, &mut errors);
        }
    }
    errors
}

fn check_trailing_return(block: &ty_expression::Block, errors: &mut Vec<String>) {
    if let Some(last) = block.statements.last()
        && let Statement::Return(Some(_)) = last
        && block.result.is_none()
    {
        errors.push(
            "unnecessary 'return' at end of function — use the expression as block result instead"
                .into(),
        );
    }
}

fn check_nesting(block: &ty_expression::Block, depth: usize, errors: &mut Vec<String>) {
    for statement in &block.statements {
        check_nesting_statement(statement, depth, errors);
    }
    if let Some(result) = &block.result {
        check_nesting_expression(result, depth, errors);
    }
}

fn check_nesting_statement(
    statement: &ty_expression::Statement,
    depth: usize,
    errors: &mut Vec<String>,
) {
    match statement {
        Statement::Expression(expression) | Statement::Return(Some(expression)) => {
            check_nesting_expression(expression, depth, errors);
        }
        Statement::Let { value, .. } => check_nesting_expression(value, depth, errors),
        Statement::While { condition, body } => {
            check_nesting_expression(condition, depth, errors);
            let new_depth = depth + 1;
            if new_depth > MAX_NESTING_DEPTH {
                errors.push(format!(
                    "nesting depth {new_depth} exceeds maximum {MAX_NESTING_DEPTH}: extract into a function"
                ));
            }
            check_nesting(body, new_depth, errors);
        }
        Statement::Defer(inner) => check_nesting_statement(inner, depth, errors),
        _ => {}
    }
}

fn check_nesting_expression(
    expression: &ty_expression::Expression,
    depth: usize,
    errors: &mut Vec<String>,
) {
    match &expression.kind {
        ExpressionKind::If {
            condition,
            then_branch,
            else_branch,
        } => {
            if !matches!(
                condition.resolved_type,
                ty_ree::types::Type::Bool | ty_ree::types::Type::Inferred
            ) {
                errors.push(format!(
                    "if-condition must be bool, got {:?}",
                    condition.resolved_type
                ));
            }
            check_nesting_expression(condition, depth, errors);
            let new_depth = depth + 1;
            if new_depth > MAX_NESTING_DEPTH {
                errors.push(format!(
                    "nesting depth {new_depth} exceeds maximum {MAX_NESTING_DEPTH}: extract into a function"
                ));
            }
            check_nesting(then_branch, new_depth, errors);
            if let Some(else_branch) = else_branch {
                check_nesting(else_branch, new_depth, errors);
            }
        }
        ExpressionKind::Block(block) => check_nesting(block, depth, errors),
        ExpressionKind::Call(callee, arguments) => {
            check_nesting_expression(callee, depth, errors);
            for argument in arguments {
                check_nesting_expression(argument, depth, errors);
            }
        }
        ExpressionKind::BinaryOperation(_, left, right) | ExpressionKind::Replace(left, right) => {
            check_nesting_expression(left, depth, errors);
            check_nesting_expression(right, depth, errors);
        }
        _ => {}
    }
}

fn check_block_unused(block: &ty_expression::Block, context_name: &str, errors: &mut Vec<String>) {
    let mut declared: HashMap<String, u32> = HashMap::new();
    let mut var_bindings: HashMap<String, u32> = HashMap::new();
    let mut assigned: HashSet<String> = HashSet::new();
    let mut used: HashSet<String> = HashSet::new();

    for statement in &block.statements {
        collect_used_in_statement(statement, &mut used);
        collect_assigned_in_statement(statement, &mut assigned);
        if let Statement::Let {
            name,
            binding,
            line,
            ..
        } = statement
        {
            if !name.starts_with('_') {
                declared.insert(name.clone(), *line);
            }
            if matches!(binding, ty_expression::Binding::Variable) {
                var_bindings.insert(name.clone(), *line);
            }
        }
    }
    if let Some(result) = &block.result {
        collect_used_in_expression(result, &mut used);
        collect_assigned_in_expression(result, &mut assigned);
    }

    for (name, line) in &declared {
        if !used.contains(name) {
            let location = if *line > 0 {
                format!("line {line}: ")
            } else {
                String::new()
            };
            errors.push(format!(
                "{location}unused variable '{name}' in {context_name} (prefix with _ to suppress)"
            ));
        }
    }

    for (name, line) in &var_bindings {
        if !name.starts_with('_') && !assigned.contains(name) {
            let location = if *line > 0 {
                format!("line {line}: ")
            } else {
                String::new()
            };
            errors.push(format!(
                "{location}unnecessary 'var {name}': never assigned to, use 'let' instead"
            ));
        }
    }

    for statement in &block.statements {
        check_nested_blocks(statement, errors);
    }
}

fn extract_root_variable(expression: &ty_expression::Expression) -> Option<&str> {
    match &expression.kind {
        ExpressionKind::Variable(name) => Some(name),
        ExpressionKind::Dereference(inner)
        | ExpressionKind::Field(inner, _)
        | ExpressionKind::Index(inner, _) => extract_root_variable(inner),
        _ => None,
    }
}

fn collect_assigned_in_statement(statement: &Statement, assigned: &mut HashSet<String>) {
    match statement {
        Statement::Expression(expression) => {
            if let ExpressionKind::Replace(target, _) = &expression.kind
                && let Some(name) = extract_root_variable(target)
            {
                assigned.insert(name.into());
            }
            collect_assigned_in_expression(expression, assigned);
        }
        Statement::Assign(target, _) => {
            if let Some(name) = extract_root_variable(target) {
                assigned.insert(name.into());
            }
        }
        Statement::While { body, .. } => {
            for inner in &body.statements {
                collect_assigned_in_statement(inner, assigned);
            }
            if let Some(result) = &body.result {
                collect_assigned_in_expression(result, assigned);
            }
        }
        _ => {}
    }
}

fn collect_assigned_in_expression(
    expression: &ty_expression::Expression,
    assigned: &mut HashSet<String>,
) {
    match &expression.kind {
        ExpressionKind::If {
            then_branch,
            else_branch,
            ..
        } => {
            for statement in &then_branch.statements {
                collect_assigned_in_statement(statement, assigned);
            }
            if let Some(result) = &then_branch.result {
                collect_assigned_in_expression(result, assigned);
            }
            if let Some(else_branch) = else_branch {
                for statement in &else_branch.statements {
                    collect_assigned_in_statement(statement, assigned);
                }
                if let Some(result) = &else_branch.result {
                    collect_assigned_in_expression(result, assigned);
                }
            }
        }
        ExpressionKind::Block(block) => {
            for statement in &block.statements {
                collect_assigned_in_statement(statement, assigned);
            }
            if let Some(result) = &block.result {
                collect_assigned_in_expression(result, assigned);
            }
        }
        ExpressionKind::Replace(target, _) => {
            if let Some(name) = extract_root_variable(target) {
                assigned.insert(name.into());
            }
        }
        _ => {}
    }
}

fn check_nested_blocks(statement: &Statement, errors: &mut Vec<String>) {
    match statement {
        Statement::While { body, .. } => check_block_unused(body, "<while>", errors),
        Statement::Defer(inner) => check_nested_blocks(inner, errors),
        _ => {}
    }
}

fn collect_used_in_statement(statement: &Statement, used: &mut HashSet<String>) {
    match statement {
        Statement::Expression(expression) | Statement::Return(Some(expression)) => {
            collect_used_in_expression(expression, used);
        }
        Statement::Let { value, .. } => {
            collect_used_in_expression(value, used);
        }
        Statement::Assign(target, value) => {
            collect_used_in_expression(target, used);
            collect_used_in_expression(value, used);
        }
        Statement::Label {
            initial_arguments, ..
        } => {
            for argument in initial_arguments {
                collect_used_in_expression(argument, used);
            }
        }
        Statement::Jump { arguments, .. } => {
            for argument in arguments {
                collect_used_in_expression(argument, used);
            }
        }
        Statement::While { condition, body } => {
            collect_used_in_expression(condition, used);
            collect_used_in_block(body, used);
        }
        Statement::Defer(inner) => collect_used_in_statement(inner, used),
        Statement::Return(None) => {}
    }
}

fn collect_used_in_block(block: &ty_expression::Block, used: &mut HashSet<String>) {
    for statement in &block.statements {
        collect_used_in_statement(statement, used);
    }
    if let Some(result) = &block.result {
        collect_used_in_expression(result, used);
    }
}

fn collect_used_in_expression(expression: &ty_expression::Expression, used: &mut HashSet<String>) {
    match &expression.kind {
        ExpressionKind::Variable(name) => {
            used.insert(name.clone());
        }
        ExpressionKind::Call(callee, arguments) => {
            collect_used_in_expression(callee, used);
            for argument in arguments {
                collect_used_in_expression(argument, used);
            }
        }
        ExpressionKind::BinaryOperation(_, left, right) | ExpressionKind::Replace(left, right) => {
            collect_used_in_expression(left, used);
            collect_used_in_expression(right, used);
        }
        ExpressionKind::UnaryOperation(_, operand)
        | ExpressionKind::Dereference(operand)
        | ExpressionKind::Convert(operand, _) => {
            collect_used_in_expression(operand, used);
        }
        ExpressionKind::Field(object, _) => collect_used_in_expression(object, used),
        ExpressionKind::Index(object, index) => {
            collect_used_in_expression(object, used);
            collect_used_in_expression(index, used);
        }
        ExpressionKind::Block(block) => collect_used_in_block(block, used),
        ExpressionKind::If {
            condition,
            then_branch,
            else_branch,
        } => {
            collect_used_in_expression(condition, used);
            collect_used_in_block(then_branch, used);
            if let Some(else_branch) = else_branch {
                collect_used_in_block(else_branch, used);
            }
        }
        ExpressionKind::ArrayLiteral(elements)
        | ExpressionKind::TupleLiteral(elements)
        | ExpressionKind::Print(elements) => {
            for element in elements {
                collect_used_in_expression(element, used);
            }
        }
        ExpressionKind::TypeConstruction(_, _, fields) => {
            for (_, expression) in fields {
                collect_used_in_expression(expression, used);
            }
        }
        ExpressionKind::Match { value, arms } => {
            collect_used_in_expression(value, used);
            for arm in arms {
                collect_used_in_block(&arm.body, used);
            }
        }
        ExpressionKind::Specialize(inner, _) => {
            collect_used_in_expression(inner, used);
        }
        _ => {}
    }
}
