package operations

import (
	"path"
	"slices"
	"sort"
	"strings"

	"github.com/go-openapi/jsonpointer"
	"github.com/go-openapi/spec"
	"github.com/go-openapi/swag/mangling"
)

// AllOpRefsByRef returns an index of sortable operations
func AllOpRefsByRef(specDoc Provider, operationIDs []string) map[string]OpRef {
	return OpRefsByRef(GatherOperations(specDoc, operationIDs))
}

// OpRefsByRef indexes a map of sortable operations
func OpRefsByRef(oprefs map[string]OpRef) map[string]OpRef {
	result := make(map[string]OpRef, len(oprefs))
	for _, v := range oprefs {
		result[v.Ref.String()] = v
	}

	return result
}

// OpRef is an indexable, sortable operation
type OpRef struct {
	Method string
	Path   string
	Key    string
	ID     string
	Op     *spec.Operation
	Ref    spec.Ref
}

// OpRefs is a sortable collection of operations
type OpRefs []OpRef

func (o OpRefs) Len() int           { return len(o) }
func (o OpRefs) Swap(i, j int)      { o[i], o[j] = o[j], o[i] }
func (o OpRefs) Less(i, j int) bool { return o[i].Key < o[j].Key }

// Provider knows how to collect operations from a spec
type Provider interface {
	Operations() map[string]map[string]*spec.Operation
}

// GatherOperations builds a map of sorted operations from a spec
func GatherOperations(specDoc Provider, operationIDs []string) map[string]OpRef {
	var oprefs OpRefs
	mangler := mangling.NewNameMangler()

	for method, pathItem := range specDoc.Operations() {
		for pth, operation := range pathItem {
			vv := *operation
			oprefs = append(oprefs, OpRef{
				Key:    mangler.ToGoName(strings.ToLower(method) + " " + pth),
				Method: method,
				Path:   pth,
				ID:     vv.ID,
				Op:     &vv,
				Ref:    spec.MustCreateRef("#" + path.Join("/paths", jsonpointer.Escape(pth), method)),
			})
		}
	}

	sort.Sort(oprefs)

	operations := make(map[string]OpRef)
	for _, opr := range oprefs {
		nm := opr.ID
		if nm == "" {
			nm = opr.Key
		}

		oo, found := operations[nm]
		if found && oo.Method != opr.Method && oo.Path != opr.Path {
			nm = opr.Key
		}

		if len(operationIDs) == 0 || slices.Contains(operationIDs, opr.ID) || slices.Contains(operationIDs, nm) {
			opr.ID = nm
			opr.Op.ID = nm
			operations[nm] = opr
		}
	}

	return operations
}
