function realmIdToHex(arg) {
  let bytes = null;
  if (arg instanceof Uint8Array || Array.isArray(arg)) bytes = Array.from(arg);
  else if (arg && typeof arg === "object") bytes = Object.values(arg);
  if (bytes?.length && bytes.every((b) => Number.isInteger(b) && b >= 0 && b <= 255)) {
    return bytes.map((b) => b.toString(16).padStart(2, "0")).join("");
  }
  return null;
}

function resolverFor(tokenMap) {
  const entries = tokenMap.token_map ?? [];
  return (realmId) => {
    const candidates = [realmIdToHex(realmId), String(realmId)].filter(Boolean);
    for (const e of entries) {
      if (candidates.includes(String(e.key))) return e.value?.token;
    }
    return null;
  };
}

export async function juiceboxRegister(tokenMap, pin, secret, { timeoutMs = 90_000 } = {}) {
  if (!tokenMap?.key_store_token_map_json) throw new Error("juicebox: missing token_map from AddXChatPublicKey");
  const { Client, Configuration, setAuthTokenResolver } = await import("./juicebox/index.js");
  setAuthTokenResolver(resolverFor(tokenMap));
  const client = new Client(new Configuration(tokenMap.key_store_token_map_json), []);
  const timeout = new Promise((_, rej) => setTimeout(() => rej(new Error("juicebox register timed out")), timeoutMs));
  await Promise.race([
    client.register(new TextEncoder().encode(pin), secret, new Uint8Array(0), Number(tokenMap.max_guess_count) || 20),
    timeout,
  ]);
}

// recover the secret with the PIN (for loading an identity on a new device)
export async function juiceboxRecover(tokenMap, pin) {
  if (!tokenMap?.key_store_token_map_json) throw new Error("juicebox: missing token_map");
  const { Client, Configuration, setAuthTokenResolver } = await import("./juicebox/index.js");
  setAuthTokenResolver(resolverFor(tokenMap));
  const client = new Client(new Configuration(tokenMap.key_store_token_map_json), []);
  return new Uint8Array(await client.recover(new TextEncoder().encode(pin), new Uint8Array(0)));
}
