const e=require("keet-identity-key"),i=require("tiny-buffer-map"),t=require("b4a"),{MemberSchema:s}=require("@holepunchto/keet-core-schemas"),{MemberCapabilities:a,MemberRoles:r,getCapabilitiesForRole:n,GENERIC_EVENTS:c,FEATURES:m,VALIDATION:o}=require("@holepunchto/keet-core-api"),{UpdateMemberOperationV1:l,UpdateMemberOperationV2:y,RemoveMemberOperation:b,ChangeMemberRoleOperation:u,MuteMemberOperation:v,UnmuteMemberOperation:d}=s.Operations,p=t.alloc(0);function h(i,t,s){return e.verify(i.proof,t,{expectedIdentity:i.memberKey,receipt:s})||null}function M(e,i,t,{latestReceipt:s}={}){return{key:e,hasAvatar:!!i?.hasAvatar||!!t?.hasAvatar,displayName:i?.displayName||t?.displayName,latestReceipt:t?.latestReceipt||s,devices:f(i,t),capabilities:w(i,t)}}function f(e,s){if(!e&&!s)return[];if(!s)return e.devices?e.devices:[e.key];if(!e)return s.devices?s.devices:[s.key];const a=new i;if(e.devices){for(const i of e.devices)a.set(i,p);for(const e of s.devices)a.set(e,p)}else{for(const e of s.devices)a.set(e,p);a.set(e.key,p)}const r=[...a.keys()];return r.sort(t.compare),r}function w(e,i){return e||i?e?i?e.capabilities|i.capabilities:e.capabilities:i.capabilities:0}function g(e){return 0!==(e&a.CAN_MODERATE)}module.exports=class{static Operations=new Set([l.TYPE,y.TYPE,b.TYPE,u.TYPE,v.TYPE,d.TYPE]);constructor(e){this.view=e,this.chat=null,this.devices=null,this.config=null,this.members=null,this.avatars=null,this.pendingMembers=null,this.inactiveMembers=null,this.mutedMembers=null,this.membersKeysByMemberName=null,this.membersByDeviceKey=null,this.inactiveMembersByDeviceKey=null,this.capabilitiesByDeviceKey=null,this.moderatorsByMemberKey=null,this.membersCount=null}initialize(){this.chat=this.view.chat,this.devices=this.view.devices,this.config=this.view.config,this.members=this.view.collections.members,this.avatars=this.view.collections.avatars,this.pendingMembers=this.view.collections.pendingMembers,this.inactiveMembers=this.view.collections.inactiveMembers,this.mutedMembers=this.view.collections.mutedMembers,this.memberKeysByMemberName=this.view.indexes.memberKeysByMemberName,this.membersByDeviceKey=this.view.indexes.membersByDeviceKey,this.inactiveMembersByDeviceKey=this.view.indexes.inactiveMembersByDeviceKey,this.capabilitiesByDeviceKey=this.view.indexes.capabilitiesByDeviceKey,this.moderatorsByMemberKey=this.view.indexes.moderatorsByMemberKey,this.membersCount=this.view.indexes.membersCount}_migratePendingMember(e,i,s){if(e.attestation)return s&&t.equals(s.memberKey,e.attestation.memberKey)?M(s.memberKey,e,i,{latestReceipt:e.attestation.receipt}):null;if(s){if(!s.proof)return null;const t=h(s,e.key,i?.latestReceipt);return M(t?s.memberKey:e.key,e,t?i:null,{latestReceipt:t?t.receipt:null})}return M(e.key,e,null)}_migrateExistingMember(e,i,s){if(!s.proof||t.equals(e.key,s.memberKey))return null;const a=h(s,e.key,i?.latestReceipt);return a?M(s.memberKey,e,i,{latestReceipt:a.receipt}):null}async _maybeReactivateMember(e){const i=await this.getInactiveMemberForDeviceKey(e,{full:!0});if(i){const s=i.devices.findIndex(i=>t.equals(i,e));-1!==s&&(i.devices.splice(s,1),i.devices.length>0?await this.inactiveMembers.update(i.key,i,{upsert:!0}):await this.inactiveMembers.delete(i.key))}}async _applyUpdateMemberOperation(e,{id:i,timestamp:s}){const{displayName:a,avatar:r,identity:n}=e.value,[l,y,b,u,v]=await Promise.all([this.getPendingMemberForDeviceKey(i.key),this.getMemberForDeviceKey(i.key,{full:!0}),n?this.getMember(n.memberKey):Promise.resolve(),this.view.internal.getCodeVersion(),this.view.isFeatureSupported(m.DISPLAY_NAME_VALIDATION)]);if(!l&&!y)return;let d=y,p=a;if(v&&a&&(a.length<o.MIN_USERNAME_LENGTH||a.length>o.MAX_USERNAME_LENGTH)&&(p=d.displayName),l){if(u>=2&&await this._maybeReactivateMember(i.key),d=this._migratePendingMember(l,b,n),!d)return;await this.pendingMembers.delete(i.key)}else if(n&&(h=b,M=i.key,!h||-1===h.devices.findIndex(e=>t.equals(e,M)))){if(d=this._migrateExistingMember(y,b,n),!d)return;await this.members.delete(y.key)}var h,M;r&&await this.avatars.update(d.key,r,{upsert:!0});const f={...d,timestamp:s,displayName:p||d.displayName,hasAvatar:!!r||d.hasAvatar};if(await this.members.update(f.key,f,{upsert:!0}),l&&!b){const[e,t]=await Promise.all([this.config.isBroadcastRoom(),this.config.isDisableMembershipEvents()]);if(!e&&!t){const e={id:i,timestamp:s,event:{type:c.MEMBER_JOINED}};await this.chat.addMessage(e)}}}async _applyRemoveMemberOperation(e,{id:t,timestamp:s}){const{memberKey:r}=e.value,[n,o,l,y]=await Promise.all([this.isModerator(t.key),this.getMemberForDeviceKey(t.key),this.getMember(r),this.view.isFeatureSupported(m.REMOVE_MEMBER)]);if(!o||!l||!y)return;if(!n)return;const b=0!==(o.capabilities&a.CAN_INDEX);if(0!==(l.capabilities&a.CAN_INDEX)&&!b)return;const u=new i;for(const e of l.inactive||[])u.set(e,p);let v=!1;for await(const e of l.devices){if(u.get(e))continue;const i=await this.devices.maybeDeleteDevice(e);i&&await this.removeMemberForDeviceKey({key:e},s,{sendLeftEvent:!1}),v=i}v&&await this.chat.addMessage({id:t,timestamp:s,event:{type:c.MEMBER_REMOVED,value:{memberKey:r}}})}async _applyChangeMemberRoleOperation(e,{id:i,timestamp:t}){const{memberKey:s,role:c}=e.value;if(![r.MODERATOR,r.PEER].includes(c))return;const[o,l,y,b,u]=await Promise.all([this.isModerator(i.key),this.getMemberForDeviceKey(i.key),this.moderatorsByMemberKey.get(s),this.getMember(s),this.view.isFeatureSupported(m.PERMISSION_DOWNGRADE)]);if(!l||!b||!u)return;const v=!!y;if(!o)return;const d=0!==(l.capabilities&a.CAN_INDEX),p=0!==(b.capabilities&a.CAN_INDEX);if(p&&!d)return;if(!p&&v&&r.PEER!==c)return;if(!p&&!v&&c!==r.MODERATOR)return;const h=n(c),M=await Promise.all(b.devices.map(e=>this.devices.getDevice(e)));for(const e of M)if(0!==(e.capabilities&a.CAN_INDEX)&&!await this.devices.isRemoveable(e.key))return;for await(const e of M){const{key:i}=e;0!==(e.capabilities&a.CAN_INDEX)&&await this.devices.isRemoveable(i)&&await this.view.base.addWriter(i,{indexer:!1}),await this.devices.updateDevice(i,{...e,capabilities:h})}await this.members.update(b.key,{...b,timestamp:t,capabilities:h},{upsert:!0})}async _applyMuteMemberOperation(e,{id:i,timestamp:t}){const{memberKey:s}=e.value,[a,r]=await Promise.all([this.view.isFeatureSupported(m.MUTE_MEMBER),this.isModerator(i.key)]);a&&r&&await this.mutedMembers.update(s,p,{upsert:!0})}async _applyUnmuteMemberOperation(e,{id:i,timestamp:t}){const{memberKey:s}=e.value,[a,r]=await Promise.all([this.view.isFeatureSupported(m.MUTE_MEMBER),this.isModerator(i.key)]);a&&r&&await this.mutedMembers.get(s)&&await this.mutedMembers.delete(s)}async _indexMembersCount(e,i){if(e&&i)return;const t=await this.membersCount.get(0);let s=t?t.value:0;e&&(s-=1),i&&(s+=1),await this.membersCount.insert(0,s)}async _indexMemberKeysByMemberName(e,i,t){e&&i&&e.displayName===i.displayName||(e&&e.displayName&&await this.memberKeysByMemberName.delete([e.displayName.toLowerCase(),t]),i&&i.displayName&&await this.memberKeysByMemberName.insert([i.displayName.toLowerCase(),t]))}async _indexMembersByDeviceKey(e,i){if(e)for(const i of e.devices)await this.membersByDeviceKey.delete(i);if(i)for(const e of i.devices)await this.membersByDeviceKey.insert(e,i)}async _indexInactiveMembersByDeviceKey(e,i){if(e)for(const i of e.devices)await this.inactiveMembersByDeviceKey.delete(i);if(i)for(const e of i.devices)await this.inactiveMembersByDeviceKey.insert(e,i)}async _indexCapabilitiesByDeviceKey(e,i){if(!(i&&e&&e.capabilities===i.capabilities&&function(e,i){if(e.length!==i.length)return!1;for(let s=0;s<e.length;s++)if(!t.equals(e[s],i[s]))return!1;return!0}(e.devices,i.devices))){if(e)for(const i of e.devices)await this.capabilitiesByDeviceKey.delete(i);if(i)for(const e of i.devices)await this.capabilitiesByDeviceKey.insert(e,i.capabilities)}}async _indexModeratorsByMemberKey(e,i){e&&i&&g(e.capabilities)&&g(i.capabilities)||(e&&g(e.capabilities)&&await this.moderatorsByMemberKey.delete(e.key),i&&g(i.capabilities)&&await this.moderatorsByMemberKey.insert(i.key,p))}async addPendingMember(e){await this.view.internal.getCodeVersion()<2&&await this._maybeReactivateMember(e.key),await this.pendingMembers.update(e.key,{invitation:e.invitation,capabilities:e.capabilities,attestation:e.attestation},{upsert:!0})}async updateExistingMember(e){const i=await this.getMemberForDeviceKey(e.key,{full:!0});if(i)return this.members.update(i.key,{...i,capabilities:i.capabilities|e.capabilities},{upsert:!0})}async getPendingMemberForDeviceKey(e){const i=await this.pendingMembers.get(e);return i?{key:i._id,...i.value}:null}async getMemberForDeviceKey(e,{full:i=!1}={}){const t=await this.membersByDeviceKey.get(e);if(!t)return null;const s=t.value;return i?this.getMember(s.key):{...s,deviceKey:e}}async getInactiveMemberForDeviceKey(e,{full:i=!1}={}){const t=await this.inactiveMembersByDeviceKey.get(e);if(!t)return null;const s=t.value;return i?this.getInactiveMember(s.key):{...s,deviceKey:e}}async removeMemberForDeviceKey(e,i,{sendLeftEvent:s=!0}={}){const a=e.key,[r,n,m]=await Promise.all([this.getMemberForDeviceKey(a,{full:!0}),this.getInactiveMemberForDeviceKey(a,{full:!0}),this.getPendingMemberForDeviceKey(a)]);if(n||!r)return;if(m&&await this.pendingMembers.delete(a),r.inactive?-1===r.inactive.findIndex(e=>t.equals(e,a))&&r.inactive.push(a):r.inactive=[a],r.inactive.sort(t.compare),await this.members.update(r.key,r,{upsert:!0}),!function(e){if(!e.inactive)return!1;if(e.inactive.length!==e.devices.length)return!1;for(let i=0;i<e.inactive.length;i++)if(!t.equals(e.inactive[i],e.devices[i]))return!1;return!0}(r))return;await this.members.delete(r.key),await this.inactiveMembers.update(r.key,r,{upsert:!0});const[o,l,y]=await Promise.all([this.config.isBroadcastRoom(),this.view.internal.getCodeVersion(),this.config.isDisableMembershipEvents()]);if((l<2||!o)&&s&&!y){const t={id:e,timestamp:i,event:{type:c.MEMBER_LEFT}};await this.chat.addMessage(t)}}async getMember(e){const i=await this.members.get(e);return i?.value}async getInactiveMember(e){const i=await this.inactiveMembers.get(e);return i?.value}async isDeviceMuted(e){const[i,t]=await Promise.all([this.getMemberForDeviceKey(e),this.getInactiveMemberForDeviceKey(e)]);return!(!i&&!t)&&this.isMemberMuted(i?.key||t?.key)}async isMemberMuted(e){return!!await this.mutedMembers.get(e)}async isModerator(e){if(t.equals(e,this.view.base.key))return!0;const i=await this.capabilitiesByDeviceKey.get(e);return g(i?.value)}autobaseApply(e,i){switch(e.type){case l.TYPE:case y.TYPE:return this._applyUpdateMemberOperation(e,i);case b.TYPE:return this._applyRemoveMemberOperation(e,i);case u.TYPE:return this._applyChangeMemberRoleOperation(e,i);case v.TYPE:return this._applyMuteMemberOperation(e,i);case d.TYPE:return this._applyUnmuteMemberOperation(e,i)}}async autobaseIndexMembers({prev:e,next:i,id:t}){await this._indexMemberKeysByMemberName(e,i,t),await this._indexMembersByDeviceKey(e,i,t),await this._indexCapabilitiesByDeviceKey(e,i,t),await this._indexModeratorsByMemberKey(e,i,t),await this._indexMembersCount(e,i)}async autobaseIndexInactiveMembers({prev:e,next:i,id:t}){await this._indexInactiveMembersByDeviceKey(e,i,t)}};