// Copyright 2020-2026 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package shake256 provides simple utilities around shake256 digests.
package shake256

import (
	"crypto/sha3"
	"fmt"
	"io"
	"slices"
)

const shake256Length = 64

// Digest is a shake256 digest.
type Digest interface {
	Value() []byte

	isDigest()
}

// NewDigest returns a new Digest for the value.
func NewDigest(value []byte) (Digest, error) {
	return newDigest(value)
}

// NewDigestForContent returns a new Digest for the content read from the Reader.
func NewDigestForContent(reader io.Reader) (Digest, error) {
	shakeHash := sha3.NewSHAKE256()
	// Use a small fixed buffer instead of io.Copy's 32 KiB default. Proto
	// files are typically small (median a few KiB), so 1 KiB reads most files
	// in a few iterations while keeping per-call allocation minimal.
	buf := make([]byte, 1024)
	if _, err := io.CopyBuffer(shakeHash, reader, buf); err != nil {
		return nil, err
	}
	value := make([]byte, shake256Length)
	if _, err := shakeHash.Read(value); err != nil {
		// sha3.ShakeHash never errors or short reads. Something horribly wrong
		// happened if your computer ended up here.
		return nil, err
	}
	return newDigest(value)
}

// *** PRIVATE ***

type digest struct {
	value []byte
}

func newDigest(value []byte) (*digest, error) {
	if len(value) != shake256Length {
		return nil, fmt.Errorf("invalid shake256 digest value: expected %d bytes, got %d", shake256Length, len(value))
	}
	return &digest{
		value: value,
	}, nil
}

func (d *digest) Value() []byte {
	return slices.Clone(d.value)
}

func (*digest) isDigest() {}
