import SwiftLintCore
import SwiftSyntax

@SwiftSyntaxRule(explicitRewriter: true)
struct NoSpaceInMethodCallRule: Rule {
    var configuration = SeverityConfiguration<Self>(.warning)

    static let description = RuleDescription(
        identifier: "no_space_in_method_call",
        name: "No Space in Method Call",
        description: "Don't add a space between the method name and the parentheses",
        kind: .style,
        nonTriggeringExamples: #examples([
            "foo()",
            "object.foo()",
            "object.foo(1)",
            "object.foo(value: 1)",
            "object.foo { print($0 }",
            "list.sorted { $0.0 < $1.0 }.map { $0.value }",
            "self.init(rgb: (Int) (colorInt))",
            """
            Button {
                print("Button tapped")
            } label: {
                Text("Button")
            }
            """,
        ]),
        triggeringExamples: #examples([
            "foo↓ ()",
            "object.foo↓ ()",
            "object.foo↓ (1)",
            "object.foo↓ (value: 1)",
            "object.foo↓ () {}",
            "object.foo↓     ()",
            "object.foo↓     (value: 1) { x in print(x) }",
        ]),
        corrections: #corrections([
            "foo↓ ()": "foo()",
            "object.foo↓ ()": "object.foo()",
            "object.foo↓ (1)": "object.foo(1)",
            "object.foo↓ (value: 1)": "object.foo(value: 1)",
            "object.foo↓ () {}": "object.foo() {}",
            "object.foo↓     ()": "object.foo()",
        ])
    )
}

private extension NoSpaceInMethodCallRule {
    final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
        override func visitPost(_ node: FunctionCallExprSyntax) {
            guard node.hasNoSpaceInMethodCallViolation else {
                return
            }

            violations.append(node.calledExpression.endPositionBeforeTrailingTrivia)
        }
    }

    final class Rewriter: ViolationsSyntaxRewriter<ConfigurationType> {
        override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
            guard node.hasNoSpaceInMethodCallViolation else {
                return super.visit(node)
            }
            numberOfCorrections += 1
            let newNode = node
                .with(\.calledExpression, node.calledExpression.with(\.trailingTrivia, []))
            return super.visit(newNode)
        }
    }
}

private extension FunctionCallExprSyntax {
    var hasNoSpaceInMethodCallViolation: Bool {
        leftParen != nil &&
            !calledExpression.is(TupleExprSyntax.self) &&
            calledExpression.trailingTrivia.isNotEmpty
    }
}
