const e=require("keet-identity-key"),{DeviceSchema:i,MemberSchema:t,InvitationSchema:a}=require("@holepunchto/keet-core-schemas"),{MemberCapabilities:s}=require("@holepunchto/keet-core-api"),n=require("compact-encoding"),c=require("b4a"),r=require("../invitations"),{AddDeviceOperationV1:o,AddDeviceOperationV2:l,UpdateDeviceOperation:v,RemoveDeviceOperationV1:u,RemoveDeviceOperationV2:d}=i.Operations,{UpdateMemberOperationV1:p,UpdateMemberOperationV2:y}=t.Operations,{InvitationUserData:h}=a.Messages;module.exports=class{static Operations=new Set([o.TYPE,l.TYPE,v.TYPE,u.TYPE,d.TYPE,p.TYPE,y.TYPE]);constructor(e){this.view=e,this.members=null,this.invitations=null,this.attestations=null,this.devices=null,this.deviceCount=null,this.mobileDeviceCount=null,this.deviceKeysByNoisePublicKey=null}initialize(){this.members=this.view.members,this.invitations=this.view.invitations,this.attestations=this.view.attestations,this.devices=this.view.collections.devices,this.deviceCount=this.view.indexes.deviceCount,this.mobileDeviceCount=this.view.indexes.mobileDeviceCount,this.deviceKeysByNoisePublicKey=this.view.indexes.deviceKeysByNoisePublicKey}_verifyInvitationReceipt(e,i){const t=r.verifyReceipt(i,e.receipt,{checkExpiration:!1});if(!t)return null;const a=n.decode(h,t);return c.equals(a.deviceKey,e.key)&&c.equals(a.noisePublicKey,e.noisePublicKey)?a:null}_verifyAttestation(i,t,a,s){if(!a)return null;const n=e.verify(a,t,{receipt:s,expectedIdentity:i.attestsTo});return n?n.receipt:null}async _addDeviceFromInvitation(e){const i=e.value;if(!i.receipt||!i.invitation)return;const[t,a]=await Promise.all([this.getDevice(i.key),this.invitations.getInvitation(i.invitation)]);if(!a)return;let n=null;if(a.attestsTo){const e=await this.members.getMember(a.attestsTo);n=e?.latestReceipt}const c=this._verifyInvitationReceipt(i,a);if(!c)return;let r=null;if(a.attestsTo&&(r=this._verifyAttestation(a,c.deviceKey,c.attestation,n),!r))return;let o=null;o=a.attestsTo?await this.members.getMember(a.attestsTo):await this.members.getMemberForDeviceKey(c.deviceKey,{full:!0}),a.uses>=1&&(a.capabilities&=~s.CAN_INDEX,a.capabilities&=~s.CAN_MODERATE);const l=(o?.capabilities||0)&~s.CAN_INDEX;if(i.capabilities=l|a.capabilities,await this.view.base.addWriter(i.key,{indexer:0!==(i.capabilities&s.CAN_INDEX)}),await this.devices.update(i.key,i,{upsert:!0}),t)await this.members.updateExistingMember(i);else{const e=r?{memberKey:a.attestsTo,receipt:n}:null;await this.members.addPendingMember({...i,attestation:e})}a.uses++,await this.invitations.updateInvitation(i.invitation,a)}async _manuallyAddDevice(e,{id:i}){const t=e.value;await this.members.isModerator(i.key)&&(await this.view.base.addWriter(t.key,{indexer:0!==(t.capabilities&s.CAN_INDEX)}),await this.devices.update(t.key,t,{upsert:!0}),await this.members.addPendingMember(t))}_applyAddDeviceOperation(e,i){return e.value.invitation?this._addDeviceFromInvitation(e,i):this._manuallyAddDevice(e,i)}async _applyUpdateDeviceOperation(e,{id:i}){const t=await this.getDevice(i.key);t&&await this.devices.update(t.key,{...t,name:e.value.name||t.name})}async _applyUpdateMemberOperation(e,{id:i}){const t=await this.getDevice(i.key);if(!t)return;const a=e.value.pushNotifications||e.value.isMobile;!t.isMobile&&a&&await this.devices.update(t.key,{...t,isMobile:a},{upsert:!0})}async _applyRemoveDeviceOperationV1(){}async _applyRemoveDeviceOperationV2(e,{id:i,timestamp:t}){c.equals(e.value.key,i.key)&&await this.isRemoveable(i.key)&&(await this.members.removeMemberForDeviceKey(i,t),await this.devices.delete(i.key),await this.view.base.removeWriter(i.key))}async _indexDevicesKeysByNoisePublicKey(e,i,t){e&&i&&c.equals(e.noisePublicKey,i.noisePublicKey)||(e?.noisePublicKey&&await this.deviceKeysByNoisePublicKey.delete(e.noisePublicKey,t),i?.noisePublicKey&&await this.deviceKeysByNoisePublicKey.insert(i.noisePublicKey,t))}async _indexDeviceCount(e,i){if(e&&i)return;const t=await this.deviceCount.get(0);let a=t?t.value:0;e&&(a-=1),i&&(a+=1),await this.deviceCount.insert(0,a)}async _indexMobileDeviceCount(e,i){if(e&&i&&e.isMobile===i.isMobile)return;const t=await this.mobileDeviceCount.get(1);let a=t?t.value:0;e?.isMobile&&(a-=1),i?.isMobile&&(a+=1),await this.mobileDeviceCount.insert(1,a)}async addBootstrapMember(e,i){const t={key:e,noisePublicKey:i,capabilities:s.ALL_CAPABILITIES};await this.view.base.addWriter(e,{indexer:!0}),await this.devices.update(e,t,{upsert:!0}),await this.members.addPendingMember(t)}async getDevice(e){const i=await this.devices.get(e);return i?.value}async updateDevice(e,i){return this.devices.update(e,i,{upsert:!0})}async isRemoveable(e){const i=await this.getDevice(e);if(!i||0===(i.capabilities&s.CAN_INDEX))return!0;for await(const{_id:i,value:t}of this.devices.createReadStream())if(!c.equals(i,e)&&0!==(t.capabilities&s.CAN_INDEX))return!0;return!1}async maybeDeleteDevice(e){return!!await this.isRemoveable(e)&&(await this.view.base.removeWriter(e),await this.devices.delete(e),!0)}autobaseApply(e,i){switch(e.type){case o.TYPE:case l.TYPE:return this._applyAddDeviceOperation(e,i);case v.TYPE:return this._applyUpdateDeviceOperation(e,i);case u.TYPE:return this._applyRemoveDeviceOperationV1(e,i);case d.TYPE:return this._applyRemoveDeviceOperationV2(e,i);case p.TYPE:case y.TYPE:return this._applyUpdateMemberOperation(e,i)}}async autobaseIndex({prev:e,next:i,id:t}){await this._indexDevicesKeysByNoisePublicKey(e,i,t),await this._indexDeviceCount(e,i,t),await this._indexMobileDeviceCount(e,i,t)}};