···11+//// AT Protocol TID (timestamp identifier) generation.
22+33+import gleam/bit_array
44+import gleam/int
55+import gleam/string
66+import gtg/sys
77+88+/// Crockford base32 alphabet used by atproto TIDs (no I, L, O, U).
99+const alphabet = "234567abcdefghijklmnopqrstuvwxyz"
1010+1111+/// Generate a new TID from the current time (13 base32 chars).
1212+pub fn new_now() -> String {
1313+ // TID is 64-bit: top bit 0, then 53 bits of microseconds since epoch-ish,
1414+ // then 10 bits of clock id. We use micros << 10 with a small random clock.
1515+ let micros = sys.unix_micros()
1616+ let clock_id = int.bitwise_and(sys.unix_seconds(), 0x3FF)
1717+ let tid_int = int.bitwise_or(int.bitwise_shift_left(micros, 10), clock_id)
1818+ encode_base32(tid_int, 13)
1919+}
2020+2121+fn encode_base32(value: Int, length: Int) -> String {
2222+ encode_loop(value, length, "")
2323+}
2424+2525+fn encode_loop(value: Int, remaining: Int, acc: String) -> String {
2626+ case remaining {
2727+ 0 -> acc
2828+ n -> {
2929+ let idx = int.bitwise_and(value, 0x1F)
3030+ let char = string.slice(alphabet, idx, 1)
3131+ encode_loop(int.bitwise_shift_right(value, 5), n - 1, char <> acc)
3232+ }
3333+ }
3434+}
3535+3636+/// Encode arbitrary bytes as unpadded base64url.
3737+pub fn base64url(data: BitArray) -> String {
3838+ bit_array.base64_url_encode(data, False)
3939+}