// Package helpers provides shared utilities for WebSocket handlers.
package helpers

import (
	"encoding/binary"
	"fmt"
)

// PayloadParser provides helpers for parsing binary WebSocket payloads.
type PayloadParser struct {
	payload []byte
	offset  int
}

// NewPayloadParser creates a new payload parser starting after the API key hash.
func NewPayloadParser(payload []byte, apiKeyHashLen int) *PayloadParser {
	return &PayloadParser{
		payload: payload,
		offset:  apiKeyHashLen,
	}
}

// ParseByte parses a single byte and advances the offset.
func (p *PayloadParser) ParseByte() (byte, error) {
	if p.offset >= len(p.payload) {
		return 0, fmt.Errorf("payload too short at offset %d", p.offset)
	}
	b := p.payload[p.offset]
	p.offset++
	return b, nil
}

// ParseUint16 parses a 2-byte big-endian uint16 and advances the offset.
func (p *PayloadParser) ParseUint16() (uint16, error) {
	if p.offset+2 > len(p.payload) {
		return 0, fmt.Errorf("payload too short for uint16 at offset %d", p.offset)
	}
	v := binary.BigEndian.Uint16(p.payload[p.offset : p.offset+2])
	p.offset += 2
	return v, nil
}

// ParseLengthPrefixedString parses a length-prefixed string.
// Format: [length:1][string:var]
func (p *PayloadParser) ParseLengthPrefixedString() (string, error) {
	if p.offset >= len(p.payload) {
		return "", fmt.Errorf("payload too short for length at offset %d", p.offset)
	}
	length := int(p.payload[p.offset])
	p.offset++
	if length < 0 {
		return "", fmt.Errorf("invalid negative length at offset %d", p.offset-1)
	}
	if p.offset+length > len(p.payload) {
		return "", fmt.Errorf("payload too short for string of length %d at offset %d", length, p.offset)
	}
	str := string(p.payload[p.offset : p.offset+length])
	p.offset += length
	return str, nil
}

// ParseUint16PrefixedString parses a string prefixed by a 2-byte length.
// Format: [length:2][string:var]
func (p *PayloadParser) ParseUint16PrefixedString() (string, error) {
	if p.offset+2 > len(p.payload) {
		return "", fmt.Errorf("payload too short for uint16 length at offset %d", p.offset)
	}
	length := int(binary.BigEndian.Uint16(p.payload[p.offset : p.offset+2]))
	p.offset += 2
	if length < 0 {
		return "", fmt.Errorf("invalid negative length at offset %d", p.offset-2)
	}
	if p.offset+length > len(p.payload) {
		return "", fmt.Errorf("payload too short for string of length %d at offset %d", length, p.offset)
	}
	str := string(p.payload[p.offset : p.offset+length])
	p.offset += length
	return str, nil
}

// Payload returns the underlying payload bytes.
func (p *PayloadParser) Payload() []byte {
	return p.payload
}

// Offset returns the current offset into the payload.
func (p *PayloadParser) Offset() int {
	return p.offset
}

// HasRemaining returns true if there are remaining bytes.
func (p *PayloadParser) HasRemaining() bool {
	return p.offset < len(p.payload)
}

// Remaining returns the remaining bytes in the payload from current offset.
func (p *PayloadParser) Remaining() []byte {
	if p.offset >= len(p.payload) {
		return nil
	}
	return p.payload[p.offset:]
}

// ParseBool parses a byte as a boolean (0 = false, non-zero = true).
func (p *PayloadParser) ParseBool() (bool, error) {
	b, err := p.ParseByte()
	if err != nil {
		return false, err
	}
	return b != 0, nil
}

// ParseFixedBytes parses a fixed-length byte slice.
func (p *PayloadParser) ParseFixedBytes(length int) ([]byte, error) {
	if p.offset+length > len(p.payload) {
		return nil, fmt.Errorf("payload too short for %d bytes at offset %d", length, p.offset)
	}
	bytes := p.payload[p.offset : p.offset+length]
	p.offset += length
	return bytes, nil
}
