<?php
defined( 'ABSPATH' ) || exit;

if ( !class_exists( 'Better_Messages_Rest_Api' ) ):

    class Better_Messages_Rest_Api
    {

        public static function instance()
        {

            static $instance = null;

            if ( null === $instance ) {
                $instance = new Better_Messages_Rest_Api();
            }

            return $instance;
        }

        public function __construct()
        {
            require_once 'api/search.php';
            Better_Messages_Search();

            require_once('api/db-migrate.php');
            Better_Messages_Rest_Api_DB_Migrate();

            require_once('api/conversations.php');
            Better_Messages_Rest_Api_Conversations();

            require_once('api/push-notifications.php');
            Better_Messages_Rest_Push_Notifications();

            require_once('api/friends.php');
            Better_Messages_Rest_Friends();

            require_once('api/groups.php');
            Better_Messages_Rest_Groups();

            require_once('api/courses.php');
            Better_Messages_Rest_Courses();

            require_once('api/users.php');
            Better_Messages_Rest_Users();

            require_once('api/favorited.php');
            Better_Messages_Rest_Api_Favorited();

            require_once('api/admin.php');
            Better_Messages_Rest_Api_Admin();

            require_once('api/live-chat-builder.php');
            Better_Messages_Live_Chat_Builder();

            require_once('api/bulk-message.php');
            Better_Messages_Rest_Api_Bulk_Message();

            add_action( 'rest_api_init',  array( $this, 'rest_api_init' ) );

            add_action( 'wp_ajax_better_messages_new_nonce_token', array( $this, 'rest_nonce' ) );
            add_action( 'wp_ajax_nopriv_better_messages_new_nonce_token', array( $this, 'rest_nonce' ) );

            add_filter('rest_post_dispatch', array( $this, 'rest_post_dispatch'), 10 , 3 );
            //add_filter('rest_pre_serve_request', array( $this, 'serve_request' ), 10 , 4 );
        }

        /* public function serve_request( $served, $result, WP_REST_Request $request, WP_REST_Server $server )
        {
            $route = $request->get_route();
            if( str_starts_with( $route, '/better-messages/') ){
                $attributes = $request->get_attributes();

                if( isset( $attributes['callback'] ) ) {
                    $callback = $attributes['callback'];

                    if( is_array( $callback ) ) {
                        $class = $callback[0];
                        $method = $callback[1];

                        if ( is_object( $class ) ) {
                            $is_same_class = ( $class === $this );

                            if( $is_same_class ) {
                                switch ($method) {
                                    case 'checkNew':
                                        break;
                                }
                            }
                        }
                    }
                }
            }

            return $served;
        } */

        public function rest_post_dispatch( $result, $server, WP_REST_Request $request ){
            $route = $request->get_route();

            if ( ! str_starts_with( $route, '/better-messages/' ) ) {
                return $result;
            }

            // Catch unauthorized send attempts
            if ( str_ends_with( $route, '/send' ) ) {
                if ( isset( $result->data ) ) {
                    if ( isset( $result->data['code'] ) && $result->data['code'] === 'rest_cookie_invalid_nonce' ) {
                        $temp_id = $request->get_param('temp_id');

                        if ( $temp_id ) {
                            $temp_id_explode = explode('_', $temp_id);
                            $thread_id = (int) $temp_id_explode[1];

                            do_action( 'better_messages_on_message_not_sent', $thread_id, $temp_id, [] );
                        }
                    }
                }
            }

            /**
             * Filter to add custom headers to all Better Messages REST API responses.
             *
             * @param array           $headers  Associative array of header name => value.
             * @param WP_REST_Response $result   The response object.
             * @param WP_REST_Request  $request  The request object.
             */
            $headers = apply_filters( 'better_messages_rest_response_headers', [], $result, $request );

            foreach ( $headers as $name => $value ) {
                $result->header( $name, $value );
            }

            return $result;
        }

        public function rest_nonce(){
            wp_send_json([
                'user_id' => Better_Messages()->functions->get_current_user_id(),
                'nonce'   => wp_create_nonce( 'wp_rest' )
            ]);
        }

        public function rest_api_init(){
            register_rest_route( 'better-messages/v1', '/ping', array(
                'methods' => 'GET',
                'callback' => array( $this, 'ping' ),
                'permission_callback' => '__return_true'
            ) );

            register_rest_route( 'better-messages/v1', '/checkNew', array(
                'methods' => 'POST',
                'callback' => array( $this, 'checkNew' ),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ) );

            register_rest_route( 'better-messages/v1', '/openThreads', array(
                'methods' => 'POST',
                'callback' => array( $this, 'openThreads' ),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ) );

            register_rest_route( 'better-messages/v1', '/markAllRead', array(
                'methods' => 'GET',
                'callback' => array( $this, 'markAllRead' ),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ) );

            register_rest_route( 'better-messages/v1', '/threads', array(
                'methods' => 'GET',
                'callback' => array( $this, 'get_threads' ),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ) );

            register_rest_route( 'better-messages/v1', '/threads', array(
                'methods' => 'POST',
                'callback' => array( $this, 'get_threads' ),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ) );

            register_rest_route( 'better-messages/v1', '/thread/(?P<id>\d+)', array(
                'methods' => 'POST',
                'callback' => array( $this, 'get_thread' ),
                'permission_callback' => array( $this, 'check_thread_access' ),
                'args' => array(
                    'id' => array(
                        'validate_callback' => function($param, $request, $key) {
                            return is_numeric( $param );
                        }
                    ),
                ),
            ) );

            register_rest_route( 'better-messages/v1', '/thread/(?P<id>\d+)/searchParticipants', array(
                'methods' => 'POST',
                'callback' => array( $this, 'search_participants' ),
                'permission_callback' => array( $this, 'check_thread_access' ),
                'args' => array(
                    'id' => array(
                        'validate_callback' => function($param, $request, $key) {
                            return is_numeric( $param );
                        }
                    ),
                ),
            ) );

            register_rest_route( 'better-messages/v1', '/thread/(?P<id>\d+)/loadMore', array(
                'methods' => 'POST',
                'callback' => array( $this, 'load_more' ),
                'permission_callback' => array( $this, 'check_thread_access' ),
                'args' => array(
                    'id' => array(
                        'validate_callback' => function($param, $request, $key) {
                            return is_numeric( $param );
                        }
                    ),
                ),
            ) );

            register_rest_route( 'better-messages/v1', '/suggestions', array(
                'methods' => 'GET',
                'callback' => array( $this, 'get_suggestions' ),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ) );

            register_rest_route( 'better-messages/v1', '/userSuggestions', array(
                'methods' => 'GET',
                'callback' => array( $this, 'get_user_suggestions' ),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ) );

            if( Better_Messages()->settings['allowMuteThreads'] === '1' ) {
                register_rest_route('better-messages/v1', '/thread/(?P<id>\d+)/mute', array(
                    'methods' => 'POST',
                    'callback' => array($this, 'mute_thread'),
                    'permission_callback' => array($this, 'check_thread_access'),
                    'args' => array(
                        'id' => array(
                            'validate_callback' => function ($param, $request, $key) {
                                return is_numeric($param);
                            }
                        ),
                    ),
                ));

                register_rest_route('better-messages/v1', '/thread/(?P<id>\d+)/unmute', array(
                    'methods' => 'POST',
                    'callback' => array($this, 'unmute_thread'),
                    'permission_callback' => array($this, 'check_thread_access'),
                    'args' => array(
                        'id' => array(
                            'validate_callback' => function ($param, $request, $key) {
                                return is_numeric($param);
                            }
                        ),
                    ),
                ));
            }

            register_rest_route('better-messages/v1', '/thread/suggest', array(
                'methods' => 'POST',
                'callback' => array($this, 'suggest_thread'),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ));

            register_rest_route('better-messages/v1', '/getPrivateThread', array(
                'methods' => 'POST',
                'callback' => array($this, 'get_pm_thread'),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ));

            register_rest_route('better-messages/v1', '/thread/new', array(
                'methods' => 'POST',
                'callback' => array($this, 'start_new_thread'),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ));

            register_rest_route('better-messages/v1', '/thread/(?P<id>\d+)/send', array(
                'methods' => 'POST',
                'callback' => array($this, 'send_message'),
                'permission_callback' => array($this, 'can_reply'),
                'args' => array(
                    'id' => array(
                        'validate_callback' => function ($param, $request, $key) {
                            return is_numeric($param);
                        }
                    ),
                ),
            ));

            register_rest_route('better-messages/v1', '/thread/(?P<id>\d+)/save', array(
                'methods' => 'POST',
                'callback' => array($this, 'save_message'),
                'permission_callback' => array($this, 'can_reply'),
                'args' => array(
                    'id' => array(
                        'validate_callback' => function ($param, $request, $key) {
                            return is_numeric($param);
                        }
                    ),
                ),
            ));

            register_rest_route('better-messages/v1', '/thread/(?P<id>\d+)/deleteMessages', array(
                'methods' => 'POST',
                'callback' => array($this, 'delete_messages'),
                'permission_callback' => array($this, 'check_thread_access'),
                'args' => array(
                    'id' => array(
                        'validate_callback' => function ($param, $request, $key) {
                            return is_numeric($param);
                        }
                    ),
                ),
            ));

            register_rest_route('better-messages/v1', '/thread/(?P<id>\d+)/delete', array(
                'methods' => 'POST',
                'callback' => array($this, 'delete_thread'),
                'permission_callback' => array($this, 'check_thread_access'),
                'args' => array(
                    'id' => array(
                        'validate_callback' => function ($param, $request, $key) {
                            return is_numeric($param);
                        }
                    ),
                ),
            ));

            register_rest_route('better-messages/v1', '/thread/(?P<id>\d+)/restore', array(
                'methods' => 'POST',
                'callback' => array($this, 'restore_thread'),
                'permission_callback' => array($this, 'is_user_authorized'),
                'args' => array(
                    'id' => array(
                        'validate_callback' => function ($param, $request, $key) {
                            return is_numeric($param);
                        }
                    ),
                ),
            ));

            register_rest_route( 'better-messages/v1', '/lazyPool', array(
                'methods' => 'POST',
                'callback' => array( $this, 'lazy_pool' ),
                'permission_callback' => array( $this, 'is_user_authorized' )
            ) );

            register_rest_route( 'better-messages/v1', '/publicProfiles', array(
                'methods' => 'POST',
                'callback' => array( $this, 'public_profiles' ),
                'permission_callback' => '__return_true'
            ) );

            if( ! empty( trim(Better_Messages()->settings['badWordsList']) ) ) {
                register_rest_route('better-messages/v1', '/getBlockList', array(
                    'methods' => 'GET',
                    'callback' => array($this, 'get_block_list'),
                    'permission_callback' => array($this, 'is_user_authorized')
                ));
            }

            register_rest_route( 'better-messages/v1', '/getUniqueConversation', array(
                'methods' => 'POST',
                'callback' => array( $this, 'get_unique_conversation' ),
                'permission_callback' => '__return_false'

                //'permission_callback' => array( $this, 'is_user_authorized' )
            ) );

            if( Better_Messages()->settings['enableForwardMessages'] === '1' ) {
                register_rest_route('better-messages/v1', '/threadsPicker', array(
                    'methods' => 'POST',
                    'callback' => array($this, 'get_threads_picker'),
                    'permission_callback' => array($this, 'is_user_authorized')
                ));

                register_rest_route('better-messages/v1', '/message/(?P<message_id>\d+)/forward', array(
                    'methods' => 'POST',
                    'callback' => array($this, 'forward_message'),
                    'permission_callback' => array($this, 'is_user_authorized'),
                    'args' => array(
                        'message_id' => array(
                            'validate_callback' => function ($param, $request, $key) {
                                return is_numeric($param);
                            }
                        ),
                    ),
                ));
            }
        }

        public function get_unique_conversation( WP_REST_Request $request ){
            $thread_id = apply_filters('better_messages_get_unique_conversation', 0, sanitize_text_field( $request->get_param('key') ), Better_Messages()->functions->get_current_user_id() );

            // Skip E2E threads when E2E is disabled globally
            if ( $thread_id
                && Better_Messages()->settings['e2eEncryption'] !== '1'
                && class_exists( 'Better_Messages_E2E_Encryption' )
                && Better_Messages_E2E_Encryption::is_e2e_thread( $thread_id )
            ) {
                $thread_id = 0;
            }

            if( $thread_id ){
                $return = $this->get_threads([ $thread_id ]);
                $return['thread_id'] = $thread_id;
                return $return;
            }

            return false;
        }

        public function get_block_list( WP_REST_Request $request ){
            return array_map('trim', explode("\n", Better_Messages()->settings['badWordsList']));
        }

        public function is_user_authorized( WP_REST_Request $request ){
            if( is_user_logged_in() ) {
                return true;
            }

            return apply_filters('better_messages_rest_is_user_authorized', false, $request );
        }

        public function lazy_pool(WP_REST_Request $request){
            $users    = (array) $request->get_param('users');
            $messages = (array) $request->get_param('messages');

            $current_user_id = Better_Messages()->functions->get_current_user_id();

            $return = [];

            if( count( $messages ) > 0 ) {
                $return['messages'] = [];
                foreach ( $messages as $message_id ){
                    $message = Better_Messages()->functions->get_message( $message_id );
                    if( $message ){
                        $has_access = Better_Messages()->functions->check_access( $message->thread_id, $current_user_id )
                            && Better_Messages()->functions->can_read_chat_messages( $message->thread_id, $current_user_id );
                        if( $has_access ){
                            $message->message_id = (int) $message->id;
                            unset( $message->id );
                            $_user_id            = (int) $message->sender_id;
                            $_thread_id          = (int) $message->thread_id;
                            $message->updated_at = (int) $message->updated_at;
                            $message->created_at = (int) $message->created_at;

                            $message->sender_id  = $_user_id;
                            $message->thread_id  = $_thread_id;

                            $meta                = apply_filters('better_messages_rest_message_meta', [], (int) $message->message_id, (int) $message->thread_id, $message->message );
                            if( empty( $meta ) ) $meta = (object) [];
                            $message->meta       = $meta;

                            $message->favorited = (Better_Messages()->functions->is_message_starred($message->message_id, $current_user_id)) ? 1 : 0;
                            $message->message   = Better_Messages()->functions->format_message($message->message, (int) $message->message_id, 'stack', $current_user_id);

                            $return['messages'][] = $message;
                        }
                    }
                }
            }

            if( count( $users ) > 0 ) {
                $return['users'] = [];
                foreach ( $users as $user_id ){
                    $item = Better_Messages()->functions->rest_user_item( $user_id );

                    $return['users'][] = $item;
                }
            }

            return $return;
        }

        public function public_profiles( WP_REST_Request $request ){
            $return = [ 'users' => [] ];

            if( ! Better_Messages()->guests->guest_access_enabled() ){
                return $return;
            }

            $users = (array) $request->get_param('users');
            $users = array_slice( array_values( array_unique( array_map( 'intval', $users ) ) ), 0, 100 );

            foreach ( $users as $user_id ){
                if( $user_id === 0 ) continue;
                if( ! Better_Messages()->functions->is_user_exists( $user_id ) ) continue;

                $return['users'][] = Better_Messages()->functions->rest_user_item( $user_id, false );
            }

            return $return;
        }

        public function markAllRead(){
            global $wpdb;

            $user_id = Better_Messages()->functions->get_current_user_id();

            $time = Better_Messages()->functions->get_microtime();
            $mysql_time = bp_core_current_time();

            $wpdb->query($wpdb->prepare(
                "UPDATE " . bm_get_table('recipients') . " 
                SET unread_count = 0, last_read = %s, last_delivered = %s, last_update = %d
                WHERE user_id = %d", $mysql_time, $mysql_time, $time, $user_id));

            do_action( 'better_messages_mark_all_read', $user_id );

            return true;
        }

        public function openThreads( WP_REST_Request $request ){
            $thread_ids = (array) $request->get_param('thread_ids');

            if( count( $thread_ids ) > 0 ){
                foreach ( $thread_ids as $thread_id ){
                    Better_Messages()->functions->messages_mark_thread_read( $thread_id, Better_Messages()->functions->get_current_user_id() );
                }
            }

            return true;
        }

        public function checkNew( WP_REST_Request $request ) {
            $lastClient = intval( $request->get_param( 'lastUpdate' ) );
            if( $lastClient < 0 ) $lastClient = 0;

            $current_user_id = Better_Messages()->functions->get_current_user_id();

            $time = Better_Messages()->functions->get_microtime();

            $visibleThreads = (array) $request->get_param('visibleThreads');

            if( count( $visibleThreads ) > 0 ){
                foreach ( $visibleThreads as $thread_id ){
                    Better_Messages()->functions->messages_mark_thread_read( $thread_id, Better_Messages()->functions->get_current_user_id() );
                }
            }

            if( $lastClient > 0 ) {
                $return = $this->get_messages(null, $lastClient);
            } else {
                $return = $this->get_threads( $request );
            }

            if( ! isset( $return['threads'] ) ) $return['threads'] = [];
            if( ! isset( $return['users'] ) ) $return['users'] = [];
            if( ! isset( $return['messages'] ) ) $return['messages'] = [];

            if( $lastClient > 0 ) {
                $changedThreads = $this->get_changed_threads($lastClient, $current_user_id);

                if (count($changedThreads) > 0) {
                    $thread_ids = array_column($return['threads'], 'thread_id');

                    foreach ($changedThreads as $index => $thread_id) {
                        if ( in_array($thread_id, $thread_ids) ) unset($changedThreads[$index]);
                    }

                    if (count($changedThreads) > 0) {
                        $deletedThreads = $changedThreads;

                        $threads = $this->get_threads($changedThreads, false);

                        $return['threads'] = array_merge( $return['threads'], $threads['threads'] );
                        $return['users'] = array_merge( $return['users'], $threads['users'] );

                        $thread_ids = array_column( $return['threads'], 'thread_id' );

                        foreach ($deletedThreads as $index => $thread_id) {
                            if ( in_array( $thread_id, $thread_ids ) ) unset($deletedThreads[$index]);
                        }

                        if (count($deletedThreads) > 0) {
                            $return['deletedThreads'] = array_values($deletedThreads);
                        }
                    }
                }
            }

            $alreadyRequestedThreads =  array_column($return['threads'], 'thread_id');
            $clientThreads = array_unique(array_merge($alreadyRequestedThreads, (array) $request->get_param( 'threadIds' )));

            if( $clientThreads ){
                /**
                 * Threads existing on client, but not available in database anymore
                 */
                $missedThreads = $this->check_missing_threads( $current_user_id, $clientThreads );

                if( count( $missedThreads ) > 0 ){
                    $return['missedThreads'] = $missedThreads;
                }

                $unloadedThreads = $this->check_unloaded_threads($current_user_id, $clientThreads);
            } else {
                $unloadedThreads = $this->check_unloaded_threads($current_user_id, []);
            }

            if( count( $unloadedThreads ) > 0 ){
                $getUnloadedThreads = $this->get_threads($unloadedThreads);

                $return['threads'] = array_merge($return['threads'], $getUnloadedThreads['threads']);
                $return['users'] = array_merge($return['users'], $getUnloadedThreads['users']);
                $return['messages'] = array_merge($return['messages'], $getUnloadedThreads['messages']);
            }

            if( $lastClient > 0 ) {
                $return['deletedMessages'] = $this->get_deleted_messages($lastClient);

                if (count($return['deletedMessages']) > 0) {
                    $message_ids = array_column($return['messages'], 'message_id');
                    foreach ($return['deletedMessages'] as $index => $message_id) {
                        if (in_array($message_id, $message_ids)) {
                            unset($return['deletedMessages'][$index]);
                        }
                    }
                }

                if (count($return['deletedMessages']) === 0) unset($return['deletedMessages']);

                /*$return['erasedThreads'] = $this->get_erased_threads($lastClient);
                if (count($return['erasedThreads']) === 0) unset($return['erasedThreads']);*/

                $changedUsers = $this->get_changed_users( $lastClient );

                if( count( $changedUsers ) > 0 ) {
                    foreach ($changedUsers as $user_id) {
                        $user_ids = array_column($return['users'], 'user_id');

                        if( ! in_array( $user_id, $user_ids ) ){
                            $return['users'][] = Better_Messages()->functions->rest_user_item( $user_id );
                        }
                    }
                }
            }

            $return['currentTime'] = $time;

            // Opt-in kill-switch. Hook this filter and return 'websocket' to tell
            // fallback-polling clients to stop hitting REST (e.g. under high WP load).
            $transport = apply_filters( 'better_messages_checknew_transport', null, $current_user_id );
            if ( $transport === 'websocket' ) {
                $return['transport'] = 'websocket';
            }

            return apply_filters( 'better_messages_rest_api_update_data', $return, $current_user_id, $lastClient );
        }

        public function start_new_thread( WP_REST_Request $request ){
            $recipients = (array) $request->get_param( 'recipients');

            // Allow addons (E2E) to intercept content/subject before sanitization.
            // Returns array with 'subject' and 'content' keys, or null for default sanitization.
            $e2e_init = apply_filters( 'better_messages_new_thread_e2e_init', null, $request, $recipients );

            if ( $e2e_init ) {
                $subject = $e2e_init['subject'];
                $content = $e2e_init['content'];
            } else {
                $subject = sanitize_text_field( $request->get_param( 'subject') );
                $content = Better_Messages()->functions->filter_message_content( $request->get_param('message') );
            }

            $uploaded_files = $request->get_param('files');

            if ( ! is_array($uploaded_files) || array_filter($uploaded_files, 'is_numeric') !== $uploaded_files ) {
                $uploaded_files = false;
            }

            $args = array(
                'subject'       => $subject,
                'content'       => $content,
                'recipients'    => $recipients,
                'return'        => 'both',
                'new_thread'    => true,
                'error_type'    => 'wp_error',
                'append_thread' => false
            );

            $current_user_id = Better_Messages()->functions->get_current_user_id();

            $is_pending = (int) Better_Messages()->moderation->is_moderation_enabled( $current_user_id, null, true );

            if( $is_pending ){
                $args['is_pending'] = $is_pending;
            }

            if( ! $e2e_init ) {
                if( trim($args['content']) == '') {
                    if( $uploaded_files ){
                        $args['content'] .= '<!-- BM-ONLY-FILES -->';
                    }
                } else if ($args['content'] === '0') {
                    $args['content'] .= ' ';
                }
            }

            if( ! empty( $uploaded_files ) ){
                $args['attachments'] = apply_filters( 'better_messages_new_thread_attachments', $uploaded_files, $request );
            }

            Better_Messages()->functions->before_new_thread_filter( $args, $errors );

            if( empty( $errors ) ){
                $sent = Better_Messages()->functions->new_message( $args );

                if ( is_wp_error( $sent ) ) {
                    $errors[] = $sent->get_error_message();
                }
            }

            if( ! empty( $errors ) ) {
                return array(
                    'result' => false,
                    'errors' => $errors
                );
            } else {
                // Allow addons (E2E) to process after thread creation (e.g. send actual encrypted message)
                // Note: the bp_better_messages_new_thread_created action is fired from within new_message()
                $sent = apply_filters( 'better_messages_new_thread_after_create', $sent, $request, $current_user_id );

                return array(
                    'result'     => true,
                    'thread_id'  => $sent['thread_id'],
                    'message_id' => $sent['message_id']
                );
            }
        }

        public function suggest_thread( WP_REST_Request $request ){
            $current_user_id = Better_Messages()->functions->get_current_user_id();
            $recipients = (array) $request->get_param( 'recipients');
            $forceNew   = (bool) $request->get_param( 'forceNew');

            if( count( $recipients ) === 0 ) return false;

            if( ! $forceNew && count( $recipients ) === 1 ){
                return Better_Messages()->functions->get_pm_thread_id( (int) $recipients[0], Better_Messages()->functions->get_current_user_id(), false );
            }

            return Better_Messages()->functions->can_start_conversation($current_user_id, $recipients);
        }

        public function get_pm_thread( WP_REST_Request $request ){
            $user_id   = intval( $request->get_param('user_id') );
            $create    = boolval( $request->get_param('create') );
            $subject   = trim( sanitize_text_field( urldecode( $request->get_param('subject') ) ) );
            $object_id = intval( $request->get_param('objectId') );

            $uniqueKeyParam = $request->get_param('uniqueKey');
            $uniqueKey = $uniqueKeyParam !== null ? trim( sanitize_text_field( urldecode( $request->get_param('uniqueKey') ) ) ) : '';

            if( empty( $uniqueKey ) ){
                $result = Better_Messages()->functions->get_pm_thread_id( $user_id, Better_Messages()->functions->get_current_user_id(), $create, $subject );
            } else {
                $result = Better_Messages()->functions->get_unique_pm_thread_id( $uniqueKey, $user_id, Better_Messages()->functions->get_current_user_id(), $create, $subject );
            }

            $result = apply_filters( 'better_messages_private_thread_result', $result, $user_id, Better_Messages()->functions->get_current_user_id() );

            if( $result['result'] === 'thread_created' && $object_id > 0 && isset( $result['thread_id'] ) ){
                $thread_id = (int) $result['thread_id'];
                $existing  = Better_Messages()->functions->get_thread_meta( $thread_id, 'context_post_id' );
                if( empty( $existing ) ){
                    Better_Messages()->functions->update_thread_meta( $thread_id, 'context_post_id', $object_id );
                }
            }

            if( $result['result'] === 'thread_found' || $result['result'] === 'thread_created' ){
                $thread_id = (int) $result['thread_id'];

                $thread_data = $this->get_threads( [ $thread_id ] );

                if( isset( $thread_data['threads'] ) )  $result['threads'] = $thread_data['threads'];
                if( isset( $thread_data['users'] ) )    $result['users'] = $thread_data['users'];
                if( isset( $thread_data['messages'] ) ) $result['messages'] = $thread_data['messages'];
            }

            return $result;
        }

        public function delete_messages( WP_REST_Request $request ){
            $messages_ids = array_map( 'absint', $request->get_param( 'messageIds') );

            $user_id = Better_Messages()->functions->get_current_user_id();

            $replaceMethod = Better_Messages()->settings['deleteMethod'] === 'replace';

            $deleted_messages = [];
            $hard_deleted_messages = [];
            $errors = [];

            foreach( $messages_ids as $message_id ){
                $message = new BM_Messages_Message( $message_id );

                $canDelete = Better_Messages()->settings['allowDeleteMessages'] === '1' && $message->sender_id === $user_id;

                if( ! $canDelete ) {
                    if ( Better_Messages()->functions->is_thread_super_moderator($user_id, $message->thread_id) ) {
                        $canDelete = true;
                    } else if ( Better_Messages()->functions->is_thread_moderator($message->thread_id, $user_id) ) {
                        if ( ! Better_Messages()->functions->is_thread_super_moderator($message->sender_id, $message->thread_id) ) {
                            $canDelete = true;
                        } else {
                            $errors[] = _x('You are not allowed to delete message of super moderator.', 'Rest API Error', 'bp-better-messages');
                        }
                    }
                }

                if( $canDelete ){
                    $is_system = $message->sender_id === 0 && is_string( $message->message ) && strpos( $message->message, '<!-- BM-SYSTEM-MESSAGE:' ) === 0;

                    Better_Messages()->functions->delete_message( $message_id, $message->thread_id );
                    $deleted_messages[] = $message_id;

                    if ( $is_system ) {
                        $hard_deleted_messages[] = $message_id;
                    }
                }
            }

            $return = [];

            if( $replaceMethod ){
                $replaced_messages = array_values( array_diff( $deleted_messages, $hard_deleted_messages ) );
                if ( ! empty( $replaced_messages ) ) {
                    $return = Better_Messages()->api->get_messages(null, $replaced_messages);
                } else {
                    $return = array( 'users' => array(), 'messages' => array() );
                }
                if ( ! empty( $hard_deleted_messages ) ) {
                    $return['deleted'] = $hard_deleted_messages;
                }
            } else {
                $return['deleted'] = $deleted_messages;
            }

            $return['errors'] = $errors;

            return $return;
        }

        public function delete_thread( WP_REST_Request $request ){
            global $wpdb;

            $thread_id  = intval($request->get_param('id'));
            $user_id    = Better_Messages()->functions->get_current_user_id();

            $has_access = (bool) $wpdb->get_var( $wpdb->prepare( "
                SELECT COUNT(*)
                FROM " . bm_get_table('recipients') . "
                WHERE `thread_id`  = %d
                AND   `user_id`    = %d
            ", $thread_id, $user_id ) );

            if( ! apply_filters( 'bp_better_messages_can_delete_thread', $has_access, $thread_id, Better_Messages()->functions->get_current_user_id() ) ){
                return new WP_Error(
                    'rest_forbidden',
                    _x( 'Sorry, you are not allowed to delete this conversation', 'Rest API Error', 'bp-better-messages' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }

            $delete_allowed = Better_Messages()->settings['restrictThreadsDeleting'] === '0';

            if( current_user_can('manage_options') ) {
                $delete_allowed = true;
            }

            if( ! $delete_allowed ){
                return new WP_Error(
                    'rest_forbidden',
                    _x( 'Sorry, you are not allowed to delete this conversation', 'Rest API Error', 'bp-better-messages' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }

            $user_id = Better_Messages()->functions->get_current_user_id();

            Better_Messages()->functions->archive_thread( $user_id, $thread_id );
        }

        public function restore_thread( WP_REST_Request $request ){
            global $wpdb;

            $user_id = Better_Messages()->functions->get_current_user_id();
            $thread_id  = intval($request->get_param('id'));

            $has_access = (bool) $wpdb->get_var( $wpdb->prepare( "
                SELECT COUNT(*)
                FROM " . bm_get_table('recipients') . "
                WHERE `thread_id`  = %d
                AND   `user_id`    = %d
            ", $thread_id, $user_id ) );

            if ( ! $has_access ) {
                return new WP_Error(
                    'rest_forbidden',
                    _x( 'Sorry, you are not allowed to restore this conversation', 'Rest API Error', 'bp-better-messages' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }

            $delete_allowed = Better_Messages()->settings['restrictThreadsDeleting'] === '0';

            if( current_user_can('manage_options') ) {
                $delete_allowed = true;
            }

            if( ! $delete_allowed ){
                return new WP_Error(
                    'rest_forbidden',
                    _x( 'Sorry, you are not allowed to restore this conversation', 'Rest API Error', 'bp-better-messages' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }

            $time = Better_Messages()->functions->get_microtime();

            $restored = $wpdb->update( bm_get_table('recipients'), array(
                'is_deleted'  => 0,
                'last_update' => $time
            ), array(
                'thread_id' => $thread_id,
                'user_id'   => $user_id
            ) );

            do_action( 'better_messages_thread_updated', $thread_id );

            return !! $restored;
        }

        public function send_message( WP_REST_Request $request ){
            $thread_id  = intval($request->get_param('id'));
            $temp_id    = sanitize_text_field($request->get_param('temp_id'));
            $temp_time  = sanitize_text_field($request->get_param('temp_time'));

            $raw_message = $request->get_param( 'message' );
            $content = apply_filters( 'better_messages_send_message_content',
                Better_Messages()->functions->filter_message_content( $raw_message ),
                $raw_message, $thread_id
            );

            $group_id = Better_Messages()->functions->get_thread_meta($thread_id, 'group_id');

            $group_thread = false;

            if( ! empty( $group_id ) ) {
                $group_thread = true;
            }

            $current_user_id = Better_Messages()->functions->get_current_user_id();

            if ( $current_user_id === 0 ) {
                do_action( 'better_messages_on_message_not_sent', $thread_id, $temp_id, [] );

                return new WP_Error(
                    'rest_anonymous_sender',
                    _x( 'Sorry, you are not allowed to reply into this conversation', 'Rest API Error', 'bp-better-messages' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }

            $is_pending = (int) Better_Messages()->moderation->is_moderation_enabled( $current_user_id, $thread_id, false );

            $args = array(
                'sender_id'    => $current_user_id,
                'content'      => $content,
                'thread_id'    => $thread_id,
                'is_pending'   => $is_pending,
                'return'       => 'message_id',
                'group_thread' => $group_thread,
                'error_type'   => 'wp_error'
            );

            $meta = (array) $request->get_param('meta');

            if( ! empty( $temp_id ) ) {
                $args['temp_id'] = $temp_id;

                if ( ! empty( $temp_time ) ) {
                    $time_diff = Better_Messages()->functions->get_microtime() - (int) $temp_time;

                    if( $time_diff < 600000 && $time_diff > -600000 ) {
                        $args['date_sent'] = gmdate( 'Y-m-d H:i:s', (int) ($temp_time / 10000) );
                        $args['created_at'] = $temp_time;
                        $args['updated_at'] = $temp_time;
                    }
                }
            }

            $uploaded_files = $request->get_param('files');

            if ( ! is_array($uploaded_files) || array_filter($uploaded_files, 'is_numeric') !== $uploaded_files ) {
                $uploaded_files = false;
            }

            if( count( $meta ) > 0 ){
                $args['meta_data'] = $meta;
            }

            if( trim($args['content']) == '') {
                if( $uploaded_files ){
                    $args['content'] .= '<!-- BM-ONLY-FILES -->';
                } else {
                    $errors['empty'] = _x('The message you were trying to send was empty', 'User tried to send empty message error', 'bp-better-messages');
                }
            } else if ($args['content'] === '0') {
                $args['content'] .= ' ';
            }

            $max_length = (int) Better_Messages()->settings['maximumMessageLength'];
            if ( $max_length > 0 && Better_Messages()->functions->message_text_length( $raw_message ) > $max_length ) {
                $errors['maxLength'] = sprintf(
                    _x( 'Message is too long. Maximum allowed length is %d characters', 'Send message error', 'bp-better-messages' ),
                    $max_length
                );
            }

            if( ! empty( $uploaded_files ) ){
                $args['attachments'] = $uploaded_files;
            }

            Better_Messages()->functions->before_message_send_filter( $args, $errors );

            // Re-read $is_pending from $args, as filters (e.g. AI moderation) may have changed it
            $is_pending = (int) ( $args['is_pending'] ?? 0 );

            if( empty( $errors ) ){
                $message_id = Better_Messages()->functions->new_message( $args );

                if ( is_wp_error( $message_id ) ) {
                    $errors[] = $message_id->get_error_message();
                } else {
                    Better_Messages()->functions->messages_mark_thread_read( $thread_id );
                }
            }

            if( ! empty( $errors ) ) {
                do_action( 'better_messages_on_message_not_sent', $thread_id, $temp_id, $errors );

                $redirect = 'refresh';

                if( count( $errors ) === 1 && ( isset( $errors['empty'] ) || isset( $errors['restrictBadWord'] ) || isset( $errors['maxLength'] ) ) ){
                    $redirect = false;
                }

                $thread = $this->get_threads( [$thread_id] );

                return array(
                    'result'   => false,
                    'errors'   => $errors,
                    'update'   => $thread,
                    'redirect' => $redirect
                );
            } else {
                $update = false;

                // Include an updated thread in response if a message was not sent with WebSocket or a message got pending moderation
                $include_updated_data = ! isset( $args['temp_id'] ) || $is_pending;

                if( $include_updated_data ) {
                    $update = $this->get_messages($thread_id, [$message_id]);

                    $get_threads = Better_Messages()->api->get_threads([$thread_id], false, false, true);

                    if (isset($get_threads['threads'][0])) {
                        $thread = $get_threads['threads'][0];
                        $update['threads'][] = $thread;
                    }
                }

                $result = array(
                    'result'     => true,
                    'message_id' => $message_id,
                    'thread_id'  => $thread_id,
                    'redirect'   => false
                );

                if( $update ){
                    $result['update'] = $update;
                }

                return $result;
            }
        }

        public function forward_message( WP_REST_Request $request ){
            if( Better_Messages()->settings['enableForwardMessages'] !== '1' ) {
                return new WP_Error(
                    'rest_forbidden',
                    _x('Sorry, you are not allowed to do that', 'Rest API Error', 'bp-better-messages'),
                    array('status' => rest_authorization_required_code())
                );
            }

            $message_id      = intval( $request->get_param('message_id') );
            $thread_ids      = (array) $request->get_param('thread_ids');
            $current_user_id = Better_Messages()->functions->get_current_user_id();

            $original_message = Better_Messages()->functions->get_message( $message_id );

            if( ! $original_message || $original_message->message === '<!-- BM-DELETED-MESSAGE -->' ) {
                return new WP_Error(
                    'rest_not_found',
                    _x('Message not found', 'Rest API Error', 'bp-better-messages'),
                    array('status' => 404)
                );
            }

            $has_access = Better_Messages()->functions->check_access( $original_message->thread_id, $current_user_id );

            if( ! $has_access ) {
                return new WP_Error(
                    'rest_forbidden',
                    _x('Sorry, you are not allowed to do that', 'Rest API Error', 'bp-better-messages'),
                    array('status' => rest_authorization_required_code())
                );
            }

            $forward_error = apply_filters( 'better_messages_can_forward_message', null, $original_message, $current_user_id );
            if ( is_wp_error( $forward_error ) ) {
                return $forward_error;
            }

            if( empty( $thread_ids ) ) {
                return new WP_Error(
                    'rest_invalid',
                    _x('No destination conversations specified', 'Rest API Error', 'bp-better-messages'),
                    array('status' => 400)
                );
            }

            $thread_ids = array_map( 'intval', $thread_ids );
            $thread_ids = array_filter( $thread_ids, function( $id ) { return $id > 0; } );
            $thread_ids = array_unique( $thread_ids );
            $thread_ids = array_slice( $thread_ids, 0, 20 );

            $content = $original_message->message;
            $show_attribution = Better_Messages()->settings['forwardMessagesAttribution'] === '1';

            $original_attachments = Better_Messages()->functions->get_message_meta( $message_id, 'attachments', true );
            $attachment_ids = [];
            if( is_array( $original_attachments ) && count( $original_attachments ) > 0 ) {
                $attachment_ids = array_keys( $original_attachments );
            }

            $voice_attachment_id = Better_Messages()->functions->get_message_meta( $message_id, 'bpbm_voice_messages', true );

            $results = [];
            $errors  = [];

            global $bp_better_messages_restrict_send_message;

            foreach( $thread_ids as $thread_id ) {
                $can_reply = Better_Messages()->functions->check_access( $thread_id, $current_user_id, 'reply' );

                if( ! $can_reply ) {
                    $errors[ $thread_id ] = _x('Sorry, you are not allowed to reply into this conversation', 'Rest API Error', 'bp-better-messages');
                    continue;
                }

                $bp_better_messages_restrict_send_message = [];

                $policy_errors = $this->get_send_policy_errors( $thread_id, $current_user_id, $can_reply );

                if( ! empty( $policy_errors ) ) {
                    $errors[ $thread_id ] = implode( ' ', $policy_errors );
                    continue;
                }

                $meta_data = array(
                    'forwarded_from_message' => $message_id,
                );

                if( $show_attribution ) {
                    $existing_forwarded_user = Better_Messages()->functions->get_message_meta( $message_id, 'forwarded_from_user', true );
                    if( $existing_forwarded_user ) {
                        $meta_data['forwarded_from_user'] = (int) $existing_forwarded_user;
                    } else {
                        $meta_data['forwarded_from_user'] = (int) $original_message->sender_id;
                    }
                }

                if( ! empty( $voice_attachment_id ) ) {
                    $meta_data['bpbm_voice_messages'] = (int) $voice_attachment_id;
                }

                $args = array(
                    'sender_id'    => $current_user_id,
                    'content'      => $content,
                    'thread_id'    => $thread_id,
                    'is_pending'   => (int) Better_Messages()->moderation->is_moderation_enabled( $current_user_id, $thread_id, false ),
                    'return'       => 'message_id',
                    'error_type'   => 'wp_error',
                    'meta_data'    => $meta_data,
                );

                $send_errors = [];
                Better_Messages()->functions->before_message_send_filter( $args, $send_errors );

                if( ! empty( $send_errors ) ) {
                    $errors[ $thread_id ] = implode( ' ', $send_errors );
                    continue;
                }

                $new_message_id = Better_Messages()->functions->new_message( $args );

                if( is_wp_error( $new_message_id ) ) {
                    $errors[ $thread_id ] = $new_message_id->get_error_message();
                } else {
                    if( ! empty( $attachment_ids ) ) {
                        $attachment_meta = [];
                        foreach( $attachment_ids as $att_id ) {
                            $url = wp_get_attachment_url( $att_id );
                            if( $url ) {
                                $attachment_meta[ $att_id ] = $url;
                                add_post_meta( $att_id, 'bp-better-messages-message-id', $new_message_id );
                                update_post_meta( $att_id, 'bp-better-messages-last-forwarded', time() );
                            }
                        }
                        if( ! empty( $attachment_meta ) ) {
                            Better_Messages()->functions->update_message_meta( $new_message_id, 'attachments', $attachment_meta );
                        }
                    }

                    if( ! empty( $voice_attachment_id ) ) {
                        add_post_meta( (int) $voice_attachment_id, 'bp-better-messages-message-id', $new_message_id );
                    }

                    $results[ $thread_id ] = $new_message_id;
                }
            }

            return array(
                'result' => empty( $errors ),
                'sent'   => $results,
                'errors' => $errors,
            );
        }

        public function save_message( WP_REST_Request $request ){
            if( Better_Messages()->settings['allowEditMessages'] !== '1' ) {
                return new WP_Error(
                    'rest_forbidden',
                    _x('Sorry, you are not allowed to do that', 'Rest API Error', 'bp-better-messages'),
                    array('status' => rest_authorization_required_code())
                );
            }

            global $wpdb;

            $user_id     = Better_Messages()->functions->get_current_user_id();
            $thread_id   = intval( $request->get_param('id') );
            $message_id  = intval( $request->get_param('message_id') );
            $raw_message = $request->get_param('message');
            $content     = Better_Messages()->functions->filter_message_content( $raw_message );

            $content = apply_filters( 'better_messages_send_message_content', $content, $raw_message, $thread_id );

            if( trim($content) == '') {
                return new WP_Error(
                    'rest_forbidden',
                    __( 'Message content was empty.', 'bp-better-messages' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }

            if ( class_exists( 'Better_Messages_E2E_Encryption' )
                && Better_Messages_E2E_Encryption::is_e2e_thread( $thread_id )
                && ! Better_Messages_E2E_Encryption::is_valid_e2e_ciphertext( $content )
            ) {
                return new WP_Error(
                    'rest_forbidden',
                    _x( 'Messages in encrypted conversations must be encrypted. Please reload the page and try again.', 'E2E Encryption', 'bp-better-messages' ),
                    array( 'status' => 400 )
                );
            }

            $max_length = (int) Better_Messages()->settings['maximumMessageLength'];
            if ( $max_length > 0 && Better_Messages()->functions->message_text_length( $raw_message ) > $max_length ) {
                return new WP_Error(
                    'too_long',
                    sprintf(
                        _x( 'Message is too long. Maximum allowed length is %d characters', 'Edit message error', 'bp-better-messages' ),
                        $max_length
                    ),
                    array( 'status' => 400 )
                );
            }

            $old_message_content = $wpdb->get_var( $wpdb->prepare( "SELECT message FROM " . bm_get_table('messages') ." WHERE id = %d AND sender_id = %d", $message_id, $user_id ) );

            $forwarded_from = Better_Messages()->functions->get_message_meta( $message_id, 'forwarded_from_user', true );
            if( Better_Messages()->settings['enableForwardMessages'] === '1' && $forwarded_from ){
                return new WP_Error(
                    'rest_forbidden',
                    _x('Forwarded messages cannot be edited', 'Rest API Error', 'bp-better-messages'),
                    array('status' => rest_authorization_required_code())
                );
            }

            $old_message = $old_message_content;

            $attachments = Better_Messages()->functions->get_message_meta( $message_id, 'attachments', true );
            if( is_array( $attachments ) && count( $attachments ) > 0 ) {
                foreach ($attachments as $attachment) {
                    $old_message_content = str_replace($attachment, '', $old_message_content);
                }
            }

            $old_message_content = trim($old_message_content);

            $update_message = str_replace( $old_message_content, $content, $old_message );

            $message = $wpdb->get_row($wpdb->prepare(
                "SELECT * 
                FROM `" . bm_get_table('messages') . "` 
                WHERE `thread_id` = %d 
                AND `id` = %d 
                AND `sender_id` = %d"
                , $thread_id, $message_id, $user_id)
            );

            if( ! $message ) {
                return new WP_Error(
                    'rest_forbidden',
                    __( 'Message not found', 'bp-better-messages' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }

            $edit_time_limit = (int) Better_Messages()->settings['editMessageTimeLimit'];
            if ( $edit_time_limit > 0 && ! current_user_can( 'bm_can_administrate' ) ) {
                $threshold = Better_Messages()->functions->to_microtime( time() - $edit_time_limit * MINUTE_IN_SECONDS );

                if ( (int) $message->created_at < $threshold ) {
                    return new WP_Error(
                        'rest_forbidden',
                        _x( 'The edit time limit for this message has expired.', 'Rest API Error', 'bp-better-messages' ),
                        array( 'status' => rest_authorization_required_code() )
                    );
                }
            }

            $args = array(
                'sender_id'   => $user_id,
                'thread_id'   => $thread_id,
                'content'     => $update_message,
                'message_id'  => $message_id,
                'return'      => 'message_id',
                'send_push'   => false,
                'mobile_push' => true
            );

            Better_Messages()->functions->record_message_edit_history( $message_id, $old_message_content );

            Better_Messages()->functions->update_message( $args );

            return Better_Messages_Rest_Api()->get_messages( $thread_id, [ $message_id ] );
        }

        public function mute_thread(WP_REST_Request $request) {
            global $wpdb;

            $thread_id  = intval($request->get_param('id'));
            $user_id   = Better_Messages()->functions->get_current_user_id();

            $current_value = $wpdb->get_var($wpdb->prepare(
                "SELECT is_muted 
                 FROM " . bm_get_table('recipients') . "
                 WHERE `user_id` = %d
                 AND `thread_id` = %d
            ", $user_id, $thread_id ));

            if( is_null( $current_value ) ) return false;

            if( (int) $current_value === 0 ){
                $time = Better_Messages()->functions->get_microtime();

                $wpdb->get_var($wpdb->prepare(
                    "UPDATE " . bm_get_table('recipients') . "
                SET   `is_muted` = 1, `last_update` = %d
                WHERE `user_id` = %d
                AND   `thread_id` = %d
                ", $time, $user_id, $thread_id ));

                do_action( 'better_messages_thread_self_update', $thread_id, $user_id );
                do_action( 'better_messages_thread_updated', $thread_id, $user_id );
            }

            return true;
        }

        public function unmute_thread(WP_REST_Request $request) {
            global $wpdb;

            $thread_id  = intval($request->get_param('id'));

            $user_id   = Better_Messages()->functions->get_current_user_id();
            $time      = Better_Messages()->functions->get_microtime();

            $current_value = $wpdb->get_var($wpdb->prepare(
                "SELECT is_muted 
                 FROM " . bm_get_table('recipients') . "
                 WHERE `user_id` = %d
                 AND `thread_id` = %d
            ", $user_id, $thread_id ));

            if( is_null( $current_value ) ) return false;

            if( (int) $current_value === 1 ){
                $wpdb->get_var($wpdb->prepare(
                "UPDATE " . bm_get_table('recipients') . "
                SET   `is_muted` = 0, `last_update` = %d
                WHERE `user_id` = %d
                AND   `thread_id` = %d
                ", $time, $user_id, $thread_id ));

                do_action( 'better_messages_thread_self_update', $thread_id, $user_id );
                do_action( 'better_messages_thread_updated', $thread_id, $user_id );
            }

            return true;
        }

        public function get_user_suggestions(){
            $user_ids = apply_filters('better_messages_predefined_suggestions_user_ids', []);

            if( ! is_array( $user_ids ) || count( $user_ids ) === 0 ){
                $total_to_get = 12;

                $friends = Better_Messages()->functions->get_friends_sorted( Better_Messages()->functions->get_current_user_id(), $total_to_get );

                $user_ids = [];

                if( count( $friends ) > 0 ) {
                    foreach ( $friends as $user_id => $time ){
                        $user_ids[] = $user_id;
                        $total_to_get--;
                    }
                }

                $get_all_users = $total_to_get > 0;

                if( Better_Messages()->functions->is_only_friends_mode() ){
                    $get_all_users = false;
                }

                if( current_user_can('manage_options') ){
                    $get_all_users = true;
                }

                if( $get_all_users ) {
                    if ( Better_Messages()->settings['searchAllUsers'] === '1' || ! Better_Messages()->functions->is_friends_active() ) {
                        $other_users = Better_Messages()->functions->get_users_sorted( Better_Messages()->functions->get_current_user_id(), array_keys($friends), $total_to_get );

                        if (count($other_users) > 0) {
                            foreach ($other_users as $user_id => $time) {
                                $user_ids[] = $user_id;
                            }
                        }
                    }
                }
            }

            return $user_ids;

        }

        public function get_suggestions( WP_REST_Request $request ){
            global $wpdb;

            $search     = sanitize_text_field($request->get_param('search'));
            $thread_id  = intval( $request->get_param('threadId') );
            $current_user_id = Better_Messages()->functions->get_current_user_id();

            $exclude_sql = '';
            $exclude = [];
            $exclude_current_user = true;


            if( $thread_id > 0 ){
                $exclude = array_unique(array_merge($exclude, array_map('intval', array_keys(Better_Messages()->functions->get_recipients( $thread_id )))));

                if( count( $exclude ) > 0 ){
                    $exclude_sql = " AND `ID` NOT IN (" . implode(',', $exclude) . ") ";
                }

                $exclude_current_user = false;
                //$current_user_id = 0;
            } else {
                $exclude[] = $current_user_id;
            }

            if( Better_Messages()->settings['disableUsersSearch'] === '1' ){
                $sql = $wpdb->prepare("
                    SELECT ID 
                    FROM `{$wpdb->users}`
                    WHERE `user_login` LIKE %s
                    OR `user_nicename` LIKE %s
                    OR `display_name`  LIKE %s 
                    {$exclude_sql}
                    LIMIT 0,1", $search, $search, $search);

                $user_id = $wpdb->get_var($sql);

                if( $user_id ) {
                    return [Better_Messages()->functions->rest_user_item($user_id)];
                } else {
                    return [];
                }
            }

            $count = apply_filters('better_messages_search_users_limit', 50 );

            $suggestions = Better_Messages_Search()->get_users_results( $search, $current_user_id, $exclude, $exclude_current_user, $count );

            // Hide AI bots from suggestions in E2E encrypted threads
            if ( $thread_id > 0 && class_exists( 'Better_Messages_E2E_Encryption' ) && Better_Messages_E2E_Encryption::is_e2e_thread( $thread_id ) ) {
                $suggestions = array_filter( $suggestions, function( $uid ) {
                    return ! ( $uid < 0 && isset( Better_Messages()->ai ) && Better_Messages()->ai->get_bot_id_from_user( $uid ) );
                } );
            }

            $users = [];

            if ( count( $suggestions ) > 0 ) {
                foreach ( $suggestions as $suggestion ) {
                    $item = Better_Messages()->functions->rest_user_item( $suggestion );
                    $users[] = $item;
                }
            }

            return $users;
        }

        public function get_messages( $thread_id = null, $message_ids = [], $added_users = [], $count = 80, $apply_filters = true ){
            global $wpdb;

            $current_user_id = Better_Messages()->functions->get_current_user_id();

            $return = [
                'users' => [],
                'messages' => []
            ];

            $mode = 'standard';

            if( is_int( $message_ids ) ) {
                $mode = 'changed_since';

                if( is_int( $thread_id ) ){
                    $mode = 'from_message';
                }
            }

            $pending_sql = user_can( $current_user_id, 'bm_can_administrate' ) ? "" : $wpdb->prepare(" AND ( `messages`.`is_pending` != 1 OR `messages`.`sender_id` = %d )", $current_user_id );

            if( $mode === 'from_message' ){
                $messages = Better_Messages()->functions->get_messages( $thread_id, $message_ids, 'from_message', $count );
            } else if( $mode === 'changed_since' ) {
                $sql = $wpdb->prepare("SELECT messages.*
                FROM " . bm_get_table('messages') . " messages
                INNER JOIN " . bm_get_table('recipients') . " recipients
                    ON messages.`thread_id` = recipients.`thread_id`
                    AND recipients.user_id = %d
                WHERE `messages`.`updated_at` > %d
                $pending_sql", $current_user_id, $message_ids);

                $messages = $wpdb->get_results($sql);
            } else {
                $message_ids = array_map('intval', $message_ids);

                if( !! $thread_id && count( $message_ids ) > 0 ) {
                    $query = $wpdb->prepare( "
                        SELECT id, thread_id, sender_id, message, created_at, updated_at, temp_id, is_pending
                        FROM  " . bm_get_table('messages') . " messages
                        WHERE `thread_id` = %d
                        AND `id` IN (" . implode(',', $message_ids) . ")
                        $pending_sql
                        ORDER BY `created_at` DESC
                        ", $thread_id );

                    $messages = $wpdb->get_results( $query );
                } elseif ( ! $thread_id && count( $message_ids ) > 0 ) {
                    $query = $wpdb->prepare( "
                        SELECT id, thread_id, sender_id, message, created_at, updated_at, temp_id, is_pending
                        FROM  " . bm_get_table('messages') . " messages
                        WHERE `id` IN (" . implode(',', $message_ids) . ")
                        ORDER BY `created_at` DESC
                    ");

                    $messages = $wpdb->get_results( $query );
                } else {
                    $messages = Better_Messages()->functions->get_messages( $thread_id, false, 'last_messages', $count );
                }
            }

            if( count ( $messages ) === 0 ) {
                return $return;
            }

            $user_ids   = [];
            $thread_ids = [];

            foreach( $messages as $key => $message ){
                $messages[ $key ]->message_id = (int) $message->id;
                unset($messages[ $key ]->id);
                $is_pending     = (int) $message->is_pending;
                unset($messages[ $key ]->is_pending);
                $user_id        = (int) $message->sender_id;
                $_thread_id     = (int) $message->thread_id;
                $user_ids[]     = $user_id;
                $thread_ids[]   = $_thread_id;
                $message->updated_at = (int) $message->updated_at;
                $message->created_at = (int) $message->created_at;

                $messages[ $key ]->sender_id  = $user_id;
                $messages[ $key ]->thread_id  = $_thread_id;

                $meta = [];
                if( $is_pending === 1 ){
                    $meta['isPending'] = true;
                }

                $last_edit_time = Better_Messages()->functions->get_message_last_edit( $message->message_id );

                if( $last_edit_time ){
                    $meta['lastEdit'] = $last_edit_time;
                }

                $meta = apply_filters('better_messages_rest_message_meta', $meta, (int) $message->message_id, (int) $message->thread_id, $message->message );
                if( empty( $meta ) ) $meta = (object) [];
                $messages[ $key ]->meta = $meta;

                if( $apply_filters ) {
                    $messages[$key]->favorited = (Better_Messages()->functions->is_message_starred($message->message_id, $current_user_id)) ? 1 : 0;
                    //$messages[$key]->onsite    = Better_Messages()->functions->format_message($message->message, (int) $message->message_id, 'site', $current_user_id);
                    $messages[$key]->message   = Better_Messages()->functions->format_message($message->message, (int) $message->message_id, 'stack', $current_user_id);
                } else {
                    $messages[$key]->favorited = 0;
                }
            }

            if( is_null($thread_id) ) {
                $thread_ids = array_unique($thread_ids);
                $result = $this->get_threads( $thread_ids, false );
                $return['threads'] = $result['threads'];
                $return['users']   = $result['users'];
            } else {
                $user_ids = array_unique( $user_ids );

                foreach ( $user_ids as $user_id ){
                    if ( $user_id === 0 ) continue;
                    if ( in_array($user_id, $added_users ) ) continue;

                    $return['users'][] = Better_Messages()->functions->rest_user_item( $user_id, $apply_filters );

                    $added_users[] = $user_id;
                }
            }

            $return['messages'] = $messages;

            return $return;
        }

        public function get_changed_threads( $since, $user_id ){
            global $wpdb;

            return array_map( 'intval', $wpdb->get_col( $wpdb->prepare(
                "SELECT thread_id
            FROM " . bm_get_table('recipients') . "
            WHERE `user_id` = %d
            AND `last_update` > %d
            ", $user_id, $since ) ) );
        }

        public function get_changed_users( $since ){
            global $wpdb;

            $user_id = Better_Messages()->functions->get_current_user_id();

            return array_map( 'intval', $wpdb->get_col(
                $wpdb->prepare(" SELECT ID
                FROM `" . bm_get_table('users') . "`
                WHERE `last_changed` > %d
                AND `ID` != %d
                ", $since, $user_id ) ) );
        }

        public function check_missing_threads( $user_id, $thread_ids = [] ){
            global $wpdb;

            $thread_ids = array_map( 'intval', $thread_ids );

            if( count( $thread_ids ) === 0 ) return [];

            $thread_ids = array_map( 'intval', $thread_ids );

            $placeholders = implode( ',', array_fill( 0, count( $thread_ids ), '%d' ) );

            $sql = $wpdb->prepare( "
                SELECT threads.id
                FROM " . bm_get_table('threads') . " threads
                WHERE threads.id IN ( $placeholders )
                  AND (
                    threads.type = 'chat-room'
                    OR EXISTS (
                        SELECT 1
                        FROM " . bm_get_table('recipients') . " r
                        WHERE r.thread_id = threads.id
                          AND r.user_id = %d
                          AND r.is_deleted = 0
                        LIMIT 1
                    )
                  )
            ", array_merge( $thread_ids, [ $user_id ] ) );

            $existing = array_map( 'intval', $wpdb->get_col( $sql ) );

            return array_values(array_diff( $thread_ids, $existing ));
        }

        public function check_unloaded_threads( $user_id, $thread_ids = [] ){
            global $wpdb;

            $thread_ids = array_map( 'intval', $thread_ids );

            $exclude_sql = '';
            if( count( $thread_ids ) > 0 ) $exclude_sql = "`threads`.`id` NOT IN(" . implode(',', $thread_ids ) . ") AND ";

            /** Find all unloaded unread */
            $sql = $wpdb->prepare("
                SELECT
                `threads`.`id` as `thread_id`,
                ( SELECT `id`
                    FROM " . bm_get_table('messages') . "
                    WHERE `thread_id` = `threads`.`id`
                    ORDER BY `created_at` DESC
                    LIMIT 1
                ) as `message_id`,
                COALESCE(" . Better_Messages()->functions->thread_last_message_at_expr() . ", 0) as `created_at`
                FROM " . bm_get_table('threads') . " threads
                INNER JOIN " . bm_get_table('recipients') . " recipients
                    ON threads.`id` = recipients.`thread_id`
                LEFT JOIN " . bm_get_table('messages') . " messages
                    ON recipients.`thread_id` = messages.`thread_id`
                LEFT JOIN " . bm_get_table('threadsmeta') . " threadsmeta ON
                    ( threadsmeta.`bm_thread_id` = threads.`id`
                AND threadsmeta.meta_key = 'exclude_from_threads_list' )
                WHERE
                    {$exclude_sql}
                    recipients.`user_id` = %d
                    AND recipients.`is_deleted` = 0
                    AND recipients.`unread_count` > 0
                    AND `threadsmeta`.`meta_value` IS NULL
                    AND (`messages`.`id` IS NOT NULL OR `threads`.`type` IN ('chat-room', 'group'))
                    " . Better_Messages()->functions->threads_order_sql() . "
                    ", $user_id);

            $results = $wpdb->get_results( $sql, ARRAY_A );

            $unloaded_unread_count = count( $results );

            if( $unloaded_unread_count < 20 ){
                $toLoad             = 20 - $unloaded_unread_count;
                $unread_thread_ids = array_map( 'intval', array_column( $results, 'thread_id' ));

                $exclude_sql = '';
                if( count( $unread_thread_ids ) > 0 ) {
                    $exclude_sql = "`threads`.`id` NOT IN(" . implode(',', $unread_thread_ids ) . ") AND ";
                }

                $sql = $wpdb->prepare("
                    SELECT
                    `threads`.`id`    	         as `thread_id`,
                    ( SELECT `id`
                        FROM " . bm_get_table('messages') . "
                        WHERE `thread_id` = `threads`.`id`
                        ORDER BY `created_at` DESC
                        LIMIT 1
                    ) as `message_id`,
                    COALESCE(" . Better_Messages()->functions->thread_last_message_at_expr() . ", 0) as `created_at`
                    FROM " . bm_get_table('threads') . " threads
                    INNER JOIN " . bm_get_table('recipients') . " recipients
                        ON threads.`id` = recipients.`thread_id`
                    LEFT JOIN " . bm_get_table('messages') . " messages
                        ON recipients.`thread_id` = messages.`thread_id`
                    LEFT JOIN " . bm_get_table('threadsmeta') . " threadsmeta ON
                        ( threadsmeta.`bm_thread_id` = threads.`id`
                    AND threadsmeta.meta_key = 'exclude_from_threads_list' )
                    WHERE
                        {$exclude_sql}
                        recipients.`user_id` = %d
                        AND recipients.`is_deleted` = 0
                        AND `threadsmeta`.`meta_value` IS NULL
                        AND (`messages`.`id` IS NOT NULL OR `threads`.`type` IN ('chat-room', 'group'))
                    " . Better_Messages()->functions->threads_order_sql() . "
                        LIMIT 0, %d
                        ", $user_id, $toLoad);

                $results = array_merge( $results, $wpdb->get_results( $sql, ARRAY_A ) );
            }


            $results = array_column( $results, 'thread_id' );
            sort( $results );

            $unloadedThreads = [];
            foreach( $results as $thread_id ){
                if( ! in_array( $thread_id, $thread_ids ) ){
                    $unloadedThreads[] = (string) $thread_id;
                }
            }

            return $unloadedThreads;
        }

        public function get_deleted_messages( $since ){
            global $wpdb;

            return array_map( 'intval', $wpdb->get_col( $wpdb->prepare(
                "SELECT messagesmeta.bm_message_id
            FROM " . bm_get_table('messages') . " messages
            RIGHT JOIN " . bm_get_table('meta') . " messagesmeta ON
                `messagesmeta`.`bm_message_id` = `messages`.`id` 
            WHERE messagesmeta.meta_value > %d
            AND messagesmeta.meta_key = 'bm_deleted_time'
            AND `messages`.`id` IS NULL", $since ) ) );
        }

        public function load_more( WP_REST_Request $request ){
            global $wpdb;

            $thread_id   = intval($request->get_param('id'));
            $message_ids = array_map('intval', $request->get_param('loaded'));
            $to          = intval($request->get_param('to'));
            $mode        = sanitize_text_field($request->get_param('mode'));

            $return = [
                'messages' => [],
                'users' => []
            ];

            if( ! Better_Messages()->functions->can_read_chat_messages( $thread_id, Better_Messages()->functions->get_current_user_id() ) ){
                return $return;
            }

            $missed_messages = Better_Messages()->functions->get_missed_message_ids( $thread_id, $message_ids );

            if( count( $missed_messages ) > 0 ){
                $message_ids = array_unique( array_merge( $message_ids, $missed_messages ) );
                $get_missed_messages = $this->get_messages( $thread_id, $missed_messages );
                $return['messages'] = array_merge($return['messages'], $get_missed_messages['messages']);
                $return['users']    = array_merge($return['users'], $get_missed_messages['users']);
            }

            $lazyLoadCount = 80;

            $from = 0;
            if( count( $message_ids ) > 0 ){
                $from = min( $message_ids );
            }

            if( $mode === 'until' ){
                $sql = $wpdb->prepare("
                SELECT `created_at`
                FROM `" . bm_get_table('messages') . "` 
                WHERE `thread_id` = %d 
                AND `id` = %d", $thread_id, $to );

                $from_time = $wpdb->get_var( $sql );

                if( $from_time ) {
                    $sql = $wpdb->prepare("
                    SELECT id
                    FROM `" . bm_get_table('messages') . "` 
                    WHERE `thread_id` = %d 
                    AND `created_at` >= %d
                    ORDER BY `created_at` DESC", $thread_id, $from_time);

                    $from = array_map('intval', $wpdb->get_col($sql));
                } else {
                    return new WP_Error(
                        'rest_forbidden',
                        __( 'Message not found in this conversation', 'bp-better-messages' ),
                        array( 'status' => 400 )
                    );
                }
            } else if( $from > 0 && $to > 0 ){
                $sql = $wpdb->prepare("
                SELECT `created_at`
                FROM `" . bm_get_table('messages') . "` 
                WHERE `thread_id` = %d 
                AND `id` = %d", $thread_id, $from );

                $from_time = $wpdb->get_var( $sql );

                if( $from_time ) {
                    $sql = $wpdb->prepare("SELECT id
                FROM `" . bm_get_table('messages') . "` 
                WHERE `thread_id` = %d 
                AND `created_at` < %d
                ORDER BY `created_at` DESC
                LIMIT %d, %d", $thread_id, $from_time, 0, $lazyLoadCount);

                    $from = array_map('intval', $wpdb->get_col($sql));
                } else {
                    $from = [];
                }
            }

            if( is_array( $from ) && count( $from ) === 0 ){
                return $return;
            }

            $get_messages = $this->get_messages( $thread_id, $from, array_column($return['users'], 'user_id') );

            if( ! empty($get_messages['messages']) ) $return['messages'] = array_merge($return['messages'], $get_messages['messages']);
            if( ! empty($get_messages['users']) )    $return['users']    = array_merge($return['users'], $get_messages['users']);

            return $return;
        }

        public function search_participants( WP_REST_Request $request ){
            global $wpdb;

            $thread_id   = intval($request->get_param('id'));
            $search      = '%' . sanitize_text_field( $request->get_param('search') ) . '%';

            $sql = $wpdb->prepare( "SELECT user_id
                FROM `" . bm_get_table('recipients') . "` `recipients`
            RIGHT JOIN `" . bm_get_table('users') . "` `user_index`
                ON `recipients`.`user_id` = `user_index`.`ID`
            WHERE `recipients`.`thread_id` = %d
            AND ( 
                `user_nicename` LIKE %s
                OR `display_name` LIKE %s
                OR `first_name` LIKE %s
                OR `last_name` LIKE %s
                OR `nickname` LIKE %s
            )
            ORDER BY `user_index`.`last_activity` DESC", $thread_id, $search, $search, $search, $search, $search );

            $results = $wpdb->get_col( $sql );

            $users = [];

            if( count( $results ) > 0 ){
                foreach( $results as $user_id ){
                    $users[] = Better_Messages()->functions->rest_user_item( $user_id );
                }
            }

            return $users;
        }

        public function get_thread( WP_REST_Request $request ) {
            global $wpdb;

            $current_user_id = Better_Messages()->functions->get_current_user_id();

            $thread_id   = intval($request->get_param('id'));

            // Loaded messages IDs
            $message_ids = (array) $request->get_param('messages');

            $thread_type = Better_Messages()->functions->get_thread_type( $thread_id );

            if( $thread_type === 'chat-room' ){
                $chat_id = Better_Messages_Chats()->get_chat_thread_id( $thread_id );
                Better_Messages_Chats()->sync_auto_add_users( $chat_id );
            }

            $return     = $this->get_threads( [ $thread_id ], false );

            Better_Messages()->functions->messages_mark_thread_read( $thread_id, Better_Messages()->functions->get_current_user_id() );

            $return['threads'][0]['unread'] = 0;
            $return['threads'][0]['mentions'] = [];

            if( ! Better_Messages()->functions->can_read_chat_messages( $thread_id, $current_user_id ) ){
                $return['messages'] = [];
                return $return;
            }

            $added_user_ids     = array_column($return['users'], 'user_id');

            $get_messages = $this->get_messages($thread_id, [], $added_user_ids);

            $return['messages'] = $get_messages['messages'];
            $return['users']    = array_merge($return['users'], $get_messages['users']);

            $pending_sql = user_can( $current_user_id, 'bm_can_administrate' ) ? "" : $wpdb->prepare(" AND ( `messages`.`is_pending` != 1 OR `messages`.`sender_id` = %d )", $current_user_id );

            if( count( $message_ids ) > 0 ){
                $sql = $wpdb->prepare("SELECT id
                FROM " . bm_get_table('messages') . " messages
                WHERE `thread_id` = %d
                $pending_sql
                AND id IN(" . implode( ",", array_map( 'intval', $message_ids ) ) . ")", $thread_id, $current_user_id );

                $existing_messages = $wpdb->get_col($sql);
                $existing_messages = array_map( 'intval', $existing_messages );

                $deleted_messages = [];

                foreach( $message_ids as $message_id ){
                    if( ! in_array( $message_id, $existing_messages ) ){
                        $deleted_messages[] = $message_id;
                    }
                }

                if( count( $deleted_messages ) > 0 ){
                    $return['deleted_messages'] = $deleted_messages;
                }

                $missed_messages = Better_Messages()->functions->get_missed_message_ids( $thread_id, $message_ids );

                if( count( $missed_messages ) > 0 && count( $return['messages'] ) > 0 ){
                    foreach ( $return['messages'] as $message ){
                        $match = array_search( $message->message_id, $missed_messages );

                        if( $match !== false ){
                            unset( $missed_messages[ $match ] );
                        }
                    }
                }

                if( count( $missed_messages ) > 0 ){
                    $get_missed_messages = $this->get_messages( $thread_id, $missed_messages, array_column($return['users'], 'user_id') );
                    $return['messages'] = array_merge($return['messages'], $get_missed_messages['messages']);
                    $return['users']    = array_merge($return['users'], $get_missed_messages['users']);
                }
            }

            return $return;
        }

        public function get_threads( $thread_ids = [], $fetch_messages = true, $fetch_users = true, $personal_data = true, $cache = true, $user_id = false ){
            set_time_limit(0);

            $excluded = [];

            if( is_a($thread_ids, 'WP_REST_Request' ) ){
                $request = $thread_ids;
                $excluded = (array) $request->get_param('exclude');
                $thread_ids = [];
            }

            global $wpdb;

            $current_user_id = $user_id ? : Better_Messages()->functions->get_current_user_id();
            $server_time     = Better_Messages()->functions->get_microtime();

            $excluded_sql = '';
            if( count( $excluded ) > 0 ){
                $excluded_sql = " AND threads.id NOT IN(" . implode(',', array_map('intval', $excluded)) . ") ";
            }

            $accessChecked = true;

            $pending_sql = user_can( $current_user_id, 'bm_can_administrate' ) ? "" : $wpdb->prepare(" AND ( `messages`.`is_pending` != 1 OR `messages`.`sender_id` = %d ) ", $current_user_id );

            $sql = $wpdb->prepare("
                SELECT
                `threads`.`id`    	        as `thread_id`,
                `threads`.`type`    	    as `type`,
                `threads`.`subject`    	    as `subject`,
                `recipients`.`unread_count` as `unread_count`,
                `recipients`.`is_muted`     as `is_muted`,
                `recipients`.`is_pinned`    as `is_pinned`,
                `recipients`.`last_update`  as `last_update`,
                COALESCE(" . Better_Messages()->functions->thread_last_message_at_expr() . ", 0) as `created_at`
                FROM " . bm_get_table('threads') . " threads
                INNER JOIN " . bm_get_table('recipients') . " recipients
                    ON threads.`id` = recipients.`thread_id`
                LEFT JOIN " . bm_get_table('messages') . " messages
                    ON recipients.`thread_id` = messages.`thread_id` $pending_sql
                LEFT JOIN " . bm_get_table('threadsmeta') . " threadsmeta ON
                    ( threadsmeta.`bm_thread_id` = threads.`id`
                AND threadsmeta.meta_key = 'exclude_from_threads_list' )
                WHERE
                    recipients.`user_id` = %d
                    AND recipients.`is_deleted` = 0
                    AND `threadsmeta`.`meta_value` IS NULL
                    AND (`messages`.`id` IS NOT NULL OR `threads`.`type` IN ('chat-room', 'group'))
                    {$excluded_sql}
                    " . Better_Messages()->functions->threads_order_sql() . "
                    LIMIT 0, 20
                    ", $current_user_id);

            if( count($thread_ids) > 0 ) {
                $sql = $wpdb->prepare("
                SELECT
                `threads`.`id`    	         as `thread_id`,
                `threads`.`type`    	     as `type`,
                `threads`.`subject`    	     as `subject`,
                `recipients`.`unread_count`  as `unread_count`,
                `recipients`.`is_muted`      as `is_muted`,
                `recipients`.`is_pinned`     as `is_pinned`,
                `recipients`.`last_update`   as `last_update`,
                `recipients`.`is_deleted`    as `is_deleted`,
                " . Better_Messages()->functions->thread_last_message_at_expr() . " as `created_at`
                FROM " . bm_get_table('threads') . " threads
                LEFT JOIN " . bm_get_table('recipients') . " recipients
                    ON threads.`id` = recipients.`thread_id`
                    AND recipients.user_id = %d
                LEFT JOIN " . bm_get_table('messages') . " messages 
                    ON recipients.`thread_id` = messages.`thread_id` $pending_sql
                LEFT JOIN " . bm_get_table('threadsmeta') . " threadsmeta ON
                    ( threadsmeta.`bm_thread_id` = messages.`thread_id`
                    AND threadsmeta.meta_key = 'exclude_from_threads_list' )
                WHERE 
                    `threads`.`id` IN (" . implode(',', array_map( 'intval', $thread_ids) ) . ")
                    " . Better_Messages()->functions->threads_order_sql() . "
                ", $current_user_id);

                $accessChecked = false;

                if( ! $personal_data ) {
                    $pending_sql = " AND `messages`.`is_pending` != 1 ";

                    $sql = "
                    SELECT
                    `threads`.`id`    	         as `thread_id`,
                    `threads`.`type`    	     as `type`,
                    `threads`.`subject`    	     as `subject`,
                    " . Better_Messages()->functions->thread_last_message_at_expr() . " as `created_at`
                    FROM " . bm_get_table('threads') . " threads
                    LEFT JOIN " . bm_get_table('messages') . " messages
                        ON threads.`id` = messages.`thread_id` $pending_sql
                    WHERE  `threads`.`id` IN (" . implode(',', array_map('intval', $thread_ids)) . ")
                    GROUP BY `threads`.`id`
                    ORDER BY `created_at` DESC";
                }
            }

            $get_threads = $wpdb->get_results( $sql );

            $users = [];

            $threads  = [];
            $messages = [];
            $user_ids = [ $current_user_id ];
            $added_users = [];

            $prevent_voice_messages = Better_Messages()->functions->user_has_role( $current_user_id, (array) Better_Messages()->settings['restrictVoiceMessages'] );

            foreach ( $get_threads as $thread ){
                /**
                 * Cache thread so child functions do not access database again
                 */
                wp_cache_set('thread_' . $thread->thread_id, (object) [
                    'id'      => $thread->thread_id,
                    'subject' => $thread->subject,
                    'type'    => $thread->type
                ], 'bm_messages');

                $admin_access = false;

                if( $personal_data && ! $accessChecked ){
                    $has_access = Better_Messages()->functions->check_access( $thread->thread_id, $current_user_id );

                    if( ! $has_access && current_user_can('bm_can_administrate') ){
                        $has_access   = true;
                        $admin_access = true;
                    }

                    if( ! $has_access ) continue;
                }

                $is_participant = false;
                $recipients = Better_Messages()->functions->get_recipients( $thread->thread_id, $cache );

                if( isset($recipients[$current_user_id]) ){
                    unset($recipients[$current_user_id]);
                    $is_participant = true;
                }

                $_all_user_ids = array_map( 'intval', array_keys($recipients) );

                if( $is_participant ){
                    $_all_user_ids[] = intval($current_user_id);
                }

                $thread_type = Better_Messages()->functions->get_thread_type( $thread->thread_id );

                $_all_user_ids = Better_Messages()->functions->maybe_sort_participants( $_all_user_ids, $thread_type );

                foreach ( array_slice($_all_user_ids, 0, 10) as $user_id ){ $user_ids[] = $user_id; }

                $title    = Better_Messages()->functions->get_thread_title( $thread->thread_id );
                $image    = Better_Messages()->functions->get_thread_image( $thread->thread_id );
                $url      = Better_Messages()->functions->get_thread_url( $thread->thread_id );

                $delete_allowed = Better_Messages()->settings['restrictThreadsDeleting'] === '0';

                if( ! $is_participant || $thread_type !== 'thread' ){
                    $delete_allowed = false;
                }

                global $bp_better_messages_restrict_send_message;
                $bp_better_messages_restrict_send_message = [];
                $can_reply = Better_Messages()->functions->can_send_message_filter( Better_Messages()->functions->check_access( $thread->thread_id, $current_user_id ), $current_user_id, $thread->thread_id );

                $thread_id  = (int) $thread->thread_id;
                $is_hidden  = ( Better_Messages()->functions->get_thread_meta($thread_id, 'exclude_from_threads_list') === '1' );
                $is_deleted = ( isset( $thread->is_deleted ) ? ($thread->is_deleted === '1') : false );
                $moderators =  (array) Better_Messages()->functions->get_moderators( $thread_id );

                $thread_item =  [
                    'thread_id'          => $thread_id,
                    'isHidden'           => (int) $is_hidden,
                    'isDeleted'          => (int) $is_deleted,
                    'type'               => $thread_type,
                    'title'              => html_entity_decode($title),
                    'subject'            => (string) html_entity_decode($thread->subject),
                    'image'              => $image,
                    'lastTime'           => (int) $thread->created_at,
                    'participants'       => array_map( 'intval', array_values($_all_user_ids) ),
                    'participantsCount'  => (int) count( $_all_user_ids ),
                    'moderators'         => $moderators,
                    'url'                => $url,
                    'meta'              => [
                        'allowInvite'              => Better_Messages()->functions->get_thread_meta($thread_id, 'allow_invite') === 'yes',
                    ]
                ];

                if( $admin_access ){
                    $thread_item['adminAccess'] = true;
                }

                if( $personal_data ){
                    $unread = (int) $thread->unread_count;

                    $thread_item['isPinned'] = ( Better_Messages()->settings['pinnedThreads'] == '1' ) ? (int) $thread->is_pinned : 0;
                    $thread_item['isMuted'] = (bool) $thread->is_muted;
                    $thread_item['permissions'] = [
                        'isModerator'          => Better_Messages()->functions->is_thread_super_moderator( $current_user_id, $thread->thread_id ),
                        'deleteAllowed'        => $delete_allowed,
                        'canDeleteOwnMessages' => Better_Messages()->settings['allowDeleteMessages'] === '1',
                        'canDeleteAllMessages' => current_user_can('bm_can_administrate'),
                        'canEditOwnMessages'   => Better_Messages()->settings['allowEditMessages'] === '1',
                        'canEditAllMessages'   => current_user_can('bm_can_administrate'),
                        'canFavorite'          => Better_Messages()->settings['disableFavoriteMessages'] !== '1',
                        'canMuteThread'        => ( Better_Messages()->settings['allowMuteThreads'] === '1' && ! $admin_access ),
                        'canEraseThread'       => Better_Messages()->functions->can_erase_thread( $current_user_id, $thread->thread_id ),
                        'canClearThread'       => Better_Messages()->functions->can_clear_thread( $current_user_id, $thread->thread_id ),
                        'canInvite'            => Better_Messages()->functions->can_invite( $current_user_id, $thread->thread_id ),
                        'canLeave'             => Better_Messages()->functions->can_leave( $current_user_id, $thread->thread_id ),
                        'canUpload'            => Better_Messages()->files->user_can_upload( $current_user_id, $thread->thread_id ),
                        'canVideoCall'         => false,
                        'canAudioCall'         => false,
                        'canMaximize'          => true,
                        'canPinMessages'       => apply_filters('better_messages_can_pin_messages', false, $current_user_id, $thread->thread_id ),
                        'canMinimize'          => Better_Messages()->functions->is_user_authorized() && Better_Messages()->settings['miniChatsEnable'] === '1',
                        'canReply'             => (bool) $can_reply,
                        'canReplyMsg'          => $bp_better_messages_restrict_send_message,
                        'requireModeration'    => Better_Messages()->moderation->is_moderation_enabled( $current_user_id, $thread_id, false ),
                        'preventVoiceMessages' => $prevent_voice_messages,
                    ];

                    if ( $thread_type !== 'thread' || count( $_all_user_ids ) > 2 ) {
                        $thread_item['systemMessages'] = Better_Messages()->system_messages->get_thread_payload(
                            $thread_id,
                            $thread_type,
                            count( $_all_user_ids ),
                            (bool) $thread_item['permissions']['isModerator']
                        );
                    }

                    $mentions = [];

                    $thread_item['mentions'] = $mentions;
                    $thread_item['unread'] = $unread;
                }

                $thread_item = apply_filters('better_messages_rest_thread_item', $thread_item, $thread_item['thread_id'], $thread_item['type'], $personal_data, $current_user_id );

                $threads[] = $thread_item;

                if( $fetch_messages ) {
                    $get_messages = $this->get_messages($thread->thread_id, [], $added_users, 20);

                    $messages = array_merge( $messages, $get_messages['messages'] );
                    $users    = array_merge( $users, $get_messages['users'] );
                    $added_users = array_merge( $added_users, array_column($users, 'user_id') );
                }
            }

            if( $fetch_users ) {
                $user_ids = array_unique($user_ids);

                foreach ($user_ids as $user_id) {
                    if (in_array($user_id, $added_users)) continue;

                    if( $user_id > 0 ){
                        $user = get_userdata($user_id);
                        if ( ! $user ) continue;
                    }

                    $users[] = Better_Messages()->functions->rest_user_item($user_id);

                    $added_users[] = $user_id;
                }
            }

            $return = apply_filters( 'better_messages_rest_api_update_data', [
                'threads'    => $threads,
                'users'      => $users,
                'messages'   => $messages,
                'serverTime' => $server_time
            ], $current_user_id, 0 );

            return $return;
        }

        public function get_threads_picker( WP_REST_Request $request ){
            global $wpdb;

            $current_user_id = Better_Messages()->functions->get_current_user_id();
            $excluded        = (array) $request->get_param('exclude');
            $search          = sanitize_text_field( (string) $request->get_param('search') );
            $exclude_thread  = intval( $request->get_param('excludeThread') );

            $excluded_sql = '';
            $excluded = array_map( 'intval', $excluded );
            if( $exclude_thread > 0 ){
                $excluded[] = $exclude_thread;
            }
            $excluded = array_filter( $excluded, function( $id ) { return $id > 0; } );
            $excluded = array_unique( $excluded );
            if( count( $excluded ) > 0 ){
                $excluded_sql = " AND threads.id NOT IN(" . implode(',', $excluded) . ") ";
            }

            $search_sql  = '';
            $prepare_args = [ $current_user_id ];

            if( ! empty( $search ) ){
                $like = '%' . $wpdb->esc_like( $search ) . '%';
                $recipients_table = bm_get_table('recipients');
                $users_table = bm_get_table('users');
                $search_sql = " AND (
                    threads.subject LIKE %s
                    OR EXISTS (
                        SELECT 1 FROM {$recipients_table} r2
                        INNER JOIN {$users_table} ui ON r2.user_id = ui.ID
                        WHERE r2.thread_id = threads.id
                        AND r2.user_id != " . intval($current_user_id) . "
                        AND (
                            ui.display_name LIKE %s
                            OR ui.user_nicename LIKE %s
                            OR ui.nickname LIKE %s
                        )
                        LIMIT 1
                    )
                ) ";
                $prepare_args[] = $like;
                $prepare_args[] = $like;
                $prepare_args[] = $like;
                $prepare_args[] = $like;
            }

            $messages_table = bm_get_table('messages');

            $sql = $wpdb->prepare("
                SELECT
                `threads`.`id`              as `thread_id`,
                `threads`.`type`            as `type`,
                `threads`.`subject`         as `subject`,
                `recipients`.`is_pinned`    as `is_pinned`,
                (SELECT " . Better_Messages()->functions->thread_last_message_at_expr( 'm.created_at', 'm.sender_id' ) . " FROM {$messages_table} m WHERE m.thread_id = threads.id) as `created_at`
                FROM " . bm_get_table('threads') . " threads
                INNER JOIN " . bm_get_table('recipients') . " recipients
                    ON threads.`id` = recipients.`thread_id`
                LEFT JOIN " . bm_get_table('threadsmeta') . " threadsmeta ON
                    ( threadsmeta.`bm_thread_id` = threads.`id`
                AND threadsmeta.meta_key = 'exclude_from_threads_list' )
                WHERE
                    recipients.`user_id` = %d
                    AND recipients.`is_deleted` = 0
                    AND `threadsmeta`.`meta_value` IS NULL
                    AND (
                        threads.`type` != 'thread'
                        OR EXISTS (
                            SELECT 1 FROM " . bm_get_table('recipients') . " rc
                            INNER JOIN " . bm_get_table('users') . " u ON rc.user_id = u.ID
                            WHERE rc.thread_id = threads.id
                            AND rc.is_deleted = 0
                            AND rc.user_id != " . intval($current_user_id) . "
                            LIMIT 1
                        )
                    )
                    {$excluded_sql}
                    {$search_sql}
                    ORDER BY `created_at` DESC
                    LIMIT 0, 20
                    ", ...$prepare_args);

            $get_threads = $wpdb->get_results( $sql );

            $threads  = [];
            $users    = [];
            $user_ids = [];
            $added_users = [];
            foreach ( $get_threads as $thread ){
                wp_cache_set('thread_' . $thread->thread_id, (object) [
                    'id'      => $thread->thread_id,
                    'subject' => $thread->subject,
                    'type'    => $thread->type
                ], 'bm_messages');

                $recipients = Better_Messages()->functions->get_recipients( $thread->thread_id );

                if( isset($recipients[$current_user_id]) ){
                    unset($recipients[$current_user_id]);
                }

                $_all_user_ids = array_map( 'intval', array_keys($recipients) );
                $_all_user_ids[] = intval($current_user_id);

                $thread_type = Better_Messages()->functions->get_thread_type( $thread->thread_id );

                $_all_user_ids = Better_Messages()->functions->maybe_sort_participants( $_all_user_ids, $thread_type );

                foreach ( array_slice($_all_user_ids, 0, 10) as $user_id ){
                    $user_ids[] = $user_id;
                }

                $title       = Better_Messages()->functions->get_thread_title( $thread->thread_id );
                $image       = Better_Messages()->functions->get_thread_image( $thread->thread_id );

                if( ! $image && $thread_type === 'chat-room' ){
                    $chat_id = (int) Better_Messages()->functions->get_thread_meta( $thread->thread_id, 'chat_id' );
                    if( $chat_id && has_post_thumbnail( $chat_id ) ){
                        $image_id = get_post_thumbnail_id( $chat_id );
                        if( $image_id ){
                            $image_src = wp_get_attachment_image_src( $image_id, [100, 100] );
                            if( $image_src ){
                                $image = $image_src[0];
                            }
                        }
                    }
                }

                $threads[] = [
                    'thread_id'          => (int) $thread->thread_id,
                    'type'               => $thread_type,
                    'title'              => html_entity_decode($title),
                    'subject'            => (string) html_entity_decode($thread->subject),
                    'image'              => $image,
                    'participants'       => array_map( 'intval', array_values($_all_user_ids) ),
                    'participantsCount'  => (int) count( $_all_user_ids ),
                ];
            }

            $user_ids = array_unique($user_ids);
            foreach ( $user_ids as $user_id ){
                if( in_array($user_id, $added_users) ) continue;
                if( $user_id > 0 ){
                    $user = get_userdata($user_id);
                    if ( ! $user ) continue;
                }
                $users[] = Better_Messages()->functions->rest_user_item( $user_id, false );
                $added_users[] = $user_id;
            }

            return [
                'threads' => $threads,
                'users'   => $users,
            ];
        }

        public function get_send_policy_errors( $thread_id, $user_id, $has_access ) {
            $errors = [];

            $type = Better_Messages()->functions->get_thread_type( $thread_id );

            if( $type === 'group' ){
                if( class_exists('BP_Groups_Member') ) {
                    $group_id = Better_Messages()->functions->get_thread_meta($thread_id, 'group_id');

                    if (!!$group_id) {
                        if ( ! Better_Messages()->functions->can_send_message_filter( BP_Groups_Member::check_is_member($user_id, $group_id), $user_id, $thread_id ) ) {
                            $errors[] = _x( 'Sorry, you are not allowed to reply into this conversation', 'Rest API Error', 'bp-better-messages' );
                        }
                    }
                }

                if( class_exists('PeepSoGroupsPlugin') ) {
                    $group_id = Better_Messages()->functions->get_thread_meta($thread_id, 'peepso_group_id');

                    if ( !! $group_id ) {
                        $has_access  = Better_Messages_Peepso_Groups::instance()->user_has_access( $group_id, $user_id );

                        if ( ! Better_Messages()->functions->can_send_message_filter( $has_access, $user_id, $thread_id ) ) {
                            $errors[] = _x( 'Sorry, you are not allowed to reply into this conversation', 'Rest API Error', 'bp-better-messages' );
                        }
                    }
                }

                if( class_exists('Better_Messages_Fluent_Community_Spaces') ) {
                    $group_id = Better_Messages()->functions->get_thread_meta($thread_id, 'fluentcommunity_group_id');

                    if ( !! $group_id ) {
                        $has_access  = Better_Messages_Fluent_Community_Spaces::instance()->user_has_access( $group_id, $user_id );

                        if ( ! Better_Messages()->functions->can_send_message_filter( $has_access, $user_id, $thread_id ) ) {
                            $errors[] = _x( 'Sorry, you are not allowed to reply into this conversation', 'Rest API Error', 'bp-better-messages' );
                        }
                    }
                }

            } else {
                if ( ! Better_Messages()->functions->can_send_message_filter( $has_access, $user_id, $thread_id ) ) {
                    global $bp_better_messages_restrict_send_message;

                    if( is_array( $bp_better_messages_restrict_send_message ) && count( $bp_better_messages_restrict_send_message ) > 0 ){
                        $errors = array_merge($errors, array_values($bp_better_messages_restrict_send_message));
                    } else {
                        $errors[] = _x( 'Sorry, you are not allowed to reply into this conversation', 'Rest API Error', 'bp-better-messages' );
                    }

                }
            }

            return $errors;
        }

        public function can_reply(WP_REST_Request $request) {

            $authorized = $this->is_user_authorized( $request );
            if ( $authorized !== true ) {
                return $authorized;
            }

            $user_id    = Better_Messages()->functions->get_current_user_id();
            $thread_id  = intval($request->get_param('id'));

            $has_access = Better_Messages()->functions->check_access( $thread_id, $user_id, 'reply' );

            if( ! $has_access ){
                $temp_id = sanitize_text_field( $request->get_param('temp_id') );
                do_action('better_messages_on_message_not_sent', $thread_id, $temp_id, [] );

                $thread = $this->get_threads([$thread_id]);

                return new WP_Error(
                    'rest_forbidden',
                    _x( 'Sorry, you are not allowed to reply into this conversation', 'Rest API Error', 'bp-better-messages' ),
                    array(
                        'status' => rest_authorization_required_code(),
                        'update' => $thread
                    )
                );
            }

            $errors = $this->get_send_policy_errors( $thread_id, $user_id, $has_access );

            if( count( $errors ) === 0 ) {
                return true;
            } else {
                $error = new WP_Error();

                $temp_id = sanitize_text_field( $request->get_param('temp_id') );

                global $bp_better_messages_restrict_send_message;
                $skip_abort = is_array( $bp_better_messages_restrict_send_message ) && isset( $bp_better_messages_restrict_send_message['waiting_for_ai_response'] );

                if( ! $skip_abort ) {
                    do_action('better_messages_on_message_not_sent', $thread_id, $temp_id, $errors );
                }

                $thread = $this->get_threads( [$thread_id] );

                foreach ( $errors as $_error ) {
                    $error->add(
                        'rest_forbidden',
                        $_error,
                        array('status' => rest_authorization_required_code())
                    );
                }

                $error->add_data(['update' => $thread]);

                return $error;
            }
        }

        public function check_thread_access(WP_REST_Request $request) {
            $authorized = $this->is_user_authorized( $request );
            if ( is_wp_error( $authorized ) ) {
                return $authorized;
            }

            $user_id    = Better_Messages()->functions->get_current_user_id();
            $thread_id  = intval($request->get_param('id'));
            $thread     = Better_Messages()->functions->get_thread( $thread_id );

            if( ! $thread ){
                return new WP_Error(
                    'rest_thread_not_exists',
                    _x( 'Sorry, the conversation which you tried to access does not exists', 'Rest API Error', 'bp-better-messages' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }

            $has_access = current_user_can('bm_can_administrate');

            if( ! $has_access ) {
                $has_access = Better_Messages()->functions->check_access($thread_id, $user_id);
            }

            if( ! $has_access ){
                return new WP_Error(
                    'rest_thread_forbidden',
                    _x( 'Sorry, you are not allowed to access this conversation', 'Rest API Error', 'bp-better-messages' ),
                    array( 'status' => rest_authorization_required_code() )
                );
            }

            return true;
        }

        public function ping(){
            return 'pong';
        }

    }
endif;

function Better_Messages_Rest_Api()
{
    return Better_Messages_Rest_Api::instance();
}
