// Package testvectors holds helpers shared by the conformance-vector tests of
// the packages under pkg/. It exists so each package pins its own accessors
// and document types without copy-pasting the assertion plumbing. It uses the
// Go standard library only and must be imported from _test.go files only.
package testvectors

import (
	"bytes"
	"encoding/json"
	"io"
	"reflect"
	"testing"
)

// AssertDefensiveCopy verifies that accessor returns non-empty vector data and
// that mutating a previously returned slice does not leak into later calls.
func AssertDefensiveCopy(t *testing.T, accessor func() []byte) {
	t.Helper()
	copyData := accessor()
	if len(copyData) == 0 {
		t.Fatal("embedded vectors are empty")
	}
	copyData[0] ^= 0xff
	if bytes.Equal(copyData, accessor()) {
		t.Fatal("ConformanceVectorsJSON returned shared mutable storage")
	}
}

// DecodeVectorDocument decodes a conformance-vector JSON document into the
// caller's document type, rejecting unknown fields and trailing data.
func DecodeVectorDocument[T any](t *testing.T, data []byte) T {
	t.Helper()
	var document T
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&document); err != nil {
		t.Fatal(err)
	}
	if err := decoder.Decode(&struct{}{}); err != io.EOF {
		t.Fatalf("unexpected trailing JSON: %v", err)
	}
	return document
}

// AssertUniqueVectorName fails when name is blank or already present in seen,
// and otherwise records it.
func AssertUniqueVectorName(t *testing.T, seen map[string]bool, name string) {
	t.Helper()
	if name == "" || seen[name] {
		t.Fatalf("vector name %q is blank or duplicated", name)
	}
	seen[name] = true
}

// AssertFields fails unless typ declares exactly the given field names in
// order.
func AssertFields(t *testing.T, typ reflect.Type, want []string) {
	t.Helper()
	if typ.NumField() != len(want) {
		t.Fatalf("%s has %d fields, want %d", typ.Name(), typ.NumField(), len(want))
	}
	for index, name := range want {
		if got := typ.Field(index).Name; got != name {
			t.Fatalf("%s field %d = %q, want %q", typ.Name(), index, got, name)
		}
	}
}
