package protoresolve

import (
	"google.golang.org/protobuf/reflect/protodesc"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/reflect/protoregistry"
)

var (
	// GlobalDescriptors provides a view of protoregistry.GlobalFiles and protoregistry.GlobalTypes
	// as a Resolver.
	GlobalDescriptors = ResolverFromPools(protoregistry.GlobalFiles, protoregistry.GlobalTypes)
)

// FileResolver can resolve file descriptors by path.
type FileResolver interface {
	FindFileByPath(string) (protoreflect.FileDescriptor, error)
}

// FilePool is a FileResolver that also allows iteration over the known file descriptors.
type FilePool interface {
	FileResolver
	NumFiles() int
	RangeFiles(fn func(protoreflect.FileDescriptor) bool)
	NumFilesByPackage(name protoreflect.FullName) int
	RangeFilesByPackage(name protoreflect.FullName, fn func(protoreflect.FileDescriptor) bool)
}

// DescriptorResolver can resolve descriptors by full name.
type DescriptorResolver interface {
	FindDescriptorByName(protoreflect.FullName) (protoreflect.Descriptor, error)
}

// DescriptorPool is a FilePool that also functions as a DescriptorResolver.
type DescriptorPool interface {
	FilePool
	DescriptorResolver
}

var _ DescriptorPool = (*Registry)(nil)
var _ DescriptorPool = (*protoregistry.Files)(nil)

// DescriptorRegistry is a file and descriptor resolver that allows the caller to add files
// (and their contained descriptors) to the set of files and descriptors it can resolve.
type DescriptorRegistry interface {
	DescriptorPool
	RegisterFile(protoreflect.FileDescriptor) error
}

var _ DescriptorRegistry = (*Registry)(nil)
var _ DescriptorRegistry = (*protoregistry.Files)(nil)

// ExtensionResolver can resolve extensions based on the containing message name and field number.
type ExtensionResolver interface {
	FindExtensionByName(protoreflect.FullName) (protoreflect.ExtensionDescriptor, error)
	FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionDescriptor, error)
}

// ExtensionPool is an ExtensionResolver that also allows iteration over all extensions for a message.
type ExtensionPool interface {
	ExtensionResolver
	RangeExtensionsByMessage(message protoreflect.FullName, fn func(protoreflect.ExtensionDescriptor) bool)
}

// MessageResolver can resolve messages based on their name or a type URL. URLs must include the
// fully-qualified type name as the last URI path component.
type MessageResolver interface {
	FindMessageByName(protoreflect.FullName) (protoreflect.MessageDescriptor, error)
	FindMessageByURL(url string) (protoreflect.MessageDescriptor, error)
}

// ExtensionTypeResolver can resolve extension types. An extension type includes more than just
// a descriptor but also refers to runtime types (which could be static types generated by the
// protoc-gen-go plugin).
//
// This interface is the same as protoregistry.ExtensionTypeResolver.
type ExtensionTypeResolver interface {
	FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error)
	FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error)
}

var _ protoregistry.ExtensionTypeResolver = ExtensionTypeResolver(nil)
var _ ExtensionTypeResolver = protoregistry.ExtensionTypeResolver(nil)

// MessageTypeResolver can resolve message types. A message type includes more than just a
// descriptor but also refers to runtime types (which could be static types generated by the
// protoc-gen-go plugin).
//
// This interface is the same as protoregistry.MessageTypeResolver.
type MessageTypeResolver interface {
	FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error)
	FindMessageByURL(url string) (protoreflect.MessageType, error)
}

var _ protoregistry.MessageTypeResolver = MessageTypeResolver(nil)
var _ MessageTypeResolver = protoregistry.MessageTypeResolver(nil)

// EnumTypeResolver can resolve enum types. An enum type includes more than just a descriptor
// but also refers to runtime types (which could be static types generated by the
// protoc-gen-go plugin).
type EnumTypeResolver interface {
	FindEnumByName(enum protoreflect.FullName) (protoreflect.EnumType, error)
}

// SerializationResolver is a named interface that can be used as a resolver for
// various marshalling and unmarshalling operations. For example it can be used
// to recognize extensions when unmarshalling the binary format. And it can be used
// for recognizing extensions and the contents of Any messages when marshalling and
// unmarshalling the JSON and text formats.
//
// This type can be assigned to the following fields:
//   - proto.UnmarshalOptions.resolver
//   - protojson.MarshalOptions.resolver
//   - protojson.UnmarshalOptions.resolver
//   - prototext.MarshalOptions.resolver
//   - prototext.UnmarshalOptions.resolver
type SerializationResolver interface {
	ExtensionTypeResolver
	MessageTypeResolver
}

// TypeResolver can resolve all types: extensions, messages, and enums.
type TypeResolver interface {
	ExtensionTypeResolver
	MessageTypeResolver
	EnumTypeResolver
}

// DependencyResolver can resolve dependencies, which is needed when
// constructing a [protoreflect.FileDescriptor] from a FileDescriptorProto.
//
// This interface is the same as [protodesc.Resolver].
type DependencyResolver interface {
	FileResolver
	DescriptorResolver
}

var _ protodesc.Resolver = DependencyResolver(nil)
var _ DependencyResolver = protodesc.Resolver(nil)

// TODO: Other sometimes-used interfaces that might warrant a named type:
// 			interface { DependencyResolver; ExtensionResolver }
//			interface { DescriptorResolver; ExtensionResolver }
//			interface { RangeExtensionsByMessage(protoreflect.FullName, func(protoreflect.ExtensionType) bool) }
//			interface { Resolver; AsTypePool() TypePool }
//		Admittedly, we've got a LOT of named types already, so maybe best to leave these out...

// Resolver is a comprehensive resolver interface with methods for resolving all kinds
// of descriptors.
//
// The AsTypeResolver method returns a view of the resolver as a TypeResolver. In most
// cases, the returned types will be dynamic types constructed using the resolver's
// descriptors and the [google.golang.org/protobuf/types/dynamicpb] package.
type Resolver interface {
	DescriptorPool
	ExtensionPool
	MessageResolver
	AsTypeResolver() TypeResolver
}

// ResolverFromPool implements the full Resolver interface on top of the
// given DescriptorPool. This can be used to upgrade a *[protoregistry.Files]
// to the Resolver interface. The AsTypeResolver method uses TypesFromResolver,
// so it returns dynamic types.
//
// See also ResolverFromPools.
func ResolverFromPool(pool DescriptorPool) Resolver {
	return &resolverFromPool{DescriptorPool: pool}
}

// ResolverFromPools (plural) is just like ResolverFromPool (singular) except it
// also accepts a TypePool that is used to implement the AsTypeResolver method.
// So instead of always returning dynamic types based on the given DescriptorPool,
// it uses the given TypePool.
func ResolverFromPools(descPool DescriptorPool, typePool TypePool) interface {
	Resolver
	AsTypePool() TypePool
} {
	return &resolverWithTypes{Resolver: ResolverFromPool(descPool), types: typePool}
}

type resolverFromPool struct {
	DescriptorPool
}

func (r *resolverFromPool) FindMessageByName(name protoreflect.FullName) (protoreflect.MessageDescriptor, error) {
	d, err := r.DescriptorPool.FindDescriptorByName(name)
	if err != nil {
		return nil, err
	}
	msg, ok := d.(protoreflect.MessageDescriptor)
	if !ok {
		return nil, NewUnexpectedTypeError(DescriptorKindMessage, d, "")
	}
	return msg, nil
}

func (r *resolverFromPool) FindExtensionByName(name protoreflect.FullName) (protoreflect.ExtensionDescriptor, error) {
	d, err := r.DescriptorPool.FindDescriptorByName(name)
	if err != nil {
		return nil, err
	}
	field, ok := d.(protoreflect.FieldDescriptor)
	if !ok {
		return nil, NewUnexpectedTypeError(DescriptorKindExtension, d, "")
	}
	if !field.IsExtension() {
		return nil, NewUnexpectedTypeError(DescriptorKindExtension, field, "")
	}
	return field, nil
}

func (r *resolverFromPool) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionDescriptor, error) {
	extd := FindExtensionByNumber(r.DescriptorPool, message, field)
	if extd == nil {
		return nil, ErrNotFound
	}
	return extd, nil
}

func (r *resolverFromPool) RangeExtensionsByMessage(message protoreflect.FullName, fn func(protoreflect.ExtensionDescriptor) bool) {
	RangeExtensionsByMessage(r.DescriptorPool, message, fn)
}

func (r *resolverFromPool) FindMessageByURL(url string) (protoreflect.MessageDescriptor, error) {
	return r.FindMessageByName(TypeNameFromURL(url))
}

func (r *resolverFromPool) AsTypeResolver() TypeResolver {
	return TypesFromResolver(r)
}

type resolverWithTypes struct {
	Resolver
	types TypePool
}

func (r *resolverWithTypes) AsTypeResolver() TypeResolver {
	return r.types
}

func (r *resolverWithTypes) AsTypePool() TypePool {
	return r.types
}
