use rustpython_parser::ast::{Expr, ExprKind, Keyword};

use crate::types::Range;

fn relocate_keyword(keyword: &mut Keyword, location: Range) {
    keyword.location = location.location;
    keyword.end_location = Some(location.end_location);
    relocate_expr(&mut keyword.node.value, location);
}

/// Change an expression's location (recursively) to match a desired, fixed
/// location.
pub fn relocate_expr(expr: &mut Expr, location: Range) {
    expr.location = location.location;
    expr.end_location = Some(location.end_location);
    match &mut expr.node {
        ExprKind::BoolOp { values, .. } => {
            for expr in values {
                relocate_expr(expr, location);
            }
        }
        ExprKind::NamedExpr { target, value } => {
            relocate_expr(target, location);
            relocate_expr(value, location);
        }
        ExprKind::BinOp { left, right, .. } => {
            relocate_expr(left, location);
            relocate_expr(right, location);
        }
        ExprKind::UnaryOp { operand, .. } => {
            relocate_expr(operand, location);
        }
        ExprKind::Lambda { body, .. } => {
            relocate_expr(body, location);
        }
        ExprKind::IfExp { test, body, orelse } => {
            relocate_expr(test, location);
            relocate_expr(body, location);
            relocate_expr(orelse, location);
        }
        ExprKind::Dict { keys, values } => {
            for expr in keys.iter_mut().flatten() {
                relocate_expr(expr, location);
            }
            for expr in values {
                relocate_expr(expr, location);
            }
        }
        ExprKind::Set { elts } => {
            for expr in elts {
                relocate_expr(expr, location);
            }
        }
        ExprKind::ListComp { elt, .. } => {
            relocate_expr(elt, location);
        }
        ExprKind::SetComp { elt, .. } => {
            relocate_expr(elt, location);
        }
        ExprKind::DictComp { key, value, .. } => {
            relocate_expr(key, location);
            relocate_expr(value, location);
        }
        ExprKind::GeneratorExp { elt, .. } => {
            relocate_expr(elt, location);
        }
        ExprKind::Await { value } => relocate_expr(value, location),
        ExprKind::Yield { value } => {
            if let Some(expr) = value {
                relocate_expr(expr, location);
            }
        }
        ExprKind::YieldFrom { value } => relocate_expr(value, location),
        ExprKind::Compare {
            left, comparators, ..
        } => {
            relocate_expr(left, location);
            for expr in comparators {
                relocate_expr(expr, location);
            }
        }
        ExprKind::Call {
            func,
            args,
            keywords,
        } => {
            relocate_expr(func, location);
            for expr in args {
                relocate_expr(expr, location);
            }
            for keyword in keywords {
                relocate_keyword(keyword, location);
            }
        }
        ExprKind::FormattedValue {
            value, format_spec, ..
        } => {
            relocate_expr(value, location);
            if let Some(expr) = format_spec {
                relocate_expr(expr, location);
            }
        }
        ExprKind::JoinedStr { values } => {
            for expr in values {
                relocate_expr(expr, location);
            }
        }
        ExprKind::Constant { .. } => {}
        ExprKind::Attribute { value, .. } => {
            relocate_expr(value, location);
        }
        ExprKind::Subscript { value, slice, .. } => {
            relocate_expr(value, location);
            relocate_expr(slice, location);
        }
        ExprKind::Starred { value, .. } => {
            relocate_expr(value, location);
        }
        ExprKind::Name { .. } => {}
        ExprKind::List { elts, .. } => {
            for expr in elts {
                relocate_expr(expr, location);
            }
        }
        ExprKind::Tuple { elts, .. } => {
            for expr in elts {
                relocate_expr(expr, location);
            }
        }
        ExprKind::Slice { lower, upper, step } => {
            if let Some(expr) = lower {
                relocate_expr(expr, location);
            }
            if let Some(expr) = upper {
                relocate_expr(expr, location);
            }
            if let Some(expr) = step {
                relocate_expr(expr, location);
            }
        }
    }
}
