#ifndef _diffusion_topics_h_
#define _diffusion_topics_h_ 1

/*
 * Copyright © 2014 - 2025 DiffusionData Ltd., All Rights Reserved.
 *
 * Use is subject to licence terms.
 *
 * NOTICE: All information contained herein is, and remains the
 * property of DiffusionData. The intellectual and technical
 * concepts contained herein are proprietary to DiffusionData and
 * may be covered by U.S. and Foreign Patents, patents in process, and
 * are protected by trade secret or copyright law.
 */

/**
 * @file topics.h
 *
 * Subscription to topics and fetching topic data.
 *
 * This feature allows a client session to subscribe to topics to receive
 * streamed topic updates, fetch the state of topics and/or update topics with new values.
 *
 * Specifically, the feature provides the ability to:
 * <ul>
 *		<li>
 * 			Subscribe to topics and specify streams to receive updates;
 * 		</li>
 *		<li>
 * 			Fetch the current state of topics (even if not subscribed);
 * 		</li>
 *		<li>
 * 			By extending the topic update feature, update topics with new values;
 * 		</li>
 *		<li>
 * 			By extending the topic views feature, manage topic views.
 * 		</li>
 * </ul>
 *
 * <H3>Subscription and unsubscription</H3>
 *
 * A session can issue requests to subscribe to topics at any time, even if the topics
 * do not exist at the server.
 * Each subscription request provides a topic selector that is evaluated by the server
 * to select matching topics.
 * The session will be subscribed to any topics that match the selector unless they are
 * already subscribed, or the session has insufficient permission.
 * The subscription request is also retained at the server and the session will be automatically
 * subscribed to newly created topics that match the selector (unless a subsequent unsubscription
 * cancels the request).
 *
 *
 * Sessions receive notifications from topics that they are subscribed to via subscription streams
 * (see below).
 * When a session is subscribed to a topic, all matching streams will first receive a subscription
 * notification that provides details about the topic.
 * If the server has a value for the topic, the value will be delivered to the streams before
 * any other notifications.
 *
 *
 * A session can unsubscribe from topics at any time.
 * This is also specified using a topic selector.
 * On unsubscription, matching streams are notified via the `on_unsubscription` notification.
 * This notification will give the reason for unsubscription (for example, by request of the session,
 * request of the server, or topic removal).
 *
 *
 * Subscriptions and unsubscriptions can occur for reasons other than requests from the session.
 * A session can be subscribed to or unsubscribed from a topic by another session using the
 * Subscription Control feature.
 * The removal of a topic also automatically causes unsubscription for subscribed sessions.
 *
 *
 * Subscription requests are subject to authorisation checks.
 * The session must have `SELECT_TOPIC SELECT_TOPIC` permission for the topic selector used to subscribe.
 * Matching topics will be further filtered to those for which the session has `READ_TOPIC READ_TOPIC`
 * permission.
 *
 *
 * <H3>Topic selection scopes</H3>
 *
 * Topic selection scopes allow an application with multiple components to use a single Diffusion session.
 * An application component can use a topic selection scope to manage a set of selectors that is unaffected
 * by unsubscriptions performed by other application components.
 * The session will be subscribed to all topics with paths matching a selector in any scope.
 * The unsubscribe operation removes a selector from specific scopes.
 *
 *
 * A scope may be specified to a `subscribe` or `unsubscribe` method, indicating that the selection
 * only applies to that scope.
 * The server manages scopes to ensure that unsubscriptions applied to one scope do not affect another.
 *
 * Scope names are case sensitive.
 * A scope name may not begin with the character `$` as this is reserved for internal use.
 *
 * Unsubscription using a wildcard selector that indicates all topics effectively removes the scope.
 * An application can request unsubscription from all scopes using `unsubscribe_all_scopes`.
 * The `DEFAULT_SELECTION_SCOPE` is used for all methods that do not explicitly specify a scope.
 *
 *
 * <H3>Subscription streams</H3>
 *
 * A session can listen to subscription events and updates for a selection of topics by adding one or
 * more streams.
 * A stream is registered using a topic selector which specifies the topics that the stream applies to.
 * When an update is received for a topic then it will be routed to every stream that matches both the
 * topic selector and the stream's value type.
 * If more than one stream matches, all will receive the update; the order in which they are notified
 * is not defined.
 *
 *
 * A stream can be added several times for different selectors.
 * If the same stream is registered for several selectors that match an event, the stream will only
 * be notified of the event once.
 * The mapping of topic selectors to streams is maintained locally in the client process.
 *
 *
 * It is also possible to add one or more fallback streams which will receive updates that do not match
 * any stream registered with a selector.
 * This is useful for default processing or simply to catch unprocessed updates.
 * A fallback stream can be added using `add_fallback_stream`.
 * Zero, one, or more fallback streams may be assigned.
 * If no fallback stream is specified, any updates that are not routed to any other stream will simply
 * be discarded.
 *
 *
 * If the session is already subscribed to a topic when a matching stream is added, the stream will
 * immediately receive a subscription notification.
 * For most topic types, the latest value is locally cached and will be provided to the stream following
 * the subscription notification.
 *
 * A stream will receive an `on_close` callback when unregistered and an `on_error` callback if the
 * session is closed.
 *
 *
 * <H4>Value streams</H4>
 *
 * A {@link VALUE_STREAM_HANDLE_T value stream} receives values for matching topics as and when updates
 * are received from the server.
 * Delta updates received from the server are automatically applied to locally cached values so that
 * the stream always receives full values for each update.
 *
 * Value streams are typed to a specified value class and only updates for compatible topics will
 * be routed to the stream.
 * The following table shows how the value class maps to compatible topic types that will be routed
 * to the stream:
 *
 * <table>
 *	<tr>
 * 		<th>Value Class</th>
 * 		<th>Compatible Topic Types</th>
 * 	</tr>
 * 	<tr>
 * 		<td>`JSON`</td>
 * 		<td>
 * 			`JSON`
 * 			`STRING`
 * 			`INT64`
 * 			`DOUBLE`
 *		</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`String`</td>
 * 		<td>`STRING`</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`Long`</td>
 * 		<td>`INT64`</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`Double`</td>
 * 		<td>`DOUBLE`</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`Binary`</td>
 * 		<td>`BINARY`</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`Bytes`</td>
 * 		<td>
 * 			`JSON`
 * 			`STRING`
 * 			`INT64`
 * 			`DOUBLE`
 * 			`BINARY`
 * 			`RECORD_V2`
 * 		</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`RecordV2`</td>
 * 		<td>`RECORD_V2`</td>
 * 	</tr>
 * </table>
 *
 *
 * Value stream implementations can be added using add_stream.
 *
 * A value stream can be added to received updates from time series topics using `add_time_series_stream`.
 * The following table shows how the value class specified when adding the stream maps to the
 * event value class of time series topics that will be routed to the stream:
 *
 * <table>
 * 	<tr>
 * 		<th>Event Value Class</th>
 * 		<th>Time Series Event Value Class</th>
 * 	</tr>
 * 	<tr>
 * 		<td>`JSON`</td>
 * 		<td>
 * 			`JSON`
 * 			`STRING`
 * 			`INT64`
 * 			`DOUBLE`
 * 		</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`String`</td>
 * 		<td>`STRING`</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`Long`</td>
 * 		<td>`INT64`</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`Double`</td>
 * 		<td>`DOUBLE`</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`Binary`</td>
 * 		<td>`BINARY`</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`Bytes`</td>
 * 		<td>
 * 			`JSON`
 * 			`STRING`
 * 			`INT64`
 * 			`DOUBLE`
 * 			`BINARY`
 * 			`RECORD_V2`
 * 		</td>
 * 	</tr>
 * 	<tr>
 * 		<td>`RecordV2`</td>
 * 		<td>`RECORD_V2`</td>
 * 	</tr>
 * </table>
 *
 *
 * <H3>Fetch</H3>
 *
 * A session can issue a request to fetch details of a topic or topics (subject to authorization) at any time.
 * The topics required are specified using a topic selector.
 *
 * The results of a fetch will return the topic path and type of each selected topic.
 * The results may also optionally return the topic values and/or properties.
 *
 * A new request can be created using `diffusion_fetch_request_init` and modified to specify
 * additional requirements of the fetch operation.
 * The request is issued to the server using the `diffusion_fetch_request_fetch`
 * method on the request.
 *
 *
 * <H3>Access control</H3>
 *
 * A session must have `SELECT_TOPIC SELECT_TOPIC` permission for the topic selector
 * used to `subscribe` or `diffusion_fetch_request_fetch`.
 * The topics that result from a subscription or fetch request are further filtered using the
 * `READ_TOPIC READ_TOPIC` permission.
 *
 * No access control restrictions are applied to unsubscription.
 */

#include "fetch-request.h"
#include "misc/deprecate.h"
#include "session.h"

#define DEFAULT_SELECTION_SCOPE ""

/**
 * @brief Callback for on_subscribe().
 *
 * @param session The current active session.
 * @param context User-supplied context from the initial subscribe() call.
 *
 * @return HANDLER_SUCCESS or HANDLER_FAILURE.
 */
typedef int (*on_subscribe_cb)(SESSION_T *session, void *context);

/**
 * @brief Callback handlers that may be registered to deal with
 * messages sent by Diffusion in response to the subscription request.
 */
#define SUBSCRIPTION_HANDLERS                                                      \
    /** A callback for when a topic message has been received. */                  \
    TOPIC_HANDLER_T on_topic_message;                                              \
    /** A callback to indicate that the subscription request has been received. */ \
    on_subscribe_cb on_subscribe;                                                  \
    /** Standard service error callback. */                                        \
    ERROR_HANDLER_T on_error;                                                      \
    /** Standard service discard callback. */                                      \
    DISCARD_HANDLER_T on_discard;

typedef struct subscription_handlers_s
{
    SUBSCRIPTION_HANDLERS
} SUBSCRIPTION_HANDLERS_T;

/**
 * @brief Structure supplied when subscribing to a topic.
 */
typedef struct subscription_params_s
{
    /// Callback handlers.
    SUBSCRIPTION_HANDLERS

    /// Topic selected describing which topics to subscribe to.
    const char *topic_selector;

    /// User-supplied context return to callbacks.
    void *context;
} SUBSCRIPTION_PARAMS_T;

/**
 * @brief Structure describing an incoming notification that the
 * client has been subscribed to a topic.
 */
typedef struct svc_notify_subscription_request_s
{
    /// Topic identification.
    struct
    {
        /// Internal identifier for the topic.
        uint32_t topic_id;
        /// Name of the topic which the client was subscribed
        /// to.
        char *topic_path;
    } topic_info;

    /// Description of the structure of the topic.
    TOPIC_SPECIFICATION_T *topic_specification;
} SVC_NOTIFY_SUBSCRIPTION_REQUEST_T;

/**
 * @brief Callback for notify_subscription_register().
 *
 * @param session The current active session.
 * @param request The incoming notification request.
 * @param context User-supplied context from the initial registration call.
 *
 * @return HANDLER_SUCCESS or HANDLER_FAILURE.
 */
typedef int (*on_notify_subscription_cb)(SESSION_T *session, const SVC_NOTIFY_SUBSCRIPTION_REQUEST_T *request, void *context);

/**
 * @brief Callback handlers for subscription notification messages.
 */
#define NOTIFY_SUBSCRIPTION                                                   \
    /** Callback fired when a subscription notification has been received. */ \
    on_notify_subscription_cb on_notify_subscription;                         \
    /** Standard service error callback. */                                   \
    ERROR_HANDLER_T on_error;

typedef struct notify_subscription_handlers_s
{
    NOTIFY_SUBSCRIPTION
} NOTIFY_SUBSCRIPTION_HANDLERS_T;

/**
 * @brief Structure supplied when registering to receive topic
 * subscription notifications.
 */
typedef struct notify_subscription_params_s
{
    NOTIFY_SUBSCRIPTION
    /// User-supplied context returned to callbacks.
    void *context;
} NOTIFY_SUBSCRIPTION_PARAMS_T;

/**
 * @brief Callback for on_unsubscribe().
 *
 * @param session The current active session.
 * @param context User-supplied context from the initial unsubscribe() call.
 *
 * @return HANDLER_SUCCESS or HANDLER_FAILURE.
 */
typedef int (*on_unsubscribe_cb)(SESSION_T *session, void *context);

/**
 * @brief Callback handlers that may be registered to deal with
 * messages sent by Diffusion in response to the unsubscription
 * request.
 */
#define UNSUBSCRIPTION_HANDLERS                                                      \
    /** A callback to indicate that the unsubscription request has been received. */ \
    on_unsubscribe_cb on_unsubscribe;                                                \
    /** Standard error handler callback. */                                          \
    ERROR_HANDLER_T on_error;                                                        \
    /** Standard discard handler callback. */                                        \
    DISCARD_HANDLER_T on_discard;

typedef struct unsubscription_handlers_s
{
    UNSUBSCRIPTION_HANDLERS
} UNSUBSCRIPTION_HANDLERS_T;

/**
 * @brief Structure supplied when unsubscribing from a topic.
 */
typedef struct unsubscription_params_s
{
    UNSUBSCRIPTION_HANDLERS

    /// Topic selector describing which topics to unsubscribe from.
    const char *topic_selector;

    /// User-supplied context returned to callbacks.
    void *context;
} UNSUBSCRIPTION_PARAMS_T;

/**
 * @brief Structure supplied when unsubscribing from a topic for all scopes.
 *
 * @since 6.12
 */
typedef UNSUBSCRIPTION_PARAMS_T UNSUBSCRIPTION_ALL_SCOPES_PARAMS_T;

/**
 * @brief Reason for unsubscription.
 */
typedef enum
{
    /// Unsubscribed by the subscribing client.
    ///
    /// @since 5.6
    UNSUBSCRIPTION_REASON_REQUESTED = 0,

    /// The unsubscription was requested either by another client
    /// or by the server.
    ///
    /// @since 5.6
    UNSUBSCRIPTION_REASON_CONTROL = 1,

    /// The unsubscription occurred because the topic was removed.
    ///
    /// @since 5.6
    UNSUBSCRIPTION_REASON_REMOVAL = 2,

    /// The unsubscription occurred because the session is no longer
    /// authorized to access the topic.
    ///
    /// @since 5.9
    UNSUBSCRIPTION_REASON_AUTHORIZATION = 3,

    /// A reason that is unsupported by the session.
    ///
    /// @since 6.1
    UNSUBSCRIPTION_REASON_UNKNOWN_UNSUBSCRIBE_REASON = 4,

    /// The server has a significant backlog of messages for the session,
    /// and the topic specification has the
    /// `DIFFUSION_CONFLATION` topic property set to "unsubscribe".
    /// The session can resubscribe to the topic. The unsubscription
    /// is not persisted to the cluster, if the session fails
    /// over to a different server it will be resubscribed to the topic.
    ///
    /// @since 6.1
    UNSUBSCRIPTION_REASON_BACK_PRESSURE = 5,

    /// The unsubscription occurred because branch mapping rules changed.
    ///
    /// @since 6.7
    UNSUBSCRIPTION_REASON_BRANCH_MAPPINGS = 6,

    /// The server has re-subscribed this session to the topic. Existing
    /// streams are unsubscribed because the topic type and other
    /// attributes may have changed.
    ///
    /// This can happen if a set of servers is configured to use session
    /// replication, and a session connected to one server reconnects
    /// ("fails over") to a different server.
    ///
    /// A stream that receives an unsubscription notification with this
    /// reason will also receive a subscription notification with the new
    /// {@link TOPIC_SPECIFICATION_T topic specification}.
    ///
    /// @since 5.9
    UNSUBSCRIPTION_REASON_SUBSCRIPTION_REFRESH = 0xfffffff0,

    /// A fallback stream has been unsubscribed due to the addition of a
    /// stream that selects the topic.
    ///
    /// @since 5.9
    UNSUBSCRIPTION_REASON_STREAM_CHANGE = 0xfffffff1
} NOTIFY_UNSUBSCRIPTION_REASON_T;

/**
 * @brief Structure describing an incoming unsubscription
 * notification.
 */
typedef struct svc_notify_unsubscription_request_s
{
    /// The internal identifier of the topic.
    uint32_t topic_id;
    /// The topic path, or NULL if not available.
    char *topic_path;
    /// The reason why the unsubscription happened.
    NOTIFY_UNSUBSCRIPTION_REASON_T reason;
} SVC_NOTIFY_UNSUBSCRIPTION_REQUEST_T;

/**
 * @brief Callback for notify_unsubscription_register.
 *
 * @param session The current active session.
 * @param request The incoming notification message.
 * @param context User-supplied context from the initial registration call.
 *
 * @return HANDLER_SUCCESS or HANDLER_FAILURE.
 */
typedef int (*on_notify_unsubscription_cb)(SESSION_T *session, const SVC_NOTIFY_UNSUBSCRIPTION_REQUEST_T *request, void *context);

/**
 * @brief Callback handlers for unsubscription notification callbacks.
 */
#define NOTIFY_UNSUBSCRIPTION                                                  \
    /** Callback fired an an unsubscription notification has been received. */ \
    on_notify_unsubscription_cb on_notify_unsubscription;                      \
    /** Standard service error callback. */                                    \
    ERROR_HANDLER_T on_error;

typedef struct notify_unsubscription_handlers_s
{
    NOTIFY_UNSUBSCRIPTION
} NOTIFY_UNSUBSCRIPTION_HANDLERS_T;

/**
 * @brief Structure supplied when registering to receive topic
 * unsubscription notifications.
 */
typedef struct notify_unsubscription_params_s
{
    NOTIFY_UNSUBSCRIPTION
    /// User-supplied context returned to callbacks.
    void *context;
} NOTIFY_UNSUBSCRIPTION_PARAMS_T;

/**
 * @ingroup PublicAPI_Topics
 *
 * @brief Request subscription to topic
 *
 * This is equivalent to calling `diffusion_subscribe_with_scope` specifying `DEFAULT_SELECTION_SCOPE`.
 *
 * @param session	The session handle. If NULL, the function returns immediately.
 *
 * @param params    Parameters describing the subscription request and
 *                  callbacks handlers which may be invoked in response.
 *
 * @return          Previous topic handler if replacing, else NULL.
 */
TOPIC_HANDLER_T subscribe(SESSION_T *session, const SUBSCRIPTION_PARAMS_T params);

/**
 * @ingroup PublicAPI_Topics
 *
 * @brief Request subscription to topics using a scope selection.
 *
 * The session will become subscribed to each existing topic matching the selector unless
 * the session is already subscribed to the topic, or the session does not have `READ_TOPIC`
 * permission for the topic path.
 * For each topic to which the session becomes subscribed, a subscription notification and
 * initial value (if any) will be delivered to registered value streams.
 *
 * The subscription request is also retained at the server and the session will be automatically
 * subscribed to newly created topics that match the selector (unless a subsequent unsubscription
 * cancels the request).
 *
 * @param session           The session handle. If NULL, this function returns immediately.
 *
 * @param selection_scope   Specifies the scope of the selection.
 *
 * @param params  	        Parameters describing the subscription request and
 *                          callbacks handlers which may be invoked in response.
 *
 * @param api_error	        Populated on API error. Can be NULL.
 *
 * @return 	true if the operation was successful, false otherwise.
 * 			In this case, if a non-NULL `api_error` pointer has been provided,
 * 			this will be populated with the error information and should be freed
 * 			with `diffusion_api_error_free`.
 *
 * @since 6.12
 */
TOPIC_HANDLER_T
diffusion_subscribe_with_scope(
    SESSION_T *session,
    const char *selection_scope,
    const SUBSCRIPTION_PARAMS_T params,
    DIFFUSION_API_ERROR *api_error);

/**
 * @ingroup PublicAPI_Topics
 *
 * @brief Unsubscribe from one or more topics.
 *
 * This is equivalent to calling `diffusion_unsubscribe_with_scope` specifying `DEFAULT_SELECTION_SCOPE`.
 *
 * @param session     The session handle. If NULL, this function returns immediately.
 *
 * @param params      Parameters describing the unsubscription request and
 *                    callback handlers which may be invoked in response.
 */
void unsubscribe(SESSION_T *session, const UNSUBSCRIPTION_PARAMS_T params);

/**
 * @ingroup PublicAPI_Topics
 *
 * @brief Unsubscribe from topics using a selection scope.
 *
 * This can be used at any time whilst connected to reduce the set of topics
 * to which the session is subscribed or negate earlier subscription
 * requests.
 *
 * @param session           The session handle. If NULL, this function returns immediately.
 *
 * @param selection_scope   Specifies the scope of the selection.
 *
 * @param params  	        Parameters describing the unsubscription request and
 *                          callback handlers which may be invoked in response.
 *
 * @param api_error	        Populated on API error. Can be NULL.
 *
 * @return 	true if the operation was successful, false otherwise.
 * 			In this case, if a non-NULL `api_error` pointer has been provided,
 * 			this will be populated with the error information and should be freed
 * 			with `diffusion_api_error_free`.
 *
 * @since 6.12
 */
bool diffusion_unsubscribe_with_scope(
    SESSION_T *session,
    const char *selection_scope,
    const UNSUBSCRIPTION_PARAMS_T params,
    DIFFUSION_API_ERROR *api_error);

/**
 * @ingroup PublicAPI_Topics
 *
 * @brief Unsubscribe topics from all topic selection scopes.
 *
 * This can be used at any time whilst connected to reduce the set of topics
 * to which the session is subscribed or negate earlier subscription
 * requests and will apply to all scopes in use.
 *
 * @param session     The session handle. If NULL, this function returns immediately.
 *
 * @param params      Parameters describing the unsubscription request and
 *                    callback handlers which may be invoked in response.
 *
 * @param api_error   Populated on API error. Can be NULL.
 *
 * @return 	true if the operation was successful, false otherwise.
 * 			In this case, if a non-NULL `api_error` pointer has been provided,
 * 			this will be populated with the error information and should be freed
 * 			with `diffusion_api_error_free`.
 *
 * @since 6.12
 */
bool diffusion_unsubscribe_all_scopes(SESSION_T *session, const UNSUBSCRIPTION_ALL_SCOPES_PARAMS_T params, DIFFUSION_API_ERROR *api_error);

/**
 * @ingroup PublicAPI_Topics
 *
 * @brief Register to receive subscription notifications.
 *
 * This function should be called by the application to register to
 * receive notifications that it has been subscribed to a topic.
 *
 * @param session	The current session. If NULL, this function returns immediately.
 *
 * @param params	Callbacks and other parameters. {@link NOTIFY_SUBSCRIPTION_PARAMS_T on_notify_subscription}
 *					should be set to receive notifications.
 */
void notify_subscription_register(SESSION_T *session, const NOTIFY_SUBSCRIPTION_PARAMS_T params);

/**
 * @ingroup PublicAPI_Topics
 *
 * @brief Register to receive unsubscription notifications.
 *
 * This function should be called by the application to receive
 * notifications that it has been unsubscribed to a topic.
 *
 * @param session	The current session. If NULL, this function returns immediately.
 *
 * @param params	Callbacks and other parameters. `on_notify_unsubscription`
 *					should be set to receive notifications.
 */
void notify_unsubscription_register(SESSION_T *session, const NOTIFY_UNSUBSCRIPTION_PARAMS_T params);

#endif
