import SwiftLintCore
import SwiftSyntax

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

    static let description = RuleDescription(
        identifier: "void_function_in_ternary",
        name: "Void Function in Ternary",
        description: "Using ternary to call Void functions should be avoided",
        kind: .idiomatic,
        minSwiftVersion: .fiveDotOne,
        nonTriggeringExamples: #examples([
            """
            if success {
                askQuestion()
            } else {
                exit()
            }
            """,
            """
            var price: Double {
                return hasDiscount ? calculatePriceWithDiscount() : calculateRegularPrice()
            }
            """,
            "foo(x == 2 ? a() : b())",
            """
            chevronView.image = collapsed ? .icon(.mediumChevronDown) : .icon(.mediumChevronUp)
            """,
            """
            array.map { elem in
                elem.isEmpty() ? .emptyValue() : .number(elem)
            }
            """,
            """
            func compute(data: [Int]) -> Int {
                data.isEmpty ? 0 : expensiveComputation(data)
            }
            """,
            """
            var value: Int {
                mode == .fast ? fastComputation() : expensiveComputation()
            }
            """,
            """
            var value: Int {
                get {
                    mode == .fast ? fastComputation() : expensiveComputation()
                }
            }
            """,
            """
            subscript(index: Int) -> Int {
                get {
                    index == 0 ? defaultValue() : compute(index)
                }
            """,
            """
            subscript(index: Int) -> Int {
                index == 0 ? defaultValue() : compute(index)
            """,
            """
            var a = b ? c() : d()
            a += b ? c() : d()
            a -= b ? c() : d()
            a *= b ? c() : d()
            a &<<= b ? c() : d()
            a &-= b ? c() : d()
            """,
        ]),
        triggeringExamples: #examples([
            "success ↓? askQuestion() : exit()",
            """
            perform { elem in
                elem.isEmpty() ↓? .emptyValue() : .number(elem)
                return 1
            }
            """,
            """
            DispatchQueue.main.async {
                self.sectionViewModels[section].collapsed.toggle()
                self.sectionViewModels[section].collapsed
                    ↓? self.tableView.deleteRows(at: [IndexPath(row: 0, section: section)], with: .automatic)
                    : self.tableView.insertRows(at: [IndexPath(row: 0, section: section)], with: .automatic)
                self.tableView.scrollToRow(at: IndexPath(row: NSNotFound, section: section), at: .top, animated: true)
            }
            """,
            """
            subscript(index: Int) -> Int {
                index == 0 ↓? something() : somethingElse(index)
                return index
            """,
            """
            var value: Int {
                mode == .fast ↓? something() : somethingElse()
                return 0
            }
            """,
            """
            var value: Int {
                get {
                    mode == .fast ↓? something() : somethingElse()
                    return 0
                }
            }
            """,
            """
            subscript(index: Int) -> Int {
                get {
                    index == 0 ↓? something() : somethingElse(index)
                    return index
                }
            """,
        ])
    )
}

private extension VoidFunctionInTernaryConditionRule {
    final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
        override func visitPost(_ node: TernaryExprSyntax) {
            guard node.thenExpression.is(FunctionCallExprSyntax.self),
                  node.elseExpression.is(FunctionCallExprSyntax.self),
                  let parent = node.parent?.as(ExprListSyntax.self),
                  !parent.containsAssignment,
                  let grandparent = parent.parent,
                  grandparent.is(SequenceExprSyntax.self),
                  let blockItem = grandparent.parent?.as(CodeBlockItemSyntax.self),
                  !blockItem.isImplicitReturn else {
                return
            }

            violations.append(node.questionMark.positionAfterSkippingLeadingTrivia)
        }

        override func visitPost(_ node: UnresolvedTernaryExprSyntax) {
            guard node.thenExpression.is(FunctionCallExprSyntax.self),
                  let parent = node.parent?.as(ExprListSyntax.self),
                  parent.last?.is(FunctionCallExprSyntax.self) == true,
                  !parent.containsAssignment,
                  let grandparent = parent.parent,
                  grandparent.is(SequenceExprSyntax.self),
                  let blockItem = grandparent.parent?.as(CodeBlockItemSyntax.self),
                  !blockItem.isImplicitReturn else {
                return
            }

            violations.append(node.questionMark.positionAfterSkippingLeadingTrivia)
        }
    }
}

private extension ExprListSyntax {
    var containsAssignment: Bool {
        children(viewMode: .sourceAccurate).contains {
            if let binOp = $0.as(BinaryOperatorExprSyntax.self) {
                // https://developer.apple.com/documentation/swift/operator-declarations
                return [
                    "*=",
                    "/=",
                    "%=",
                    "+=",
                    "-=",
                    "<<=",
                    ">>=",
                    "&=",
                    "|=",
                    "^=",
                    "&*=",
                    "&+=",
                    "&-=",
                    "&<<=",
                    "&>>=",
                    ".&=",
                    ".|=",
                    ".^=",
                ].contains(binOp.operator.text)
            }
            return $0.is(AssignmentExprSyntax.self)
        }
    }
}

private extension CodeBlockItemSyntax {
    var isImplicitReturn: Bool {
        isClosureImplicitReturn || isFunctionImplicitReturn ||
        isVariableImplicitReturn || isSubscriptImplicitReturn ||
        isAccessorImplicitReturn
    }

    var isClosureImplicitReturn: Bool {
        guard let parent = parent?.as(CodeBlockItemListSyntax.self),
              let grandparent = parent.parent else {
            return false
        }

        return parent.children(viewMode: .sourceAccurate).count == 1 && grandparent.is(ClosureExprSyntax.self)
    }

    var isFunctionImplicitReturn: Bool {
        guard let parent = parent?.as(CodeBlockItemListSyntax.self),
              let functionDecl = parent.parent?.parent?.as(FunctionDeclSyntax.self) else {
            return false
        }

        return parent.children(viewMode: .sourceAccurate).count == 1 && functionDecl.signature.allowsImplicitReturns
    }

    var isVariableImplicitReturn: Bool {
        guard let parent = parent?.as(CodeBlockItemListSyntax.self) else {
            return false
        }

        let isVariableDecl = parent.parent?.parent?.as(PatternBindingSyntax.self) != nil
        return parent.children(viewMode: .sourceAccurate).count == 1 && isVariableDecl
    }

    var isSubscriptImplicitReturn: Bool {
        guard let parent = parent?.as(CodeBlockItemListSyntax.self),
              let subscriptDecl = parent.parent?.parent?.as(SubscriptDeclSyntax.self) else {
            return false
        }

        return parent.children(viewMode: .sourceAccurate).count == 1 && subscriptDecl.allowsImplicitReturns
    }

    var isAccessorImplicitReturn: Bool {
        guard let parent = parent?.as(CodeBlockItemListSyntax.self),
              parent.parent?.parent?.as(AccessorDeclSyntax.self) != nil else {
            return false
        }

        return parent.children(viewMode: .sourceAccurate).count == 1
    }
}

private extension FunctionSignatureSyntax {
    var allowsImplicitReturns: Bool {
        returnClause?.allowsImplicitReturns ?? false
    }
}

private extension SubscriptDeclSyntax {
    var allowsImplicitReturns: Bool {
        returnClause.allowsImplicitReturns
    }
}

private extension ReturnClauseSyntax {
    var allowsImplicitReturns: Bool {
        if let simpleType = type.as(IdentifierTypeSyntax.self) {
            return simpleType.name.text != "Void" && simpleType.name.text != "Never"
        }
        if let tupleType = type.as(TupleTypeSyntax.self) {
            return !tupleType.elements.isEmpty
        }
        return true
    }
}
