import SwiftSyntax

@SwiftSyntaxRule
struct FunctionParameterCountRule: Rule {
    var configuration = FunctionParameterCountConfiguration()

    static let description = RuleDescription(
        identifier: "function_parameter_count",
        name: "Function Parameter Count",
        description: "Number of function parameters should be low.",
        kind: .metrics,
        nonTriggeringExamples: [
            Example("init(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}"),
            Example("init (a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}"),
            Example("`init`(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}"),
            Example("init?(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}"),
            Example("init?<T>(a: T, b: Int, c: Int, d: Int, e: Int, f: Int) {}"),
            Example("init?<T: String>(a: T, b: Int, c: Int, d: Int, e: Int, f: Int) {}"),
            Example("func f2(p1: Int, p2: Int) { }"),
            Example("func f(a: Int, b: Int, c: Int, d: Int, x: Int = 42) {}"),
            Example("""
            func f(a: [Int], b: Int, c: Int, d: Int, f: Int) -> [Int] {
                let s = a.flatMap { $0 as? [String: Int] } ?? []}}
            """),
            Example("override func f(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}"),
        ],
        triggeringExamples: [
            Example("↓func f(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}"),
            Example("↓func initialValue(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}"),
            Example("private ↓func f(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int = 2, g: Int) {}"),
            Example("""
            struct Foo {
                init(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}
                ↓func bar(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) {}}
            """),
        ]
    )
}

private extension FunctionParameterCountRule {
    final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
        override func visitPost(_ node: FunctionDeclSyntax) {
            guard !node.modifiers.contains(keyword: .override) else {
                return
            }

            let parameterList = node.signature.parameterClause.parameters
            guard let minThreshold = configuration.severityConfiguration.params.map(\.value).min(by: <) else {
                return
            }

            let allParameterCount = parameterList.count
            if allParameterCount < minThreshold {
                return
            }

            var parameterCount = allParameterCount
            if configuration.ignoresDefaultParameters {
                parameterCount -= parameterList.filter { $0.defaultValue != nil }.count
            }

            for parameter in configuration.severityConfiguration.params where parameterCount > parameter.value {
                let reason = "Function should have \(configuration.severityConfiguration.warning) parameters " +
                             "or less: it currently has \(parameterCount)"

                violations.append(
                    ReasonedRuleViolation(
                        position: node.funcKeyword.positionAfterSkippingLeadingTrivia,
                        reason: reason,
                        severity: parameter.severity
                    )
                )
                return
            }
        }
    }
}
