package catalogpublicationv1

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

// CanonicalBytes returns the v1 cross-language publication-binding digest
// grammar:
//
//   - the ASCII CanonicalBindingDomain, encoded as a byte string;
//   - the nine Binding members in declaration order;
//   - strings as their minimal unsigned LEB128 UTF-8 byte length followed by
//     the raw UTF-8 bytes; and
//   - revisions, generations, and epoch as minimal unsigned LEB128 values
//     (seven low-order bits per byte, continuation bit first).
//
// The grammar is independent of Go, JSON, database, and transport encodings.
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.LayoutActivationSetDigest)
	writeUint(&out, binding.SecurityEpoch)
	writeString(&out, binding.LayoutRoutingSecurityViewDigest)
	return out.Bytes(), nil
}

// BindingDigest returns the CandidatePublicationDigest or
// ActivePublicationDigest that identifies binding in the Catalog publication
// authority. Candidate and active status are authority state, not digest input.
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])
}
