use oxc_ast::{
    AstKind,
    ast::{Expression, Statement},
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{ContentEq, GetSpan, Span};
use oxc_syntax::operator::LogicalOperator;

use crate::{AstNode, context::LintContext, rule::Rule};

fn no_dupe_else_if_diagnostic(first_test: Span, second_test: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Duplicate conditions in if-else-if chain")
        .with_help(
            "Remove or modify the duplicate condition, as its branch will never be executed.",
        )
        .with_labels([
            first_test.label("condition first checked here"),
            second_test.label("this branch will never be executed"),
        ])
}

#[derive(Debug, Default, Clone)]
pub struct NoDupeElseIf;

declare_oxc_lint!(
    /// ### What it does
    ///
    /// Disallow duplicate conditions in if-else-if chains.
    ///
    /// ### Why is this bad?
    ///
    /// if-else-if chains are commonly used when there is a need to execute only one branch (or at most one branch) out of several possible branches, based on certain conditions.
    /// Two identical test conditions in the same chain are almost always a mistake in the code. Unless there are side effects in the expressions,
    /// a duplicate will evaluate to the same true or false value as the identical expression earlier in the chain, meaning that its branch can never execute.
    ///
    /// ### Examples
    ///
    /// Examples of **incorrect** code for this rule:
    ///
    /// ```javascript
    /// if (a) {
    ///     foo();
    /// } else if (b) {
    ///     bar();
    /// } else if (b) {
    ///     baz();
    /// }
    /// ```
    ///
    /// ```javascript
    /// if (a || b) {
    ///    foo();
    /// } else if (a) {
    ///    bar();
    /// }
    /// ```
    ///
    /// ```javascript
    /// if (n === 1) {
    ///     foo();
    /// } else if (n === 2) {
    ///     bar();
    /// } else if (n === 3) {
    ///     baz();
    /// } else if (n === 2) {
    ///     quux();
    /// } else if (n === 5) {
    ///     quuux();
    /// }
    /// ```
    ///
    /// Examples of **correct** code for this rule:
    ///
    /// ```javascript
    /// if (a) {
    ///     foo();
    /// } else if (b) {
    ///     bar();
    /// } else if (c) {
    ///     baz();
    /// }
    /// ```
    ///
    /// ```javascript
    /// if (a || b) {
    ///    foo();
    /// } else if (c) {
    ///    bar();
    /// }
    /// ```
    ///
    /// ```javascript
    /// if (n === 1) {
    ///     foo();
    /// } else if (n === 2) {
    ///     bar();
    /// } else if (n === 3) {
    ///     baz();
    /// } else if (n === 4) {
    ///     quux();
    /// } else if (n === 5) {
    ///     quuux();
    /// }
    /// ```
    NoDupeElseIf,
    eslint,
    correctness,
    version = "0.0.5",
    short_description = "Disallow duplicate conditions in if-else-if chains.",
);

impl Rule for NoDupeElseIf {
    fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
        // if (a) {} else if (a) {}
        //                ^^ get this if statement
        let AstKind::IfStatement(if_stmt) = node.kind() else {
            return;
        };
        let AstKind::IfStatement(parent_if_stmt) = ctx.nodes().parent_kind(node.id()) else {
            return;
        };
        let Some(Statement::IfStatement(child_if_stmt)) = &parent_if_stmt.alternate else {
            return;
        };
        if child_if_stmt.span != if_stmt.span {
            return;
        }

        let mut conditions_to_check = vec![&if_stmt.test];

        if let Expression::LogicalExpression(expr) = &if_stmt.test
            && expr.operator == LogicalOperator::And
        {
            conditions_to_check.extend(split_by_and(&if_stmt.test));
        }

        let mut list_to_check: Vec<Vec<Vec<_>>> = conditions_to_check
            .iter()
            .map(|expr| split_by_or(expr).into_iter().map(split_by_and).collect())
            .collect();

        let mut current_node = node;
        loop {
            let parent_node = ctx.nodes().parent_node(current_node.id());
            let AstKind::IfStatement(stmt) = parent_node.kind() else {
                break;
            };

            if stmt.alternate.as_ref().is_none_or(|stmt| stmt.span() != current_node.kind().span())
            {
                break;
            }

            current_node = parent_node;

            let current_or_operands: Vec<_> =
                split_by_or(&stmt.test).into_iter().map(split_by_and).collect();

            list_to_check = list_to_check
                .into_iter()
                .map(|or_operands| {
                    or_operands
                        .into_iter()
                        .filter(|or_operand| {
                            !current_or_operands
                                .iter()
                                .any(|current_or_operand| is_subset(current_or_operand, or_operand))
                        })
                        .collect()
                })
                .collect();

            if list_to_check.iter().any(Vec::is_empty) {
                ctx.diagnostic(no_dupe_else_if_diagnostic(stmt.test.span(), if_stmt.test.span()));
                break;
            }
        }
    }
}

fn split_by_or<'a, 'b>(expr: &'a Expression<'b>) -> Vec<&'a Expression<'b>> {
    split_by_logical_operator(expr, LogicalOperator::Or)
}

fn split_by_and<'a, 'b>(expr: &'a Expression<'b>) -> Vec<&'a Expression<'b>> {
    split_by_logical_operator(expr, LogicalOperator::And)
}

fn split_by_logical_operator<'a, 'b>(
    expr: &'a Expression<'b>,
    operator: LogicalOperator,
) -> Vec<&'a Expression<'b>> {
    match expr {
        Expression::LogicalExpression(expr) if expr.operator == operator => [
            split_by_logical_operator(&expr.left, operator),
            split_by_logical_operator(&expr.right, operator),
        ]
        .concat(),
        Expression::ParenthesizedExpression(expr) => {
            split_by_logical_operator(&expr.expression, operator)
        }
        _ => vec![expr],
    }
}

fn is_subset<'a, 'b>(a: &'a [&'a Expression<'b>], b: &'a [&'a Expression<'b>]) -> bool {
    a.iter().all(|expr_a| b.iter().any(|expr_b| is_equal(expr_a, expr_b)))
}

fn is_equal<'a, 'b>(a: &'a Expression<'b>, b: &'a Expression<'b>) -> bool {
    match (a, b) {
        (Expression::LogicalExpression(a), Expression::LogicalExpression(b))
            if matches!(a.operator, LogicalOperator::And | LogicalOperator::Or)
                && a.operator == b.operator =>
        {
            (is_equal(&a.left, &b.left) && is_equal(&a.right, &b.right))
                || (is_equal(&a.left, &b.right) && is_equal(&a.right, &b.left))
        }

        (a, b) => a.content_eq(b),
    }
}

#[test]
fn test() {
    use crate::tester::Tester;

    let pass = vec![
        "if (a) {} else if (b) {}",
        "if (a); else if (b); else if (c);",
        "if (true) {} else if (false) {} else {}",
        "if (1) {} else if (2) {}",
        "if (f) {} else if (f()) {}",
        "if (f(a)) {} else if (g(a)) {}",
        "if (f(a)) {} else if (f(b)) {}",
        "if (a === 1) {} else if (a === 2) {}",
        "if (a === 1) {} else if (b === 1) {}",
        "if (a) {}",
        "if (a);",
        "if (a) {} else {}",
        "if (a) if (a) {}",
        "if (a) if (a);",
        "if (a) { if (a) {} }",
        "if (a) {} else { if (a) {} }",
        "if (a) {} if (a) {}",
        "if (a); if (a);",
        "while (a) if (a);",
        "if (a); else a ? a : a;",
        "if (a) { if (b) {} } else if (b) {}",
        "if (a) if (b); else if (a);",
        "if (a) {} else if (!!a) {}",
        "if (a === 1) {} else if (a === (1)) {}",
        "if (a || b) {} else if (c || d) {}",
        "if (a || b) {} else if (a || c) {}",
        "if (a) {} else if (a || b) {}",
        "if (a) {} else if (b) {} else if (a || b || c) {}",
        "if (a && b) {} else if (a) {} else if (b) {}",
        "if (a && b) {} else if (b && c) {} else if (a && c) {}",
        "if (a && b) {} else if (b || c) {}",
        "if (a) {} else if (b && (a || c)) {}",
        "if (a) {} else if (b && (c || d && a)) {}",
        "if (a && b && c) {} else if (a && b && (c || d)) {}",
    ];

    let fail = vec![
        "if (a) {} else if (a) {}",
        "if (a); else if (a);",
        "if (a) {} else if (a) {} else {}",
        "if (a) {} else if (b) {} else if (a) {} else if (c) {}",
        "if (a) {} else if (b) {} else if (a) {}",
        "if (a) {} else if (b) {} else if (c) {} else if (a) {}",
        "if (a) {} else if (b) {} else if (b) {}",
        "if (a) {} else if (b) {} else if (b) {} else {}",
        "if (a) {} else if (b) {} else if (c) {} else if (b) {}",
        "if (a); else if (b); else if (c); else if (b); else if (d); else;",
        "if (a); else if (b); else if (c); else if (d); else if (b); else if (e);",
        "if (a) {} else if (a) {} else if (a) {}",
        "if (a) {} else if (b) {} else if (a) {} else if (b) {} else if (a) {}",
        "if (a) { if (b) {} } else if (a) {}",
        "if (a === 1) {} else if (a === 1) {}",
        "if (1 < a) {} else if (1 < a) {}",
        "if (true) {} else if (true) {}",
        "if (a && b) {} else if (a && b) {}",
        "if (a && b || c)  {} else if (a && b || c) {}",
        "if (f(a)) {} else if (f(a)) {}",
        "if (a === 1) {} else if (a===1) {}",
        "if (a === 1) {} else if (a === /* comment */ 1) {}",
        "if (a === 1) {} else if ((a === 1)) {}",
        "if (a || b) {} else if (a) {}",
        "if (a || b) {} else if (a) {} else if (b) {}",
        "if (a || b) {} else if (b || a) {}",
        "if (a) {} else if (b) {} else if (a || b) {}",
        "if (a || b) {} else if (c || d) {} else if (a || d) {}",
        "if ((a === b && fn(c)) || d) {} else if (fn(c) && a === b) {}",
        "if (a) {} else if (a && b) {}",
        "if (a && b) {} else if (b && a) {}",
        "if (a && b) {} else if (a && b && c) {}",
        "if (a || c) {} else if (a && b || c) {}",
        "if (a) {} else if (b) {} else if (c && a || b) {}",
        "if (a) {} else if (b) {} else if (c && (a || b)) {}",
        "if (a) {} else if (b && c) {} else if (d && (a || e && c && b)) {}",
        "if (a || b && c) {} else if (b && c && d) {}",
        "if (a || b) {} else if (b && c) {}",
        "if (a) {} else if (b) {} else if ((a || b) && c) {}",
        "if ((a && (b || c)) || d) {} else if ((c || b) && e && a) {}",
        "if (a && b || b && c) {} else if (a && b && c) {}",
        "if (a) {} else if (b && c) {} else if (d && (c && e && b || a)) {}",
        "if (a || (b && (c || d))) {} else if ((d || c) && b) {}",
        "if (a || b) {} else if ((b || a) && c) {}",
        "if (a || b) {} else if (c) {} else if (d) {} else if (b && (a || c)) {}",
        "if (a || b || c) {} else if (a || (b && d) || (c && e)) {}",
        "if (a || (b || c)) {} else if (a || (b && c)) {}",
        "if (a || b) {} else if (c) {} else if (d) {} else if ((a || c) && (b || d)) {}",
        "if (a) {} else if (b) {} else if (c && (a || d && b)) {}",
        "if (a) {} else if (a || a) {}",
        "if (a || a) {} else if (a || a) {}",
        "if (a || a) {} else if (a) {}",
        "if (a) {} else if (a && a) {}",
        "if (a && a) {} else if (a && a) {}",
        "if (a && a) {} else if (a) {}",
    ];

    Tester::new(NoDupeElseIf::NAME, NoDupeElseIf::PLUGIN, pass, fail).test_and_snapshot();
}
