package resources

import (
	"fmt"

	"github.com/databricks/cli/bundle"
	"github.com/databricks/cli/bundle/config"
)

// Reference is a reference to a resource.
// It includes the resource type description, and a reference to the resource itself.
type Reference struct {
	// Key is the unique key of the resource, e.g. "my_job".
	Key string

	// KeyWithType is the unique key of the resource, including the resource type, e.g. "jobs.my_job".
	KeyWithType string

	// Description is the resource type description.
	Description config.ResourceDescription

	// Resource is the resource itself.
	Resource config.ConfigResource
}

// Map is the core type for resource lookup and completion.
type Map map[string][]Reference

// Filter defines the function signature for filtering resources.
type Filter func(Reference) bool

// includeReference checks if the specified reference passes all filters.
// If the list of filters is empty, the reference is always included.
func includeReference(filters []Filter, ref Reference) bool {
	for _, filter := range filters {
		if !filter(ref) {
			return false
		}
	}
	return true
}

// References returns maps of resource keys to a slice of [Reference].
//
// The first map is indexed by the resource key only.
// The second map is indexed by the resource type name and its key.
//
// While the return types allows for multiple resources to share the same key,
// this is confirmed not to happen in the [validate.UniqueResourceKeys]	mutator.
func References(b *bundle.Bundle, filters ...Filter) (Map, Map) {
	keyOnly := make(Map)
	keyWithType := make(Map)

	// Collect map of resource references indexed by their keys.
	for _, group := range b.Config.Resources.AllResources() {
		for k, v := range group.Resources {
			ref := Reference{
				Key:         k,
				KeyWithType: fmt.Sprintf("%s.%s", group.Description.PluralName, k),
				Description: group.Description,
				Resource:    v,
			}

			// Skip resources that do not pass all filters.
			if !includeReference(filters, ref) {
				continue
			}

			keyOnly[ref.Key] = append(keyOnly[ref.Key], ref)
			keyWithType[ref.KeyWithType] = append(keyWithType[ref.KeyWithType], ref)
		}
	}

	return keyOnly, keyWithType
}

// Lookup returns the resource with the specified key.
// If the key maps to more than one resource, an error is returned.
// If the key does not map to any resource, an error is returned.
func Lookup(b *bundle.Bundle, key string, filters ...Filter) (Reference, error) {
	keyOnlyRefs, keyWithTypeRefs := References(b, filters...)
	refs, ok := keyOnlyRefs[key]
	if !ok {
		refs, ok = keyWithTypeRefs[key]
		if !ok {
			return Reference{}, fmt.Errorf("resource with key %q not found", key)
		}
	}

	switch {
	case len(refs) == 1:
		return refs[0], nil
	case len(refs) > 1:
		return Reference{}, fmt.Errorf("multiple resources with key %q found", key)
	default:
		panic("unreachable")
	}
}