// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package recursiveiter

import (
	_ "embed"
	"fmt"
	"go/ast"
	"go/types"

	"golang.org/x/tools/go/analysis"
	"golang.org/x/tools/go/analysis/passes/inspect"
	"golang.org/x/tools/go/ast/inspector"
	"golang.org/x/tools/go/types/typeutil"
	"golang.org/x/tools/internal/analysis/analyzerutil"
	typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
	"golang.org/x/tools/internal/typesinternal/typeindex"
)

//go:embed doc.go
var doc string

var Analyzer = &analysis.Analyzer{
	Name:     "recursiveiter",
	Doc:      analyzerutil.MustExtractDoc(doc, "recursiveiter"),
	Requires: []*analysis.Analyzer{inspect.Analyzer, typeindexanalyzer.Analyzer},
	Run:      run,
	URL:      "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/recursiveiter",
}

func run(pass *analysis.Pass) (any, error) {
	var (
		inspector = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
		index     = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
		info      = pass.TypesInfo
		iterSeq   = index.Object("iter", "Seq")
		iterSeq2  = index.Object("iter", "Seq2")
	)
	if iterSeq == nil || iterSeq2 == nil {
		return nil, nil // fast path: no iterators
	}

	// Search for a function or method f that returns an iter.Seq
	// or Seq2 and calls itself recursively within a range stmt:
	//
	// func f(...) iter.Seq[E] {
	//	return func(yield func(E) bool) {
	//		...
	//		for range f(...) { ... }
	// 	}
	// }
	for curDecl := range inspector.Root().Preorder((*ast.FuncDecl)(nil)) {
		decl := curDecl.Node().(*ast.FuncDecl)
		fn := info.Defs[decl.Name].(*types.Func)
		results := fn.Signature().Results()
		if results.Len() != 1 {
			continue // result not a singleton
		}
		retType, ok := results.At(0).Type().(*types.Named)
		if !ok {
			continue // result not a named type
		}
		switch retType.Origin().Obj() {
		case iterSeq, iterSeq2:
		default:
			continue // result not iter.Seq{,2}
		}
		// Have: a FuncDecl that returns an iterator.
		for curRet := range curDecl.Preorder((*ast.ReturnStmt)(nil)) {
			ret := curRet.Node().(*ast.ReturnStmt)
			if len(ret.Results) != 1 || !is[*ast.FuncLit](ret.Results[0]) {
				continue // not "return func(){...}"
			}
			for curRange := range curRet.Preorder((*ast.RangeStmt)(nil)) {
				rng := curRange.Node().(*ast.RangeStmt)
				call, ok := rng.X.(*ast.CallExpr)
				if !ok {
					continue
				}
				if typeutil.StaticCallee(info, call) == fn {
					pass.Report(analysis.Diagnostic{
						Pos:     rng.Range,
						End:     rng.X.End(),
						Message: fmt.Sprintf("inefficient recursion in iterator %s", fn.Name()),
					})
				}
			}
		}
	}

	return nil, nil
}

func is[T any](x any) bool {
	_, ok := x.(T)
	return ok
}
