package rule

import (
	"go/ast"

	"github.com/mgechev/revive/lint"
)

// ErrorReturnRule lints given else constructs.
type ErrorReturnRule struct{}

// Apply applies the rule to given file.
func (*ErrorReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
	var failures []lint.Failure

	fileAst := file.AST
	walker := lintErrorReturn{
		file:    file,
		fileAst: fileAst,
		onFailure: func(failure lint.Failure) {
			failures = append(failures, failure)
		},
	}

	ast.Walk(walker, fileAst)

	return failures
}

// Name returns the rule name.
func (*ErrorReturnRule) Name() string {
	return "error-return"
}

type lintErrorReturn struct {
	file      *lint.File
	fileAst   *ast.File
	onFailure func(lint.Failure)
}

func (w lintErrorReturn) Visit(n ast.Node) ast.Visitor {
	fn, ok := n.(*ast.FuncDecl)
	if !ok || fn.Type.Results == nil {
		return w
	}
	ret := fn.Type.Results.List
	if len(ret) <= 1 {
		return w
	}
	if isIdent(ret[len(ret)-1].Type, "error") {
		return nil
	}
	// An error return parameter should be the last parameter.
	// Flag any error parameters found before the last.
	for _, r := range ret[:len(ret)-1] {
		if isIdent(r.Type, "error") {
			w.onFailure(lint.Failure{
				Category:   "arg-order",
				Confidence: 0.9,
				Node:       fn,
				Failure:    "error should be the last type when returning multiple items",
			})
			break // only flag one
		}
	}
	return w
}
