package memtable

import (
	"errors"
	"sync"
	"sync/atomic"
)

// ErrArenaFull is returned when the arena cannot satisfy an allocation request.
var ErrArenaFull = errors.New("arena: insufficient capacity")

// Arena is a bump allocator backed by a fixed-size byte slice.
// It provides thread-safe allocation with 8-byte alignment.
//
// The arena is used by the memtable to store keys, values, and skip-list
// node structures. Once allocated, bytes are never freed — the entire
// arena is discarded when the memtable is flushed.
type Arena struct {
	buf      []byte
	offset   uint32 // next allocation offset (atomic for reads)
	capacity uint32 // total capacity (immutable)
	mu       sync.Mutex
}

// NewArena creates a new Arena with the given capacity in bytes.
// The entire buffer is allocated upfront.
func NewArena(capacity uint32) *Arena {
	return &Arena{
		buf:      make([]byte, capacity),
		capacity: capacity,
	}
}

// Allocate reserves alignedSize bytes in the arena and returns the offset
// at which the caller can write. The size is aligned up to 8 bytes.
func (a *Arena) Allocate(size uint32) (uint32, error) {
	alignedSize := (size + 7) &^ uint32(7)

	a.mu.Lock()
	rem := a.capacity - a.offset
	if rem < alignedSize {
		a.mu.Unlock()
		return 0, ErrArenaFull
	}
	off := a.offset
	a.offset += alignedSize
	a.mu.Unlock()

	return off, nil
}

// GetBytes returns a slice of the arena buffer at [offset, offset+size).
// It panics if the range is out of bounds.
func (a *Arena) GetBytes(offset, size uint32) []byte {
	end := offset + size
	if end > a.capacity {
		panic("arena: GetBytes out of bounds")
	}
	return a.buf[offset:end]
}

// Remaining returns the number of bytes still available for allocation.
func (a *Arena) Remaining() uint32 {
	return a.capacity - atomic.LoadUint32(&a.offset)
}

// Capacity returns the total capacity of the arena.
func (a *Arena) Capacity() uint32 {
	return a.capacity
}

// Reserve checks whether the arena has at least totalSize bytes remaining
// without actually allocating. It is used by the WAL writer to verify
// capacity before appending entries.
func (a *Arena) Reserve(totalSize uint32) error {
	a.mu.Lock()
	rem := a.capacity - a.offset
	a.mu.Unlock()

	if rem < totalSize {
		return ErrArenaFull
	}
	return nil
}
