use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::AstNodes;
use oxc_span::GetSpan;
use oxc_span::Span;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::Value;

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

fn max_depth_diagnostic(num: u32, max: u32, span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn(format!("Blocks are nested too deeply ({num}). Maximum allowed is {max}."))
        .with_help("Consider refactoring your code.")
        .with_label(span)
}

const DEFAULT_MAX_DEPTH: u32 = 4;

#[derive(Debug, Clone, JsonSchema, Deserialize)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
pub struct MaxDepth {
    /// The `max` enforces a maximum depth that blocks can be nested
    max: u32,
}

impl Default for MaxDepth {
    fn default() -> Self {
        Self { max: DEFAULT_MAX_DEPTH }
    }
}

#[derive(Debug, JsonSchema, Deserialize)]
#[serde(untagged)]
#[expect(unused)]
enum MaxDepthConfigEnum {
    Number(u32),
    Object(MaxDepth),
}

declare_oxc_lint!(
    /// ### What it does
    ///
    /// Enforce a maximum depth that blocks can be nested. This rule helps to limit the complexity
    /// of nested blocks, improving readability and maintainability by ensuring that code does not
    /// become too deeply nested.
    ///
    /// ### Why is this bad?
    ///
    /// Many developers consider code difficult to read if blocks are nested beyond a certain depth.
    /// Excessive nesting can make it harder to follow the flow of the code, increasing cognitive load
    /// and making maintenance more error-prone. By enforcing a maximum block depth, this rule encourages
    /// cleaner, more readable code.
    ///
    /// ### Examples
    ///
    /// Examples of **incorrect** code for this rule with the default `{ "max": 3 }` option:
    /// ```js
    /// function foo() {
    ///   for (;;) { // Nested 1 deep
    ///     while (true) { // Nested 2 deep
    ///       if (true) { // Nested 3 deep
    ///         if (true) { // Nested 4 deep }
    ///       }
    ///     }
    ///   }
    /// }
    /// ```
    ///
    /// Examples of **correct** code for this rule with the default `{ "max": 3 }` option:
    /// ```js
    /// function foo() {
    ///   for (;;) { // Nested 1 deep
    ///     while (true) { // Nested 2 deep
    ///       if (true) { // Nested 3 deep }
    ///     }
    ///   }
    /// }
    /// ```
    ///
    /// Note that class static blocks do not count as nested blocks, and that the depth in
    /// them is calculated separately from the enclosing context.
    ///
    /// Example:
    /// ```js
    /// function foo() {
    ///   if (true) { // Nested 1 deep
    ///     class C {
    ///       static {
    ///         if (true) { // Nested 1 deep
    ///           if (true) { // Nested 2 deep }
    ///         }
    ///       }
    ///     }
    ///   }
    /// }
    /// ```
    MaxDepth,
    eslint,
    pedantic,
    config = MaxDepthConfigEnum,
    version = "0.15.12",
    short_description = "Enforce a maximum depth that blocks can be nested.",
);

impl Rule for MaxDepth {
    fn from_configuration(value: serde_json::Value) -> Result<Self, serde_json::error::Error> {
        if let Some(max) = value
            .get(0)
            .and_then(Value::as_number)
            .and_then(serde_json::Number::as_u64)
            .and_then(|v| u32::try_from(v).ok())
        {
            Ok(MaxDepth { max })
        } else {
            serde_json::from_value::<DefaultRuleConfig<Self>>(value)
                .map(DefaultRuleConfig::into_inner)
        }
    }

    #[expect(clippy::cast_possible_truncation)] // the length of ancestors can't be over u32::MAX, because the source code is already limited by u32::MAX.
    fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
        match node.kind() {
            // An `else if`'s `IfStatement` continues its parent's chain, so it isn't a new level.
            AstKind::IfStatement(_)
                if matches!(ctx.nodes().parent_kind(node.id()), AstKind::IfStatement(_)) =>
            {
                return;
            }
            AstKind::IfStatement(_)
            | AstKind::SwitchStatement(_)
            | AstKind::TryStatement(_)
            | AstKind::DoWhileStatement(_)
            | AstKind::WhileStatement(_)
            | AstKind::WithStatement(_)
            | AstKind::ForStatement(_)
            | AstKind::ForInStatement(_)
            | AstKind::ForOfStatement(_) => {}
            _ => return,
        }

        let depth = 1 + ctx
            .nodes()
            .ancestors(node.id())
            .take_while(|node| !should_stop(node))
            .filter(|node| should_count(node, ctx.nodes()))
            .count() as u32;
        if depth > self.max {
            ctx.diagnostic(max_depth_diagnostic(depth, self.max, node.span()));
        }
    }
}

/// Whether an ancestor node counts as a nesting level. Mirrors the kinds matched in `run`;
/// kept as a predicate because the ancestor walk needs to test arbitrary nodes.
fn should_count(node: &AstNode<'_>, nodes: &AstNodes<'_>) -> bool {
    matches!(node.kind(), AstKind::IfStatement(_) if !matches!(nodes.parent_kind(node.id()), AstKind::IfStatement(_)))
        || matches!(node.kind(), |AstKind::SwitchStatement(_)| AstKind::TryStatement(_)
            | AstKind::DoWhileStatement(_)
            | AstKind::WhileStatement(_)
            | AstKind::WithStatement(_)
            | AstKind::ForStatement(_)
            | AstKind::ForInStatement(_)
            | AstKind::ForOfStatement(_))
}

fn should_stop(node: &AstNode<'_>) -> bool {
    is_function_node(node) || matches!(node.kind(), AstKind::Program(_) | AstKind::StaticBlock(_))
}

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

    let defaults = MaxDepth::default();
    assert_eq!(defaults.max, 4);

    let pass = vec![
        (
            "function foo() { if (true) { if (false) { if (true) { } } } }",
            Some(serde_json::json!([3])),
        ),
        (
            "function foo() { if (true) { } else if (false) { } else if (true) { } else if (false) {} }",
            Some(serde_json::json!([3])),
        ),
        (
            "var foo = () => { if (true) { if (false) { if (true) { } } } }",
            Some(serde_json::json!([3])),
        ), // { "ecmaVersion": 6 },
        ("function foo() { if (true) { if (false) { if (true) { } } } }", None),
        (
            "function foo() { if (true) { if (false) { if (true) { } } } }",
            Some(serde_json::json!([{ "max": 3 }])),
        ),
        ("class C { static { if (1) { if (2) {} } } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 },
        (
            "class C { static { if (1) { if (2) {} } if (1) { if (2) {} } } }",
            Some(serde_json::json!([2])),
        ), // { "ecmaVersion": 2022 },
        (
            "class C { static { if (1) { if (2) {} } } static { if (1) { if (2) {} } } }",
            Some(serde_json::json!([2])),
        ), // { "ecmaVersion": 2022 },
        ("if (1) { class C { static { if (1) { if (2) {} } } } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 },
        (
            "function foo() { if (1) { class C { static { if (1) { if (2) {} } } } } }",
            Some(serde_json::json!([2])),
        ), // { "ecmaVersion": 2022 },
        (
            "function foo() { if (1) { if (2) { class C { static { if (1) { if (2) {} } if (1) { if (2) {} } } } } } if (1) { if (2) {} } }",
            Some(serde_json::json!([2])),
        ), // { "ecmaVersion": 2022 }
    ];

    let fail = vec![
        (
            "function foo() { if (true) { if (false) { if (true) { } } } }",
            Some(serde_json::json!([2])),
        ),
        (
            "var foo = () => { if (true) { if (false) { if (true) { } } } }",
            Some(serde_json::json!([2])),
        ), // { "ecmaVersion": 6 },
        ("function foo() { if (true) {} else { for(;;) {} } }", Some(serde_json::json!([1]))),
        ("function foo() { while (true) { if (true) {} } }", Some(serde_json::json!([1]))),
        ("function foo() { for (let x of foo) { if (true) {} } }", Some(serde_json::json!([1]))), // { "ecmaVersion": 6 },
        (
            "function foo() { while (true) { if (true) { if (false) { } } } }",
            Some(serde_json::json!([1])),
        ),
        (
            "function foo() { if (true) { if (false) { if (true) { if (false) { if (true) { } } } } } }",
            None,
        ),
        (
            "function foo() { if (true) { if (false) { if (true) { } } } }",
            Some(serde_json::json!([{ "max": 2 }])),
        ),
        (
            "function foo() { if (a) { if (b) { if (c) { if (d) { if (e) {} } } } } }",
            Some(serde_json::json!([{}])),
        ),
        ("function foo() { if (true) {} }", Some(serde_json::json!([{ "max": 0 }]))),
        ("class C { static { if (1) { if (2) { if (3) {} } } } }", Some(serde_json::json!([2]))), // { "ecmaVersion": 2022 },
        (
            "if (1) { class C { static { if (1) { if (2) { if (3) {} } } } } }",
            Some(serde_json::json!([2])),
        ), // { "ecmaVersion": 2022 },
        (
            "function foo() { if (1) { class C { static { if (1) { if (2) { if (3) {} } } } } } }",
            Some(serde_json::json!([2])),
        ), // { "ecmaVersion": 2022 },
        (
            "function foo() { if (1) { class C { static { if (1) { if (2) {} } } } if (2) { if (3) {} } } }",
            Some(serde_json::json!([2])),
        ), // { "ecmaVersion": 2022 }
    ];

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