@testable import SwiftLintBuiltInRules
import TestHelpers

// swiftlint:disable file_length

private let detectingTypes = ["actor", "class", "struct", "enum"]

// swiftlint:disable:next type_body_length
final class NestingRuleTests: SwiftLintTestCase {
    // swiftlint:disable:next function_body_length
    func testNestingWithAlwaysAllowOneTypeInFunctions() {
        var nonTriggeringExamples = NestingRule.description.nonTriggeringExamples
        nonTriggeringExamples.append(contentsOf: detectingTypes.flatMap { type -> [Example] in
            [
                .init("""
                    \(type) Example_0 {
                        \(type) Example_1 {
                            func f_0() {
                                \(type) Example_2 {}
                            }
                        }
                    }
                """),

                .init("""
                    \(type) Example_0 {
                        \(type) Example_1 {
                            func f_0() {
                                \(type) Example_2 {
                                    func f_1() {
                                        \(type) Example_3 {}
                                    }
                                }
                            }
                        }
                    }
                """),

                .init("""
                    func f_0() {
                        \(type) Example_0 {
                            \(type) Example_1 {}
                        }
                    }
                """),
            ]
        })
        nonTriggeringExamples.append(contentsOf: detectingTypes.flatMap { type -> [Example] in
            [
                .init("""
                    exampleFunc(closure: {
                        \(type) Example_0 {
                            \(type) Example_1 {
                                func f_0() {
                                   \(type) Example_2 {}
                               }
                           }
                       }
                       func f_0() {
                           \(type) Example_0 {
                               func f_1() {
                                   \(type) Example_1 {
                                       func f_2() {
                                           \(type) Example_2 {}
                                       }
                                   }
                               }
                           }
                       }
                    })
                """),

                .init("""
                    switch example {
                    case .exampleCase:
                       \(type) Example_0 {
                           \(type) Example_1 {
                               func f_0() {
                                   \(type) Example_2 {}
                               }
                           }
                       }
                    default:
                       func f_0() {
                           \(type) Example_0 {
                               func f_1() {
                                   \(type) Example_1 {
                                       func f_2() {
                                           \(type) Example_2 {}
                                       }
                                   }
                               }
                           }
                       }
                    }
                """),
            ]
        })

        var triggeringExamples = detectingTypes.flatMap { type -> [Example] in
            [
                .init("""
                    \(type) Example_0 {
                       \(type) Example_1 {
                           func f_0() {
                               \(type) Example_2 {
                                   ↓\(type) Example_3 {}
                               }
                           }
                       }
                    }
                """),

                .init("""
                    \(type) Example_0 {
                       \(type) Example_1 {
                           func f_0() {
                               \(type) Example_2 {
                                   func f_1() {
                                       \(type) Example_3 {
                                           ↓\(type) Example_4 {}
                                       }
                                   }
                               }
                           }
                       }
                    }
                """),

                .init("""
                    func f_0() {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               ↓\(type) Example_2 {}
                           }
                       }
                    }
                """),
            ]
        }

        // swiftlint:disable:next closure_body_length
        triggeringExamples.append(contentsOf: detectingTypes.flatMap { type -> [Example] in
            [
                .init("""
                    exampleFunc(closure: {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               func f_0() {
                                   \(type) Example_2 {
                                       ↓\(type) Example_3 {}
                                   }
                               }
                           }
                       }
                       func f_0() {
                           \(type) Example_0 {
                               func f_1() {
                                   \(type) Example_1 {
                                       func f_2() {
                                           \(type) Example_2 {
                                               ↓\(type) Example_3 {}
                                           }
                                       }
                                   }
                               }
                           }
                       }
                    })
                """),

                .init("""
                    switch example {
                    case .exampleCase:
                       \(type) Example_0 {
                           \(type) Example_1 {
                               func f_0() {
                                   \(type) Example_2 {
                                       ↓\(type) Example_3 {}
                                   }
                               }
                           }
                       }
                    default:
                       func f_0() {
                           \(type) Example_0 {
                               func f_1() {
                                   \(type) Example_1 {
                                       func f_2() {
                                           \(type) Example_2 {
                                               ↓\(type) Example_3 {}
                                           }
                                       }
                                   }
                               }
                           }
                       }
                    }
                """),
            ]
        })

        let description = RuleDescription(
            identifier: NestingRule.identifier,
            name: NestingRule.description.name,
            description: NestingRule.description.description,
            kind: .metrics,
            nonTriggeringExamples: nonTriggeringExamples,
            triggeringExamples: triggeringExamples
        )

        verifyRule(description, ruleConfiguration: ["always_allow_one_type_in_functions": true])
    }

    // swiftlint:disable:next function_body_length
    func testNestingWithoutCheckNestingInClosuresAndStatements() {
        var nonTriggeringExamples = NestingRule.description.nonTriggeringExamples
        // swiftlint:disable:next closure_body_length
        nonTriggeringExamples.append(contentsOf: detectingTypes.flatMap { type -> [Example] in
            [
                .init("""
                    exampleFunc(closure: {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               \(type) Example_2 {}
                           }
                       }
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   func f_3() {}
                               }
                           }
                       }
                    })
                """),

                .init("""
                    switch example {
                    case .exampleCase:
                       \(type) Example_0 {
                           \(type) Example_1 {
                               \(type) Example 2 {}
                           }
                       }
                    default:
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   func f_3() {}
                               }
                           }
                       }
                    }
                """),

                .init("""
                    for i in indicies {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               \(type) Example 2 {}
                           }
                       }
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   func f_3() {}
                               }
                           }
                       }
                    }
                """),

                .init("""
                    while true {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               \(type) Example 2 {}
                           }
                       }
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   func f_3() {}
                               }
                           }
                       }
                    }
                """),

                .init("""
                    repeat {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               \(type) Example 2 {}
                           }
                       }
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   func f_3() {}
                               }
                           }
                       }
                    } while true
                """),

                .init("""
                    if flag {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               \(type) Example 2 {}
                           }
                       }
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   func f_3() {}
                               }
                           }
                       }
                    }
                """),

                .init("""
                    guard flag else {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               \(type) Example 2 {}
                           }
                       }
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   func f_3() {}
                               }
                           }
                       }
                       return
                    }
                """),

                .init("""
                    defer {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               \(type) Example 2 {}
                           }
                       }
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   func f_3() {}
                               }
                           }
                       }
                    }
                """),

                .init("""
                    do {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               \(type) Example 2 {}
                           }
                       }
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   func f_3() {}
                               }
                           }
                       }
                    } catch {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               \(type) Example 2 {}
                           }
                       }
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   func f_3() {}
                               }
                           }
                       }
                    }
                """),
            ]
        })

        // swiftlint:disable:next closure_body_length
        var triggeringExamples = detectingTypes.flatMap { type -> [Example] in
            [
                .init("""
                    \(type) Example_0 {
                        \(type) Example_1 {
                            ↓\(type) Example_2 {}
                        }
                    }
                """),

                .init("""
                    var example: Int {
                       \(type) Example_0 {
                           \(type) Example_1 {
                               ↓\(type) Example_2 {}
                           }
                       }
                       return 5
                    }
                """),

                .init("""
                    var example: Int = 5 {
                       didSet {
                           \(type) Example_0 {
                               \(type) Example_1 {
                                   ↓\(type) Example_2 {}
                               }
                           }
                       }
                    }
                """),

                .init("""
                    extension Example_0 {
                       \(type) Example_1 {
                           ↓\(type) Example_2 {}
                       }
                    }
                """),

                .init("""
                    \(type) Example_0 {
                       func f_0() {
                           \(type) Example_1 {
                               func f_1() {
                                   func f_2() {
                                       ↓\(type) Example_2 {}
                                       ↓func f_3() {}
                                   }
                               }
                           }
                       }
                    }
                """),
            ]
        }

        triggeringExamples.append(contentsOf: [
            .init("""
                func f_0() {
                   func f_1() {
                       func f_2() {
                           ↓func f_3() {}
                       }
                   }
                }
            """),

            .init("""
                var example: Int {
                   func f_0() {
                       func f_1() {
                           func f_2() {
                               ↓func f_3() {}
                           }
                       }
                   }
                   return 5
                }
            """),

            .init("""
                var example: Int = 5 {
                   didSet {
                       func f_0() {
                           func f_1() {
                               func f_2() {
                                   ↓func f_3() {}
                               }
                           }
                       }
                   }
                }
            """),

            .init("""
                extension Example_0 {
                   func f_0() {
                       func f_1() {
                           func f_2() {
                               ↓func f_3() {}
                           }
                       }
                   }
                }
            """),
        ])

        let description = RuleDescription(
            identifier: NestingRule.identifier,
            name: NestingRule.description.name,
            description: NestingRule.description.description,
            kind: .metrics,
            nonTriggeringExamples: nonTriggeringExamples,
            triggeringExamples: triggeringExamples
        )

        verifyRule(description, ruleConfiguration: ["check_nesting_in_closures_and_statements": false])
    }

    func testNestingWithoutTypealiasAndAssociatedtype() {
        var nonTriggeringExamples = NestingRule.description.nonTriggeringExamples
        nonTriggeringExamples.append(contentsOf: detectingTypes.flatMap { type -> [Example] in
            [
                .init("""
                    \(type) Example_0 {
                        \(type) Example_1 {
                            typealias Example_2_Type = Example_2.Type
                        }
                        \(type) Example_2 {}
                    }
                """),
                .init("""
                    protocol Example_Protcol {
                        associatedtype AssociatedType
                    }

                    \(type) Example_1 {
                        \(type) Example_2: Example_Protcol {
                            typealias AssociatedType = Int
                        }
                    }
                """),
                .init("""
                    protocol Example_Protcol {
                        associatedtype AssociatedType
                    }

                    \(type) Example_1 {
                        \(type) Example_2: SomeProtcol {
                            typealias Example_2_Type = Example_2.Type
                        }
                        \(type) Example_3: Example_Protcol {
                            typealias AssociatedType = Int
                        }
                    }
                """),
            ]
        })

        let description = NestingRule.description.with(nonTriggeringExamples: nonTriggeringExamples)

        verifyRule(description, ruleConfiguration: ["ignore_typealiases_and_associatedtypes": true])
    }
}
