func (p *Patcher) Tag(id protogen.GoIdent, tags string) {
	p.tags[id] = tags
	log.Printf("Tags:\t%s.%s `%s`", id.GoImportPath, id.GoName, tags)
}
Tag adds the specified struct tags to the field specified by selector,
in the form of "Message.Field". The tags argument should omit outer backticks (`).
The value of id.GoName should be the original generated identifier name, not a renamed identifier.
The struct tags will be applied when Patch is called.
package patch
import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"go/types"
	"log"
	"path/filepath"
	"regexp"
	"strings"
	"github.com/alta/protopatch/lint"
	"github.com/alta/protopatch/patch/ident"
	"github.com/fatih/structtag"
	"golang.org/x/tools/go/ast/astutil"
	"google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo"
	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/types/pluginpb"
)
type Patcher struct {
	gen            *protogen.Plugin
	fset           *token.FileSet
	filesByName    map[string]*ast.File
	info           *types.Info
	packages       []*Package
	packagesByPath map[string]*Package
	packagesByName map[string]*Package
	renames        map[protogen.GoIdent]string
	typeRenames    map[protogen.GoIdent]string
	valueRenames   map[protogen.GoIdent]string
	fieldRenames   map[protogen.GoIdent]string
	methodRenames  map[protogen.GoIdent]string
	objectRenames  map[types.Object]string
	tags           map[protogen.GoIdent]string
	fieldTags      map[types.Object]string
	embeds         map[protogen.GoIdent]string
	fieldEmbeds    map[types.Object]string
}
func NewPatcher(gen *protogen.Plugin) (*Patcher, error) {
	p := &Patcher{
		gen:            gen,
		packagesByPath: make(map[string]*Package),
		packagesByName: make(map[string]*Package),
		renames:        make(map[protogen.GoIdent]string),
		typeRenames:    make(map[protogen.GoIdent]string),
		valueRenames:   make(map[protogen.GoIdent]string),
		fieldRenames:   make(map[protogen.GoIdent]string),
		methodRenames:  make(map[protogen.GoIdent]string),
		objectRenames:  make(map[types.Object]string),
		tags:           make(map[protogen.GoIdent]string),
		fieldTags:      make(map[types.Object]string),
		embeds:         make(map[protogen.GoIdent]string),
		fieldEmbeds:    make(map[types.Object]string),
	}
	return p, p.scan()
}
func (p *Patcher) scan() error {
	for _, f := range p.gen.Files {
		p.scanFile(f)
	}
	return nil
}
func (p *Patcher) scanFile(f *protogen.File) {
	log.Printf("\nScan proto:\t%s", f.Desc.Path())
	if f.Generate {
		log.Printf("Generating:\t%s", f.Desc.Path())
		internal_gengo.GenerateFile(p.gen, f)
	}
	_ = p.getPackage(string(f.GoImportPath), string(f.GoPackageName), true)
	for _, e := range f.Enums {
		p.scanEnum(e, nil)
	}
	for _, m := range f.Messages {
		p.scanMessage(m, nil)
	}
	for _, e := range f.Extensions {
		p.scanExtension(e)
	}
}
func (p *Patcher) scanEnum(e *protogen.Enum, parent *protogen.Message) {
	opts := enumOptions(e)
	lints := fileLintOptions(e.Desc)
	newName := opts.GetName()
	if newName == "" && parent != nil && p.isRenamed(parent.GoIdent) {
		newName = replacePrefix(e.GoIdent.GoName, parent.GoIdent.GoName, p.nameFor(parent.GoIdent))
		log.Printf("•••• %s → newName: %s", e.GoIdent.GoName, newName)
	}
	if lints.GetEnums() || lints.GetAll() {
		if newName == "" {
			newName = e.GoIdent.GoName
		}
		newName = lint.Name(newName, lints.InitialismsMap())
	}
	if newName != "" {
		p.RenameType(e.GoIdent, newName)                                        
		p.RenameValue(ident.WithSuffix(e.GoIdent, "_name"), newName+"_name")    
		p.RenameValue(ident.WithSuffix(e.GoIdent, "_value"), newName+"_value")  
	}
	newStringer := opts.GetStringer()
	if newStringer == "" {
		newStringer = opts.GetStringerName()
		if newStringer != "" {
			log.Printf("Warning: stringer_name is deprecated and will be removed in a future version. Please use stringer.")
		}
	}
	if newStringer != "" {
		p.RenameMethod(ident.WithChild(e.GoIdent, "String"), newStringer)
	}
	for _, v := range e.Values {
		p.scanEnumValue(v, parent)
	}
}
func (p *Patcher) scanEnumValue(v *protogen.EnumValue, parent *protogen.Message) {
	parentIdent := v.Parent.GoIdent
	if parent != nil {
		parentIdent = parent.GoIdent
	}
	opts := valueOptions(v)
	lints := fileLintOptions(v.Desc)
	newName := opts.GetName()
	if newName == "" {
		newName = replacePrefix(v.GoIdent.GoName, parentIdent.GoName, p.nameFor(parentIdent))
	}
	if lints.GetValues() || lints.GetAll() {
		vname := string(v.Desc.Name())
		if vname == strings.ToUpper(vname) && strings.HasSuffix(newName, vname) {
			newName = strings.TrimSuffix(newName, vname) + "_" + strings.ToLower(vname)
		}
		newName = lint.Name(newName, lints.InitialismsMap())
		pname := p.nameFor(parentIdent)
		pfx := pname + pname
		if len(newName) > len(pfx) && strings.HasPrefix(newName, pfx) {
			newName = strings.TrimPrefix(newName, pname)
		}
	}
	if newName != "" {
		p.RenameValue(v.GoIdent, newName)
	}
}
func (p *Patcher) scanMessage(m *protogen.Message, parent *protogen.Message) {
	opts := messageOptions(m)
	lints := fileLintOptions(m.Desc)
	newName := opts.GetName()
	if newName == "" && parent != nil && p.isRenamed(parent.GoIdent) {
		newName = replacePrefix(m.GoIdent.GoName, parent.GoIdent.GoName, p.nameFor(parent.GoIdent))
	}
	if lints.GetMessages() || lints.GetAll() {
		log.Printf("Linting: %q.%s", m.GoIdent.GoImportPath, m.GoIdent.GoName)
		if newName == "" {
			newName = m.GoIdent.GoName
		}
		newName = lint.Name(newName, lints.InitialismsMap())
	}
	if newName != "" {
		p.RenameType(m.GoIdent, newName)  
	}
	for _, o := range m.Oneofs {
		p.scanOneof(o)
	}
	for _, f := range m.Fields {
		p.scanField(f)
	}
	for _, e := range m.Enums {
		p.scanEnum(e, m)
	}
	for _, mm := range m.Messages {
		p.scanMessage(mm, m)
	}
}
func replacePrefix(s, prefix, with string) string {
	if !strings.HasPrefix(s, prefix) {
		return s
	}
	return with + strings.TrimPrefix(s, prefix)
}
func (p *Patcher) scanOneof(o *protogen.Oneof) {
	m := o.Parent
	opts := oneofOptions(o)
	lints := fileLintOptions(o.Desc)
	newName := opts.GetName()
	if newName == "" && p.isRenamed(m.GoIdent) {
		newName = o.GoName
	}
	if lints.GetFields() || lints.GetAll() {
		if newName == "" {
			newName = o.GoIdent.GoName
		}
		newName = lint.Name(newName, lints.InitialismsMap())
	}
	if newName != "" {
		p.RenameField(ident.WithChild(m.GoIdent, o.GoName), newName, false)        
		p.RenameMethod(ident.WithChild(m.GoIdent, "Get"+o.GoName), "Get"+newName)  
		ifName := ident.WithPrefix(o.GoIdent, "is")
		newIfName := "is" + p.nameFor(m.GoIdent) + "_" + newName
		p.RenameType(ifName, newIfName)                                    
		p.RenameMethod(ident.WithChild(ifName, ifName.GoName), newIfName)  
	}
	tags := opts.GetTags()
	if tags != "" {
		p.Tag(ident.WithChild(m.GoIdent, o.GoName), tags)
	}
}
func (p *Patcher) scanField(f *protogen.Field) {
	m := f.Parent
	o := f.Oneof
	if f.Desc.HasOptionalKeyword() {
		o = nil
	}
	opts := fieldOptions(f)
	lints := fileLintOptions(f.Desc)
	newName := opts.GetName()
	if newName == "" && o != nil && (p.isRenamed(m.GoIdent) || p.isRenamed(o.GoIdent)) {
		newName = f.GoName
	}
	embed := false
	if opts.GetEmbed() {
		switch {
		case f.Message == nil:
			log.Printf("Warning: embed declared for non-message field: %s", f.Desc.Name())
		case f.Oneof != nil:
			log.Printf("Warning: embed declared for oneof field: %s", f.Desc.Name())
		default:
			embed = true
			if mOpts := messageOptions(f.Message); mOpts.GetName() != "" {
				newName = mOpts.GetName()
			} else {
				newName = f.Message.GoIdent.GoName
			}
		}
	}
	if lints.GetFields() || lints.GetAll() {
		if newName == "" {
			newName = f.GoName
		}
		newName = lint.Name(newName, lints.InitialismsMap())
	}
	if newName != "" {
		if o != nil {
			p.RenameType(f.GoIdent, p.nameFor(m.GoIdent)+"_"+newName)            
			p.RenameField(ident.WithChild(f.GoIdent, f.GoName), newName, false)  
			ifName := ident.WithPrefix(o.GoIdent, "is")
			p.RenameMethod(ident.WithChild(f.GoIdent, ifName.GoName), p.nameFor(ifName))  
		} else {
			p.RenameField(ident.WithChild(m.GoIdent, f.GoName), newName, embed)  
		}
		p.RenameMethod(ident.WithChild(m.GoIdent, "Get"+f.GoName), "Get"+newName)  
	}
	tags := opts.GetTags()
	if tags != "" {
		if o != nil {
			p.Tag(ident.WithChild(f.GoIdent, f.GoName), tags)  
		} else {
			p.Tag(ident.WithChild(m.GoIdent, f.GoName), tags)  
		}
	}
}
func (p *Patcher) scanExtension(f *protogen.Field) {
	opts := fieldOptions(f)
	lints := fileLintOptions(f.Desc)
	newName := opts.GetName()
	if lints.GetExtensions() || lints.GetAll() {
		if newName == "" {
			newName = "Ext" + f.GoName
		}
		newName = lint.Name(newName, lints.InitialismsMap())
	}
	if newName != "" {
		id := f.GoIdent
		id.GoName = "E_" + f.GoName
		p.RenameValue(id, newName)
	}
}
func (p *Patcher) RenameType(id protogen.GoIdent, newName string) {
	p.renames[id] = newName
	p.typeRenames[id] = newName
	log.Printf("Rename type:\t%s.%s → %s", id.GoImportPath, id.GoName, newName)
}
func (p *Patcher) RenameValue(id protogen.GoIdent, newName string) {
	p.renames[id] = newName
	p.valueRenames[id] = newName
	log.Printf("Rename value:\t%s.%s → %s", id.GoImportPath, id.GoName, newName)
}
func (p *Patcher) RenameField(id protogen.GoIdent, newName string, embed bool) {
	p.renames[id] = newName
	p.fieldRenames[id] = newName
	if embed {
		p.embeds[id] = newName
	}
	log.Printf("Rename field:\t%s.%s → %s", id.GoImportPath, id.GoName, newName)
}
func (p *Patcher) RenameMethod(id protogen.GoIdent, newName string) {
	p.renames[id] = newName
	p.methodRenames[id] = newName
	log.Printf("Rename method:\t%s.%s → %s", id.GoImportPath, id.GoName, newName)
}
func (p *Patcher) isRenamed(id protogen.GoIdent) bool {
	_, ok := p.renames[id]
	return ok
}
func (p *Patcher) nameFor(id protogen.GoIdent) string {
	if name, ok := p.renames[id]; ok {
		return name
	}
	return ident.LeafName(id)
}