package layoutbundlekeyv1

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"sort"
)

// CanonicalBundleKeyBytes returns the standalone v1 tuple grammar:
// S(BundleKeyVersion), one owner-kind byte, S(owner namespace), and
// S(local component key), where S uses minimal unsigned-LEB128 byte length.
func CanonicalBundleKeyBytes(key BundleKey) ([]byte, error) {
	if err := ValidateBundleKey(key); err != nil {
		return nil, err
	}
	var out bytes.Buffer
	out.Grow(int(canonicalStringSize(BundleKeyVersion) + canonicalBundleKeyTupleSize(key)))
	writeCanonicalString(&out, BundleKeyVersion)
	writeBundleKeyTuple(&out, key)
	return out.Bytes(), nil
}

// CanonicalBundleKeyTupleBytes returns the validated tuple bytes used when a
// versioned outer contract embeds a BundleKey without repeating
// BundleKeyVersion: one owner-kind byte, S(owner namespace), and S(local key).
func CanonicalBundleKeyTupleBytes(key BundleKey) ([]byte, error) {
	if err := ValidateBundleKey(key); err != nil {
		return nil, err
	}
	var out bytes.Buffer
	out.Grow(int(canonicalBundleKeyTupleSize(key)))
	writeBundleKeyTuple(&out, key)
	return out.Bytes(), nil
}

// CanonicalSlotOwnerKindByte returns the exact v1 enum byte used by BundleKey
// canonical ordering and embedding contracts.
func CanonicalSlotOwnerKindByte(kind SlotOwnerKind) (byte, error) {
	value := slotOwnerKindByte(kind)
	if value == 0 {
		return 0, fmt.Errorf("SlotOwnerKind must be app, tenant, or system")
	}
	return value, nil
}

// CanonicalizeRequiredBundleKeySet returns a defensive key slice sorted by
// owner-kind byte, raw namespace bytes, and raw local-key bytes.
func CanonicalizeRequiredBundleKeySet(set RequiredBundleKeySet) (RequiredBundleKeySet, error) {
	if err := ValidateRequiredBundleKeySet(set); err != nil {
		return RequiredBundleKeySet{}, err
	}
	canonical := RequiredBundleKeySet{
		Version:    set.Version,
		BundleKeys: append([]BundleKey{}, set.BundleKeys...),
	}
	sort.Slice(canonical.BundleKeys, func(left, right int) bool {
		return bundleKeyLess(canonical.BundleKeys[left], canonical.BundleKeys[right])
	})
	return canonical, nil
}

// CanonicalRequiredBundleKeySetBytes returns the v1 set grammar:
// S(RequiredBundleKeySetVersion), S(BundleKeyVersion), minimal-uLEB128 key
// count, and each sorted key tuple without repeating BundleKeyVersion.
func CanonicalRequiredBundleKeySetBytes(set RequiredBundleKeySet) ([]byte, error) {
	canonical, err := CanonicalizeRequiredBundleKeySet(set)
	if err != nil {
		return nil, err
	}
	size := canonicalRequiredSetSize(canonical)
	if size > MaxCanonicalRequiredBundleKeySetBytes {
		return nil, fmt.Errorf("RequiredBundleKeySet canonical bytes exceed %d", MaxCanonicalRequiredBundleKeySetBytes)
	}
	var out bytes.Buffer
	out.Grow(int(size))
	writeCanonicalString(&out, canonical.Version)
	writeCanonicalString(&out, BundleKeyVersion)
	writeCanonicalUint(&out, uint64(len(canonical.BundleKeys)))
	for _, key := range canonical.BundleKeys {
		writeBundleKeyTuple(&out, key)
	}
	return out.Bytes(), nil
}

func bundleKeyLess(left, right BundleKey) bool {
	leftKind := slotOwnerKindByte(left.SlotOwnerKind)
	rightKind := slotOwnerKindByte(right.SlotOwnerKind)
	if leftKind != rightKind {
		return leftKind < rightKind
	}
	return left.SlotOwnerNamespace < right.SlotOwnerNamespace ||
		(left.SlotOwnerNamespace == right.SlotOwnerNamespace && string(left.LocalComponentKey) < string(right.LocalComponentKey))
}

func slotOwnerKindByte(kind SlotOwnerKind) byte {
	switch kind {
	case SlotOwnerKindApp:
		return 0x01
	case SlotOwnerKindTenant:
		return 0x02
	case SlotOwnerKindSystem:
		return 0x03
	default:
		return 0x00
	}
}

func writeBundleKeyTuple(out *bytes.Buffer, key BundleKey) {
	out.WriteByte(slotOwnerKindByte(key.SlotOwnerKind))
	writeCanonicalString(out, key.SlotOwnerNamespace)
	writeCanonicalString(out, string(key.LocalComponentKey))
}

func canonicalRequiredSetSize(set RequiredBundleKeySet) uint64 {
	size := canonicalStringSize(set.Version) +
		canonicalStringSize(BundleKeyVersion) + canonicalUintSize(uint64(len(set.BundleKeys)))
	for _, key := range set.BundleKeys {
		size += canonicalBundleKeyTupleSize(key)
	}
	return size
}

func canonicalBundleKeyTupleSize(key BundleKey) uint64 {
	return 1 + canonicalStringSize(key.SlotOwnerNamespace) + canonicalStringSize(string(key.LocalComponentKey))
}

func canonicalStringSize(value string) uint64 {
	return canonicalUintSize(uint64(len(value))) + uint64(len(value))
}

func canonicalUintSize(value uint64) uint64 {
	var encoded [binary.MaxVarintLen64]byte
	return uint64(binary.PutUvarint(encoded[:], value))
}

func writeCanonicalString(out *bytes.Buffer, value string) {
	writeCanonicalUint(out, uint64(len(value)))
	out.WriteString(value)
}

func writeCanonicalUint(out *bytes.Buffer, value uint64) {
	var encoded [binary.MaxVarintLen64]byte
	count := binary.PutUvarint(encoded[:], value)
	out.Write(encoded[:count])
}
