{-|
Module      : Gargantext.Core.AsyncUpdates
Description : Asynchronous updates to the frontend
Copyright   : (c) CNRS, 2024-Present
License     : AGPL + CECILL v3
Maintainer  : team@gargantext.org
Stability   : experimental
Portability : POSIX
-}

{-# OPTIONS_GHC -Wno-deprecations #-} -- FIXME(cgenie) undefined remains in code

module Gargantext.Core.AsyncUpdates
where

import Gargantext.Core.Types (NodeId, UserId)
import Protolude


{-

Please note that we have 2 different notification mechanisms:
- external (i.e. WebSocket or SSE connection to the frontend)
- internal (e.g. job workers would like to report either progress or
  that some node changed in the tree)
    
I imagine the workflow as follows (this is a mix of internal and
external communication):
- somewhere in the code (or in the async job worker) we decide to send
  an update message to all interested users
- such an action (UserAction) can be associated with the triggering
  user (but doesn't have to be)
- we compute interested users for given notification
- we broadcast (using our broker) these notifications to all
  interested users 
- the broadcast message is either simple (meaning: hey, we have new
  data, if you want you can send an update request) or we could send
  the changed data already

On the client side it looks more or less like this (external
communication):
- specific components decide to subscribe to specific
  UserNotifications: task component is interested in running tasks (for
  given node id), tree component is interested in the tree and its
  first-level children (same for the children components)
- the components react to events accordingly (usually by pulling in
  new data)

Thus, for example, the triple (user_id, node_id, "update_tree")
defines a "update tree for given user and given node" subscription to
this event, both for server and client. This triple is then the
"touching point" between client and server. Through that point, update
messages are sent from server.

Subscription to topics is important IMHO because it allows to target
clients directly rather than broadcasting messages to everyone. This
reduces latency and is more secure. At the same time it is a bit more
complicated because we need to agree on the topic schema both on
server and client.

As for internal communication, we don't need topics: we always want to
get all notifications and process them accordingly (send messages to
connected users, ignore any messages that would be sent to
non-connected users).

-}
    
-------------------------
-- EXTERNAL COMMUNICATION
-------------------------

    
-- | Various topics that users can subscribe to
data Topic =
  -- | Update given Servant Job (we currently send a request every
  -- | second to get job status).
  --  UpdateJob JobID
  -- | Given parent node id, trigger update of the node and its
  --   children (e.g. list is automatically created in a corpus)
  UpdateTree NodeId
  deriving (Eq, Show)


-- TODO: I'm not sure if UserAction/UserSource is needed. I initially
-- created that to mark who initiated the action, but I think we don't
-- need it.
--
-- Suppose we send an 'UpdateTree node_id' message: from the DB we can
-- infer all users that are associated with that node (I do keep in
-- mind that we can share nodes to other users).
data UserSource =
    USUser UserId
  | USSystem
  deriving (Eq, Show)
    
-- | Action possibly associated with user who triggered it (there can
--   be system actions as well)
data UserAction =
  UserAction UserSource Topic
  deriving (Eq, Show)

  
-- | Represents a notification that goes to a given user. This is
--   directly sent via WebSockets.
--
-- NOTE: Do we need public notifications? I.e. sent out to non-logged
-- in users?
data UserNotification =
  UserNotification UserId UserAction
  deriving (Eq, Show)

-- | What we want now is, given a UserAction action, generate all
--   interested users to which the notification will be sent.
--   This function lives in a monad because we have to fetch users
--   from DB.
notificationsForUserAction :: UserAction -> m [ UserNotification ]
notificationsForUserAction = undefined


-- | A connected user can be either associated with his UserId or
--   don't have it, since he's not logged in (for public messages).
data ConnectedUser =
    CUUser UserId
  | CUPublic
  deriving (Eq, Show)
    
-- | Stores connection type associated with given user, subscribed to
-- | a given topic.
--
--   We probably should set conn = Servant.API.WebSocket.Connection
data Subscription conn =
  Subscription ConnectedUser conn Topic


-- | Given a UserNotification and all subscriptions, send it to all
--   matching ones. Possibly we could make this function as part of a
--   typeclass so that we can decide how to send the notification
--   based on whether we choose pure WebSockets, NATS or something
--   else.
sendNotification :: UserNotification -> [ Subscription conn ] -> m ()
sendNotification = undefined


