package caveats

import (
	"fmt"
	"strings"

	"github.com/authzed/spicedb/pkg/caveats/types"
	core "github.com/authzed/spicedb/pkg/proto/core/v1"
)

// UnknownParameterOption is the option to ConvertContextToParameters around handling
// of unknown parameters.
type UnknownParameterOption int

const (
	// SkipUnknownParameters indicates that unknown parameters should be skipped in conversion.
	SkipUnknownParameters UnknownParameterOption = 0

	// ErrorForUnknownParameters indicates that unknown parameters should return an error.
	ErrorForUnknownParameters UnknownParameterOption = 1
)

// ConvertContextToParameters converts the given context into parameters of the types specified.
// Returns a type error if type conversion failed.
func ConvertContextToParameters(
	ts *types.TypeSet,
	contextMap map[string]any,
	parameterTypes map[string]*core.CaveatTypeReference,
	unknownParametersOption UnknownParameterOption,
) (map[string]any, error) {
	if len(contextMap) == 0 {
		return nil, nil
	}

	if len(parameterTypes) == 0 {
		return nil, fmt.Errorf("missing parameters for caveat")
	}

	converted := make(map[string]any, len(contextMap))

	for key, value := range contextMap {
		paramType, ok := parameterTypes[key]
		if !ok {
			if unknownParametersOption == ErrorForUnknownParameters {
				return nil, fmt.Errorf("unknown parameter `%s`", key)
			}

			continue
		}

		varType, err := types.DecodeParameterType(ts, paramType)
		if err != nil {
			return nil, err
		}

		convertedParam, err := varType.ConvertValue(value)
		if err != nil {
			return nil, ParameterConversionError{fmt.Errorf("could not convert context parameter `%s`: %w", key, err), key}
		}

		converted[key] = convertedParam
	}
	return converted, nil
}

// ParameterTypeString returns the string form of the type reference.
func ParameterTypeString(typeRef *core.CaveatTypeReference) string {
	var sb strings.Builder
	sb.WriteString(typeRef.TypeName)
	if len(typeRef.ChildTypes) > 0 {
		sb.WriteString("<")
		for idx, childType := range typeRef.ChildTypes {
			if idx > 0 {
				sb.WriteString(", ")
			}
			sb.WriteString(ParameterTypeString(childType))
		}
		sb.WriteString(">")
	}

	return sb.String()
}
