package catalogpublicationv2

import (
	"bytes"
	"crypto/sha256"
	"encoding/binary"
	"encoding/hex"
)

// CanonicalBytes returns the v2 cross-language publication-binding grammar:
//
//   - CanonicalBindingDomain encoded as a byte string;
//   - the ten Binding members in declaration order;
//   - strings as minimal unsigned-LEB128 UTF-8 byte length followed by exact
//     UTF-8 bytes; and
//   - revision, generations, and epoch as minimal unsigned LEB128 values.
//
// The grammar is independent of Go, JSON, database, and transport encodings
// and does not delegate to the v1 canonicalizer.
func CanonicalBytes(binding Binding) ([]byte, error) {
	if err := ValidateBinding(binding); err != nil {
		return nil, err
	}

	var out bytes.Buffer
	writeString(&out, CanonicalBindingDomain)
	writeString(&out, binding.OrgID)
	writeUint(&out, binding.CatalogRevision)
	writeString(&out, binding.CatalogSnapshotDigest)
	writeUint(&out, binding.SchemaGeneration)
	writeUint(&out, binding.EngineGeneration)
	writeString(&out, binding.LayoutValidationViewDigest)
	writeString(&out, binding.LayoutBundleOwnerVerificationReceiptSetDigest)
	writeString(&out, binding.LayoutActivationSetDigest)
	writeUint(&out, binding.SecurityEpoch)
	writeString(&out, binding.LayoutRoutingSecurityViewDigest)
	return out.Bytes(), nil
}

// BindingDigest returns the CandidatePublicationDigest or
// ActivePublicationDigest for binding. Candidate and active status are
// authority state and therefore not digest inputs.
func BindingDigest(binding Binding) (string, error) {
	canonical, err := CanonicalBytes(binding)
	if err != nil {
		return "", err
	}
	digest := sha256.Sum256(canonical)
	return "sha256:" + hex.EncodeToString(digest[:]), nil
}

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

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