// 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 astutil

import (
	"go/ast"
	"go/token"
	"iter"
	"strings"
)

// Deprecation returns the paragraph of the doc comment that starts with the
// conventional "Deprecation: " marker, as defined by
// https://go.dev/wiki/Deprecated, or "" if the documented symbol is not
// deprecated.
func Deprecation(doc *ast.CommentGroup) string {
	for p := range strings.SplitSeq(doc.Text(), "\n\n") {
		// There is still some ambiguity for deprecation message. This function
		// only returns the paragraph introduced by "Deprecated: ". More
		// information related to the deprecation may follow in additional
		// paragraphs, but the deprecation message should be able to stand on
		// its own. See golang/go#38743.
		if strings.HasPrefix(p, "Deprecated: ") {
			return p
		}
	}
	return ""
}

// -- plundered from the future (CL 605517, issue #68021) --

// TODO(adonovan): replace with ast.Directive after go1.25 (#68021).
// Beware of our local mods to handle analysistest
// "want" comments on the same line.

// A directive is a comment line with special meaning to the Go
// toolchain or another tool. It has the form:
//
//	//tool:name args
//
// The "tool:" portion is missing for the three directives named
// line, extern, and export.
//
// See https://go.dev/doc/comment#Syntax for details of Go comment
// syntax and https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives
// for details of directives used by the Go compiler.
type Directive struct {
	Pos  token.Pos // of preceding "//"
	Tool string
	Name string
	Args string // may contain internal spaces
}

// isDirective reports whether c is a comment directive.
// This code is also in go/printer.
func isDirective(c string) bool {
	// "//line " is a line directive.
	// "//extern " is for gccgo.
	// "//export " is for cgo.
	// (The // has been removed.)
	if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") {
		return true
	}

	// "//[a-z0-9]+:[a-z0-9]"
	// (The // has been removed.)
	colon := strings.Index(c, ":")
	if colon <= 0 || colon+1 >= len(c) {
		return false
	}
	for i := 0; i <= colon+1; i++ {
		if i == colon {
			continue
		}
		b := c[i]
		if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') {
			return false
		}
	}
	return true
}

// Directives returns the directives within the comment.
func Directives(g *ast.CommentGroup) (res []*Directive) {
	if g != nil {
		// Avoid (*ast.CommentGroup).Text() as it swallows directives.
		for _, c := range g.List {
			if len(c.Text) > 2 &&
				c.Text[1] == '/' &&
				c.Text[2] != ' ' &&
				isDirective(c.Text[2:]) {

				tool, nameargs, ok := strings.Cut(c.Text[2:], ":")
				if !ok {
					// Must be one of {line,extern,export}.
					tool, nameargs = "", tool
				}
				name, args, _ := strings.Cut(nameargs, " ") // tab??
				// Permit an additional line comment after the args, chiefly to support
				// [golang.org/x/tools/go/analysis/analysistest].
				args, _, _ = strings.Cut(args, "//")
				res = append(res, &Directive{
					Pos:  c.Slash,
					Tool: tool,
					Name: name,
					Args: strings.TrimSpace(args),
				})
			}
		}
	}
	return
}

// Comments returns an iterator over the comments overlapping the specified interval.
func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] {
	// TODO(adonovan): optimize use binary O(log n) instead of linear O(n) search.
	return func(yield func(*ast.Comment) bool) {
		for _, cg := range file.Comments {
			for _, co := range cg.List {
				if co.Pos() > end {
					return
				}
				if co.End() < start {
					continue
				}

				if !yield(co) {
					return
				}
			}
		}
	}
}
