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

import (
	"sync"

	"github.com/bufbuild/buf/private/pkg/protodescriptor"
	"google.golang.org/protobuf/reflect/protodesc"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/reflect/protoregistry"
	"google.golang.org/protobuf/types/dynamicpb"
)

const maxTagNumber = 536870911 // 2^29 - 1

func newResolver[F protodescriptor.FileDescriptor](fileDescriptors ...F) (Resolver, error) {
	if len(fileDescriptors) == 0 {
		return nil, nil
	}
	fileDescriptorSet := protodescriptor.FileDescriptorSetForFileDescriptors(fileDescriptors...)
	if err := stripLegacyOptions(fileDescriptorSet.File); err != nil {
		return nil, err
	}
	// TODO: handle if resolvable
	files, err := protodesc.FileOptions{
		AllowUnresolvable: true,
	}.NewFiles(fileDescriptorSet)
	if err != nil {
		return nil, err
	}
	return &resolver{Files: files, Types: dynamicpb.NewTypes(files)}, nil
}

type resolver struct {
	*protoregistry.Files
	*dynamicpb.Types
}

type lazyResolver struct {
	fn       func() (Resolver, error)
	init     sync.Once
	resolver Resolver
	err      error
}

func (l *lazyResolver) maybeInit() error {
	l.init.Do(func() {
		l.resolver, l.err = l.fn()
	})
	return l.err
}

func (l *lazyResolver) FindFileByPath(path string) (protoreflect.FileDescriptor, error) {
	if err := l.maybeInit(); err != nil {
		return nil, err
	}
	return l.resolver.FindFileByPath(path)
}

func (l *lazyResolver) FindDescriptorByName(name protoreflect.FullName) (protoreflect.Descriptor, error) {
	if err := l.maybeInit(); err != nil {
		return nil, err
	}
	return l.resolver.FindDescriptorByName(name)
}

func (l *lazyResolver) FindEnumByName(enum protoreflect.FullName) (protoreflect.EnumType, error) {
	if err := l.maybeInit(); err != nil {
		return nil, err
	}
	return l.resolver.FindEnumByName(enum)
}

func (l *lazyResolver) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
	if err := l.maybeInit(); err != nil {
		return nil, err
	}
	return l.resolver.FindExtensionByName(field)
}

func (l *lazyResolver) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
	if err := l.maybeInit(); err != nil {
		return nil, err
	}
	return l.resolver.FindExtensionByNumber(message, field)
}

func (l *lazyResolver) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) {
	if err := l.maybeInit(); err != nil {
		return nil, err
	}
	return l.resolver.FindMessageByName(message)
}

func (l *lazyResolver) FindMessageByURL(url string) (protoreflect.MessageType, error) {
	if err := l.maybeInit(); err != nil {
		return nil, err
	}
	return l.resolver.FindMessageByURL(url)
}

type combinedResolver []Resolver

func (c combinedResolver) FindFileByPath(s string) (protoreflect.FileDescriptor, error) {
	var lastErr error
	for _, res := range c {
		file, err := res.FindFileByPath(s)
		if err == nil {
			return file, nil
		}
		lastErr = err
	}
	if lastErr != nil {
		return nil, lastErr
	}
	return nil, protoregistry.NotFound
}

func (c combinedResolver) FindDescriptorByName(name protoreflect.FullName) (protoreflect.Descriptor, error) {
	var lastErr error
	for _, res := range c {
		desc, err := res.FindDescriptorByName(name)
		if err == nil {
			return desc, nil
		}
		lastErr = err
	}
	if lastErr != nil {
		return nil, lastErr
	}
	return nil, protoregistry.NotFound
}

func (c combinedResolver) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
	var lastErr error
	for _, res := range c {
		extension, err := res.FindExtensionByName(field)
		if err == nil {
			return extension, nil
		}
		lastErr = err
	}
	if lastErr != nil {
		return nil, lastErr
	}
	return nil, protoregistry.NotFound
}

func (c combinedResolver) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
	var lastErr error
	for _, res := range c {
		extension, err := res.FindExtensionByNumber(message, field)
		if err == nil {
			return extension, nil
		}
		lastErr = err
	}
	if lastErr != nil {
		return nil, lastErr
	}
	return nil, protoregistry.NotFound
}

func (c combinedResolver) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) {
	var lastErr error
	for _, res := range c {
		msg, err := res.FindMessageByName(message)
		if err == nil {
			return msg, nil
		}
		lastErr = err
	}
	if lastErr != nil {
		return nil, lastErr
	}
	return nil, protoregistry.NotFound
}

func (c combinedResolver) FindMessageByURL(url string) (protoreflect.MessageType, error) {
	var lastErr error
	for _, res := range c {
		msg, err := res.FindMessageByURL(url)
		if err == nil {
			return msg, nil
		}
		lastErr = err
	}
	if lastErr != nil {
		return nil, lastErr
	}
	return nil, protoregistry.NotFound
}

func (c combinedResolver) FindEnumByName(enum protoreflect.FullName) (protoreflect.EnumType, error) {
	var lastErr error
	for _, res := range c {
		msg, err := res.FindEnumByName(enum)
		if err == nil {
			return msg, nil
		}
		lastErr = err
	}
	if lastErr != nil {
		return nil, lastErr
	}
	return nil, protoregistry.NotFound
}

type emptyResolver struct{}

func (emptyResolver) FindFileByPath(string) (protoreflect.FileDescriptor, error) {
	return nil, protoregistry.NotFound
}

func (emptyResolver) FindDescriptorByName(protoreflect.FullName) (protoreflect.Descriptor, error) {
	return nil, protoregistry.NotFound
}

func (emptyResolver) FindEnumByName(protoreflect.FullName) (protoreflect.EnumType, error) {
	return nil, protoregistry.NotFound
}

func (emptyResolver) FindExtensionByName(protoreflect.FullName) (protoreflect.ExtensionType, error) {
	return nil, protoregistry.NotFound
}

func (emptyResolver) FindExtensionByNumber(protoreflect.FullName, protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
	return nil, protoregistry.NotFound
}

func (emptyResolver) FindMessageByName(protoreflect.FullName) (protoreflect.MessageType, error) {
	return nil, protoregistry.NotFound
}

func (emptyResolver) FindMessageByURL(string) (protoreflect.MessageType, error) {
	return nil, protoregistry.NotFound
}
