use rustpython_parser::ast::Constant;

use ruff_python_ast::source_code::Locator;

use crate::cst::helpers::is_radix_literal;
use crate::cst::visitor;
use crate::cst::visitor::Visitor;
use crate::cst::{Expr, ExprKind, Stmt, StmtKind};
use crate::trivia::Parenthesize;

/// Modify an [`Expr`] to infer parentheses, rather than respecting any user-provided trivia.
fn use_inferred_parens(expr: &mut Expr) {
    // Remove parentheses, unless it's a generator expression, in which case, keep them.
    if !matches!(expr.node, ExprKind::GeneratorExp { .. }) {
        expr.trivia.retain(|trivia| !trivia.kind.is_parentheses());
    }

    // If it's a tuple, add parentheses if it's a singleton; otherwise, we only need parentheses
    // if the tuple expands.
    if let ExprKind::Tuple { elts, .. } = &expr.node {
        expr.parentheses = if elts.len() > 1 {
            Parenthesize::IfExpanded
        } else {
            Parenthesize::Always
        };
    }
}

struct ParenthesesNormalizer<'a> {
    locator: &'a Locator<'a>,
}

impl<'a> Visitor<'a> for ParenthesesNormalizer<'_> {
    fn visit_stmt(&mut self, stmt: &'a mut Stmt) {
        // Always remove parentheses around statements, unless it's an expression statement,
        // in which case, remove parentheses around the expression.
        let before = stmt.trivia.len();
        stmt.trivia.retain(|trivia| !trivia.kind.is_parentheses());
        let after = stmt.trivia.len();
        if let StmtKind::Expr { value } = &mut stmt.node {
            if before != after {
                stmt.parentheses = Parenthesize::Always;
                value.parentheses = Parenthesize::Never;
            }
        }

        // In a variety of contexts, remove parentheses around sub-expressions. Right now, the
        // pattern is consistent (and repeated), but it may not end up that way.
        // https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#parentheses
        match &mut stmt.node {
            StmtKind::FunctionDef { .. } => {}
            StmtKind::AsyncFunctionDef { .. } => {}
            StmtKind::ClassDef { .. } => {}
            StmtKind::Return { value } => {
                if let Some(value) = value {
                    use_inferred_parens(value);
                }
            }
            StmtKind::Delete { .. } => {}
            StmtKind::Assign { targets, value, .. } => {
                for target in targets {
                    use_inferred_parens(target);
                }
                use_inferred_parens(value);
            }
            StmtKind::AugAssign { value, .. } => {
                use_inferred_parens(value);
            }
            StmtKind::AnnAssign { value, .. } => {
                if let Some(value) = value {
                    use_inferred_parens(value);
                }
            }
            StmtKind::For { target, iter, .. } | StmtKind::AsyncFor { target, iter, .. } => {
                use_inferred_parens(target);
                if !matches!(iter.node, ExprKind::Tuple { .. }) {
                    use_inferred_parens(iter);
                }
            }
            StmtKind::While { test, .. } => {
                use_inferred_parens(test);
            }
            StmtKind::If { test, .. } => {
                use_inferred_parens(test);
            }
            StmtKind::With { .. } => {}
            StmtKind::AsyncWith { .. } => {}
            StmtKind::Match { .. } => {}
            StmtKind::Raise { .. } => {}
            StmtKind::Try { .. } => {}
            StmtKind::TryStar { .. } => {}
            StmtKind::Assert { test, msg } => {
                use_inferred_parens(test);
                if let Some(msg) = msg {
                    use_inferred_parens(msg);
                }
            }
            StmtKind::Import { .. } => {}
            StmtKind::ImportFrom { .. } => {}
            StmtKind::Global { .. } => {}
            StmtKind::Nonlocal { .. } => {}
            StmtKind::Expr { .. } => {}
            StmtKind::Pass => {}
            StmtKind::Break => {}
            StmtKind::Continue => {}
        }

        visitor::walk_stmt(self, stmt);
    }

    fn visit_expr(&mut self, expr: &'a mut Expr) {
        // Always retain parentheses around expressions.
        let before = expr.trivia.len();
        expr.trivia.retain(|trivia| !trivia.kind.is_parentheses());
        let after = expr.trivia.len();
        if before != after {
            expr.parentheses = Parenthesize::Always;
        }

        match &mut expr.node {
            ExprKind::BoolOp { .. } => {}
            ExprKind::NamedExpr { .. } => {}
            ExprKind::BinOp { .. } => {}
            ExprKind::UnaryOp { .. } => {}
            ExprKind::Lambda { .. } => {}
            ExprKind::IfExp { .. } => {}
            ExprKind::Dict { .. } => {}
            ExprKind::Set { .. } => {}
            ExprKind::ListComp { .. } => {}
            ExprKind::SetComp { .. } => {}
            ExprKind::DictComp { .. } => {}
            ExprKind::GeneratorExp { .. } => {}
            ExprKind::Await { .. } => {}
            ExprKind::Yield { .. } => {}
            ExprKind::YieldFrom { .. } => {}
            ExprKind::Compare { .. } => {}
            ExprKind::Call { .. } => {}
            ExprKind::FormattedValue { .. } => {}
            ExprKind::JoinedStr { .. } => {}
            ExprKind::Constant { .. } => {}
            ExprKind::Attribute { value, .. } => {
                if matches!(
                    value.node,
                    ExprKind::Constant {
                        value: Constant::Float(..),
                        ..
                    },
                ) {
                    value.parentheses = Parenthesize::Always;
                } else if matches!(
                    value.node,
                    ExprKind::Constant {
                        value: Constant::Int(..),
                        ..
                    },
                ) {
                    // TODO(charlie): Encode this in the AST via separate node types.
                    if !is_radix_literal(self.locator.slice(&**value)) {
                        value.parentheses = Parenthesize::Always;
                    }
                }
            }
            ExprKind::Subscript { value, slice, .. } => {
                // If the slice isn't manually parenthesized, ensure that we _never_ parenthesize
                // the value.
                if !slice
                    .trivia
                    .iter()
                    .any(|trivia| trivia.kind.is_parentheses())
                {
                    value.parentheses = Parenthesize::Never;
                }
            }
            ExprKind::Starred { .. } => {}
            ExprKind::Name { .. } => {}
            ExprKind::List { .. } => {}
            ExprKind::Tuple { .. } => {}
            ExprKind::Slice { .. } => {}
        }

        visitor::walk_expr(self, expr);
    }
}

/// Normalize parentheses in a Python CST.
///
/// It's not always possible to determine the correct parentheses to use during formatting
/// from the node (and trivia) alone; sometimes, we need to know the parent node. This
/// visitor normalizes parentheses via a top-down traversal, which simplifies the formatting
/// code later on.
///
/// TODO(charlie): It's weird that we have both `TriviaKind::Parentheses` (which aren't used
/// during formatting) and `Parenthesize` (which are used during formatting).
pub fn normalize_parentheses(python_cst: &mut [Stmt], locator: &Locator) {
    let mut normalizer = ParenthesesNormalizer { locator };
    for stmt in python_cst {
        normalizer.visit_stmt(stmt);
    }
}
