/**
* resolveUserId — translate a free-form identifier (userId, identity-hex,
* or username) into a canonical userId.
*
* Returns null if the input does not resolve to any active user. Caller
* decides how to surface that (404 to API caller, skip in migrations).
*
* The lookup order is intentional:
* 1. Direct userId — if the input is already a stored userId, this
* short-circuits without touching identity / username indexes.
* 2. Identity pubkey — 64-hex root or device pubkey linked to a user.
* 3. Username — the indexed `username` field (set via SET_USERNAME).
*
* Each step is wrapped in `.catch(() => null)` so a missing-index miss
* never throws — the function only "throws" by returning null. Callers
* that need to differentiate "no match" from "registry crashed" can
* call the underlying registry methods directly.
*/
export async function resolveUserId (registry, input) {
if (!registry || typeof input !== 'string') return null
const trimmed = input.trim()
if (!trimmed) return null
// 1. Direct userId
const byId = await registry.getUser(trimmed).catch(() => null)
if (byId && byId.userId) return byId.userId
// 2. Identity pubkey (root or device)
const byIdentity = await registry.getUserByIdentity(trimmed).catch(() => null)
if (byIdentity && byIdentity.userId) return byIdentity.userId
// 3. Username
const byUsername = await registry.getUserByUsername(trimmed).catch(() => null)
if (byUsername && byUsername.userId) return byUsername.userId
return null
}