// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package defectdetector

import (
	"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"
)

// DiagsAppendAnalyzer reports ignored return values from tfdiags.Diagnostics.Append.
var DiagsAppendAnalyzer = &analysis.Analyzer{
	Name: "tfdiagsappendcheck",
	Doc:  "reports ignored return values from tfdiags.Diagnostics.Append",
	Requires: []*analysis.Analyzer{
		inspect.Analyzer,
	},
	Run: run,
}

func run(pass *analysis.Pass) (interface{}, error) {
	ins, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
	if !ok {
		return nil, nil
	}

	nodeFilter := []ast.Node{(*ast.ExprStmt)(nil)}
	ins.Preorder(nodeFilter, func(n ast.Node) {
		exprStmt := n.(*ast.ExprStmt)
		call, ok := exprStmt.X.(*ast.CallExpr)
		if !ok {
			return
		}

		sel, ok := call.Fun.(*ast.SelectorExpr)
		if !ok || sel.Sel == nil || sel.Sel.Name != "Append" {
			return
		}

		selInfo, ok := pass.TypesInfo.Selections[sel]
		if !ok || selInfo.Kind() != types.MethodVal {
			return
		}

		if !isTfdiagsDiagnostics(selInfo.Recv()) {
			// Ignore calls to other Append methods that aren't tfdiags.Diagnostics.Append.
			return
		}

		pass.Reportf(sel.Sel.Pos(), "ignored return value from tfdiags.Diagnostics.Append")
	})

	return nil, nil
}

// Check that the receiver type is tfdiags.Diagnostics or *tfdiags.Diagnostics
func isTfdiagsDiagnostics(t types.Type) bool {
	if t == nil {
		return false
	}

	if ptr, ok := t.(*types.Pointer); ok {
		t = ptr.Elem()
	}

	named, ok := t.(*types.Named)
	if !ok {
		return false
	}

	obj := named.Obj()
	if obj == nil || obj.Name() != "Diagnostics" {
		return false
	}

	pkg := obj.Pkg()
	if pkg == nil {
		return false
	}

	return pkg.Path() == "github.com/hashicorp/terraform/internal/tfdiags"
}
