const e=require("ready-resource"),t=require("hyperdb"),r=require("hyperblobs"),i=require("hypercore-id-encoding"),s=require("keet-identity-key"),a=require("hypercore-crypto"),n=require("compact-encoding"),o=require("b4a"),c=require("./spec/hyperschema"),h=require("./spec/hyperdb"),l=require("./lib/errors.js"),d=require("./lib/map-stream.js"),y=require("./lib/watcher.js"),[u]=a.namespace("keet-registry",1),g=c.getEncoding("@keet/member-profile");class m extends e{static IDENTITY_ENCODING=g;static MAX_RECORDS_PER_KEY=5;static MAX_RESERVATIONS_PER_KEY=5;constructor(e,i,s,{extension:a=!0}={}){super(),this.db=t.bee(e,h,{extension:a,autoUpdate:!0}),this.reserved=t.bee(i,h,{extension:a,autoUpdate:!0}),this.blobs=new r(s),this._inflateRecordBound=this._inflateRecord.bind(this),this._recordWatcher=null,this._reservationWatcher=null}get key(){return this.db.core.key}get discoveryKey(){return this.db.core.discoveryKey}get keys(){return{db:b(this.db.core),reserved:b(this.reserved.core),blobs:b(this.blobs.core)}}async _open(){await this.db.ready()}async _close(){this.unwatch(),await this.db.close(),await this.reserved.close(),await this.blobs.close()}async put({profile:e,attestation:t}={}){const r=i.decode(t.memberKey);this.opened||await this.ready();const s={version:3,count:1,profile:e,attestation:t};await m.verifyPut(this,s),s.profile=await this._extractProfile(e);const a=await this.getByKey(r),n=this.db.transaction(),o=this.reserved.transaction();a&&s.profile.username!==a.profile.username&&(await n.delete("@keet-registry-v3/identity",a),s.count=a.count+1);const c={username:s.profile.username,memberKey:t.memberKey},h=[o.insert("@keet-registry-v3/reserved",c),n.insert("@keet-registry-v3/attestation",t),n.insert("@keet-registry-v3/identity",s)];await Promise.all(h),await Promise.all([n.flush(),o.flush()])}async isAvailable(e){return!await this.getReserved(e)&&!await this.get(e)}async reserve(e){this.opened||await this.ready(),await m.verifyReserve(this,e);const{username:t,attestation:r}=e,i={username:t,memberKey:r.memberKey},s=this.reserved.transaction();await s.insert("@keet-registry-v3/reserved",i),await s.flush()}async getReserved(e){return this.opened||await this.ready(),this.reserved.get("@keet-registry-v3/reserved",{username:e})}async block(e,{reason:t=""}={}){this.opened||await this.ready();const r=await this.getByKey(e),i=this.db.transaction();r&&await i.delete("@keet-registry-v3/identity",r),await i.insert("@keet-registry-v3/blocked",{memberKey:e,reason:t}),await i.flush()}async getBlocked(e){return this.opened||await this.ready(),await this.db.get("@keet-registry-v3/blocked",{memberKey:e})}async isBlocked(e){return this.opened||await this.ready(),null!==await this.getBlocked(e)}async get(e){this.opened||await this.ready();const t=await this.db.get("@keet-registry-v3/identity",{profile:{username:e}});return t?this._inflateRecord(t):null}async _extractProfile(e){return{...e,avatar:await w(this.blobs,e.avatar)}}async _inflateProfile(e){return{...e,avatar:await _(this.blobs,e.avatar)}}async _inflateRecord(e){return{...e,profile:await this._inflateProfile(e.profile),attestation:await this._getAttestation(e.attestation,this.db)}}_getAttestation({memberKey:e},t){return t.get("@keet-registry-v3/attestation",{memberKey:e})}async _getProfileByKey(e,t){const r={attestation:{memberKey:e}};return t.get("@keet-registry-v3/identity-by-key",r)}async updateManifest(e){const t=await this.db.get("@keet-registry-v3/manifest");if(i=e,(r=t)&&r.version===i.version)return;var r,i;if(t&&e.version<t.version)throw l.INVALID_MANIFEST();t||await this._migrateFromV2();const s=this.db.transaction();await s.insert("@keet-registry-v3/manifest",e),await s.flush()}async _migrateFromV2(){const e=this.reserved.transaction(),t=[];for await(const{name:r,publicKey:i}of this._v2ListAll())t.push(e.insert("@keet-registry-v3/reserved",{username:r,memberKey:i}));return await Promise.all(t),e.flush()}async v2Get(e){return this.opened||await this.ready(),this.db.get("@keet-registry/identity",{name:e})}async has(e){return null!==await this.get(e)}async hasKey(e){return null!==await this.getByKey(e)}async getByKey(e){const t=i.decode(e);this.opened||await this.ready();const r={attestation:{memberKey:t}},s=this.db.snapshot(),a=[this._getProfileByKey(t,s),this._getAttestation(r.attestation,s)],[n,o]=await Promise.all(a);return await s.close(),n?{...n,attestation:o}:null}async getReservationsByKey(e){const t=i.decode(e);this.opened||await this.ready();const r={memberKey:t};return this.reserved.find("@keet-registry-v3/reserved-by-key",{gte:r,lte:r}).toArray()}async getNrRecordsByKey(e){const t=await this._getProfileByKey(e,this.db);return t?t.count:0}async getNrReservationsByKey(e){return(await this.getReservationsByKey(e)).length}list(e={},t={}){const r=this.db.find("@keet-registry-v3/identity",e,t);return new d(r,this._inflateRecordBound)}_v2ListAll(){return this.db.find("@keet-registry/identity")}search(e,t={}){if(t.limit||(t.limit=10),0===e.length)return this.list({},t);const r=e.slice(0,e.length-1)+String.fromCharCode(e.charCodeAt(e.length-1)+1),i={gte:{profile:{username:e}},lt:{profile:{username:r}}};return this.list(i,t)}unwatch(){this._recordWatcher&&this._recordWatcher.destroy(),this._reservationWatcher&&this._reservationWatcher.destroy(),this._recordWatcher=null,this._reservationWatcher=null}_getRecordWatcher(){return this._recordWatcher||(this._recordWatcher=new y(this.db,"@keet-registry-v3/identity-by-key")),this._recordWatcher}watchRecord(e,t){return this._getRecordWatcher().add(e,t)}_getReservationWatcher(){return this._reservationWatcher||(this._reservationWatcher=new y(this.reserved,"@keet-registry-v3/reserved")),this._reservationWatcher}watchReservation(e){return this._getReservationWatcher().add({username:e})}registerMetrics(e){const t=this;new e.Gauge({name:"keet_registry_db_version",help:"The current version of the records db",collect(){this.set(t.db.core.length)}}),new e.Gauge({name:"keet_registry_reserved_version",help:"The current version of the reserved db",collect(){this.set(t.reserved.core.length)}}),new e.Gauge({name:"keet_registry_blobs_version",help:"The current version of the blobs core (with avatars)",collect(){this.set(t.blobs.core.length)}}),new e.Gauge({name:"keet_registry_db_signed_length",help:"The current signed length of the records db",collect(){this.set(t.db.core.signedLength)}}),new e.Gauge({name:"keet_registry_reserved_signed_length",help:"The current signed length of the reserved db",collect(){this.set(t.reserved.core.signedLength)}}),new e.Gauge({name:"keet_registry_blobs_signed_length",help:"The current signed length of the blobs core (with avatars)",collect(){this.set(t.blobs.core.signedLength)}})}static async verifyReserve(e,t){if(!m.verifyReservation(t))throw l.INVALID_ATTESTATION();const{username:r,attestation:i}=t,{memberKey:s}=i,[a,n,o,c]=await Promise.all([e.isBlocked(s),e.get(r),e.getNrReservationsByKey(s),e.getReserved(r)]);if(a)throw l.BLOCKED();if(n||c)throw l.NAME_UNAVAILABLE();if(o>=m.MAX_RESERVATIONS_PER_KEY)throw l.EXCEEDS_RESERVATION_LIMIT()}static async verifyPut(e,t){if(!m.verifyAttestation(t))throw l.INVALID_ATTESTATION();const{timestamp:r,username:i,avatar:s}=t.profile;if(r<0)throw new l("Negative timestamp value");if(!i)throw l.INVALID_NAME();if(s&&!function(e){return!(e.small&&!f(e.small)||e.medium&&!f(e.medium)||e.large&&!f(e.large))}(s))throw l.INVALID_AVATAR();const{memberKey:a}=t.attestation,[n,c,h,d,y]=await Promise.all([e.isBlocked(a),e.getByKey(a,{attestation:!1}),e.get(i),e.getNrRecordsByKey(a),e.getReserved(i)]);if(n)throw l.BLOCKED();const u=h&&h.attestation||y;if(u&&!o.equals(u.memberKey,a))throw l.NAME_UNAVAILABLE();if(d>=m.MAX_RECORDS_PER_KEY)throw l.EXCEEDS_RECORD_LIMIT();if(c&&r<=c.profile.timestamp)throw l.OUTDATED_CLOCK()}static encodeReservation({username:e}){const t=o.alloc(32+e.length),r={start:0,end:t.length,buffer:t};return n.fixed32.encode(r,u),n.string.encode(r,e),t}static verifyReservation({username:e,attestation:t}){return v(t,this.encodeReservation({username:e}))}static verifyAttestation({profile:e,attestation:t}){return v(t,n.encode(g,e))}}function v(e,t,r){return!!s.verify(e.proof,t,{expectedIdentity:e.memberKey})}function b(e){return{key:e.key,discoveryKey:e.discoveryKey}}function f(e){return!e||!!e.pointer.inlined&&e.pointer.inlined.byteLength<=524288}async function w(e,t){if(!t)return t;const{small:r,medium:i,large:s}=t;return{small:r?await p(e,r):null,medium:i?await p(e,i):null,large:s?await p(e,s):null}}async function _(e,t){if(!t)return t;const{small:r,medium:i,large:s}=t;return{small:r?await k(e,r):null,medium:i?await k(e,i):null,large:s?await k(e,s):null}}async function p(e,t){if(!f(t))throw new Error("Cannot inline a blob that is larger than 512 KB");const r=await e.put(t.pointer.inlined);return{...t,pointer:{blob:r}}}async function k(e,t){if(!t)return t;if(!t.pointer.blob)return t;const r=await e.get(t.pointer.blob);return{...t,pointer:{inlined:r}}}module.exports=m;