package cart

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
)

// cart_id.go
//
// Breaking change:
//   Unified cart identifier as a raw 64-bit unsigned integer (type CartId uint64).
//   External textual representation: base62 (0-9 A-Z a-z), shortest possible
//   encoding for 64 bits (max 11 characters, since 62^11 > 2^64).
//
// Rationale:
//   - Replaces legacy fixed [16]byte padded string and transitional CartID wrapper.
//   - Provides compact, URL/cookie-friendly identifiers.
//   - O(1) hashing and minimal memory footprint.
//   - 64 bits of crypto randomness => negligible collision probability at realistic scale.
//
// Public API:
//   type CartId uint64
//   func NewCartId() (CartId, error)
//   func MustNewCartId() CartId
//   func ParseCartId(string) (CartId, bool)
//   func MustParseCartId(string) CartId
//   (CartId).String() string
//   (CartId).MarshalJSON() / UnmarshalJSON()
//
// NOTE:
//   All legacy helpers (UpgradeLegacyCartId, Fallback hashing, Canonicalize variants,
//   CartIDToLegacy, LegacyToCartID) have been removed as part of the breaking change.
//
// ---------------------------------------------------------------------------

type CartId uint64

const base62Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

// Reverse lookup (0xFF marks invalid)
var base62Rev [256]byte

func init() {
	for i := range base62Rev {
		base62Rev[i] = 0xFF
	}
	for i := 0; i < len(base62Alphabet); i++ {
		base62Rev[base62Alphabet[i]] = byte(i)
	}
}

// String returns the canonical base62 encoding of the 64-bit id.
func (id CartId) String() string {
	return encodeBase62(uint64(id))
}

// MarshalJSON encodes the cart id as a JSON string.
func (id CartId) MarshalJSON() ([]byte, error) {
	return json.Marshal(id.String())
}

// UnmarshalJSON decodes a cart id from a JSON string containing base62 text.
func (id *CartId) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}
	parsed, ok := ParseCartId(s)
	if !ok {
		return fmt.Errorf("invalid cart id: %q", s)
	}
	*id = parsed
	return nil
}

// NewCartId generates a new cryptographically random non-zero 64-bit id.
func NewCartId() (CartId, error) {
	var b [8]byte
	if _, err := rand.Read(b[:]); err != nil {
		return 0, fmt.Errorf("NewCartId: %w", err)
	}
	u := (uint64(b[0]) << 56) |
		(uint64(b[1]) << 48) |
		(uint64(b[2]) << 40) |
		(uint64(b[3]) << 32) |
		(uint64(b[4]) << 24) |
		(uint64(b[5]) << 16) |
		(uint64(b[6]) << 8) |
		uint64(b[7])
	if u == 0 {
		// Extremely unlikely; regenerate once to avoid "0" identifier if desired.
		return NewCartId()
	}
	return CartId(u), nil
}

// MustNewCartId panics if generation fails.
func MustNewCartId() CartId {
	id, err := NewCartId()
	if err != nil {
		panic(err)
	}
	return id
}

// ParseCartId parses a base62 string into a CartId.
// Returns (0,false) for invalid input.
func ParseCartId(s string) (CartId, bool) {
	// Accept length 1..11 (11 sufficient for 64 bits). Reject >11 immediately.
	// Provide a slightly looser upper bound (<=16) only if you anticipate future
	// extensions; here we stay strict.
	if len(s) == 0 || len(s) > 11 {
		return 0, false
	}
	u, ok := decodeBase62(s)
	if !ok {
		return 0, false
	}
	return CartId(u), true
}

// MustParseCartId panics on invalid base62 input.
func MustParseCartId(s string) CartId {
	id, ok := ParseCartId(s)
	if !ok {
		panic(fmt.Sprintf("invalid cart id: %q", s))
	}
	return id
}

// encodeBase62 converts a uint64 to base62 (shortest form).
func encodeBase62(u uint64) string {
	if u == 0 {
		return "0"
	}
	var buf [11]byte
	i := len(buf)
	for u > 0 {
		i--
		buf[i] = base62Alphabet[u%62]
		u /= 62
	}
	return string(buf[i:])
}

// decodeBase62 converts base62 text to uint64.
func decodeBase62(s string) (uint64, bool) {
	var v uint64
	for i := 0; i < len(s); i++ {
		c := s[i]
		d := base62Rev[c]
		if d == 0xFF {
			return 0, false
		}
		v = v*62 + uint64(d)
	}
	return v, true
}
