;; ported-from: src/profile.ts @ v1.1.0
;;
;; Cross-app profile: the small set of personal fields every suite app renders.
;; `hueOfPub` keeps the exact historical hash (h*31 + charCode over the pub) so
;; a person's color is stable everywhere and identical to what any peer
;; computes locally. A chosen `hue` overrides the derived one; a chosen `glyph`
;; overrides the first fingerprint emoji.
(ns ardegazu.id.profile
(:refer-clojure :exclude [object?])
(:require [ardegazu.id.identity :as identity]))
(defn- object?
"Exactly TS's `typeof x === \"object\"` + truthiness gate (arrays pass)."
[x]
(and (some? x) (identical? "object" (js* "typeof ~{}" x))))
(def EMPTY-PROFILE (js-obj "name" "" "hue" nil "glyph" nil "lang" nil))
(def MAX-NAME-LEN 32)
(defn hue-of-pub
"Same hash the suite always used for colors — over the identity pub."
[pub-b64]
(let [n (.-length pub-b64)]
(loop [i 0 h 0]
(if (< i n)
(recur (inc i)
(unsigned-bit-shift-right (+ (* h 31) (.charCodeAt pub-b64 i)) 0))
(js-mod h 360)))))
(defn effective-hue
"Effective hue for an identity: chosen hue if set, else derived."
([pub-b64] (effective-hue pub-b64 nil))
([pub-b64 profile]
(let [hue (when (some? profile) (unchecked-get profile "hue"))]
(if (and (number? hue) (>= hue 0) (< hue 360))
(js/Math.floor hue)
(hue-of-pub pub-b64)))))
(defn color-of-pub
"The suite's standard chat-style color for an identity."
([pub-b64] (color-of-pub pub-b64 nil))
([pub-b64 profile]
(str "hsl(" (effective-hue pub-b64 profile) " 45% 40%)")))
(defn- first-grapheme [s]
(cond
(or (nil? s) (identical? s "")) nil
(and (exists? js/Intl) (js-in "Segmenter" js/Intl))
(let [seg (.segment (js/Intl.Segmenter.) s)
iter (.call (unchecked-get seg js/Symbol.iterator) seg)
r (.next ^js iter)]
(if (.-done ^js r) nil (unchecked-get (.-value ^js r) "segment")))
:else
(let [arr (js/Array.from s)]
(if (pos? (.-length arr)) (aget arr 0) nil))))
(defn default-glyph
"Effective glyph: the first fingerprint emoji (promise)."
[pub-b64]
(.then (identity/fingerprint-of pub-b64)
(fn [fp]
(let [g (first-grapheme (unchecked-get fp "emoji"))]
(if (some? g) g "◆")))))
(defn clamp-profile
"Clamp untrusted profile data (bridge messages, peer hellos) to a safe shape.
Never throws; unusable fields collapse to their null/empty defaults."
[p]
(let [src (if (object? p) p (js-obj))
name-v (let [v (unchecked-get src "name")]
(if (string? v)
(.join (.slice (js/Array.from (.trim v)) 0 MAX-NAME-LEN) "")
""))
hue-v (let [v (unchecked-get src "hue")]
(if (and (number? v) (js/Number.isFinite v) (>= v 0) (< v 360))
(js/Math.floor v)
nil))
glyph-v (let [v (unchecked-get src "glyph")]
(if (string? v) (first-grapheme (.trim v)) nil))
lang-v (let [v (unchecked-get src "lang")]
(if (and (string? v) (.test #"^[a-z]{2}$" v)) v nil))]
(js-obj "name" name-v "hue" hue-v "glyph" glyph-v "lang" lang-v)))