import SwiftLintCore
import SwiftSyntax

@SwiftSyntaxRule(optIn: true)
struct VariableShadowingRule: Rule {
    var configuration = VariableShadowingConfiguration()

    static let description = RuleDescription(
        identifier: "variable_shadowing",
        name: "Variable Shadowing",
        description: "Do not shadow variables declared in outer scopes",
        kind: .lint,
        nonTriggeringExamples: #examples([
            """
            var a: String?
            func test(a: String?) {
                print(a)
            }
            """.asExample(configuration: ["ignore_parameters": true]),
            """
            var a: String = "hello"
            if let b = a {
                print(b)
            }
            """,
            """
            var a: String?
            func test() {
                if let b = a {
                    print(b)
                }
            }
            """,
            """
            for i in 1...10 {
                print(i)
            }
            for i in 1...10 {
                print(i)
            }
            """,
            """
            func test() {
                var a: String = "hello"
                func nested() {
                    var b: String = "world"
                    print(a, b)
                }
            }
            """,
            """
            class Test {
                var a: String?
                func test(a: String?) {
                    print(a)
                }
            }
            """,
            """
            let a: Int?
            if let a { print(a) }
            """,
            """
            let a: Int?
            guard let a else { return }
            """,
            """
            let a: Int?
            while let a { print(a) }
            """,
            """
            var a = 1
            if let a = a {
                print(a)
            }
            """,
            """
            var a = 1
            if let a = self.a {
                print(a)
            }
            """,
            """
            struct S {
                static let c: Int? = nil
                var a: Int?
                var b: Int {
                    if let a = self.a { a }
                    else if let c = Self.c { c }
                    else { 0 }
                }
            }
            """,
        ]),
        triggeringExamples: #examples([
            """
            var foo = 1
            do {
                let ↓foo = 2
            }
            """,
            """
            var a = 1
            if let ↓a = Optional(2) {
                let ↓a = 3
                print(a)
            }
            """,
            """
            var i = 1
            for ↓i in 1...3 {
                let ↓i = 2
                print(i)
            }
            """,
            """
            var a = 1
            func test() {
                do {
                    var ↓a = 2
                    print(a)
                }
            }
            """,
            """
            func test() {
                var a = 1
                if var ↓a = Optional(2) {
                    var ↓a = 2
                    print(a)
                }
            }
            """,
            """
            func test() {
                var a = 1
                for ↓a in 0..<1 {
                    var ↓a = 2
                    print(a)
                }
            }
            """,
            """
            func test() {
                var a = 1
                while true {
                    var ↓a = 2
                    break
                }
            }
            """,
            """
            var a: String?
            func test(↓a: String?) {
                let ↓a = ""
                print(a)
            }
            """.asExample(configuration: ["ignore_parameters": false]),
            """
            struct S {
                var a = 1
                var b: Int {
                    let ↓a = 2
                    return a
                }
            }
            """,
            """
            var a: String?
            while let ↓a = Optional("hello") {}
            """,
            """
            var a = "outer"
            let (↓a, c) = ("first", "second")
            """,
        ])
    )
}

private extension VariableShadowingRule {
    final class Visitor: DeclaredIdentifiersTrackingVisitor<VariableShadowingConfiguration> {
        init(configuration: ConfigurationType, file: SwiftLintFile) {
            super.init(configuration: configuration, file: file, includeMembers: true)
        }

        override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
            if node.parent?.is(MemberBlockItemSyntax.self) == false {
                let isUnmodifiable = node.bindingSpecifier.tokenKind == .keyword(.let)
                for binding in node.bindings where isUnmodifiable || !binding.isIdentity {
                    checkForShadowing(in: binding.pattern)
                }
            }
            return super.visit(node)
        }

        override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
            if !configuration.ignoreParameters {
                for param in node.signature.parameterClause.parameters {
                    let nameToken = param.secondName ?? param.firstName
                    if nameToken.text != "_", hasSeenDeclaration(for: nameToken.text) {
                        violations.append(nameToken.positionAfterSkippingLeadingTrivia)
                    }
                }
            }
            return super.visit(node)
        }

        override func visit(_ node: ForStmtSyntax) -> SyntaxVisitorContinueKind {
            checkForShadowing(in: node.pattern)
            return super.visit(node)
        }

        override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind {
            for condition in node.conditions {
                if let optBinding = condition.condition.as(OptionalBindingConditionSyntax.self) {
                    checkForShadowing(in: optBinding.pattern, binding: optBinding)
                }
            }
            return super.visit(node)
        }

        override func visit(_ node: WhileStmtSyntax) -> SyntaxVisitorContinueKind {
            for condition in node.conditions {
                if let optBinding = condition.condition.as(OptionalBindingConditionSyntax.self) {
                    checkForShadowing(in: optBinding.pattern, binding: optBinding)
                }
            }
            return super.visit(node)
        }

        override func visit(_ node: GuardStmtSyntax) -> SyntaxVisitorContinueKind {
            for condition in node.conditions {
                if let optBinding = condition.condition.as(OptionalBindingConditionSyntax.self) {
                    checkForShadowing(in: optBinding.pattern, binding: optBinding)
                }
            }
            return super.visit(node)
        }

        // Checking shadowing in both variable declarations and optional bindings.
        // For optional bindings, skips idiomatic patterns (if let a / if let a = a).
        private func checkForShadowing(in pattern: PatternSyntax, binding: OptionalBindingConditionSyntax? = nil) {
            if let identifier = pattern.as(IdentifierPatternSyntax.self) {
                let name = identifier.identifier.text
                if let binding, binding.initializer?.isBindingFor(name: name) != false {
                    return
                }
                if hasSeenDeclaration(for: name) {
                    violations.append(identifier.identifier.positionAfterSkippingLeadingTrivia)
                }
            } else if let tuple = pattern.as(TuplePatternSyntax.self) {
                tuple.elements.forEach { element in
                    checkForShadowing(in: element.pattern, binding: binding)
                }
            } else if let valueBinding = pattern.as(ValueBindingPatternSyntax.self) {
                checkForShadowing(in: valueBinding.pattern, binding: binding)
            }
        }
    }
}

private extension PatternBindingSyntax {
    var isIdentity: Bool {
        guard let initializer, let identifierPattern = pattern.as(IdentifierPatternSyntax.self) else {
            return false
        }
        return initializer.isBindingFor(name: identifierPattern.identifier.text)
    }
}

private extension InitializerClauseSyntax {
    func isBindingFor(name: String) -> Bool {
        if let identifierExpr = value.as(DeclReferenceExprSyntax.self) {
            return identifierExpr.baseName.text == name
        }
        if let memberAccessExpr = value.as(MemberAccessExprSyntax.self),
           let baseName = memberAccessExpr.base?.as(DeclReferenceExprSyntax.self)?.baseName.text,
           ["self", "Self"].contains(baseName) {
            return memberAccessExpr.declName.baseName.text == name
        }
        return false
    }
}
