-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2022 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module Galley.API.MLS.One2One
  ( localMLSOne2OneConversation,
    localMLSOne2OneConversationAsRemote,
    localMLSOne2OneConversationMetadata,
    remoteMLSOne2OneConversation,
    createMLSOne2OneConversation,
  )
where

import Data.Id as Id
import Data.Qualified
import Galley.API.MLS.Types
import Galley.Data.Conversation.Types qualified as Data
import Galley.Effects.ConversationStore
import Galley.Types.UserList
import Imports
import Polysemy
import Wire.API.Conversation hiding (Member)
import Wire.API.Conversation.Protocol
import Wire.API.Conversation.Role
import Wire.API.Federation.API.Galley
import Wire.API.MLS.Group.Serialisation
import Wire.API.MLS.Keys
import Wire.API.MLS.SubConversation
import Wire.API.User

-- | Construct a local MLS 1-1 'Conversation' between a local user and another
-- (possibly remote) user.
localMLSOne2OneConversation ::
  Local UserId ->
  Local ConvId ->
  Conversation
localMLSOne2OneConversation :: Local UserId -> Local ConvId -> Conversation
localMLSOne2OneConversation Local UserId
lself (Local ConvId -> Qualified ConvId
forall (t :: QTag) a. QualifiedWithTag t a -> Qualified a
tUntagged -> Qualified ConvId
convId) =
  let members :: ConvMembers
members =
        ConvMembers
          { $sel:cmSelf:ConvMembers :: Member
cmSelf = Qualified UserId -> Member
defMember (Local UserId -> Qualified UserId
forall (t :: QTag) a. QualifiedWithTag t a -> Qualified a
tUntagged Local UserId
lself),
            $sel:cmOthers:ConvMembers :: [OtherMember]
cmOthers = []
          }
      (ConversationMetadata
metadata, ConversationMLSData
mlsData) = Qualified ConvId -> (ConversationMetadata, ConversationMLSData)
localMLSOne2OneConversationMetadata Qualified ConvId
convId
   in Conversation
        { $sel:cnvQualifiedId:Conversation :: Qualified ConvId
cnvQualifiedId = Qualified ConvId
convId,
          $sel:cnvMetadata:Conversation :: ConversationMetadata
cnvMetadata = ConversationMetadata
metadata,
          $sel:cnvMembers:Conversation :: ConvMembers
cnvMembers = ConvMembers
members,
          $sel:cnvProtocol:Conversation :: Protocol
cnvProtocol = ConversationMLSData -> Protocol
ProtocolMLS ConversationMLSData
mlsData
        }

-- | Construct a 'RemoteConversation' structure for a local MLS 1-1
-- conversation to be returned to a remote backend.
localMLSOne2OneConversationAsRemote ::
  Local ConvId ->
  RemoteConversationV2
localMLSOne2OneConversationAsRemote :: Local ConvId -> RemoteConversationV2
localMLSOne2OneConversationAsRemote Local ConvId
lcnv =
  let members :: RemoteConvMembers
members =
        RemoteConvMembers
          { $sel:selfRole:RemoteConvMembers :: RoleName
selfRole = RoleName
roleNameWireMember,
            $sel:others:RemoteConvMembers :: [OtherMember]
others = []
          }
      (ConversationMetadata
metadata, ConversationMLSData
mlsData) = Qualified ConvId -> (ConversationMetadata, ConversationMLSData)
localMLSOne2OneConversationMetadata (Local ConvId -> Qualified ConvId
forall (t :: QTag) a. QualifiedWithTag t a -> Qualified a
tUntagged Local ConvId
lcnv)
   in RemoteConversationV2
        { $sel:id:RemoteConversationV2 :: ConvId
id = Local ConvId -> ConvId
forall (t :: QTag) a. QualifiedWithTag t a -> a
tUnqualified Local ConvId
lcnv,
          $sel:metadata:RemoteConversationV2 :: ConversationMetadata
metadata = ConversationMetadata
metadata,
          $sel:members:RemoteConversationV2 :: RemoteConvMembers
members = RemoteConvMembers
members,
          $sel:protocol:RemoteConversationV2 :: Protocol
protocol = ConversationMLSData -> Protocol
ProtocolMLS ConversationMLSData
mlsData
        }

localMLSOne2OneConversationMetadata ::
  Qualified ConvId ->
  (ConversationMetadata, ConversationMLSData)
localMLSOne2OneConversationMetadata :: Qualified ConvId -> (ConversationMetadata, ConversationMLSData)
localMLSOne2OneConversationMetadata Qualified ConvId
convId =
  let metadata :: ConversationMetadata
metadata =
        (Maybe UserId -> ConversationMetadata
defConversationMetadata Maybe UserId
forall a. Maybe a
Nothing)
          { cnvmType = One2OneConv
          }
      groupId :: GroupId
groupId = GroupIdParts -> GroupId
convToGroupId (GroupIdParts -> GroupId) -> GroupIdParts -> GroupId
forall a b. (a -> b) -> a -> b
$ ConvType -> Qualified ConvOrSubConvId -> GroupIdParts
groupIdParts ConvType
One2OneConv ((ConvId -> ConvOrSubConvId)
-> Qualified ConvId -> Qualified ConvOrSubConvId
forall a b. (a -> b) -> Qualified a -> Qualified b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ConvId -> ConvOrSubConvId
forall c s. c -> ConvOrSubChoice c s
Conv Qualified ConvId
convId)
      mlsData :: ConversationMLSData
mlsData =
        ConversationMLSData
          { $sel:cnvmlsGroupId:ConversationMLSData :: GroupId
cnvmlsGroupId = GroupId
groupId,
            $sel:cnvmlsActiveData:ConversationMLSData :: Maybe ActiveMLSConversationData
cnvmlsActiveData = Maybe ActiveMLSConversationData
forall a. Maybe a
Nothing
          }
   in (ConversationMetadata
metadata, ConversationMLSData
mlsData)

-- | Convert an MLS 1-1 conversation returned by a remote backend into a
-- 'Conversation' to be returned to the client.
remoteMLSOne2OneConversation ::
  Local UserId ->
  Remote UserId ->
  RemoteMLSOne2OneConversation ->
  (MLSOne2OneConversation MLSPublicKey)
remoteMLSOne2OneConversation :: Local UserId
-> Remote UserId
-> RemoteMLSOne2OneConversation
-> MLSOne2OneConversation MLSPublicKey
remoteMLSOne2OneConversation Local UserId
lself Remote UserId
rother RemoteMLSOne2OneConversation
rc =
  let members :: ConvMembers
members =
        ConvMembers
          { $sel:cmSelf:ConvMembers :: Member
cmSelf = Qualified UserId -> Member
defMember (Local UserId -> Qualified UserId
forall (t :: QTag) a. QualifiedWithTag t a -> Qualified a
tUntagged Local UserId
lself),
            $sel:cmOthers:ConvMembers :: [OtherMember]
cmOthers = RemoteMLSOne2OneConversation
rc.conversation.members.others
          }
      conv :: Conversation
conv =
        Conversation
          { $sel:cnvQualifiedId:Conversation :: Qualified ConvId
cnvQualifiedId = QualifiedWithTag 'QRemote ConvId -> Qualified ConvId
forall (t :: QTag) a. QualifiedWithTag t a -> Qualified a
tUntagged (Remote UserId -> ConvId -> QualifiedWithTag 'QRemote ConvId
forall (t :: QTag) x a.
QualifiedWithTag t x -> a -> QualifiedWithTag t a
qualifyAs Remote UserId
rother RemoteMLSOne2OneConversation
rc.conversation.id),
            $sel:cnvMetadata:Conversation :: ConversationMetadata
cnvMetadata = RemoteMLSOne2OneConversation
rc.conversation.metadata,
            $sel:cnvMembers:Conversation :: ConvMembers
cnvMembers = ConvMembers
members,
            $sel:cnvProtocol:Conversation :: Protocol
cnvProtocol = RemoteMLSOne2OneConversation
rc.conversation.protocol
          }
   in MLSOne2OneConversation
        { $sel:conversation:MLSOne2OneConversation :: Conversation
conversation = Conversation
conv,
          $sel:publicKeys:MLSOne2OneConversation :: MLSKeysByPurpose (MLSKeys MLSPublicKey)
publicKeys = RemoteMLSOne2OneConversation
rc.publicKeys
        }

-- | Create a new record for an MLS 1-1 conversation in the database and add
-- the two members to it.
createMLSOne2OneConversation ::
  (Member ConversationStore r) =>
  Qualified UserId ->
  Qualified UserId ->
  Local MLSConversation ->
  Sem r Data.Conversation
createMLSOne2OneConversation :: forall (r :: EffectRow).
Member ConversationStore r =>
Qualified UserId
-> Qualified UserId -> Local MLSConversation -> Sem r Conversation
createMLSOne2OneConversation Qualified UserId
self Qualified UserId
other Local MLSConversation
lconv = do
  Local ConvId -> NewConversation -> Sem r Conversation
forall (r :: EffectRow).
Member ConversationStore r =>
Local ConvId -> NewConversation -> Sem r Conversation
createConversation
    ((MLSConversation -> ConvId)
-> Local MLSConversation -> Local ConvId
forall a b.
(a -> b)
-> QualifiedWithTag 'QLocal a -> QualifiedWithTag 'QLocal b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap MLSConversation -> ConvId
mcId Local MLSConversation
lconv)
    Data.NewConversation
      { $sel:ncMetadata:NewConversation :: ConversationMetadata
ncMetadata = MLSConversation -> ConversationMetadata
mcMetadata (Local MLSConversation -> MLSConversation
forall (t :: QTag) a. QualifiedWithTag t a -> a
tUnqualified Local MLSConversation
lconv),
        $sel:ncUsers:NewConversation :: UserList (UserId, RoleName)
ncUsers = (UserId -> (UserId, RoleName))
-> UserList UserId -> UserList (UserId, RoleName)
forall a b. (a -> b) -> UserList a -> UserList b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (,RoleName
roleNameWireMember) (Local MLSConversation -> [Qualified UserId] -> UserList UserId
forall (f :: * -> *) x a.
Foldable f =>
Local x -> f (Qualified a) -> UserList a
toUserList Local MLSConversation
lconv [Qualified UserId
self, Qualified UserId
other]),
        $sel:ncProtocol:NewConversation :: BaseProtocolTag
ncProtocol = BaseProtocolTag
BaseProtocolMLSTag
      }