import SwiftSyntax

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

    static let description = RuleDescription(
        identifier: "nslocalizedstring_key",
        name: "NSLocalizedString Key",
        description: "Static strings should be used as key/comment" +
            " in NSLocalizedString in order for genstrings to work",
        kind: .lint,
        nonTriggeringExamples: [
            Example("NSLocalizedString(\"key\", comment: \"\")"),
            Example("NSLocalizedString(\"key\" + \"2\", comment: \"\")"),
            Example("NSLocalizedString(\"key\", comment: \"comment\")"),
            Example("""
            NSLocalizedString("This is a multi-" +
                "line string", comment: "")
            """),
            Example("""
            let format = NSLocalizedString("%@, %@.", comment: "Accessibility label for a post in the post list." +
            " The parameters are the title, and date respectively." +
            " For example, \"Let it Go, 1 hour ago.\"")
            """),
        ],
        triggeringExamples: [
            Example("NSLocalizedString(↓method(), comment: \"\")"),
            Example("NSLocalizedString(↓\"key_\\(param)\", comment: \"\")"),
            Example("NSLocalizedString(\"key\", comment: ↓\"comment with \\(param)\")"),
            Example("NSLocalizedString(↓\"key_\\(param)\", comment: ↓method())"),
        ]
    )
}

private extension NSLocalizedStringKeyRule {
    final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
        override func visitPost(_ node: FunctionCallExprSyntax) {
            guard node.calledExpression.as(DeclReferenceExprSyntax.self)?.baseName.text == "NSLocalizedString" else {
                return
            }

            if let keyArgument = node.arguments.first(where: { $0.label == nil })?.expression,
               keyArgument.hasViolation {
                violations.append(keyArgument.positionAfterSkippingLeadingTrivia)
            }

            if let commentArgument = node.arguments.first(where: { $0.label?.text == "comment" })?.expression,
               commentArgument.hasViolation {
                violations.append(commentArgument.positionAfterSkippingLeadingTrivia)
            }
        }
    }
}

private extension ExprSyntax {
    var hasViolation: Bool {
        if let strExpr = `as`(StringLiteralExprSyntax.self) {
            return strExpr.segments.contains { segment in
                !segment.is(StringSegmentSyntax.self)
            }
        }

        if let sequenceExpr = `as`(SequenceExprSyntax.self) {
            return sequenceExpr.elements.contains { expr in
                if expr.is(BinaryOperatorExprSyntax.self) {
                    return false
                }

                return expr.hasViolation
            }
        }

        return true
    }
}
