(** CIDv1 computation — pure OCaml, WASM-safe.

    Multihash + CIDv1 + multibase base32-lower (RFC 4648, no pad,
    multibase prefix 'b'). Codecs: dag-cbor 0x71, raw 0x55. Hash
    codes: sha2-256 0x12, sha3-256 0x16. Reference: the multiformats
    specs (unsigned-varint, multihash, cid, multibase). No deps. *)

open Sx_types

(* Unsigned LEB128 (multiformats unsigned-varint). *)
let varint (n : int) : string =
  let buf = Buffer.create 4 in
  let n = ref n in
  let cont = ref true in
  while !cont do
    let b = !n land 0x7f in
    n := !n lsr 7;
    if !n = 0 then (Buffer.add_char buf (Char.chr b); cont := false)
    else Buffer.add_char buf (Char.chr (b lor 0x80))
  done;
  Buffer.contents buf

(* RFC 4648 base32 lowercase, no padding. *)
let b32_alpha = "abcdefghijklmnopqrstuvwxyz234567"

let base32_lower (s : string) : string =
  let buf = Buffer.create ((String.length s * 8 + 4) / 5) in
  let acc = ref 0 and bits = ref 0 in
  String.iter (fun c ->
    acc := (!acc lsl 8) lor (Char.code c);
    bits := !bits + 8;
    while !bits >= 5 do
      bits := !bits - 5;
      Buffer.add_char buf b32_alpha.[(!acc lsr !bits) land 0x1f]
    done) s;
  if !bits > 0 then
    Buffer.add_char buf b32_alpha.[(!acc lsl (5 - !bits)) land 0x1f];
  Buffer.contents buf

(* "abef" -> the 2 raw bytes. *)
let unhex (h : string) : string =
  let n = String.length h / 2 in
  let b = Bytes.create n in
  for i = 0 to n - 1 do
    Bytes.set b i
      (Char.chr (int_of_string ("0x" ^ String.sub h (2 * i) 2)))
  done;
  Bytes.unsafe_to_string b

(* multihash = varint(code) || varint(len) || digest *)
let multihash (code : int) (digest : string) : string =
  varint code ^ varint (String.length digest) ^ digest

(* CIDv1 = 0x01 || varint(codec) || multihash ; multibase 'b' base32. *)
let cidv1 (codec : int) (mh : string) : string =
  "b" ^ base32_lower ("\x01" ^ varint codec ^ mh)

let codec_dag_cbor = 0x71
let mh_sha2_256 = 0x12

(* Canonicalize an SX value: dag-cbor encode -> sha2-256 ->
   multihash -> CIDv1 (dag-cbor codec). *)
let cid_from_sx (v : value) : string =
  let cbor = Sx_cbor.encode v in
  let digest = unhex (Sx_sha2.sha256_hex cbor) in
  cidv1 codec_dag_cbor (multihash mh_sha2_256 digest)
