// Package ulid generates ULIDs (https://github.com/ulid/spec): 26-character
// Crockford-base32 strings of a 48-bit millisecond timestamp followed by
// 80 bits of cryptographic randomness. Lexicographic order equals creation
// order at millisecond resolution, which the schema relies on for cursor
// pagination and time-ordered message ids.
package ulid

import (
	"crypto/rand"
	"fmt"
	"time"
)

const alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"

// Len is the length of the canonical string form.
const Len = 26

var decode [256]byte

func init() {
	for i := range decode {
		decode[i] = 0xFF
	}
	for i := 0; i < len(alphabet); i++ {
		decode[alphabet[i]] = byte(i)
		// Crockford base32 is case-insensitive.
		decode[alphabet[i]|0x20] = byte(i)
	}
}

// New returns a fresh ULID for the current time.
func New() string { return At(time.Now()) }

// At returns a ULID whose timestamp component is t (randomness still fresh).
func At(t time.Time) string {
	var b [16]byte
	ms := uint64(t.UnixMilli())
	b[0] = byte(ms >> 40)
	b[1] = byte(ms >> 32)
	b[2] = byte(ms >> 24)
	b[3] = byte(ms >> 16)
	b[4] = byte(ms >> 8)
	b[5] = byte(ms)
	if _, err := rand.Read(b[6:]); err != nil {
		// crypto/rand never fails on supported platforms; if it does, the
		// process cannot safely generate ids at all.
		panic(fmt.Sprintf("ulid: crypto/rand failed: %v", err))
	}

	// Base32-encode 128 bits into 26 chars (130-bit capacity, top 2 bits 0):
	// consume bytes from the least-significant end, emit 5-bit groups
	// right-to-left, which yields the canonical big-endian representation.
	var out [Len]byte
	acc, nbits, j := uint32(0), 0, Len-1
	for i := 15; i >= 0; i-- {
		acc |= uint32(b[i]) << nbits
		nbits += 8
		for nbits >= 5 && j > 0 {
			out[j] = alphabet[acc&31]
			acc >>= 5
			nbits -= 5
			j--
		}
	}
	out[0] = alphabet[acc&31] // remaining 3 timestamp bits
	return string(out[:])
}

// IsValid reports whether s is a well-formed ULID.
func IsValid(s string) bool {
	if len(s) != Len {
		return false
	}
	for i := 0; i < Len; i++ {
		if decode[s[i]] == 0xFF {
			return false
		}
	}
	// First char encodes the top 3 bits of a 48-bit value; > '7' overflows.
	return decode[s[0]] <= 7
}

// Timestamp extracts the embedded creation time.
func Timestamp(s string) (time.Time, error) {
	if !IsValid(s) {
		return time.Time{}, fmt.Errorf("ulid: invalid id %q", s)
	}
	var ms uint64
	for i := 0; i < 10; i++ { // first 10 chars = 50 bits, low 48 are the timestamp
		ms = ms<<5 | uint64(decode[s[i]])
	}
	return time.UnixMilli(int64(ms)).UTC(), nil
}
