/**
 * Agenistrator - Chat Interface.
 *
 * Handles the chat interface functionality in the admin area.
 * Refactored for better modularity and separation of concerns.
 *
 * @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';

    // Initialize the global namespace if it doesn't exist
    window.Agenistrator = window.Agenistrator || {};

    // Register templates if they exist in the chat object
    if (agenistrator_chat && agenistrator_chat.templates) {
        Agenistrator.Template.registerTemplates(agenistrator_chat.templates);
    }

    // Tools module - Provides tool name lookup from localized tools map
    Agenistrator.Tools = {
        /**
         * Get a tool's display name by its ID
         *
         * @param {string} toolId - The tool ID
         * @returns {string} The tool name, or the ID if not found
         * @since 1.0.0
         */
        getName: function(toolId) {
            var tool = agenistrator_chat.tools && agenistrator_chat.tools[toolId];
            return tool ? tool.name : toolId;
        }
    };

    // SessionDataManager - Handles session storage with caching
    Agenistrator.SessionDataManager = {
        // Properties
        cache: null, // Cache for session data to avoid repeated JSON parsing

        /**
         * Get all session data with caching.
         *
         * @returns {Object} The session data object.
         * @since 1.0.0
         */
        getSessionData: function() {
            // Return cached data if available
            if (this.cache !== null) {
                return this.cache;
            }

            // Load and cache data from sessionStorage
            try {
                var data = sessionStorage.getItem('agenistrator');
                this.cache = data ? JSON.parse(data) : {};
                return this.cache;
            } catch (e) {
                // sessionStorage might not be available or data might be corrupted
                Agenistrator.Log.warn(agenistrator_chat.strings.session_storage_read_error);
                this.cache = {};
                return this.cache;
            }
        },

        /**
         * Set all session data and persist to storage.
         *
         * @param {Object} data - The session data to store.
         * @since 1.0.0
         */
        setSessionData: function(data) {
            // Update cache
            this.cache = data;

            // Persist to sessionStorage
            try {
                sessionStorage.setItem('agenistrator', JSON.stringify(data));
            } catch (e) {
                // sessionStorage might not be available (private browsing, etc.)
                Agenistrator.Log.warn(agenistrator_chat.strings.session_storage_write_error);
            }
        },

        /**
         * Get a single session value.
         *
         * @param {string} key - The session key.
         * @param {*} defaultValue - Default value if key doesn't exist.
         * @returns {*} The session value or default.
         * @since 1.0.0
         */
        getSessionValue: function(key, defaultValue) {
            var data = this.getSessionData();
            return data.hasOwnProperty(key) ? data[key] : defaultValue;
        },

        /**
         * Set a single session value.
         *
         * @param {string} key - The session key.
         * @param {*} value - The value to store.
         * @since 1.0.0
         */
        setSessionValue: function(key, value) {
            var data = this.getSessionData();
            data[key] = value;
            this.setSessionData(data);
        }
    };

    // ChatManager - Handles the chat interface functionality
    Agenistrator.ChatManager = {
        // State constants
        WIDGET_STATES: {
            CLOSED: 'closed',
            OPEN: 'open',
            MAXIMIZED: 'maximized'
        },

        CHAT_STATES: {
            READY: 'ready',
            PROCESSING: 'processing',
            ERROR: 'error'
        },

        RENDER_THROTTLE_MS: 300,

        // Properties
        elements: {},
        currentChatState: null, // Will be initialized to CHAT_STATES.READY
        currentWidgetState: null,
        chatPosition: agenistrator_chat.chat_position,
        isInitialized: false,
        isUserAtBottom: true, // Track if user is at the bottom of chat
        trackedProgressInstanceId: null, // Track the progress operation instance we're displaying
        completedProgressInstances: {}, // Track completed instances to ignore late-arriving events
        renderTimers: {}, // Per-message throttle timers for markdown rendering

        /**
         * Initialize the chat interface.
         *
         * @since 1.0.0
         */
        init: function() {
            // Only initialize if we have the chat container
            if (!jQuery('.agenistrator-chat-widget').length) {
                return;
            }

            // Cache DOM elements
            this.elements = {
                $chatContainer: jQuery('.agenistrator-chat-container'),
                $chatMessages: jQuery('.agenistrator-chat-messages'),
                $chatStatus: jQuery('.agenistrator-chat-status'),
                $chatForm: jQuery('.agenistrator-chat-prompt-form'),
                $chatInput: jQuery('.agenistrator-chat-prompt-form textarea'),
                $sendButton: jQuery('.agenistrator-chat-prompt-form button'),
                $chatButton: jQuery('.agenistrator-chat-btn'),
                $chatButtonContainer: jQuery('.agenistrator-chat-button'),
                $chatBody: jQuery('.agenistrator-chat-body')
            };

            // Position chat
            this.elements.$chatContainer.removeClass('agenistrator-position-bottom-right agenistrator-position-bottom-left agenistrator-position-top-right agenistrator-position-top-left')
                .addClass('agenistrator-position-' + this.chatPosition);
            this.elements.$chatButtonContainer.removeClass('agenistrator-position-bottom-right agenistrator-position-bottom-left agenistrator-position-top-right agenistrator-position-top-left')
                .addClass('agenistrator-position-' + this.chatPosition);

            // Bind chat button events (always available)
            this.bindChatButtonEvents();

            this.initializeWidgetState();

            // Only initialize chat functionality if state is open or maximized
            var currentWidgetState = this.getWidgetState();
            if (currentWidgetState === this.WIDGET_STATES.OPEN ||
                currentWidgetState === this.WIDGET_STATES.MAXIMIZED) {
                this.initializeWidget();
            }
        },

        /**
         * Initialize widget state from session storage.
         *
         * @since 1.0.0
         */
        initializeWidgetState: function() {
            // Remove the hidden class added in HTML template to prevent chat window flashing on page load
            this.elements.$chatContainer.removeClass('hidden');
            this.elements.$chatButtonContainer.removeClass('hidden');

            this.currentWidgetState = this.getWidgetState();
            this.applyWidgetState(this.currentWidgetState);
        },

        /**
         * Get the current widget state.
         *
         * @returns {string} The widget state (closed, open, or maximized).
         * @since 1.0.0
         */
        getWidgetState: function() {
            var widgetState = Agenistrator.SessionDataManager.getSessionValue('widgetState');

            // Validate state value
            if (widgetState && Object.values(this.WIDGET_STATES).indexOf(widgetState) !== -1) {
                return widgetState;
            }

            // Default to closed
            return this.WIDGET_STATES.CLOSED;
        },

        /**
         * Set the widget state and apply changes.
         *
         * @param {string} newState - The new widget state.
         * @returns {boolean} True if state was changed.
         * @since 1.0.0
         */
        setWidgetState: function(newState) {
            var oldState = this.currentWidgetState;

            // Only proceed if state actually changed
            if (oldState === newState) {
                return true;
            }

            // Update current state
            this.currentWidgetState = newState;

            // Persist to sessionStorage
            Agenistrator.SessionDataManager.setSessionValue('widgetState', newState);

            // Apply state changes
            this.applyWidgetState(newState);

            // Handle widget initialization/deinitialization based on state
            switch (newState) {
                case this.WIDGET_STATES.OPEN:
                case this.WIDGET_STATES.MAXIMIZED:
                    if (!this.isInitialized) {
                        this.initializeWidget();
                    }
                    break;
                case this.WIDGET_STATES.CLOSED:
                    if (this.isInitialized) {
                        this.deinitializeWidget();
                    }
                    break;
            }

            return true;
        },

        /**
         * Apply visual state changes to the widget.
         *
         * @param {string} state - The widget state to apply.
         * @since 1.0.0
         */
        applyWidgetState: function(state) {
            // Remove all state classes
            this.elements.$chatContainer.removeClass('agenistrator-state-closed agenistrator-state-open agenistrator-state-maximized');
            this.elements.$chatButtonContainer.removeClass('agenistrator-state-closed agenistrator-state-open agenistrator-state-maximized');

            // Add current state class
            this.elements.$chatContainer.addClass('agenistrator-state-' + state);
            this.elements.$chatButtonContainer.addClass('agenistrator-state-' + state);

            // Hide main window scrollbar when chat is maximized
            if (state === this.WIDGET_STATES.MAXIMIZED) {
                jQuery('html').addClass('agenistrator-chat-maximized');
            } else {
                jQuery('html').removeClass('agenistrator-chat-maximized');
            }
        },

        /**
         * Initialize widget functionality when opened.
         *
         * @since 1.0.0
         */
        initializeWidget: function() {
            if (this.isInitialized) {
                return; // Already initialized
            }

            // Bind chat widget events
            this.bindChatWidgetEvents();

            // Initialize SSE connection
            this.initializeSSE();

            this.isInitialized = true;
        },

        /**
         * Deinitialize widget and clean up resources.
         *
         * @since 1.0.0
         */
        deinitializeWidget: function() {
            if (!this.isInitialized) {
                return; // Not initialized
            }

            // Disconnect SSE first to prevent any callbacks from interfering
            Agenistrator.SSEClient.deinitialize();

            // Stop any active agent loop only if current window initiated it
            // Must check isInitiator() before cancelRateLimitTimeout() which clears the flag
            if (this.isProcessing() && Agenistrator.ChatAPI.isInitiator()) {
                Agenistrator.ChatAPI.stopAgentLoop();
            }

            // Cancel any pending rate limit timeout
            Agenistrator.ChatAPI.cancelRateLimitTimeout();

            // Cancel all pending render timers
            for (var id in this.renderTimers) {
                clearTimeout(this.renderTimers[id]);
            }
            this.renderTimers = {};

            // Reset UI state (preserve server-side chat log)
            this.elements.$chatMessages.empty();

            // Reset chat state to ready
            this.setChatState(this.CHAT_STATES.READY);

            // Unbind chat widget events
            this.unbindChatWidgetEvents();

            this.isInitialized = false;
        },

        /**
         * Bind chat button events.
         *
         * @since 1.0.0
         */
        bindChatButtonEvents: function() {
            var self = this;

            // Chat button (always available)
            this.elements.$chatButton.on('click', function() {
                self.setWidgetState(self.WIDGET_STATES.OPEN);
            });
        },

        /**
         * Bind chat widget events when widget is active.
         *
         * @since 1.0.0
         */
        bindChatWidgetEvents: function() {
            var self = this;

            // Widget control events (using chat container for event delegation)
            // Close button
            this.elements.$chatContainer.on('click', '.agenistrator-close-chat', function() {
                self.setWidgetState(self.WIDGET_STATES.CLOSED);
            });

            // Maximize button
            this.elements.$chatContainer.on('click', '.agenistrator-maximize-chat', function(e) {
                e.preventDefault();
                e.stopPropagation();
                self.setWidgetState(self.WIDGET_STATES.MAXIMIZED);
            });

            // Restore button
            this.elements.$chatContainer.on('click', '.agenistrator-restore-chat', function(e) {
                e.preventDefault();
                e.stopPropagation();
                self.setWidgetState(self.WIDGET_STATES.OPEN);
            });

            // Chat functionality events
            // Track scroll position to determine if user is at bottom
            this.elements.$chatMessages.on('scroll.agenistrator', function() {
                self.isUserAtBottom = self.isAtBottom();
            });

            // Clear chat log
            this.elements.$chatContainer.on('click.agenistrator', '.agenistrator-clear-chat', function() {
                if (confirm(agenistrator_chat.strings.confirm_clear_chat)) {
                    self.clearChatLog();
                }
            });

            // Report agent issue
            this.elements.$chatContainer.on('click.agenistrator', '.agenistrator-report-issue', function(e) {
                e.preventDefault();
                self.reportIssue();
            });

            // Chat form submission handler
            this.elements.$chatForm.on('submit.agenistrator', function(e) {
                e.preventDefault();
                // If processing, stop the agent loop; otherwise send message
                if (self.isProcessing()) {
                    self.stopAgentLoop();
                } else if (self.isReady()) {
                    self.sendMessage();
                }
            });

            // Enter key shortcut - trigger form submission
            this.elements.$chatInput.on('keydown.agenistrator', function(e) {
                if (e.key === 'Enter' && !e.shiftKey) {
                    e.preventDefault();
                    self.elements.$chatForm.trigger('submit');
                }
            });

            // Save input to sessionStorage as user types
            this.elements.$chatInput.on('input.agenistrator', function() {
                self.saveInputBuffer();
            });

            // Restore any saved input and focus
            this.restoreInputBuffer();
            this.elements.$chatInput.focus();

            // Handle collapsible tool calls and results
            this.elements.$chatMessages.on('click.agenistrator', '.agenistrator-collapsible-message .agenistrator-message-header', function(e) {
                e.preventDefault();
                var $header = jQuery(this);
                var $body = $header.siblings('.agenistrator-message-body');
                var $parent = $header.parent();

                if ($parent.hasClass('agenistrator-collapsed-message')) {
                    // Expand
                    $body.slideDown(200);
                    $parent.removeClass('agenistrator-collapsed-message');
                } else {
                    // Collapse
                    $body.slideUp(200);
                    $parent.addClass('agenistrator-collapsed-message');
                }
            });
        },

        /**
         * Unbind chat widget events when widget is closed.
         *
         * @since 1.0.0
         */
        unbindChatWidgetEvents: function() {
            // Remove widget control events
            this.elements.$chatContainer.off('click', '.agenistrator-close-chat');
            this.elements.$chatContainer.off('click', '.agenistrator-maximize-chat');
            this.elements.$chatContainer.off('click', '.agenistrator-restore-chat');

            // Remove chat functionality events
            this.elements.$chatContainer.off('click.agenistrator', '.agenistrator-clear-chat');
            this.elements.$chatContainer.off('click.agenistrator', '.agenistrator-report-issue');
            this.elements.$chatForm.off('.agenistrator');
            this.elements.$chatInput.off('.agenistrator');
            this.elements.$chatMessages.off('.agenistrator');
        },

        /**
         * Clear the chat log.
         *
         * @since 1.0.0
         */
        clearChatLog: function() {
            var self = this;

            // Clear UI immediately to avoid race condition with SSE welcome message
            self.elements.$chatMessages.empty();

            // Reset UI state to ensure send button is enabled
            self.setChatState(self.CHAT_STATES.READY);
            self.scrollToBottom();

            // Use the ChatAPI module to clear chat log
            Agenistrator.ChatAPI.clearChatLog(
                function(data) {
                    if (!data.success) {
                        var errorMessage = Agenistrator.Template.replaceTags(
                            agenistrator_chat.strings.failed_to_clear_chat_log,
                            { error_details: data.data }
                        );
                        Agenistrator.Log.logError(errorMessage);
                    }
                },
                function(error) {
                    var errorMessage = Agenistrator.Template.replaceTags(
                        agenistrator_chat.strings.server_error_clearing_chat_log,
                        { error_details: error.message || error.toString() }
                    );
                    Agenistrator.Log.logError(errorMessage, error);
                }
            );
        },

        /**
         * Report an agent issue with optional email.
         *
         * @since 1.0.0
         */
        reportIssue: function() {
            // First, prompt for issue description
            var description = prompt(agenistrator_chat.strings.prompt_issue_description);

            // If user cancelled the prompt, abort
            if (description === null) {
                return;
            }

            // Then ask about emailing the report
            var sendEmail = confirm(agenistrator_chat.strings.confirm_report_issue);

            Agenistrator.ChatAPI.reportIssue(
                description,
                sendEmail,
                function(data) {
                    var message = sendEmail
                        ? agenistrator_chat.strings.report_sent_success
                        : agenistrator_chat.strings.report_created_success;
                    alert(message);
                },
                function(error) {
                    var errorMessage = Agenistrator.Template.replaceTags(
                        agenistrator_chat.strings.report_issue_error,
                        { error_details: error.message || error.toString() }
                    );
                    alert(errorMessage);
                }
            );
        },

        /**
         * Save the current input buffer to session storage.
         *
         * @since 1.0.0
         */
        saveInputBuffer: function() {
            var value = this.elements.$chatInput.val();
            Agenistrator.SessionDataManager.setSessionValue('inputBuffer', value || '');
        },

        /**
         * Restore the input buffer from session storage.
         *
         * @since 1.0.0
         */
        restoreInputBuffer: function() {
            var value = Agenistrator.SessionDataManager.getSessionValue('inputBuffer', '');
            if (value) {
                this.elements.$chatInput.val(value);
            }
        },

        /**
         * Clear the input buffer from session storage.
         *
         * @since 1.0.0
         */
        clearInputBuffer: function() {
            Agenistrator.SessionDataManager.setSessionValue('inputBuffer', '');
        },

        /**
         * Initialize Server-Sent Events for real-time updates.
         *
         * @since 1.0.0
         */
        initializeSSE: function() {
            var self = this;

            // Initialize the SSE client with event handlers
            Agenistrator.SSEClient.initialize({

                // Handle message event (complete message)
                onMessageEvent: function(data) {
                    var messageId = 'msg-' + data.message_id;

                    // Add the message (works for all roles)
                    self.addMessage(data.role, messageId, data.content, false, data.metadata);

                    // Finalize assistant messages when complete or from chat log (hide cursor)
                    if (data.role === 'assistant' && (data.is_complete || data.is_from_chat_log)) {
                        self.finalizeMessage(messageId);
                    }

                    self.scrollToBottom();
                },

                // Handle stream chunk event (partial message)
                onStreamChunkEvent: function(data) {
                    var messageId = 'msg-' + data.message_id;

                    // Append content to the message
                    self.addMessage('assistant', messageId, data.content, true);

                    // If this is the final chunk, finalize the message
                    if (data.is_final) {
                        self.finalizeMessage(messageId);
                    }
                },

                // Handle control event
                onControlEvent: function(data) {

                    // Handle different control actions
                    switch (data.action) {
                        case 'clear':
                            // Clear chat log
                            Agenistrator.ChatAPI.cancelRateLimitTimeout();
                            self.elements.$chatMessages.empty();
                            break;
                        case 'processing':
                            // Set processing state (synchronized across tabs)
                            self.setChatState(self.CHAT_STATES.PROCESSING);
                            break;
                        case 'ready':
                            // Set ready state (synchronized across tabs)
                            Agenistrator.ChatAPI.cancelRateLimitTimeout();
                            self.setChatState(self.CHAT_STATES.READY);
                            break;
                        case 'stop':
                            // Agent loop stopping - cancel rate limit timeout
                            // Ready event will handle UI transition to send button
                            Agenistrator.ChatAPI.cancelRateLimitTimeout();
                            break;
                        case 'rate_limit_wait':
                            // Show rate limit waiting status with countdown
                            var waitTime = data.params && data.params.wait_time ? data.params.wait_time : 0;
                            self.showStatus(agenistrator_chat.strings.rate_limit_waiting, waitTime);
                            break;
                        case 'rate_limit_resume':
                            // Hide rate limit status and cancel any pending timeout
                            Agenistrator.ChatAPI.cancelRateLimitTimeout();
                            self.hideStatus();
                            break;
                    }
                },

                // Handle error event
                onErrorEvent: function(data) {
                    if (data.message_id) {
                        // Message-related error - show error in the specific message
                        var messageId = 'msg-' + data.message_id;
                        self.addMessage('assistant', messageId, data.message);
                        self.finalizeMessage(messageId);
                    } else {
                        // General error - show in error display
                        self.displayError(
                            data.message || agenistrator.strings.unknown_error
                        );
                    }

                    // Always reset UI state on error
                    self.setChatState(self.CHAT_STATES.READY);
                    self.scrollToBottom();
                },

                // Handle progress event (for embeddings sync and cleanup operations)
                onProgressEvent: function(data) {
                    if (typeof NProgress === 'undefined' || (data.operation !== 'embeddings' && data.operation !== 'embeddings_cleanup')) {
                        return;
                    }

                    var instanceId = data.instance_id;
                    var isRunning = data.status === 'running';

                    // Ignore events for completed instances (handles out-of-order events)
                    if (self.completedProgressInstances[instanceId]) {
                        return;
                    }

                    // If we're tracking an instance, only respond to events for that instance
                    if (self.trackedProgressInstanceId !== null) {
                        if (instanceId !== self.trackedProgressInstanceId) {
                            return;
                        }

                        // This is our tracked instance - if it's done, mark as completed
                        if (!isRunning) {
                            self.trackedProgressInstanceId = null;
                            self.completedProgressInstances[instanceId] = true;
                            NProgress.done();

                            // Clean up after 60 seconds to prevent memory leaks
                            setTimeout(function() {
                                delete self.completedProgressInstances[instanceId];
                            }, 60000);
                            return;
                        }
                    } else {
                        // Not tracking anything yet - start tracking this instance if running
                        if (!isRunning) {
                            return;
                        }
                        self.trackedProgressInstanceId = instanceId;
                    }

                    // Update progress bar
                    var progress = data.total > 0 ? data.current / data.total : 0;
                    if (progress > 0) {
                        NProgress.set(progress);
                    } else {
                        NProgress.start();
                    }
                }
            });

            // Keep reference to disconnect on cleanup
            this.sseClient = Agenistrator.SSEClient;

            // Configure NProgress for the chat body container (not messages, so it doesn't scroll)
            if (typeof NProgress !== 'undefined') {
                NProgress.configure({
                    parent: '.agenistrator-chat-body',
                    showSpinner: false,
                    trickle: false,
                    minimum: 0.01
                });
            }
        },

        /**
         * Send a message to the assistant.
         *
         * @since 1.0.0
         */
        sendMessage: function() {
            var self = this;
            var rawMessage = this.elements.$chatInput.val().trim();

            // Prevent sending if already processing or no message
            if (!rawMessage || this.isProcessing()) {
                return;
            }

            // Escape HTML outside of code blocks to prevent accidental HTML rendering of user messages due to
            // users not being aware of the need to escape their input that looks like HTML tags and entities.
            var message = Agenistrator.Format.escapeHTMLInMarkdown(rawMessage);

            // Additional safety check - ensure send button is not disabled
            if (this.elements.$sendButton.prop('disabled')) {
                return;
            }

            // Add user message to chat with temporary element ID
            this.addMessage('user', 'new-user-message', message);

            // Add empty assistant message with blinking cursor immediately
            this.addMessage('assistant', 'new-assistant-message', '');

            // Clear input and saved buffer, show processing state
            this.elements.$chatInput.val('');
            this.clearInputBuffer();
            this.setChatState(this.CHAT_STATES.PROCESSING);
            this.forceScrollToBottom();

            // Use the ChatAPI to send the message
            Agenistrator.ChatAPI.sendMessage(
                message,
                null, // No success callback needed
                // Error callback
                function(error, data) {
                    // Remove the temporary assistant message since no response will come
                    self.elements.$chatMessages.find('.agenistrator-message-new-assistant-message').remove();

                    // Handle thread_busy error - restore message to input and set processing state
                    if (data && data.code === 'thread_busy') {
                        // Remove temporary user message
                        self.elements.$chatMessages.find('.agenistrator-message-new-user-message').remove();

                        // Restore original (unescaped) message to input
                        self.elements.$chatInput.val(rawMessage);

                        // Set processing state to reflect actual thread state
                        self.setChatState(self.CHAT_STATES.PROCESSING);
                        self.scrollToBottom();
                        return;
                    }

                    // Handle other errors
                    // Display error message in chat (don't replace user message)
                    self.displayError(error);

                    // Reset UI state using the centralized method
                    self.setChatState(self.CHAT_STATES.READY);
                    self.scrollToBottom();
                }
            );
        },

        /**
         * Stop the agent loop.
         *
         * @since 1.0.0
         */
        stopAgentLoop: function() {
            // Disable button immediately to prevent spam and show feedback
            this.elements.$sendButton.prop('disabled', true);

            var self = this;
            Agenistrator.ChatAPI.stopAgentLoop(
                null, // No success callback - ready event handles UI transition
                function(error) {
                    // On error, re-enable stop button so user can retry
                    self.elements.$sendButton.prop('disabled', false);
                }
            );
        },

        /**
         * Add or update a message in the chat.
         *
         * @param {string} role - The message role (user, assistant, tool_call, tool_result).
         * @param {string} messageId - Unique message identifier.
         * @param {string} content - The message content.
         * @param {boolean} append - Whether to append to existing content.
         * @param {Object} metadata - Optional message metadata.
         * @since 1.0.0
         */
        addMessage: function(role, messageId, content, append, metadata) {
            // Generate message ID if not provided
            if (!messageId) {
                messageId = 'msg-' + Date.now() + '-' + Math.random().toString(36).substring(2, 11);
            }

            var messageElement = this.elements.$chatMessages.find('.agenistrator-message-' + messageId);

            // If message doesn't exist, create it
            if (messageElement.length === 0) {
                this.createMessage(role, messageId, content, metadata);
                return;
            }

            // Update existing message
            this.updateMessageContent(messageElement, messageId, content, append);
        },

        /**
         * Create a new message element.
         *
         * @param {string} role - The message role.
         * @param {string} messageId - Unique message identifier.
         * @param {string} content - The message content.
         * @param {Object} metadata - Optional message metadata.
         * @since 1.0.0
         */
        createMessage: function(role, messageId, content, metadata) {
            var templateName, templateData;

            // Format content as markdown
            var formattedContent = Agenistrator.Format.renderMarkdown(content || '');

            if (role === 'user') {
                templateName = 'chat-message-user';
                templateData = {
                    message: formattedContent,
                    message_id: messageId,
                    raw_content: Agenistrator.Format.escapeHTML(content || '')
                };
            } else if (role === 'assistant') {
                templateName = 'chat-message-assistant';
                templateData = {
                    message: formattedContent,
                    message_id: messageId,
                    cursor_style: '',  // Always show cursor during streaming, hide via finalizeMessage
                    cursor_character: agenistrator_chat.strings.cursor_character,
                    raw_content: Agenistrator.Format.escapeHTML(content || '')
                };
            } else if (role === 'tool_call') {
                // Remove the placeholder - it's now visually above us which is wrong
                this.elements.$chatMessages.find('.agenistrator-message-new-assistant-message').remove();

                // Format arguments as JSON (same pattern as tool results)
                var argumentsJson = metadata.arguments && Object.keys(metadata.arguments).length > 0
                    ? JSON.stringify(metadata.arguments, null, 2)
                    : agenistrator_chat.strings.no_arguments;

                templateName = 'chat-message-tool-call';
                templateData = {
                    message_id: messageId,
                    tool_icon: agenistrator_chat.strings.tool_icon,
                    tool_call_label: agenistrator_chat.strings.tool_call_label,
                    tool_name: Agenistrator.Format.escapeHTML(Agenistrator.Tools.getName(metadata.tool_id)),
                    arguments_label: agenistrator_chat.strings.arguments_label,
                    arguments: Agenistrator.Format.escapeHTML(argumentsJson),
                    raw_content: Agenistrator.Format.escapeHTML(content || '')
                };

                // Show status indicator while tool is running
                var toolStatusMessage = Agenistrator.Template.replaceTags(
                    agenistrator_chat.strings.running_tool,
                    { tool_name: Agenistrator.Tools.getName(metadata.tool_id) }
                );
                this.showStatus(toolStatusMessage);
            } else if (role === 'tool_result') {
                // Hide the tool running status indicator
                this.hideStatus();

                var success = metadata.success !== false;
                templateName = 'chat-message-tool-result';
                templateData = {
                    message_id: messageId,
                    result_icon: success ? agenistrator_chat.strings.tool_result_success_icon : agenistrator_chat.strings.tool_result_error_icon,
                    tool_result_label: agenistrator_chat.strings.tool_result_label,
                    tool_name: Agenistrator.Format.escapeHTML(Agenistrator.Tools.getName(metadata.tool_id)),
                    success_class: success ? 'agenistrator-tool-result-success' : 'agenistrator-tool-result-error',
                    formatted_result: Agenistrator.Format.escapeHTML(typeof metadata.result === 'string' ? metadata.result : JSON.stringify(metadata.result, null, 2)),
                    raw_content: Agenistrator.Format.escapeHTML(content || '')
                };
            } else {
                Agenistrator.Log.logError('Unknown message role: ' + role);
                return;
            }

            var $messageElement = jQuery(Agenistrator.Template.render(templateName, templateData));

            // Add streaming class for assistant messages (enables cursor-following layout)
            if (role === 'assistant') {
                $messageElement.addClass('agenistrator-message-streaming');
            }

            // Auto-detect replacement element based on role
            var replaceElement = null;
            if (role === 'user') {
                replaceElement = this.elements.$chatMessages.find('.agenistrator-message-new-user-message');
            } else if (role === 'assistant') {
                replaceElement = this.elements.$chatMessages.find('.agenistrator-message-new-assistant-message');
            }

            // If we have an element to replace, replace it; otherwise append
            if (replaceElement && replaceElement.length > 0) {
                replaceElement.replaceWith($messageElement);
            } else {
                this.elements.$chatMessages.append($messageElement);
            }

            // After tool_result, add a new placeholder to show agent is still working
            if (role === 'tool_result') {
                this.createMessage('assistant', 'new-assistant-message', '', null);
            }

            this.scrollToBottom();
        },

        /**
         * Update content of an existing message.
         *
         * @param {jQuery} messageElement - The message element.
         * @param {string} messageId - The message identifier.
         * @param {string} content - The new content.
         * @param {boolean} append - Whether to append to existing content.
         * @since 1.0.0
         */
        updateMessageContent: function(messageElement, messageId, content, append) {
            var contentElement = messageElement.find('.agenistrator-markdown-content');
            var rawTextElement = messageElement.find('.agenistrator-raw-content');

            if (append) {
                // Append to raw text storage
                rawTextElement.text(rawTextElement.text() + content);

                // Append raw chunk as text node for immediate display (createTextNode escapes HTML)
                contentElement.append(document.createTextNode(content));

                // Schedule throttled markdown render
                this.scheduleRender(messageId);
            } else {
                // Replace: cancel any pending render and render immediately
                this.cancelRender(messageId);
                rawTextElement.text(content);
                contentElement.html(Agenistrator.Format.renderMarkdown(content));
            }

            this.scrollToBottom();
        },

        /**
         * Schedule a throttled markdown render for a streaming message.
         *
         * @param {string} messageId - The message identifier.
         * @since 1.0.0
         */
        scheduleRender: function(messageId) {
            if (this.renderTimers[messageId]) {
                return; // Timer already pending — throttle, don't reset
            }

            var self = this;
            this.renderTimers[messageId] = setTimeout(function() {
                delete self.renderTimers[messageId];
                self.renderMarkdown(messageId);
            }, this.RENDER_THROTTLE_MS);
        },

        /**
         * Cancel a pending render timer without rendering.
         *
         * @param {string} messageId - The message identifier.
         * @since 1.0.0
         */
        cancelRender: function(messageId) {
            if (this.renderTimers[messageId]) {
                clearTimeout(this.renderTimers[messageId]);
                delete this.renderTimers[messageId];
            }
        },

        /**
         * Flush any pending render immediately.
         *
         * @param {string} messageId - The message identifier.
         * @since 1.0.0
         */
        flushRender: function(messageId) {
            if (!this.renderTimers[messageId]) {
                return;
            }
            this.cancelRender(messageId);
            this.renderMarkdown(messageId);
        },

        /**
         * Render markdown for a message.
         *
         * @param {string} messageId - The message identifier.
         * @since 1.0.0
         */
        renderMarkdown: function(messageId) {
            var messageElement = this.elements.$chatMessages.find('.agenistrator-message-' + messageId);
            if (messageElement.length > 0) {
                var contentElement = messageElement.find('.agenistrator-markdown-content');
                var rawTextElement = messageElement.find('.agenistrator-raw-content');
                contentElement.html(Agenistrator.Format.renderMarkdown(rawTextElement.text()));
                this.scrollToBottom();
            }
        },

        /**
         * Finalize a message after streaming completes.
         *
         * @param {string} messageId - The message identifier.
         * @since 1.0.0
         */
        finalizeMessage: function(messageId) {
            this.flushRender(messageId);

            var messageElement = this.elements.$chatMessages.find('.agenistrator-message-' + messageId);
            if (messageElement.length > 0) {
                // Check if message content is empty or whitespace-only
                var rawContent = messageElement.find('.agenistrator-raw-content').text();
                if (!rawContent || !rawContent.trim()) {
                    // Remove empty messages entirely
                    messageElement.remove();
                } else {
                    // Remove blinking cursor and streaming class for proper block rendering
                    messageElement.find('.agenistrator-blinking-cursor').remove();
                    messageElement.removeClass('agenistrator-message-streaming');
                }
                this.scrollToBottom();
            }
        },

        /**
         * Set the chat state and update UI.
         *
         * @param {string} state - The chat state (ready, processing, error).
         * @since 1.0.0
         */
        setChatState: function(state) {
            // Store current state
            this.currentChatState = state;

            switch (state) {
                case this.CHAT_STATES.PROCESSING:
                    // Transform send button to stop button
                    this.elements.$sendButton
                        .prop('disabled', false)
                        .text(agenistrator_chat.strings.stop_button_text)
                        .removeClass('button-primary')
                        .addClass('button-secondary agenistrator-stop-button');
                    break;
                case this.CHAT_STATES.READY:
                default:
                    // Transform stop button back to send button
                    this.elements.$sendButton
                        .prop('disabled', false)
                        .text(agenistrator_chat.strings.send_button_text)
                        .removeClass('button-secondary agenistrator-stop-button')
                        .addClass('button-primary');
                    // Remove any temporary messages that might be left over
                    this.elements.$chatMessages.find('.agenistrator-message-new-user-message, .agenistrator-message-new-assistant-message').remove();
                    // Focus input so user can continue typing
                    this.elements.$chatInput.focus();
                    break;
            }
        },

        /**
         * Get the current chat state.
         *
         * @returns {string} The current chat state.
         * @since 1.0.0
         */
        getChatState: function() {
            return this.currentChatState || this.CHAT_STATES.READY;
        },

        /**
         * Check if chat is currently processing.
         *
         * @returns {boolean} True if processing.
         * @since 1.0.0
         */
        isProcessing: function() {
            return this.getChatState() === this.CHAT_STATES.PROCESSING;
        },

        /**
         * Check if chat is ready for new messages.
         *
         * @returns {boolean} True if ready.
         * @since 1.0.0
         */
        isReady: function() {
            return this.getChatState() === this.CHAT_STATES.READY;
        },

        /**
         * Check if chat is scrolled to the bottom.
         *
         * @returns {boolean} True if at bottom.
         * @since 1.0.0
         */
        isAtBottom: function() {
            var el = this.elements.$chatMessages[0];
            // Allow 20px threshold for "at bottom" detection
            return el.scrollHeight - el.scrollTop - el.clientHeight < 20;
        },

        /**
         * Scroll chat to bottom if user was already at bottom.
         *
         * @since 1.0.0
         */
        scrollToBottom: function() {
            if (this.isUserAtBottom) {
                this.elements.$chatMessages.scrollTop(this.elements.$chatMessages[0].scrollHeight);
            }
        },

        /**
         * Force scroll to bottom.
         *
         * @since 1.0.0
         */
        forceScrollToBottom: function() {
            this.isUserAtBottom = true;
            this.elements.$chatMessages.scrollTop(this.elements.$chatMessages[0].scrollHeight);
        },

        /**
         * Display an error message in the chat.
         *
         * @param {string|Error} error - The error to display.
         * @returns {string} The error HTML.
         * @since 1.0.0
         */
        displayError: function(error) {
            var errorMessage = typeof error === 'string' ? error : (error.message || agenistrator_chat.strings.network_error);

            // Format error message
            var errorHtml = Agenistrator.Template.render('chat-error-message', {
                error_message: Agenistrator.Template.replaceTags(
                    Agenistrator.Format.escapeHTMLChars(agenistrator_chat.strings.error_message_format),
                    {
                        error_details: Agenistrator.Format.escapeHTML(errorMessage),
                        strong: '<strong>',
                        '/strong': '</strong>'
                    }
                ),
                try_again_message: Agenistrator.Format.escapeHTML(agenistrator_chat.strings.try_again_message)
            });

            // Append error to chat messages container
            this.elements.$chatMessages.append(errorHtml);

            return errorHtml;
        },

        /**
         * Show status message with spinner and optional countdown.
         *
         * @param {string} message - The status message.
         * @param {number} countdownSeconds - Optional countdown in seconds.
         * @since 1.0.0
         */
        showStatus: function(message, countdownSeconds) {
            var self = this;

            // Clear any existing countdown
            if (this.statusCountdownInterval) {
                clearInterval(this.statusCountdownInterval);
                this.statusCountdownInterval = null;
            }

            var remaining = countdownSeconds ? Math.ceil(countdownSeconds) : 0;

            var updateDisplay = function() {
                var displayMessage = remaining > 0 ? message + ' ' + remaining : message;
                var html = Agenistrator.Template.render('chat-status', {
                    message: Agenistrator.Format.escapeHTML(displayMessage)
                });
                self.elements.$chatStatus.html(html).removeClass('hidden');
            };

            updateDisplay();
            this.scrollToBottom();

            if (remaining > 0) {
                this.statusCountdownInterval = setInterval(function() {
                    remaining--;
                    if (remaining <= 0) {
                        clearInterval(self.statusCountdownInterval);
                        self.statusCountdownInterval = null;
                    }
                    updateDisplay();
                }, 1000);
            }
        },

        /**
         * Hide status message.
         *
         * @since 1.0.0
         */
        hideStatus: function() {
            if (this.statusCountdownInterval) {
                clearInterval(this.statusCountdownInterval);
                this.statusCountdownInterval = null;
            }
            this.elements.$chatStatus.addClass('hidden').empty();
        },
    };

    // Initialize the chat manager
    Agenistrator.ChatManager.init();
});
