/**
 * Agenistrator - SSE Client.
 *
 * Handles Server-Sent Events (SSE) connections for real-time updates.
 *
 * @package   Agenistrator
 * @since     1.0.0
 * @author    Alex Ruban <alex@maximum.guru>
 * @author    Maximum Software Inc <contact@maximum.software>
 * @license   GPL-3.0-only
 * @link      https://agenistrator.com/
 *
 * SPDX-FileCopyrightText: 2025-2026 Alex Ruban <alex@maximum.guru>
 * SPDX-FileCopyrightText: 2025-2026 Maximum Software Inc <contact@maximum.software>
 * SPDX-License-Identifier: GPL-3.0-only
 */
jQuery(document).ready(function() {
    'use strict';

    window.Agenistrator = window.Agenistrator || {};

    // SSE Client - Handles Server-Sent Events connections
    Agenistrator.SSEClient = {
        // Properties
        eventSource: null,
        connected: false,
        hasBeenConnected: false, // Track if we've ever successfully connected
        // Suppresses connection error when SSE closes after server timeout event.
        // Firefox throws "Error in input stream" on stream close; other browsers may not.
        expectingReconnect: false,
        handlers: {},
        isPageUnloading: false,
        isInitialized: false,

        /**
         * Initialize the SSE client.
         *
         * @param {Object} handlers - Event handler callbacks.
         * @since 1.0.0
         */
        initialize: function(handlers) {
            // Prevent double initialization
            if (this.isInitialized) {
                return;
            }

            // Store event handlers
            this.handlers = handlers || {};

            // Set up page unload detection
            this.setupPageUnloadDetection();

            // Connect to the SSE endpoint
            this.connect();

            this.isInitialized = true;
        },

        /**
         * Set up page unload detection to prevent false error notifications.
         *
         * @since 1.0.0
         */
        setupPageUnloadDetection: function() {
            var self = this;

            // Listen for page unload events
            jQuery(window).on('beforeunload unload pagehide', function() {
                self.isPageUnloading = true;

                // Gracefully close the connection
                if (self.eventSource) {
                    self.eventSource.close();
                }
            });
        },

        /**
         * Connect to the SSE endpoint.
         *
         * @since 1.0.0
         */
        connect: function() {
            var self = this;

            // Close any existing connection
            this.disconnect();

            // Check if SSE is supported
            if (typeof EventSourcePolyfill === 'undefined' && typeof EventSource === 'undefined') {
                Agenistrator.Log.logWarning(agenistrator_chat.strings.sse_not_supported);
                return;
            }

            // Create the SSE URL using WordPress rewrite endpoint
            var sseUrl = agenistrator_chat.home_url + '/agenistrator/sse/';

            // Create a new EventSource with custom headers using the polyfill
            // EventSource will automatically send Last-Event-ID header on reconnection
            this.eventSource = new EventSourcePolyfill(sseUrl, {
                headers: {
                    'X-WP-Nonce': agenistrator.nonce
                }
            });

            // Set up event listeners
            this.eventSource.onopen = function() {
                self.expectingReconnect = false;
                self.connected = true;
                self.hasBeenConnected = true; // Mark that we've successfully connected

                if (self.handlers.onConnect) {
                    self.handlers.onConnect();
                }
            };

            this.eventSource.onerror = function(event) {
                self.connected = false;

                // If page is unloading, don't treat this as an error
                if (self.isPageUnloading) {
                    return;
                }

                // Only show critical errors if we've never successfully connected
                if (!self.hasBeenConnected) {

                    Agenistrator.Log.logError(agenistrator_chat.strings.sse_connection_error, event);

                    if (self.handlers.onError) {
                        self.handlers.onError(event);
                    }
                }

                // EventSource will automatically attempt to reconnect
            };

            // Set up event type listeners
            this.setupEventListeners();
        },

        /**
         * Set up listeners for different SSE event types.
         *
         * @since 1.0.0
         */
        setupEventListeners: function() {
            var self = this;

            // Listen for message events (complete messages)
            this.eventSource.addEventListener('message', function(event) {
                try {
                    var data = JSON.parse(event.data);
                    if (self.handlers.onMessageEvent) {
                        self.handlers.onMessageEvent(data);
                    }
                } catch (error) {
                    var errorMessage = Agenistrator.Template.replaceTags(
                        agenistrator_chat.strings.error_parsing_message_event,
                        { error_details: error.message || error.toString() }
                    );
                    Agenistrator.Log.logError(errorMessage, error);
                }
            });

            // Listen for stream chunk events (partial messages)
            this.eventSource.addEventListener('stream_chunk', function(event) {
                try {
                    var data = JSON.parse(event.data);
                    if (self.handlers.onStreamChunkEvent) {
                        self.handlers.onStreamChunkEvent(data);
                    }
                } catch (error) {
                    var errorMessage = Agenistrator.Template.replaceTags(
                        agenistrator_chat.strings.error_parsing_stream_chunk_event,
                        { error_details: error.message || error.toString() }
                    );
                    Agenistrator.Log.logError(errorMessage, error);
                }
            });

            // Listen for control events
            this.eventSource.addEventListener('control', function(event) {
                try {
                    var data = JSON.parse(event.data);
                    if (self.handlers.onControlEvent) {
                        self.handlers.onControlEvent(data);
                    }
                } catch (error) {
                    var errorMessage = Agenistrator.Template.replaceTags(
                        agenistrator_chat.strings.error_parsing_control_event,
                        { error_details: error.message || error.toString() }
                    );
                    Agenistrator.Log.logError(errorMessage, error);
                }
            });

            // Listen for progress events
            this.eventSource.addEventListener('progress', function(event) {
                try {
                    var data = JSON.parse(event.data);
                    if (self.handlers.onProgressEvent) {
                        self.handlers.onProgressEvent(data);
                    }
                } catch (error) {
                    Agenistrator.Log.logError('Error parsing progress event', error);
                }
            });

            // Listen for timeout events (server-side connection timeout)
            this.eventSource.addEventListener('timeout', function(event) {
                // Do nothing - EventSource will automatically reconnect with Last-Event-ID; manual reconnect does not preserve Last-Event-ID
                self.expectingReconnect = true; // Suppress error
            });

            // Listen for error events
            // Handle SSE error events from EventSourcePolyfill:
            // - event.error: stream/network errors (ErrorEvent)
            // - event.status: HTTP errors like 4xx/5xx (ConnectionEvent)
            // - event.data: backend "event: error" messages from PHP
            this.eventSource.addEventListener('error', function(event) {

                // Expected shape: { message: string, code?: string|number, details?: any, message_id?: string|number }
                var data;

                // Connection-level errors from EventSourcePolyfill (stream/network errors)
                if (event.error) {
                    // Suppress connection-level errors when EventSource is already reconnecting.
                    // readyState CONNECTING (0) means auto-reconnect is in progress - this is normal
                    // behavior after server closes the connection (e.g., timeout, restart).
                    // Firefox throws "Error in input stream" on stream closure which is benign.
                    if (self.expectingReconnect || self.eventSource.readyState === EventSource.CONNECTING) {
                        return;
                    }
                    data = { message: event.error };
                }

                // HTTP errors (4xx, 5xx) from EventSourcePolyfill (ConnectionEvent)
                if (event.status && event.status >= 400) {
                    data = {
                        message: 'HTTP ' + event.status + (event.statusText ? ' ' + event.statusText : ''),
                        code: event.status
                    };
                }

                // Backend error events: JSON payload from Thread_Error_Event::to_sse_payload()
                if (event.data) {
                    try { data = JSON.parse(event.data); }
                    catch (error) { data = { message: event.data }; }
                }

                if (data && data.message) {
                    var errorMessage = Agenistrator.Template.replaceTags(
                        agenistrator_chat.strings.sse_error_event_received,
                        { error_data: JSON.stringify(data) }
                    );

                    Agenistrator.Log.logError(errorMessage);

                    // Forward data to error handler
                    if (self.handlers.onErrorEvent) {
                        self.handlers.onErrorEvent(data);
                    }
                }
            });

            // Listen for chat log loaded events
            this.eventSource.addEventListener('chat_log_loaded', function(event) {
                try {
                    var data = JSON.parse(event.data);
                    if (self.handlers.onLogLoadedEvent) {
                        self.handlers.onLogLoadedEvent(data);
                    }
                } catch (error) {
                    var errorMessage = Agenistrator.Template.replaceTags(
                        agenistrator_chat.strings.error_parsing_log_loaded_event,
                        { error_details: error.message || error.toString() }
                    );
                    Agenistrator.Log.logError(errorMessage, error);
                }
            });

            // Listen for last_id events (sent after log to establish synchronization point)
            this.eventSource.addEventListener('last_id', function(event) {
                if (self.handlers.onLastIdEvent) {
                    self.handlers.onLastIdEvent(event);
                }
            });
        },

        /**
         * Deinitialize the SSE client and clean up resources.
         *
         * @since 1.0.0
         */
        deinitialize: function() {
            if (!this.isInitialized) {
                return;
            }

            // Disconnect from SSE
            this.disconnect();

            // Clear handlers
            this.handlers = {};

            this.isInitialized = false;
        },

        /**
         * Disconnect from the SSE endpoint.
         *
         * @since 1.0.0
         */
        disconnect: function() {
            if (this.eventSource) {
                this.eventSource.close();
                this.eventSource = null;
                this.connected = false;
            }
        }
    };
});
