// Copyright 2020-2025 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package bufprotosource

import (
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/types/descriptorpb"
)

type message struct {
	namedDescriptor
	optionExtensionDescriptor

	fields                             []Field
	extensions                         []Field
	nestedMessages                     []Message
	nestedEnums                        []Enum
	oneofs                             []Oneof
	reservedMessageRanges              []MessageRange
	reservedNames                      []ReservedName
	extensionRanges                    []ExtensionRange
	parent                             Message
	isMapEntry                         bool
	messageSetWireFormat               bool
	noStandardDescriptorAccessor       bool
	deprecatedLegacyJSONFieldConflicts bool
	deprecated                         bool
	messageSetWireFormatPath           []int32
	noStandardDescriptorAccessorPath   []int32
}

func newMessage(
	namedDescriptor namedDescriptor,
	optionExtensionDescriptor optionExtensionDescriptor,
	parent Message,
	isMapEntry bool,
	messageSetWireFormat bool,
	noStandardDescriptorAccessor bool,
	deprecatedLegacyJSONFieldConflicts bool,
	deprecated bool,
	messageSetWireFormatPath []int32,
	noStandardDescriptorAccessorPath []int32,
) *message {
	return &message{
		namedDescriptor:                    namedDescriptor,
		optionExtensionDescriptor:          optionExtensionDescriptor,
		parent:                             parent,
		isMapEntry:                         isMapEntry,
		messageSetWireFormat:               messageSetWireFormat,
		noStandardDescriptorAccessor:       noStandardDescriptorAccessor,
		deprecatedLegacyJSONFieldConflicts: deprecatedLegacyJSONFieldConflicts,
		deprecated:                         deprecated,
		messageSetWireFormatPath:           messageSetWireFormatPath,
		noStandardDescriptorAccessorPath:   noStandardDescriptorAccessorPath,
	}
}

func (m *message) Fields() []Field {
	return m.fields
}

func (m *message) Extensions() []Field {
	return m.extensions
}

func (m *message) Messages() []Message {
	return m.nestedMessages
}

func (m *message) Enums() []Enum {
	return m.nestedEnums
}

func (m *message) Oneofs() []Oneof {
	return m.oneofs
}

func (m *message) ReservedMessageRanges() []MessageRange {
	return m.reservedMessageRanges
}

func (m *message) ReservedTagRanges() []TagRange {
	tagRanges := make([]TagRange, len(m.reservedMessageRanges))
	for i, reservedMessageRange := range m.reservedMessageRanges {
		tagRanges[i] = reservedMessageRange
	}
	return tagRanges
}

func (m *message) ReservedNames() []ReservedName {
	return m.reservedNames
}

func (m *message) ExtensionRanges() []ExtensionRange {
	return m.extensionRanges
}

func (m *message) ExtensionMessageRanges() []MessageRange {
	extMsgRanges := make([]MessageRange, len(m.extensionRanges))
	for i, extensionRange := range m.extensionRanges {
		extMsgRanges[i] = extensionRange
	}
	return extMsgRanges
}

func (m *message) Parent() Message {
	return m.parent
}

func (m *message) IsMapEntry() bool {
	return m.isMapEntry
}

func (m *message) MessageSetWireFormat() bool {
	return m.messageSetWireFormat
}

func (m *message) NoStandardDescriptorAccessor() bool {
	return m.noStandardDescriptorAccessor
}

func (m *message) DeprecatedLegacyJSONFieldConflicts() bool {
	return m.deprecatedLegacyJSONFieldConflicts
}

func (m *message) Deprecated() bool {
	return m.deprecated
}

func (m *message) MessageSetWireFormatLocation() Location {
	return m.getLocation(m.messageSetWireFormatPath)
}

func (m *message) NoStandardDescriptorAccessorLocation() Location {
	return m.getLocation(m.noStandardDescriptorAccessorPath)
}

func (m *message) Location() Location {
	loc := m.namedDescriptor.Location()
	if loc == nil {
		return m.maybeMapEntryLocation()
	}
	return loc
}

func (m *message) NameLocation() Location {
	loc := m.namedDescriptor.NameLocation()
	if loc == nil {
		return m.maybeMapEntryLocation()
	}
	return loc
}

func (m *message) maybeMapEntryLocation() Location {
	parent, _ := m.parent.(*message)
	if !m.isMapEntry || parent == nil || m.namedDescriptor.locationStore.isEmpty() {
		// not a map entry
		return nil
	}
	// Synthetic map messages come from the type of the corresponding
	// map field. So report that location.
	if field := parent.findMapField(m.FullName()); field != nil {
		return field.TypeNameLocation()
	}
	return nil
}

func (m *message) findMapField(entryName string) Field {
	for _, field := range m.fields {
		if field.Type() == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE &&
			field.Label() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED &&
			field.TypeName() == entryName {
			return field
		}
	}
	return nil
}

func (m *message) addField(field Field) {
	m.fields = append(m.fields, field)
}

func (m *message) addExtension(extension Field) {
	m.extensions = append(m.extensions, extension)
}

func (m *message) addNestedMessage(nestedMessage Message) {
	m.nestedMessages = append(m.nestedMessages, nestedMessage)
}

func (m *message) addNestedEnum(nestedEnum Enum) {
	m.nestedEnums = append(m.nestedEnums, nestedEnum)
}

func (m *message) addOneof(oneof Oneof) {
	m.oneofs = append(m.oneofs, oneof)
}

func (m *message) addReservedMessageRange(reservedMessageRange MessageRange) {
	m.reservedMessageRanges = append(m.reservedMessageRanges, reservedMessageRange)
}

func (m *message) addReservedName(reservedName ReservedName) {
	m.reservedNames = append(m.reservedNames, reservedName)
}

func (m *message) addExtensionRange(extensionRange ExtensionRange) {
	m.extensionRanges = append(m.extensionRanges, extensionRange)
}

func (m *message) AsDescriptor() (protoreflect.MessageDescriptor, error) {
	return asDescriptor[protoreflect.MessageDescriptor](&m.descriptor, m.FullName(), "a message")
}
