// Copyright 2024 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 protogen

import (
	"fmt"

	"google.golang.org/protobuf/internal/filedesc"
	"google.golang.org/protobuf/internal/genid"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/types/gofeaturespb"
)

func fileAPILevel(fd protoreflect.FileDescriptor, def gofeaturespb.GoFeatures_APILevel) gofeaturespb.GoFeatures_APILevel {
	level := gofeaturespb.GoFeatures_API_OPEN
	level = def
	if fd, ok := fd.(*filedesc.File); ok {
		al := fd.L1.EditionFeatures.APILevel
		if al != genid.GoFeatures_API_LEVEL_UNSPECIFIED_enum_value {
			level = gofeaturespb.GoFeatures_APILevel(al)
		}
	}

	return level
}

func messageAPILevel(md protoreflect.MessageDescriptor, def gofeaturespb.GoFeatures_APILevel) gofeaturespb.GoFeatures_APILevel {
	level := def
	if md, ok := md.(*filedesc.Message); ok {
		al := md.L1.EditionFeatures.APILevel
		if al != genid.GoFeatures_API_LEVEL_UNSPECIFIED_enum_value {
			level = gofeaturespb.GoFeatures_APILevel(al)
		}
	}

	return level
}

func (p *Plugin) defaultAPILevel() gofeaturespb.GoFeatures_APILevel {
	if p.opts.DefaultAPILevel != gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED {
		return p.opts.DefaultAPILevel
	}

	return gofeaturespb.GoFeatures_API_OPEN
}

// MethodName returns the (possibly mangled) name of the generated accessor
// method, along with the backwards-compatible name (if needed).
//
// method must be one of Get, Set, Has, Clear. MethodName panics otherwise.
func (field *Field) MethodName(method string) (name, compat string) {
	switch method {
	case "Get":
		return field.getterName()

	case "Set":
		return field.setterName()

	case "Has", "Clear":
		return field.methodName(method), ""

	default:
		panic(fmt.Sprintf("Field.MethodName called for unknown method %q", method))
	}
}

// methodName returns the (possibly mangled) name of the generated method with
// the given prefix.
//
// For the Open API, the return value is "".
func (field *Field) methodName(prefix string) string {
	switch field.Parent.APILevel {
	case gofeaturespb.GoFeatures_API_OPEN:
		// In the Open API, only generate getters (no Has or Clear methods).
		return ""

	case gofeaturespb.GoFeatures_API_HYBRID:
		var infix string
		if field.hasConflictHybrid {
			infix = "_"
		}
		return prefix + infix + field.camelCase

	case gofeaturespb.GoFeatures_API_OPAQUE:
		return prefix + field.camelCase

	default:
		panic("BUG: message is neither open, nor hybrid, nor opaque?!")
	}
}

// getterName returns the (possibly mangled) name of the generated Get method,
// along with the backwards-compatible name (if needed).
func (field *Field) getterName() (getter, compat string) {
	switch field.Parent.APILevel {
	case gofeaturespb.GoFeatures_API_OPEN:
		// In the Open API, only generate a getter with the old style mangled name.
		return "Get" + field.GoName, ""

	case gofeaturespb.GoFeatures_API_HYBRID:
		// In the Hybrid API, return the mangled getter name and the old style
		// name if needed, for backwards compatibility with the Open API.
		var infix string
		if field.hasConflictHybrid {
			infix = "_"
		}
		orig := "Get" + infix + field.camelCase
		mangled := "Get" + field.GoName
		if mangled == orig {
			mangled = ""
		}
		return orig, mangled

	case gofeaturespb.GoFeatures_API_OPAQUE:
		return field.methodName("Get"), ""

	default:
		panic("BUG: message is neither open, nor hybrid, nor opaque?!")
	}
}

// setterName returns the (possibly mangled) name of the generated Set method,
// along with the backwards-compatible name (if needed).
func (field *Field) setterName() (setter, compat string) {
	// TODO(b/359846588): remove weak field support?
	if field.Desc.IsWeak() && field.Parent.APILevel != gofeaturespb.GoFeatures_API_OPAQUE {
		switch field.Parent.APILevel {
		case gofeaturespb.GoFeatures_API_OPEN:
			return "Set" + field.GoName, ""

		default:
			var infix string
			if field.hasConflictHybrid {
				infix = "_"
			}
			orig := "Set" + infix + field.camelCase
			mangled := "Set" + field.GoName
			if mangled == orig {
				mangled = ""
			}
			return orig, mangled
		}
	}
	return field.methodName("Set"), ""
}

// BuilderFieldName returns the name of this field in the corresponding _builder
// struct.
func (field *Field) BuilderFieldName() string {
	return field.camelCase
}

// MethodName returns the (possibly mangled) name of the generated accessor
// method.
//
// method must be one of Has, Clear, Which. MethodName panics otherwise.
func (oneof *Oneof) MethodName(method string) string {
	switch method {
	case "Has", "Clear", "Which":
		return oneof.methodName(method)

	default:
		panic(fmt.Sprintf("Oneof.MethodName called for unknown method %q", method))
	}
}

// methodName returns the (possibly mangled) name of the generated method with
// the given prefix.
//
// For the Open API, the return value is "".
func (oneof *Oneof) methodName(prefix string) string {
	switch oneof.Parent.APILevel {
	case gofeaturespb.GoFeatures_API_OPEN:
		// In the Open API, only generate getters.
		return ""

	case gofeaturespb.GoFeatures_API_HYBRID:
		var infix string
		if oneof.hasConflictHybrid {
			infix = "_"
		}
		return prefix + infix + oneof.camelCase

	case gofeaturespb.GoFeatures_API_OPAQUE:
		return prefix + oneof.camelCase

	default:
		panic("BUG: message is neither open, nor hybrid, nor opaque?!")
	}
}
