//go:generate ../tools/cmd/genjwa.sh

// Package jwa defines the various algorithm described in https://tools.ietf.org/html/rfc7518
package jwa

import (
	"errors"
	"fmt"
)

const maxKeyAlgorithmErrorPreview = 64

// KeyAlgorithm is a workaround for jwk.Key being able to contain different
// types of algorithms in its `alg` field.
//
// Previously the storage for the `alg` field was represented as a string,
// but this caused some users to wonder why the field was not typed appropriately
// like other fields.
//
// Ideally we would like to keep track of Signature Algorithms and
// Key Encryption Algorithms separately, and force the APIs to
// type-check at compile time, but this allows users to pass a value from a
// jwk.Key directly
type KeyAlgorithm interface {
	String() string
	IsDeprecated() bool
}

var errInvalidKeyAlgorithm = errors.New(`invalid key algorithm`)

func ErrInvalidKeyAlgorithm() error {
	return errInvalidKeyAlgorithm
}

func formatInvalidKeyAlgorithmValue(v string) string {
	runes := []rune(v)
	if len(runes) <= maxKeyAlgorithmErrorPreview {
		return fmt.Sprintf("%q", v)
	}

	return fmt.Sprintf("%q", string(runes[:maxKeyAlgorithmErrorPreview])+`...`)
}

// KeyAlgorithmFrom takes either a string, `jwa.SignatureAlgorithm`,
// `jwa.KeyEncryptionAlgorithm`, or `jwa.ContentEncryptionAlgorithm`.
// and returns a `jwa.KeyAlgorithm`.
//
// If the value cannot be handled, it returns an `jwa.InvalidKeyAlgorithm`
// object instead of returning an error. This design choice was made to allow
// users to directly pass the return value to functions such as `jws.Sign()`
func KeyAlgorithmFrom(v any) (KeyAlgorithm, error) {
	switch v := v.(type) {
	case SignatureAlgorithm:
		return v, nil
	case KeyEncryptionAlgorithm:
		return v, nil
	case ContentEncryptionAlgorithm:
		return v, nil
	case string:
		salg, ok := LookupSignatureAlgorithm(v)
		if ok {
			return salg, nil
		}

		kalg, ok := LookupKeyEncryptionAlgorithm(v)
		if ok {
			return kalg, nil
		}

		calg, ok := LookupContentEncryptionAlgorithm(v)
		if ok {
			return calg, nil
		}

		return nil, fmt.Errorf(`invalid key value: %s: %w`, formatInvalidKeyAlgorithmValue(v), errInvalidKeyAlgorithm)
	default:
		return nil, fmt.Errorf(`invalid key type: %T: %w`, v, errInvalidKeyAlgorithm)
	}
}
