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

import (
	"google.golang.org/protobuf/types/descriptorpb"

	"github.com/bufbuild/protocompile/ast"
)

type hasOptionNode interface {
	OptionNode(part *descriptorpb.UninterpretedOption) ast.OptionDeclNode
	FileNode() ast.FileDeclNode // needed in order to query for NodeInfo
}

type errorHandler func(span ast.SourceSpan, format string, args ...any) error

func FindFirstOption(res hasOptionNode, handler errorHandler, scope string, opts []*descriptorpb.UninterpretedOption, name string) (int, error) {
	return findOption(res, handler, scope, opts, name, false, true)
}

func FindOption(res hasOptionNode, handler errorHandler, scope string, opts []*descriptorpb.UninterpretedOption, name string) (int, error) {
	return findOption(res, handler, scope, opts, name, true, false)
}

func findOption(res hasOptionNode, handler errorHandler, scope string, opts []*descriptorpb.UninterpretedOption, name string, exact, first bool) (int, error) {
	found := -1
	for i, opt := range opts {
		if exact && len(opt.Name) != 1 {
			continue
		}
		if opt.Name[0].GetIsExtension() || opt.Name[0].GetNamePart() != name {
			continue
		}
		if first {
			return i, nil
		}
		if found >= 0 {
			optNode := res.OptionNode(opt)
			fn := res.FileNode()
			node := optNode.GetName()
			nodeInfo := fn.NodeInfo(node)
			return -1, handler(nodeInfo, "%s: option %s cannot be defined more than once", scope, name)
		}
		found = i
	}
	return found, nil
}

func RemoveOption(uo []*descriptorpb.UninterpretedOption, indexToRemove int) []*descriptorpb.UninterpretedOption {
	switch {
	case indexToRemove == 0:
		return uo[1:]
	case indexToRemove == len(uo)-1:
		return uo[:len(uo)-1]
	default:
		return append(uo[:indexToRemove], uo[indexToRemove+1:]...)
	}
}
