"""ULID minting: a 26-character, lexicographically sortable id — 48 bits of millisecond timestamp
followed by 80 bits of randomness, rendered in Crockford's base32. Sortable ids are what let two
ticks be ordered without carrying a second field, and 80 random bits make a collision between two
ticks of the same millisecond unreachable.

Mirrors src/utils/ulid.ts so a scaffold id and a runtime id are the same kind of value. The random
half is read out of a `uuid.uuid4()`, taking only the ten bytes that carry no version or variant
bits, so all 80 of them are random.
"""

from __future__ import annotations

import math
import time
import uuid

# Crockford's base32: no I, L, O or U, so a transcribed id cannot be misread.
CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"

TIME_CHARS = 10
RANDOM_CHARS = 16
MAX_TIME = 2 ** 48 - 1


def _encode_time(ms) -> str:
    """Render `ms` as the 10-character time prefix.

    Clamped, and never raised from: a clock that reads non-finite, negative, or past the 48-bit
    ceiling must still yield a valid id rather than kill the tick that asked for one.
    """
    try:
        remaining = min(max(math.floor(ms), 0), MAX_TIME)
    except (OverflowError, ValueError, TypeError):
        remaining = 0
    out = ""
    for _ in range(TIME_CHARS):
        out = CROCKFORD[remaining % 32] + out
        remaining //= 32
    return out


def _encode_random() -> str:
    """Render 80 random bits as the 16-character suffix, five bits at a time."""
    # uuid4 holds its version and variant in bytes 6 and 8; the ten bytes taken here are the
    # fully random ones.
    raw = uuid.uuid4().bytes
    acc = int.from_bytes(raw[:6] + raw[9:13], "big")
    return "".join(
        CROCKFORD[(acc >> shift) & 31]
        for shift in range(RANDOM_CHARS * 5 - 5, -1, -5)
    )


def new_ulid(now_ms: int | None = None) -> str:
    """Mint a ULID. `now_ms` is injectable so a caller can pin the time half in a test."""
    if now_ms is None:
        now_ms = int(time.time() * 1000)
    return _encode_time(now_ms) + _encode_random()
