//SCREEN-BOILERPLATE
//this boilerplate builds screens that are gonna be shown in the <div class='content'> tag of our main HTML.
//do not build app components with this boilerplate, only screens (reports)

define([
    // Application variable, always include it to have access to app methods.
    'app',

    //templates-loader: this loads templates async.
    'js/templates-loader',
    'backgrid',
    'moment',

    'components/ag-grid/ag-grid-module',
],
    function (app, T, Backgrid, moment, AgGridModule) {

        var Screen = { Models: {}, Views: {}, Collections: {} }

        // API Base URL Configuration
        var API_BASE_URL = 'http://localhost:5000/api/opcua';

        // API Helper Functions with enhanced error handling
        var OPCUA_API = {
            browseRootNodes: function (callback) {
                $.ajax({
                    url: API_BASE_URL + '/browse',
                    type: 'GET',
                    dataType: 'json',
                    timeout: 30000, // 30 second timeout
                    success: function (response) {
                        if (response && response.success && response.nodes) {
                            callback(null, response.nodes);
                        } else {
                            var errorMsg = response && response.message ? response.message : 'Invalid response format from server';
                            callback(errorMsg, null);
                        }
                    },
                    error: function (xhr, status, error) {
                        var errorMsg = 'Failed to browse root nodes';
                        
                        if (status === 'timeout') {
                            errorMsg = 'Request timeout: The server did not respond in time. Please check your connection.';
                        } else if (status === 'error') {
                            if (xhr.status === 0) {
                                errorMsg = 'Network error: Cannot connect to the API server. Please check if the API is running.';
                            } else if (xhr.status === 404) {
                                errorMsg = 'API endpoint not found. Please verify the API is deployed correctly.';
                            } else if (xhr.status === 500) {
                                errorMsg = 'Server error: The API encountered an internal error.';
                            } else if (xhr.status === 503) {
                                errorMsg = 'Service unavailable: The OPC UA server may be unreachable.';
                            } else if (xhr.responseJSON && xhr.responseJSON.message) {
                                errorMsg = xhr.responseJSON.message;
                            } else {
                                errorMsg = 'Failed to browse root nodes: ' + (error || status || 'Unknown error (Status: ' + xhr.status + ')');
                            }
                        } else {
                            errorMsg = 'Unexpected error: ' + (error || status || 'Unknown error');
                        }
                        
                        callback(errorMsg, null);
                    }
                });
            },

            browseNode: function (nodeId, callback) {
                if (!nodeId) {
                    callback('Invalid node ID provided', null);
                    return;
                }

                $.ajax({
                    url: API_BASE_URL + '/browse/' + nodeId,
                    type: 'GET',
                    dataType: 'json',
                    timeout: 30000, // 30 second timeout
                    success: function (response) {
                        if (response && response.success && response.nodes) {
                            callback(null, response.nodes);
                        } else {
                            var errorMsg = response && response.message ? response.message : 'Invalid response format from server';
                            callback(errorMsg, null);
                        }
                    },
                    error: function (xhr, status, error) {
                        var errorMsg = 'Failed to browse node';
                        
                        if (status === 'timeout') {
                            errorMsg = 'Request timeout: The server did not respond in time.';
                        } else if (status === 'error') {
                            if (xhr.status === 0) {
                                errorMsg = 'Network error: Cannot connect to the API server.';
                            } else if (xhr.status === 404) {
                                if (xhr.responseJSON && xhr.responseJSON.error && xhr.responseJSON.error.name === 'mockRequestNotFoundError') {
                                    errorMsg = 'Mock Server Error: Endpoint not configured. Please configure GET /api/opcua/browse/{nodeId} in your Postman Mock Server with a wildcard (*) or specific examples.';
                                } else {
                                    errorMsg = 'Node not found: The specified node ID may be invalid.';
                                }
                            } else if (xhr.status === 500) {
                                errorMsg = 'Server error: The API encountered an internal error.';
                            } else if (xhr.status === 503) {
                                errorMsg = 'Service unavailable: The OPC UA server may be unreachable.';
                            } else if (xhr.responseJSON && xhr.responseJSON.message) {
                                errorMsg = xhr.responseJSON.message;
                            } else {
                                errorMsg = 'Failed to browse node: ' + (error || status || 'Unknown error (Status: ' + xhr.status + ')');
                            }
                        } else {
                            errorMsg = 'Unexpected error: ' + (error || status || 'Unknown error');
                        }
                        
                        callback(errorMsg, null);
                    }
                });
            },

            getTagValue: function (nodeId, callback) {
                if (!nodeId) {
                    callback('Invalid node ID provided', null);
                    return;
                }

                $.ajax({
                    url: API_BASE_URL + '/tag/' + nodeId,
                    type: 'GET',
                    dataType: 'json',
                    timeout: 15000, // 15 second timeout for tag reads
                    success: function (response) {
                        if (response && response.nodeId) {
                            callback(null, response);
                        } else {
                            callback('Invalid tag response format: Missing required fields', null);
                        }
                    },
                    error: function (xhr, status, error) {
                        var errorMsg = 'Failed to get tag value';
                        
                        if (status === 'timeout') {
                            errorMsg = 'Request timeout: Tag read timed out.';
                        } else if (status === 'error') {
                            if (xhr.status === 0) {
                                errorMsg = 'Network error: Cannot connect to the API server.';
                            } else if (xhr.status === 404) {
                                errorMsg = 'Tag not found: The specified node ID may be invalid or not a variable.';
                            } else if (xhr.status === 500) {
                                errorMsg = 'Server error: The API encountered an internal error.';
                            } else if (xhr.status === 503) {
                                errorMsg = 'Service unavailable: The OPC UA server may be unreachable.';
                            } else if (xhr.responseJSON && xhr.responseJSON.message) {
                                errorMsg = xhr.responseJSON.message;
                            } else {
                                errorMsg = 'Failed to get tag value: ' + (error || status || 'Unknown error (Status: ' + xhr.status + ')');
                            }
                        } else {
                            errorMsg = 'Unexpected error: ' + (error || status || 'Unknown error');
                        }
                        
                        // For tag reads, we don't always want to show errors to avoid spam
                        // Only log to console, callback will handle whether to show user
                        console.error('Tag read error for ' + nodeId + ':', errorMsg);
                        callback(errorMsg, null);
                    }
                });
            },

            // Batch get multiple tag values (more efficient for polling)
            getTagValues: function (nodeIds, callback) {
                if (!nodeIds || !Array.isArray(nodeIds) || nodeIds.length === 0) {
                    callback('Invalid node IDs provided', null);
                    return;
                }

                $.ajax({
                    url: API_BASE_URL + '/tags/values',
                    type: 'POST',
                    contentType: 'application/json',
                    dataType: 'json',
                    data: JSON.stringify(nodeIds),
                    timeout: 20000, // 20 second timeout for batch reads
                    success: function (response) {
                        if (Array.isArray(response)) {
                            callback(null, response);
                        } else {
                            callback('Invalid tag values response format: Expected array', null);
                        }
                    },
                    error: function (xhr, status, error) {
                        var errorMsg = 'Failed to get tag values';
                        
                        if (status === 'timeout') {
                            errorMsg = 'Request timeout: Batch tag read timed out.';
                        } else if (status === 'error') {
                            if (xhr.status === 0) {
                                errorMsg = 'Network error: Cannot connect to the API server.';
                            } else if (xhr.status === 400) {
                                errorMsg = 'Bad request: Invalid node IDs format.';
                            } else if (xhr.status === 500) {
                                errorMsg = 'Server error: The API encountered an internal error.';
                            } else if (xhr.status === 503) {
                                errorMsg = 'Service unavailable: The OPC UA server may be unreachable.';
                            } else if (xhr.responseJSON && xhr.responseJSON.message) {
                                errorMsg = xhr.responseJSON.message;
                            } else {
                                errorMsg = 'Failed to get tag values: ' + (error || status || 'Unknown error (Status: ' + xhr.status + ')');
                            }
                        } else {
                            errorMsg = 'Unexpected error: ' + (error || status || 'Unknown error');
                        }
                        
                        console.error('Batch tag read error:', errorMsg);
                        callback(errorMsg, null);
                    }
                });
            }
        };

        // LocalStorage helper for persisting tags per user
        var TagStorage = {
            STORAGE_KEY_PREFIX: 'opcua_browser_tags_',

            // Get current user ID from the app (fallback to 'default' if not available)
            getUserId: function () {
                try {
                    // Try to get user ID from common app patterns
                    if (app && app.models && app.models.session && app.models.session.get('userId')) {
                        return app.models.session.get('userId');
                    }
                    if (app && app.models && app.models.user && app.models.user.get('id')) {
                        return app.models.user.get('id');
                    }
                    if (app && app.user && app.user.id) {
                        return app.user.id;
                    }
                    // Fallback to username if available
                    if (app && app.models && app.models.session && app.models.session.get('username')) {
                        return app.models.session.get('username');
                    }
                    if (app && app.models && app.models.user && app.models.user.get('username')) {
                        return app.models.user.get('username');
                    }
                } catch (e) {
                    console.warn('Could not get user ID for tag storage:', e);
                }
                return 'default';
            },

            getStorageKey: function () {
                return this.STORAGE_KEY_PREFIX + this.getUserId();
            },

            // Save tags to localStorage
            saveTags: function (tagsCollection) {
                try {
                    var tagsData = tagsCollection.models.map(function (tag) {
                        return {
                            nodeId: tag.get('nodeId'),
                            displayName: tag.get('displayName'),
                            dataType: tag.get('dataType')
                        };
                    });
                    localStorage.setItem(this.getStorageKey(), JSON.stringify(tagsData));
                } catch (e) {
                    console.error('Failed to save tags to localStorage:', e);
                }
            },

            // Load tags from localStorage
            loadTags: function () {
                try {
                    var data = localStorage.getItem(this.getStorageKey());
                    if (data) {
                        return JSON.parse(data);
                    }
                } catch (e) {
                    console.error('Failed to load tags from localStorage:', e);
                }
                return [];
            },

            // Clear saved tags
            clearTags: function () {
                try {
                    localStorage.removeItem(this.getStorageKey());
                } catch (e) {
                    console.error('Failed to clear tags from localStorage:', e);
                }
            }
        };

        // Tag Groups API - Calls stored procedures via Core.Json.CallProcedure()
        var TAG_GROUPS_API = {
            // Database and connection settings
            getDbName: function () {
                // Use MES database - adjust if your database name is different
                return (app.DatabaseNames && app.DatabaseNames.MES) ? app.DatabaseNames.MES : '[MES]';
            },

            getConnectionString: function () {
                return (app.ConnectionStrings && app.ConnectionStrings.app) ? app.ConnectionStrings.app : 'APP';
            },

            // Get all groups (returns both user groups and predefined groups)
            getAll: function (callback) {
                var that = this;
                var qp = new Core.Database.QueryParameters();

                Core.Json.CallProcedure(
                    that.getDbName() + '.MES.TagGroupGetAll',
                    qp,
                    {
                        onSuccess: function (resp) {
                            try {
                                var result = {
                                    groups: [],
                                    predefinedGroups: []
                                };
                                if (resp && resp.Table) {
                                    result.groups = resp.Table;
                                }
                                if (resp && resp.Table1) {
                                    result.predefinedGroups = resp.Table1;
                                }
                                callback(null, result);
                            } catch (e) {
                                console.error('Error parsing groups response:', e);
                                callback(null, { groups: [], predefinedGroups: [] });
                            }
                        },
                        onFailure: function (resp) {
                            console.error('Failed to get tag groups:', resp);
                            callback('Failed to load groups: ' + (resp && resp.Message ? resp.Message : 'Unknown error'), null);
                        },
                        Secured: true
                    },
                    that.getConnectionString()
                );
            },

            // Get group by ID
            getById: function (groupId, callback) {
                var that = this;
                var qp = new Core.Database.QueryParameters();
                qp.Add('@GroupId', 'INT', groupId);

                Core.Json.CallProcedure(
                    that.getDbName() + '.MES.TagGroupGetById',
                    qp,
                    {
                        onSuccess: function (resp) {
                            try {
                                if (resp && resp.Table && resp.Table[0]) {
                                    callback(null, resp.Table[0]);
                                } else {
                                    callback('Group not found', null);
                                }
                            } catch (e) {
                                console.error('Error parsing group response:', e);
                                callback('Group not found', null);
                            }
                        },
                        onFailure: function (resp) {
                            console.error('Failed to get tag group:', resp);
                            callback('Failed to load group: ' + (resp && resp.Message ? resp.Message : 'Unknown error'), null);
                        },
                        Secured: true
                    },
                    that.getConnectionString()
                );
            },

            // Create new group
            // Note: Stored procedure returns GroupId via SELECT (not OUTPUT parameter)
            create: function (groupName, description, callback) {
                var that = this;
                var qp = new Core.Database.QueryParameters();
                qp.Add('@GroupName', 'NVARCHAR(100)', groupName);
                qp.Add('@Description', 'NVARCHAR(500)', description || null);
                // Note: @currentUser is handled by Secured: true via extended property

                Core.Json.CallProcedure(
                    that.getDbName() + '.MES.TagGroupCreate',
                    qp,
                    {
                        onSuccess: function (resp) {
                            try {
                                // GroupId is returned via SELECT SCOPE_IDENTITY() AS GroupId
                                var groupId = null;
                                if (resp && resp.Table && resp.Table[0] && resp.Table[0].GroupId) {
                                    groupId = resp.Table[0].GroupId;
                                }

                                if (groupId) {
                                    callback(null, { GroupId: groupId });
                                } else {
                                    // Fallback: try to get the newly created group by name
                                    TAG_GROUPS_API.getAll(function (err, result) {
                                        if (!err && result && result.groups) {
                                            var newGroup = result.groups.find(function (g) {
                                                return g.GroupName === groupName;
                                            });
                                            if (newGroup) {
                                                callback(null, { GroupId: newGroup.GroupId });
                                                return;
                                            }
                                        }
                                        callback('Failed to create group: Could not retrieve group ID', null);
                                    });
                                }
                            } catch (e) {
                                console.error('Error parsing create response:', e);
                                callback('Failed to create group: ' + e.message, null);
                            }
                        },
                        onFailure: function (resp) {
                            console.error('Failed to create tag group:', resp);
                            var errorMsg = 'Failed to create group';
                            if (resp && resp.Message) {
                                if (resp.Message.indexOf('already exists') > -1) {
                                    errorMsg = 'A group with this name already exists';
                                } else {
                                    errorMsg = resp.Message;
                                }
                            }
                            callback(errorMsg, null);
                        },
                        Secured: true
                    },
                    that.getConnectionString()
                );
            },

            // Update group
            update: function (groupId, groupName, description, callback) {
                var that = this;
                var qp = new Core.Database.QueryParameters();
                qp.Add('@GroupId', 'INT', groupId);
                qp.Add('@GroupName', 'NVARCHAR(100)', groupName);
                qp.Add('@Description', 'NVARCHAR(500)', description || null);

                Core.Json.CallProcedure(
                    that.getDbName() + '.MES.TagGroupUpdate',
                    qp,
                    {
                        onSuccess: function (resp) {
                            callback(null, { success: true });
                        },
                        onFailure: function (resp) {
                            console.error('Failed to update tag group:', resp);
                            var errorMsg = 'Failed to update group';
                            if (resp && resp.Message) {
                                if (resp.Message.indexOf('already exists') > -1) {
                                    errorMsg = 'A group with this name already exists';
                                } else {
                                    errorMsg = resp.Message;
                                }
                            }
                            callback(errorMsg, null);
                        },
                        Secured: true
                    },
                    that.getConnectionString()
                );
            },

            // Delete group (soft delete)
            delete: function (groupId, callback) {
                var that = this;
                var qp = new Core.Database.QueryParameters();
                qp.Add('@GroupId', 'INT', groupId);

                Core.Json.CallProcedure(
                    that.getDbName() + '.MES.TagGroupDelete',
                    qp,
                    {
                        onSuccess: function (resp) {
                            callback(null, { success: true });
                        },
                        onFailure: function (resp) {
                            console.error('Failed to delete tag group:', resp);
                            callback('Failed to delete group: ' + (resp && resp.Message ? resp.Message : 'Unknown error'), null);
                        },
                        Secured: true
                    },
                    that.getConnectionString()
                );
            },

            // Get tags in a group (only tag IDs)
            getTagIds: function (groupId, callback) {
                var that = this;
                var qp = new Core.Database.QueryParameters();
                qp.Add('@GroupId', 'INT', groupId);

                Core.Json.CallProcedure(
                    that.getDbName() + '.MES.TagGroupItemGetTagIds',
                    qp,
                    {
                        onSuccess: function (resp) {
                            try {
                                if (resp && resp.Table) {
                                    var tagIds = resp.Table.map(function (row) {
                                        return row.TagId;
                                    });
                                    callback(null, tagIds);
                                } else {
                                    callback(null, []);
                                }
                            } catch (e) {
                                console.error('Error parsing tag IDs response:', e);
                                callback(null, []);
                            }
                        },
                        onFailure: function (resp) {
                            console.error('Failed to get tag IDs:', resp);
                            callback('Failed to load tags: ' + (resp && resp.Message ? resp.Message : 'Unknown error'), null);
                        },
                        Secured: true
                    },
                    that.getConnectionString()
                );
            },

            // Get full tag details in a group
            getTagsByGroup: function (groupId, callback) {
                var that = this;
                var qp = new Core.Database.QueryParameters();
                qp.Add('@GroupId', 'INT', groupId);

                Core.Json.CallProcedure(
                    that.getDbName() + '.MES.TagGroupItemGetByGroup',
                    qp,
                    {
                        onSuccess: function (resp) {
                            try {
                                if (resp && resp.Table) {
                                    callback(null, resp.Table);
                                } else {
                                    callback(null, []);
                                }
                            } catch (e) {
                                console.error('Error parsing group tags response:', e);
                                callback(null, []);
                            }
                        },
                        onFailure: function (resp) {
                            console.error('Failed to get group tags:', resp);
                            callback('Failed to load tags: ' + (resp && resp.Message ? resp.Message : 'Unknown error'), null);
                        },
                        Secured: true
                    },
                    that.getConnectionString()
                );
            },

            // Helper: Convert array of tag IDs to XML format
            // Output: <tags><t>tag1</t><t>tag2</t></tags>
            tagsToXml: function (tagIds) {
                if (!tagIds || tagIds.length === 0) {
                    return '<tags></tags>';
                }
                var xmlParts = tagIds.map(function (tagId) {
                    // Escape XML special characters in tag IDs
                    var escaped = String(tagId)
                        .replace(/&/g, '&amp;')
                        .replace(/</g, '&lt;')
                        .replace(/>/g, '&gt;')
                        .replace(/"/g, '&quot;')
                        .replace(/'/g, '&apos;');
                    return '<t>' + escaped + '</t>';
                });
                return '<tags>' + xmlParts.join('') + '</tags>';
            },

            // Add tags to group (bulk)
            addTagsBulk: function (groupId, tagIds, callback) {
                if (!tagIds || tagIds.length === 0) {
                    callback(null, { InsertedCount: 0 });
                    return;
                }

                var that = this;
                var qp = new Core.Database.QueryParameters();
                qp.Add('@GroupId', 'INT', groupId);
                qp.Add('@TagIdsXml', 'NVARCHAR(MAX)', that.tagsToXml(tagIds));

                Core.Json.CallProcedure(
                    that.getDbName() + '.MES.TagGroupItemAddBulk',
                    qp,
                    {
                        onSuccess: function (resp) {
                            try {
                                var insertedCount = 0;
                                if (resp && resp.Table && resp.Table[0] && resp.Table[0].InsertedCount !== undefined) {
                                    insertedCount = resp.Table[0].InsertedCount;
                                }
                                callback(null, { InsertedCount: insertedCount });
                            } catch (e) {
                                console.error('Error parsing add tags response:', e);
                                callback(null, { InsertedCount: 0 });
                            }
                        },
                        onFailure: function (resp) {
                            console.error('Failed to add tags to group:', resp);
                            callback('Failed to add tags: ' + (resp && resp.Message ? resp.Message : 'Unknown error'), null);
                        },
                        Secured: true
                    },
                    that.getConnectionString()
                );
            },

            // Remove tags from group (bulk)
            removeTagsBulk: function (groupId, tagIds, callback) {
                if (!tagIds || tagIds.length === 0) {
                    callback(null, { DeletedCount: 0 });
                    return;
                }

                var that = this;
                var qp = new Core.Database.QueryParameters();
                qp.Add('@GroupId', 'INT', groupId);
                qp.Add('@TagIdsXml', 'NVARCHAR(MAX)', that.tagsToXml(tagIds));

                Core.Json.CallProcedure(
                    that.getDbName() + '.MES.TagGroupItemRemoveBulk',
                    qp,
                    {
                        onSuccess: function (resp) {
                            try {
                                var deletedCount = 0;
                                if (resp && resp.Table && resp.Table[0] && resp.Table[0].DeletedCount !== undefined) {
                                    deletedCount = resp.Table[0].DeletedCount;
                                }
                                callback(null, { DeletedCount: deletedCount });
                            } catch (e) {
                                console.error('Error parsing remove tags response:', e);
                                callback(null, { DeletedCount: 0 });
                            }
                        },
                        onFailure: function (resp) {
                            console.error('Failed to remove tags from group:', resp);
                            callback('Failed to remove tags: ' + (resp && resp.Message ? resp.Message : 'Unknown error'), null);
                        },
                        Secured: true
                    },
                    that.getConnectionString()
                );
            }
        };

        // Tag Group Model
        Screen.Models.TagGroup = Backbone.Model.extend({
            defaults: {
                GroupId: 0,
                GroupName: '',
                Description: '',
                CreatedBy: '',
                CreatedDate: null,
                ModifiedBy: null,
                ModifiedDate: null,
                TagCount: 0
            }
        });

        // Tag Groups Collection
        Screen.Collections.TagGroups = Backbone.Collection.extend({
            model: Screen.Models.TagGroup,

            findByGroupId: function (groupId) {
                return this.find(function (group) {
                    return group.get('GroupId') === groupId;
                });
            }
        });

        // Tree Node Model
        Screen.Models.TreeNode = Backbone.Model.extend({
            defaults: {
                nodeId: '',
                displayName: '',
                browseName: '',
                nodeClass: '',
                dataType: null,
                hasChildren: false,
                parentNodeId: null,
                expanded: false,
                loading: false,
                childrenLoaded: false,
                isVariable: false
            },

            initialize: function () {
                // Determine if this is a variable node (can be monitored)
                var nodeClass = this.get('nodeClass');
                var dataType = this.get('dataType');
                // Variable nodes have nodeClass === 'Variable' and may have dataType (can be null for some variables)
                this.set('isVariable', nodeClass === 'Variable');
            }
        });

        // Tag Model (for real-time monitoring)
        Screen.Models.Tag = Backbone.Model.extend({
            defaults: {
                nodeId: '',
                displayName: '',
                dataType: null,
                value: null,
                timestamp: null,
                quality: '',
                statusCode: 0,
                lastUpdate: null
            },

            initialize: function () {
                this.on('change:value change:timestamp change:quality', this.updateLastUpdate, this);
            },

            updateLastUpdate: function () {
                this.set('lastUpdate', new Date());
            },

            formatValue: function () {
                var value = this.get('value');
                var dataType = this.get('dataType');
                
                if (value === null || value === undefined) return 'N/A';
                
                // Format based on data type
                if (dataType && typeof dataType === 'string' && dataType.toLowerCase().includes('date')) {
                    try {
                        return moment(value).format('YYYY-MM-DD HH:mm:ss');
                    } catch (e) {
                        return String(value);
                    }
                }
                
                return String(value);
            },

            formatTimestamp: function () {
                var timestamp = this.get('timestamp');
                if (!timestamp) return 'N/A';
                return moment(timestamp).format('YYYY-MM-DD HH:mm:ss.SSS');
            },

            getQualityClass: function () {
                var quality = this.get('quality');
                if (!quality) return '';
                
                quality = quality.toLowerCase();
                if (quality === 'good') return 'quality-good';
                if (quality === 'bad') return 'quality-bad';
                if (quality === 'uncertain') return 'quality-uncertain';
                return '';
            }
        });

        // Main Model
        Screen.Models.Main = Backbone.Epoxy.Model.extend({
            defaults: {
                hasData: false,
                isLoading: false,
                isPageLoading: false,
                isTreeLoading: false,
                selectedTagsCount: 0,
                pollingInterval: 5000, // 2 seconds
                maxTags: 100 // Maximum number of tags to monitor
            },

            computeds: {
                selectedTagsCount$: {
                    deps: ['selectedTagsCount'],
                    get: function (value) {
                        return value || 0;
                    }
                },
                selectedTagsCountText: {
                    deps: ['selectedTagsCount'],
                    get: function (value) {
                        return 'Tags: ' + (value || 0);
                    }
                }
            },

            initialize: function () {
                this.treeNodes = new Screen.Collections.TreeNodes();
                this.selectedTags = new Screen.Collections.Tags();
                this.tagGroups = new Screen.Collections.TagGroups();

                // Initialize selectedTagsCount
                this.set('selectedTagsCount', 0);

                // Listen to tag collection changes
                this.listenTo(this.selectedTags, 'add remove reset', this.updateTagCount, this);
            },

            updateTagCount: function () {
                this.set('selectedTagsCount', this.selectedTags.length);
            }
        });

        // Tree Nodes Collection
        Screen.Collections.TreeNodes = Backbone.Collection.extend({
            model: Screen.Models.TreeNode,

            // Find node by nodeId
            findByNodeId: function (nodeId) {
                return this.find(function (node) {
                    return node.get('nodeId') === nodeId;
                });
            },

            // Get children of a node
            getChildren: function (parentNodeId) {
                return this.filter(function (node) {
                    return node.get('parentNodeId') === parentNodeId;
                });
            }
        });

        // Tags Collection
        Screen.Collections.Tags = Backbone.Collection.extend({
            model: Screen.Models.Tag,

            findByNodeId: function (nodeId) {
                return this.find(function (tag) {
                    return tag.get('nodeId') === nodeId;
                });
            }
        });

        // Custom AgGrid Module for Real-Time Panel
        var RealTimeAgGridModule = AgGridModule.extend({
            initialize: function (options) {
                this.bus = options.bus;
                this.itemsColl = options.itemsColl;
                this.onRemoveTag = options.onRemoveTag || function () { };
                
                this.columnDefs = [
                    {
                        field: 'displayName',
                        headerName: 'Display Name',
                        flex: 2,
                        minWidth: 200,
                        showFilter: true,
                        cellRenderer: function (params) {
                            var displayName = params.value || '';
                            // Always use nodeId for the tooltip (full tag path)
                            var nodeId = params.data && params.data.nodeId ? params.data.nodeId : displayName;
                            var parts = displayName.split('.');
                            var leafName = parts[parts.length - 1] || displayName;
                            var span = document.createElement('span');
                            span.className = 'display-name-cell';
                            span.textContent = leafName;

                            // Tooltip appended to body to avoid overflow clipping
                            // Always show the full nodeId in the tooltip
                            span.addEventListener('mouseenter', function (e) {
                                var $tip = $('#display-name-tooltip');
                                if ($tip.length === 0) {
                                    $tip = $('<div id="display-name-tooltip"></div>');
                                    $('body').append($tip);
                                }
                                $tip.text(nodeId);
                                $tip.show();
                                var rect = span.getBoundingClientRect();
                                var tipW = $tip.outerWidth();
                                var left = rect.left + (rect.width / 2) - (tipW / 2);
                                var top = rect.top - $tip.outerHeight() - 10;
                                $tip.css({ left: left, top: top });
                            });
                            span.addEventListener('mouseleave', function () {
                                $('#display-name-tooltip').hide();
                            });

                            return span;
                        },
                        cellStyle: { textAlign: 'center' },
                        headerClass: 'ag-header-cell-center'
                    },
                    {
                        field: 'dataType',
                        headerName: 'Data Type',
                        flex: 1,
                        minWidth: 100,
                        showFilter: false,
                        cellStyle: { textAlign: 'center' },
                        headerClass: 'ag-header-cell-center'
                    },
                    {
                        field: 'value',
                        headerName: 'Value',
                        flex: 2,
                        minWidth: 120,
                        showFilter: false,
                        cellRenderer: this.valueRenderer.bind(this),
                        cellStyle: { textAlign: 'center' },
                        headerClass: 'ag-header-cell-center'
                    },
                    {
                        field: 'timestamp',
                        headerName: 'Timestamp',
                        flex: 2,
                        minWidth: 180,
                        showFilter: false,
                        cellRenderer: this.timestampRenderer.bind(this),
                        cellStyle: { textAlign: 'center' },
                        headerClass: 'ag-header-cell-center'
                    },
                    {
                        field: 'quality',
                        headerName: 'Quality',
                        flex: 1,
                        minWidth: 100,
                        showFilter: false,
                        cellRenderer: this.qualityRenderer.bind(this),
                        cellStyle: { textAlign: 'center' },
                        headerClass: 'ag-header-cell-center'
                    },
                    {
                        colId: 'actions',
                        headerName: 'Actions',
                        flex: 1,
                        minWidth: 100,
                        cellRenderer: this.removeButtonRenderer.bind(this),
                        cellStyle: { display: 'flex', justifyContent: 'center', alignItems: 'center' },
                        headerClass: 'ag-header-cell-center'
                    }
                ];

                options.columnDefs = this.columnDefs;
                
                // Configure row ID to use nodeId for proper row updates
                if (!options.gridOptions) options.gridOptions = {};
                options.gridOptions.getRowId = function(params) {
                    return params.data.nodeId;
                };
                
                // Pagination is enabled by default (can be disabled by setting options.pagination = 'hidden')
                // For real-time monitoring, pagination helps when monitoring many tags
                
                AgGridModule.prototype.initialize.call(this, options);
            },

            valueRenderer: function (params) {
                if (!params.data) return '';
                var tag = params.data;
                var value = tag.value;
                if (value === null || value === undefined) return '<span style="color: #999;">N/A</span>';
                
                var dataType = tag.dataType;
                var formattedValue = '';
                
                // Handle dataType that might be null or string
                if (dataType && typeof dataType === 'string' && 
                    (dataType.toLowerCase().includes('date') || dataType.toLowerCase().includes('time'))) {
                    try {
                        formattedValue = moment(value).format('YYYY-MM-DD HH:mm:ss');
                    } catch (e) {
                        formattedValue = String(value);
                    }
                } else {
                    formattedValue = String(value);
                }
                
                // Add update indicator - check if this is a recent update
                var cellClass = 'tag-value-cell';
                if (tag.lastUpdateTime) {
                    var timeSinceUpdate = Date.now() - tag.lastUpdateTime;
                    if (timeSinceUpdate < 1000) { // Highlight for 1 second after update
                        cellClass += ' value-updated';
                    }
                }
                
                return '<span class="' + cellClass + '">' + _.escape(formattedValue) + '</span>';
            },

            timestampRenderer: function (params) {
                if (!params.data || !params.data.timestamp) return '<span style="color: #999;">N/A</span>';
                try {
                    // Handle timestamp format: 2026-01-20T14:08:10.1668397Z
                    var timestamp = params.data.timestamp;
                    // Parse and format the timestamp
                    return moment(timestamp).format('YYYY-MM-DD HH:mm:ss.SSS');
                } catch (e) {
                    return String(params.data.timestamp);
                }
            },

            qualityRenderer: function (params) {
                if (!params.data || !params.data.quality) return '';
                
                var quality = params.data.quality;
                var qualityLower = quality.toLowerCase();
                var className = '';
                var displayText = quality;
                
                if (qualityLower === 'good') {
                    className = 'quality-good';
                } else if (qualityLower === 'bad') {
                    className = 'quality-bad';
                } else if (qualityLower === 'uncertain') {
                    className = 'quality-uncertain';
                }
                
                return '<span class="' + className + '">' + _.escape(displayText) + '</span>';
            },

            removeButtonRenderer: function (params) {
                if (!params.data) return '';
                
                var button = document.createElement('button');
                button.className = 'remove-tag-btn';
                button.innerHTML = '<i class="fa fa-trash-o"></i>';
                button.title = 'Remove tag from monitoring';
                
                var that = this;
                button.addEventListener('click', function (e) {
                    e.stopPropagation();
                    if (that.onRemoveTag) {
                        that.onRemoveTag(params.data.nodeId);
                    }
                });
                
                return button;
            }
        });

        // Tree View Component - Optimized for large datasets
        var TreeView = {
            // Cache for children lookup to avoid repeated filtering
            childrenCache: null,
            // Current search term
            searchTerm: '',
            // Set of nodeIds that match search or have matching descendants
            matchingNodes: null,

            // Build a children lookup map for O(1) access
            buildChildrenCache: function (treeNodes) {
                this.childrenCache = {};
                for (var i = 0; i < treeNodes.length; i++) {
                    var node = treeNodes[i];
                    var parentId = node.get('parentNodeId');
                    if (parentId) {
                        if (!this.childrenCache[parentId]) {
                            this.childrenCache[parentId] = [];
                        }
                        this.childrenCache[parentId].push(node);
                    }
                }
            },

            getChildren: function (nodeId) {
                return this.childrenCache[nodeId] || [];
            },

            // Set search term and rebuild matching nodes
            setSearchTerm: function (term) {
                this.searchTerm = (term || '').toLowerCase().trim();
            },

            // Build set of matching nodes and their ancestors
            buildMatchingNodes: function (treeNodes) {
                this.matchingNodes = {};
                if (!this.searchTerm) return;

                var nodeById = {};
                var parentMap = {};

                // First pass: build lookup maps
                for (var i = 0; i < treeNodes.length; i++) {
                    var node = treeNodes[i];
                    var nodeId = node.get('nodeId');
                    nodeById[nodeId] = node;
                    parentMap[nodeId] = node.get('parentNodeId');
                }

                // Second pass: find matching nodes and mark ancestors
                for (var j = 0; j < treeNodes.length; j++) {
                    var n = treeNodes[j];
                    var nId = n.get('nodeId');
                    var displayName = (n.get('displayName') || '').toLowerCase();
                    var browseName = (n.get('browseName') || '').toLowerCase();

                    if (displayName.indexOf(this.searchTerm) !== -1 ||
                        browseName.indexOf(this.searchTerm) !== -1 ||
                        nId.toLowerCase().indexOf(this.searchTerm) !== -1) {
                        // Mark this node and all ancestors
                        var current = nId;
                        while (current) {
                            this.matchingNodes[current] = true;
                            current = parentMap[current];
                        }
                    }
                }
            },

            // Check if a node should be visible based on search
            isNodeVisible: function (nodeId) {
                if (!this.searchTerm) return true;
                return this.matchingNodes[nodeId] === true;
            },

            // Check if a node directly matches the search term
            isDirectMatch: function (node) {
                if (!this.searchTerm) return false;
                var displayName = (node.get('displayName') || '').toLowerCase();
                var browseName = (node.get('browseName') || '').toLowerCase();
                var nodeId = node.get('nodeId').toLowerCase();
                return displayName.indexOf(this.searchTerm) !== -1 ||
                       browseName.indexOf(this.searchTerm) !== -1 ||
                       nodeId.indexOf(this.searchTerm) !== -1;
            },

            render: function (container, treeNodes, onNodeClick, onNodeExpand, onNodeContext) {
                var $container = $(container);

                // Build children cache for fast lookup
                this.buildChildrenCache(treeNodes);

                // Build matching nodes if searching
                this.buildMatchingNodes(treeNodes);

                // Find root nodes
                var rootNodes = [];
                for (var i = 0; i < treeNodes.length; i++) {
                    if (!treeNodes[i].get('parentNodeId')) {
                        rootNodes.push(treeNodes[i]);
                    }
                }

                if (rootNodes.length === 0) {
                    $container.html('<div class="empty-state"><div class="empty-state-message">No nodes found</div></div>');
                    return;
                }

                // Use document fragment for better performance
                var fragment = document.createDocumentFragment();
                var treeDiv = document.createElement('div');
                treeDiv.className = 'tree-nodes';

                // Store callbacks for event delegation
                this.onNodeClick = onNodeClick;
                this.onNodeExpand = onNodeExpand;
                this.onNodeContext = onNodeContext;
                this.nodeMap = {};

                // Render root nodes
                var visibleCount = 0;
                for (var j = 0; j < rootNodes.length; j++) {
                    var rootNode = rootNodes[j];
                    if (this.isNodeVisible(rootNode.get('nodeId'))) {
                        treeDiv.appendChild(this.renderNodeDOM(rootNode, 0));
                        visibleCount++;
                    }
                }

                // Show message if search has no results
                if (this.searchTerm && visibleCount === 0) {
                    $container.html('<div class="empty-state"><div class="empty-state-message">No matching nodes found for "' + _.escape(this.searchTerm) + '"</div></div>');
                    return;
                }

                fragment.appendChild(treeDiv);

                // Clear and append in one operation
                $container.empty();
                container.appendChild(fragment);

                // Use event delegation instead of individual handlers
                this.attachEventDelegation($container);
            },

            renderNodeDOM: function (node, level) {
                var nodeId = node.get('nodeId');
                var hasChildren = node.get('hasChildren');
                var expanded = node.get('expanded');
                var loading = node.get('loading');
                var isVariable = node.get('isVariable');
                var displayName = node.get('displayName') || node.get('browseName') || nodeId;

                // Store node reference for event handling
                this.nodeMap[nodeId] = node;

                // Create container
                var container = document.createElement('div');
                container.className = 'tree-node-container';

                // Create node element
                var nodeEl = document.createElement('div');
                nodeEl.className = 'tree-node';
                // Add highlight class if this node directly matches search
                if (this.isDirectMatch(node)) {
                    nodeEl.className += ' search-match';
                }
                nodeEl.setAttribute('data-node-id', nodeId);
                nodeEl.style.paddingLeft = (level * 20 + 8) + 'px';

                // Expand icon
                var expandIcon = document.createElement('span');
                expandIcon.className = 'tree-expand-icon';
                if (hasChildren) {
                    if (loading) {
                        expandIcon.className += ' loading';
                    } else if (expanded) {
                        expandIcon.className += ' expanded';
                    } else {
                        expandIcon.className += ' collapsed';
                    }
                }
                nodeEl.appendChild(expandIcon);

                // Node label - highlight matching text if searching
                var label = document.createElement('span');
                label.className = 'tree-node-label';
                if (hasChildren) label.className += ' has-children';
                if (isVariable) label.className += ' variable';

                if (this.searchTerm && this.isDirectMatch(node)) {
                    // Highlight matching portion
                    label.innerHTML = this.highlightMatch(displayName, this.searchTerm);
                } else {
                    label.textContent = displayName;
                }
                nodeEl.appendChild(label);

                container.appendChild(nodeEl);

                // Render children if expanded OR if searching (auto-expand matching paths)
                var shouldShowChildren = (expanded && !loading) || (this.searchTerm && this.matchingNodes[nodeId]);
                if (shouldShowChildren) {
                    var children = this.getChildren(nodeId);
                    if (children.length > 0) {
                        var childrenDiv = document.createElement('div');
                        childrenDiv.className = 'tree-children';

                        for (var i = 0; i < children.length; i++) {
                            var child = children[i];
                            // Only render visible children when searching
                            if (this.isNodeVisible(child.get('nodeId'))) {
                                childrenDiv.appendChild(this.renderNodeDOM(child, level + 1));
                            }
                        }

                        if (childrenDiv.childNodes.length > 0) {
                            container.appendChild(childrenDiv);
                        }
                    }
                }

                return container;
            },

            // Highlight matching text in a string
            highlightMatch: function (text, term) {
                var lowerText = text.toLowerCase();
                var index = lowerText.indexOf(term);
                if (index === -1) return _.escape(text);

                var before = text.substring(0, index);
                var match = text.substring(index, index + term.length);
                var after = text.substring(index + term.length);

                return _.escape(before) + '<mark class="search-highlight">' + _.escape(match) + '</mark>' + _.escape(after);
            },

            attachEventDelegation: function ($container) {
                var that = this;

                // Remove previous handlers if exist
                $container.off('click.treeview');
                $container.off('contextmenu.treeview');

                // Use event delegation - single handler for all nodes
                $container.on('click.treeview', '.tree-node', function (e) {
                    e.stopPropagation();

                    var nodeId = this.getAttribute('data-node-id');
                    var node = that.nodeMap[nodeId];

                    if (!node) return;

                    var isVariable = node.get('isVariable');
                    var hasChildren = node.get('hasChildren');

                    if (isVariable) {
                        if (that.onNodeClick) that.onNodeClick(node);
                    } else if (hasChildren) {
                        if (that.onNodeExpand) that.onNodeExpand(node);
                    }
                });

                // Right-click context menu for variable (leaf) nodes
                $container.on('contextmenu.treeview', '.tree-node', function (e) {
                    var nodeId = this.getAttribute('data-node-id');
                    var node = that.nodeMap[nodeId];
                    if (!node || !node.get('isVariable')) return;

                    e.preventDefault();
                    e.stopPropagation();

                    that.contextNode = node;

                    if (that.onNodeContext) {
                        that.onNodeContext(node, e.clientX, e.clientY);
                    }
                });
            },

            updateNode: function (nodeId, updates) {
                var $node = $('.tree-node[data-node-id="' + nodeId.replace(/"/g, '\\"') + '"]');
                if (updates.expanded !== undefined) {
                    var $icon = $node.find('.tree-expand-icon');
                    $icon.removeClass('expanded collapsed loading');
                    if (updates.loading) {
                        $icon.addClass('loading');
                    } else if (updates.expanded) {
                        $icon.addClass('expanded');
                    } else {
                        $icon.addClass('collapsed');
                    }
                }
            }
        };

        // Main View
        Screen.Views.Main = Backbone.Epoxy.View.extend({
            template: 'opcua-browser',
            id: 'opcua-browser',
            title: 'OPC UA Browser',
            events: function () {
                return {
                    // Groups Toolbar
                    'click #btn-load-group': 'onLoadGroupClick',
                    'click #btn-save-group': 'onSaveGroupClick',
                    'click #btn-manage-groups': 'onManageGroupsClick',

                    // Predefined Groups
                    'change #predefined-groups-dropdown': 'onPredefinedGroupChange',
                    'click #btn-clone-predefined': 'onClonePredefinedClick',

                    // Save Group Modal
                    'click #btn-save-group-confirm': 'onSaveGroupConfirm',

                    // Load Group Modal
                    'click #btn-load-group-confirm': 'onLoadGroupConfirm',

                    // Edit Group Modal
                    'click #btn-edit-group-confirm': 'onEditGroupConfirm',

                    // Modal close buttons
                    'click .groups-modal [data-dismiss="modal"]': 'closeModal',
                    'click .groups-modal-overlay': 'closeModal',

                    // Context menu modals
                    'click #btn-ctx-add-to-group-confirm': 'onCtxAddToGroupConfirm',
                    'click #btn-ctx-new-group-confirm': 'onCtxNewGroupConfirm'
                };
            },
            bindings: 'data-bind',
            bindingSources: null,
            grids: null,
            itemsColl: null,
            subviews: null,
            viewParams: null,
            pollingTimer: null,

            initialize: function () {
                this.options.state = app.view_states.loading;
                this.options.onappend = (_.isFunction(this.options.onappend)) ? this.options.onappend : function () { };

                var that = this;

                if (!this.model)
                    this.model = new Screen.Models.Main();

                // Initialize collections
                this.itemsColl = this.model.selectedTags;
                this.treeNodesColl = this.model.treeNodes;
                this.tagGroupsColl = this.model.tagGroups;

                // Initialize ag-Grid
                this.agGridModule = new RealTimeAgGridModule({
                    itemsColl: this.itemsColl,
                    bus: this.bus,
                    rowData: [],
                    onRemoveTag: this.removeTag.bind(this),
                    fileName: 'opcua-tags',
                    screenTitle: 'OPC UA Tags',
                    isPageLoading: that.model.get('isPageLoading'),
                });
                this.grids = {};

                this.bindingSources = {};
                this.subviews = {};
                this.bus = _.extend({}, Backbone.Events);

                this.bindEvents();
            },

            bindEvents: function () {
                // Use debounced render for tree to avoid multiple rapid re-renders
                this.debouncedRenderTree = _.debounce(this.renderTree.bind(this), 50);
                this.listenTo(this.treeNodesColl, 'reset change:expanded change:loading', this.debouncedRenderTree, this);
                // Don't listen to 'add' - we handle bulk adds separately to avoid performance issues
                this.listenTo(this.itemsColl, 'add remove change reset', this.updateGrid, this);

                // Save tags to localStorage when collection changes
                this.listenTo(this.itemsColl, 'add remove reset', this.saveTagsToStorage, this);
            },

            // Save tags to localStorage (only when default group is active)
            saveTagsToStorage: function () {
                if (!this.activeGroupId || this.activeGroupId === this.DEFAULT_GROUP_ID) {
                    TagStorage.saveTags(this.itemsColl);
                }
            },

            // Load saved tags from localStorage
            loadSavedTags: function () {
                var that = this;
                var savedTags = TagStorage.loadTags();

                // Default group is active on page load
                this.activeGroupId = this.DEFAULT_GROUP_ID;

                if (savedTags && savedTags.length > 0) {
                    var tagsToAdd = [];
                    savedTags.forEach(function (tagData) {
                        // Check if not already in collection
                        if (!that.itemsColl.findByNodeId(tagData.nodeId)) {
                            tagsToAdd.push(new Screen.Models.Tag({
                                nodeId: tagData.nodeId,
                                displayName: tagData.displayName || tagData.nodeId,
                                dataType: tagData.dataType || null,
                                value: null,
                                timestamp: null,
                                quality: '',
                                statusCode: 0
                            }));
                        }
                    });

                    if (tagsToAdd.length > 0) {
                        // Add all tags at once
                        that.itemsColl.add(tagsToAdd, { silent: true });

                        // Manually update the tag count since we used silent: true
                        that.model.set('selectedTagsCount', that.itemsColl.length);

                        that.updateGrid();

                        // Fetch initial values for all loaded tags
                        tagsToAdd.forEach(function (tag) {
                            that.updateTagValue(tag);
                        });

                        console.log('Loaded ' + tagsToAdd.length + ' saved tags from localStorage');
                    }
                }
            },

            // Setup search input functionality
            setupSearchInput: function () {
                var that = this;
                var $searchInput = this.$('#tree-search-input');
                var $clearBtn = this.$('#tree-search-clear');

                if ($searchInput.length === 0) return;

                // Debounced search function
                var debouncedSearch = _.debounce(function (term) {
                    TreeView.setSearchTerm(term);
                    that.renderTree();
                }, 300);

                // Handle input
                $searchInput.on('input', function () {
                    var term = $(this).val();
                    $clearBtn.toggle(term.length > 0);
                    debouncedSearch(term);
                });

                // Handle clear button
                $clearBtn.on('click', function () {
                    $searchInput.val('');
                    $clearBtn.hide();
                    TreeView.setSearchTerm('');
                    that.renderTree();
                    $searchInput.focus();
                });

                // Handle Enter key to prevent form submission
                $searchInput.on('keydown', function (e) {
                    if (e.key === 'Enter') {
                        e.preventDefault();
                    }
                    // Clear on Escape
                    if (e.key === 'Escape') {
                        $searchInput.val('');
                        $clearBtn.hide();
                        TreeView.setSearchTerm('');
                        that.renderTree();
                    }
                });
            },

            render: function (container, viewParams) {
                var that = this;

                this.viewParams = viewParams;
                that.model.set({ isPageLoading: true });

                var thatContainer = (this.options.container) ? this.options.container : container;
                this.options.container = thatContainer;

                var customPath = '/app/pages/opcua/' + this.template + '/';

                T.render.call(
                    this,
                    this.template,
                    function (tmp) {
                        if (!that.options.i18n) that.options.i18n = {};

                        app.getI18NJed(
                            that,
                            that.template,
                            function (i18nJED) {
                                that.options.i18n[that.template] = i18nJED;
                                that.$el.html(tmp());
                                that.applyBindings();

                                // Render ag-Grid
                                that.agGridModule.render();
                                that.$el.find('.realtime-grid-container').append(that.agGridModule.$el);

                                // Setup search input
                                that.setupSearchInput();

                                // Append view to container
                                that.append(thatContainer, that.$el);

                                // Load root nodes
                                that.loadRootNodes();

                                // Load saved tags from localStorage
                                that.loadSavedTags();

                                // Load tag groups
                                that.loadTagGroups();

                                // Start polling for tag values
                                that.startPolling();

                                // Set loading state to false
                                that.model.set({ isPageLoading: false });
                            },
                            true,
                            customPath
                        );
                    },
                    customPath
                );
            },

            loadRootNodes: function () {
                var that = this;
                this.model.set({ isTreeLoading: true });

                OPCUA_API.browseRootNodes(function (error, nodes) {
                    that.model.set({ isTreeLoading: false });

                    if (error) {
                        app.views.topMessages.showMessage('Failed to load root nodes: ' + error, { stay: 5000 });
                        that.$('#tree-content').html('<div class="tree-error">Error: ' + _.escape(error) + '</div>');
                        return;
                    }

                    if (!nodes || nodes.length === 0) {
                        that.$('#tree-content').html('<div class="empty-state"><div class="empty-state-message">No root nodes found</div></div>');
                        return;
                    }

                    // Add nodes to collection
                    // Root nodes from /browse endpoint should have parentNodeId set to null
                    // so they render at the top level of the tree (regardless of API's parentNodeId value)
                    that.treeNodesColl.reset(nodes.map(function (nodeData) {
                        return {
                            nodeId: nodeData.nodeId,
                            displayName: nodeData.displayName || nodeData.browseName || nodeData.nodeId,
                            browseName: nodeData.browseName || nodeData.nodeId,
                            nodeClass: nodeData.nodeClass || '',
                            dataType: nodeData.dataType || null, // Can be null
                            hasChildren: nodeData.hasChildren || false,
                            parentNodeId: null, // Root nodes have no parent in the tree view
                            expanded: false,
                            loading: false,
                            childrenLoaded: false
                        };
                    }));

                    that.renderTree();
                });
            },

            expandNode: function (node) {
                var that = this;

                if (node.get('childrenLoaded')) {
                    // Toggle expansion
                    node.set('expanded', !node.get('expanded'));
                    return;
                }

                // Load children
                node.set({ loading: true, expanded: true });

                OPCUA_API.browseNode(node.get('nodeId'), function (error, childNodes) {
                    node.set({ loading: false, childrenLoaded: true });

                    if (error) {
                        app.views.topMessages.showMessage('Failed to load children: ' + error, { stay: 3000 });
                        node.set('expanded', false);
                        return;
                    }

                    if (childNodes && childNodes.length > 0) {
                        // Prepare all child node models at once
                        var parentNodeId = node.get('nodeId');
                        var childModels = childNodes.map(function (childData) {
                            return {
                                nodeId: childData.nodeId,
                                displayName: childData.displayName || childData.browseName || childData.nodeId,
                                browseName: childData.browseName || childData.nodeId,
                                nodeClass: childData.nodeClass || '',
                                dataType: childData.dataType || null,
                                hasChildren: childData.hasChildren || false,
                                parentNodeId: parentNodeId,
                                expanded: false,
                                loading: false,
                                childrenLoaded: false
                            };
                        });

                        // Add all children at once (silent to prevent multiple renders)
                        that.treeNodesColl.add(childModels, { silent: true });

                        // Use requestAnimationFrame to render without blocking UI
                        requestAnimationFrame(function () {
                            that.renderTree();
                        });
                    } else {
                        node.set('hasChildren', false);
                    }
                });
            },

            addTagToMonitoring: function (node) {
                var that = this;

                // If viewing predefined group, exit that mode first (can't add to predefined groups)
                if (this._viewingPredefinedGroup) {
                    // Exit predefined view but don't restore - we're starting fresh
                    this._viewingPredefinedGroup = false;
                    this._currentPredefinedGroup = null;
                    this._savedTags = null;
                    this._savedActiveGroupId = null;
                    this.setActionsColumnVisible(true);
                    this.$('#predefined-groups-dropdown').val('');
                    this.$('#btn-clone-predefined').prop('disabled', true);
                    // Clear current items and set to default group
                    this.itemsColl.reset([], { silent: true });
                    this.activeGroupId = this.DEFAULT_GROUP_ID;
                    this.startPolling();
                }

                // Check if already added
                var existingTag = this.itemsColl.findByNodeId(node.get('nodeId'));
                if (existingTag) {
                    app.views.topMessages.showMessage('Tag already in monitoring panel', { stay: 2000 });
                    return;
                }

                // Check max tags limit
                if (this.itemsColl.length >= this.model.get('maxTags')) {
                    app.views.topMessages.showMessage('Maximum number of tags (' + this.model.get('maxTags') + ') reached. Remove some tags first.', { stay: 4000 });
                    return;
                }

                // Create tag model
                var tag = new Screen.Models.Tag({
                    nodeId: node.get('nodeId'),
                    displayName: node.get('displayName') || node.get('browseName') || node.get('nodeId'),
                    dataType: node.get('dataType') || null,
                    value: null,
                    timestamp: null,
                    quality: '',
                    statusCode: 0
                });

                // Add to collection
                this.itemsColl.add(tag);

                // Immediately fetch initial value
                this.updateTagValue(tag);

                // If a DB group is active, also save the tag to the database
                if (this.activeGroupId && this.activeGroupId !== this.DEFAULT_GROUP_ID) {
                    var that = this;
                    TAG_GROUPS_API.addTagsBulk(this.activeGroupId, [tag.get('nodeId')], function (error, result) {
                        if (error) {
                            console.error('Failed to add tag to group:', error);
                            app.views.topMessages.showMessage('Tag added to panel but failed to save to group', { stay: 3000 });
                        } else {
                            app.views.topMessages.showMessage('Tag added to monitoring and group: ' + tag.get('displayName'), { stay: 2000 });
                            that.loadTagGroups();
                        }
                    });
                } else {
                    app.views.topMessages.showMessage('Tag added to monitoring: ' + tag.get('displayName'), { stay: 2000 });
                }
            },

            // =============================================
            // Tree Context Menu
            // =============================================

            showTreeContextMenu: function (node, x, y) {
                var that = this;
                this._contextMenuNode = node;

                // Create context menu if it doesn't exist yet
                var $menu = $('#tree-context-menu');
                if ($menu.length === 0) {
                    $menu = $(
                        '<div id="tree-context-menu">' +
                            '<div class="ctx-menu-item" id="ctx-add-default"><i class="fa fa-plus"></i> Add to Default Group</div>' +
                            '<div class="ctx-menu-item" id="ctx-add-to-group"><i class="fa fa-folder-o"></i> Add to Group...</div>' +
                            '<div class="ctx-menu-separator"></div>' +
                            '<div class="ctx-menu-item" id="ctx-add-to-new-group"><i class="fa fa-folder-open-o"></i> Add to New Group...</div>' +
                        '</div>'
                    );
                    $('body').append($menu);

                    // Menu item click handlers
                    $menu.on('click', '#ctx-add-default', function () {
                        that.hideTreeContextMenu();
                        that.addTagToDefaultGroup(that._contextMenuNode);
                    });
                    $menu.on('click', '#ctx-add-to-group', function () {
                        that.hideTreeContextMenu();
                        that.openCtxAddToGroupModal(that._contextMenuNode);
                    });
                    $menu.on('click', '#ctx-add-to-new-group', function () {
                        that.hideTreeContextMenu();
                        that.openCtxNewGroupModal(that._contextMenuNode);
                    });
                }

                // Position and show
                $menu.css({ left: x, top: y }).show();

                // Dismiss on click outside, scroll, or escape
                setTimeout(function () {
                    $(document).on('click.ctxmenu', function () {
                        that.hideTreeContextMenu();
                    });
                    $(document).on('keydown.ctxmenu', function (e) {
                        if (e.keyCode === 27) that.hideTreeContextMenu();
                    });
                }, 0);
            },

            hideTreeContextMenu: function () {
                $('#tree-context-menu').hide();
                $(document).off('click.ctxmenu');
                $(document).off('keydown.ctxmenu');
            },

            addTagToDefaultGroup: function (node) {
                var that = this;
                var nodeId = node.get('nodeId');
                var displayName = node.get('displayName') || node.get('browseName') || nodeId;

                // Load current localStorage tags, add this one, save back
                var savedTags = TagStorage.loadTags() || [];
                var alreadyExists = savedTags.some(function (t) { return t.nodeId === nodeId; });
                if (alreadyExists) {
                    app.views.topMessages.showMessage('Tag already in Default Group', { stay: 2000 });
                    return;
                }

                savedTags.push({
                    nodeId: nodeId,
                    displayName: displayName,
                    dataType: node.get('dataType') || null
                });
                localStorage.setItem(TagStorage.getStorageKey(), JSON.stringify(savedTags));

                app.views.topMessages.showMessage('Tag added to Default Group: ' + displayName, { stay: 2000 });
                this.loadTagGroups();
            },

            openCtxAddToGroupModal: function (node) {
                var that = this;
                this._contextMenuNode = node;

                var displayName = node.get('displayName') || node.get('browseName') || node.get('nodeId');
                this.$('#ctx-select-tag-name').text(displayName);

                // Populate dropdown with DB groups only
                var $dropdown = this.$('#ctx-group-dropdown');
                $dropdown.empty();
                $dropdown.append('<option value="">-- Select a group --</option>');

                TAG_GROUPS_API.getAll(function (error, result) {
                    if (error) {
                        console.error('Failed to load groups:', error);
                        return;
                    }
                    var groups = result && result.groups ? result.groups : [];
                    if (groups.length > 0) {
                        groups.forEach(function (group) {
                            $dropdown.append(
                                '<option value="' + group.GroupId + '">' +
                                _.escape(group.GroupName) + '</option>'
                            );
                        });
                    }
                    that.showModal('#ctx-group-select-modal');
                });
            },

            onCtxAddToGroupConfirm: function (e) {
                e.preventDefault();
                var that = this;
                var node = this._contextMenuNode;
                if (!node) return;

                var groupId = parseInt(this.$('#ctx-group-dropdown').val(), 10);
                if (!groupId) {
                    app.views.topMessages.showMessage('Please select a group', { stay: 2000 });
                    return;
                }

                var $btn = this.$('#btn-ctx-add-to-group-confirm');
                $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Adding...');

                TAG_GROUPS_API.addTagsBulk(groupId, [node.get('nodeId')], function (error, result) {
                    $btn.prop('disabled', false).html('Add to Group');
                    if (error) {
                        app.views.topMessages.showMessage('Failed to add tag to group: ' + error, { stay: 3000 });
                    } else {
                        var displayName = node.get('displayName') || node.get('nodeId');
                        app.views.topMessages.showMessage('Tag "' + displayName + '" added to group', { stay: 2000 });
                        that.loadTagGroups();
                    }
                    that.closeModal();
                });
            },

            openCtxNewGroupModal: function (node) {
                this._contextMenuNode = node;
                var displayName = node.get('displayName') || node.get('browseName') || node.get('nodeId');
                this.$('#ctx-new-tag-name').text(displayName);
                this.$('#ctx-new-group-name').val('');
                this.$('#ctx-new-group-description').val('');
                this.showModal('#ctx-new-group-modal');
                var that = this;
                setTimeout(function () { that.$('#ctx-new-group-name').focus(); }, 100);
            },

            onCtxNewGroupConfirm: function (e) {
                e.preventDefault();
                var that = this;
                var node = this._contextMenuNode;
                if (!node) return;

                var groupName = this.$('#ctx-new-group-name').val().trim();
                if (!groupName) {
                    app.views.topMessages.showMessage('Please enter a group name', { stay: 2000 });
                    this.$('#ctx-new-group-name').focus();
                    return;
                }

                var description = this.$('#ctx-new-group-description').val().trim();
                var $btn = this.$('#btn-ctx-new-group-confirm');
                $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Creating...');

                TAG_GROUPS_API.create(groupName, description, function (error, result) {
                    if (error) {
                        $btn.prop('disabled', false).html('Create &amp; Add');
                        app.views.topMessages.showMessage(error, { stay: 3000 });
                        return;
                    }

                    var groupId = result.GroupId;

                    TAG_GROUPS_API.addTagsBulk(groupId, [node.get('nodeId')], function (tagError, tagResult) {
                        $btn.prop('disabled', false).html('Create &amp; Add');
                        if (tagError) {
                            app.views.topMessages.showMessage('Group created but failed to add tag: ' + tagError, { stay: 3000 });
                        } else {
                            var displayName = node.get('displayName') || node.get('nodeId');
                            app.views.topMessages.showMessage('Group "' + groupName + '" created with tag "' + displayName + '"', { stay: 3000 });
                        }
                        that.loadTagGroups();
                        that.closeModal();
                    });
                });
            },

            removeTag: function (nodeId) {
                var that = this;
                var tag = this.itemsColl.findByNodeId(nodeId);
                if (tag) {
                    this.itemsColl.remove(tag);

                    // If a DB group is active, also remove the tag from the database
                    if (this.activeGroupId && this.activeGroupId !== this.DEFAULT_GROUP_ID) {
                        TAG_GROUPS_API.removeTagsBulk(this.activeGroupId, [nodeId], function (error, result) {
                            if (error) {
                                console.error('Failed to remove tag from group:', error);
                                app.views.topMessages.showMessage('Tag removed from panel but failed to remove from group', { stay: 3000 });
                            } else {
                                app.views.topMessages.showMessage('Tag removed from monitoring and group', { stay: 2000 });
                                that.loadTagGroups();
                            }
                        });
                    } else {
                        app.views.topMessages.showMessage('Tag removed from monitoring', { stay: 2000 });
                    }
                }
            },

            updateTagValue: function (tag) {
                var that = this;
                var nodeId = tag.get('nodeId');
                var previousValue = tag.get('value');

                OPCUA_API.getTagValue(nodeId, function (error, tagData) {
                    if (error) {
                        // Only log to console to avoid spam, but update quality if it's a persistent error
                        console.error('Failed to update tag value for ' + nodeId + ':', error);
                        
                        // Update quality to indicate error state
                        var currentQuality = tag.get('quality');
                        if (currentQuality !== 'Bad' && currentQuality !== 'bad') {
                            tag.set('quality', 'Bad');
                        }
                        return;
                    }

                    if (tagData) {
                        try {
                            var valueChanged = previousValue !== tagData.value;
                            
                            tag.set({
                                value: tagData.value,
                                timestamp: tagData.timestamp,
                                quality: tagData.quality || 'Good',
                                statusCode: tagData.statusCode || 0,
                                lastUpdateTime: Date.now() // Track update time for visual feedback
                            });
                            
                            // Trigger visual update in grid if value changed
                            if (that.agGridModule && that.agGridModule.gridApi) {
                                // Refresh the specific row to show update animation
                                var rowNode = that.agGridModule.gridApi.getRowNode(nodeId);
                                if (rowNode) {
                                    rowNode.setData(tag.toJSON());
                                    // Refresh the value and timestamp cells to show animation
                                    that.agGridModule.gridApi.refreshCells({
                                        rowNodes: [rowNode],
                                        columns: ['value', 'timestamp', 'quality'],
                                        force: true
                                    });
                                }
                            }
                        } catch (e) {
                            console.error('Error updating tag model:', e);
                        }
                    } else {
                        console.warn('Empty tag data received for ' + nodeId);
                    }
                });
            },

            startPolling: function () {
                var that = this;
                var interval = this.model.get('pollingInterval');

                // Clear existing timer
                if (this.pollingTimer) {
                    clearInterval(this.pollingTimer);
                }

                // Start polling with error handling
                // Use batch endpoint if available, otherwise fall back to individual requests
                this.pollingTimer = setInterval(function () {
                    try {
                        if (that.itemsColl.length > 0) {
                            // Use batch endpoint for better performance
                            var nodeIds = that.itemsColl.models.map(function (tag) {
                                return tag.get('nodeId');
                            });
                            
                            if (nodeIds.length > 0) {
                                that.updateTagValuesBatch(nodeIds);
                            }
                        }
                    } catch (e) {
                        console.error('Error in polling loop:', e);
                        // Don't stop polling on individual errors
                    }
                }, interval);
            },

            updateTagValuesBatch: function (nodeIds) {
                var that = this;

                OPCUA_API.getTagValues(nodeIds, function (error, tagDataArray) {
                    if (error) {
                        console.error('Failed to update tag values batch:', error);
                        // Fall back to individual requests if batch fails
                        that.itemsColl.models.forEach(function (tag) {
                            try {
                                that.updateTagValue(tag);
                            } catch (e) {
                                console.error('Error updating tag in fallback:', e);
                            }
                        });
                        return;
                    }

                    if (tagDataArray && Array.isArray(tagDataArray)) {
                        // Update each tag with its corresponding data
                        tagDataArray.forEach(function (tagData) {
                            if (tagData && tagData.nodeId) {
                                var tag = that.itemsColl.findByNodeId(tagData.nodeId);
                                if (tag) {
                                    var previousValue = tag.get('value');
                                    var valueChanged = previousValue !== tagData.value;
                                    
                                    try {
                                        tag.set({
                                            value: tagData.value,
                                            timestamp: tagData.timestamp,
                                            quality: tagData.quality || 'Good',
                                            statusCode: tagData.statusCode || 0,
                                            displayName: tagData.displayName || tag.get('displayName') || '',
                                            dataType: tagData.dataType || tag.get('dataType'),
                                            lastUpdateTime: Date.now()
                                        });
                                        
                                        // Trigger visual update in grid if value changed
                                        if (valueChanged && that.agGridModule && that.agGridModule.gridApi) {
                                            var rowNode = that.agGridModule.gridApi.getRowNode(tagData.nodeId);
                                            if (rowNode) {
                                                rowNode.setData(tag.toJSON());
                                                that.agGridModule.gridApi.refreshCells({
                                                    rowNodes: [rowNode],
                                                    columns: ['value', 'timestamp', 'quality'],
                                                    force: true
                                                });
                                            }
                                        }
                                    } catch (e) {
                                        console.error('Error updating tag model from batch:', e);
                                    }
                                }
                            }
                        });
                    }
                });
            },

            stopPolling: function () {
                if (this.pollingTimer) {
                    clearInterval(this.pollingTimer);
                    this.pollingTimer = null;
                }
            },

            renderTree: function () {
                var that = this;
                var $container = this.$('#tree-content');

                if (this.treeNodesColl.length === 0) {
                    $container.html('<div class="empty-state"><div class="empty-state-message">No nodes available</div></div>');
                    return;
                }

                // Cancel any pending render
                if (this.renderTreeRAF) {
                    cancelAnimationFrame(this.renderTreeRAF);
                }

                // Use requestAnimationFrame to avoid blocking UI
                this.renderTreeRAF = requestAnimationFrame(function () {
                    TreeView.render(
                        $container[0],
                        that.treeNodesColl.models,
                        function (node) {
                            that.addTagToMonitoring(node);
                        },
                        function (node) {
                            that.expandNode(node);
                        },
                        function (node, x, y) {
                            that.showTreeContextMenu(node, x, y);
                        }
                    );
                    that.renderTreeRAF = null;
                });
            },

            updateGrid: function () {
                var that = this;
                setTimeout(function () {
                    that.agGridModule.setRowDataAsCollection(that.itemsColl);
                    
                    // Refresh grid to show updated values
                    if (that.agGridModule.gridApi) {
                        that.agGridModule.gridApi.refreshCells({
                            force: true,
                            columns: ['value', 'timestamp', 'quality']
                        });
                    }
                }, 100);
            },

            // Common functions
            append: function (container, el) {
                el = (el != null && el != undefined) ? el : this.$el;

                if (this.options.state == app.view_states.loading
                    || this.options.state == app.view_states.shown) {
                    this.options.state = app.view_states.shown;
                    container.append(el);
                    this.options.onappend(this);
                }

                if (this.options.state == app.view_states.hidden) {
                    container.append(el);
                }

                if (this.options.state == app.view_states.closed) {
                    return;
                }
            },

            show: function () {
                this.options.state = app.view_states.shown;
                this.showSubviews();
                this.bindEvents();
                this.$el.show();
                this.startPolling();
            },

            showSubviews: function () {
                _.each(this.subviews, function (sview) {
                    sview.show();
                });
            },

            hide: function () {
                this.options.state = app.view_states.hidden;
                this.hideSubviews();
                this.$el.hide();
                this.unbind();
                this.stopListening();
                this.stopPolling();
            },

            hideSubviews: function () {
                _.each(this.subviews, function (sview) {
                    sview.hide();
                });
            },

            close: function () {
                this.options.state = app.view_states.closed;
                this.closeSubviews();
                this.remove();
                this.unbindViewScopedEvents();
                this.unbind();
                this.stopPolling();

                // Cancel any pending render
                if (this.renderTreeRAF) {
                    cancelAnimationFrame(this.renderTreeRAF);
                    this.renderTreeRAF = null;
                }
            },

            closeSubviews: function () {
                _.each(this.subviews, function (sview) {
                    sview.close();
                });
            },

            preRender: function () {
                app.models.subnavbar.set("subnavbar", false);
            },

            reRender: function (viewParams) {
                try {
                    // Refresh tree if needed
                } catch (Error) { }
            },

            // =============================================
            // Tag Groups Methods
            // =============================================

            // Load groups from database and populate dropdown
            // Default group ID constant (not a database group)
            DEFAULT_GROUP_ID: '__default__',

            loadTagGroups: function () {
                var that = this;
                var $dropdown = this.$('#groups-dropdown');
                var $predefinedDropdown = this.$('#predefined-groups-dropdown');

                TAG_GROUPS_API.getAll(function (error, result) {
                    if (error) {
                        console.error('Failed to load tag groups:', error);
                        return;
                    }

                    var groups = result && result.groups ? result.groups : [];
                    var predefinedGroups = result && result.predefinedGroups ? result.predefinedGroups : [];

                    // Store predefined groups for later use
                    that._predefinedGroups = predefinedGroups;

                    // Clear and populate user groups dropdown
                    $dropdown.empty();

                    // Add default localStorage group (reads live from localStorage)
                    var currentActiveId = that.activeGroupId || that.DEFAULT_GROUP_ID;
                    var defaultTags = TagStorage.loadTags() || [];
                    var defaultCount = defaultTags.length;
                    var defaultCountText = defaultCount === 1 ? '1 tag' : defaultCount + ' tags';
                    var defaultSelected = currentActiveId === that.DEFAULT_GROUP_ID ? ' selected' : '';
                    $dropdown.append(
                        '<option value="' + that.DEFAULT_GROUP_ID + '"' + defaultSelected + '>' +
                        'Real Time (default) (' + defaultCountText + ')' +
                        '</option>'
                    );

                    if (groups.length > 0) {
                        that.tagGroupsColl.reset(groups);

                        groups.forEach(function (group) {
                            var tagCountText = group.TagCount === 1 ? '1 tag' : group.TagCount + ' tags';
                            var groupSelected = currentActiveId === group.GroupId ? ' selected' : '';
                            $dropdown.append(
                                '<option value="' + group.GroupId + '"' + groupSelected + '>' +
                                _.escape(group.GroupName) + ' (' + tagCountText + ')' +
                                '</option>'
                            );
                        });
                    } else {
                        that.tagGroupsColl.reset([]);
                    }

                    // Clear and populate predefined groups dropdown
                    $predefinedDropdown.empty();
                    $predefinedDropdown.append('<option value="">-- View predefined --</option>');

                    if (predefinedGroups.length > 0) {
                        predefinedGroups.forEach(function (group) {
                            var tagCountText = group.TagCount === 1 ? '1 tag' : group.TagCount + ' tags';
                            $predefinedDropdown.append(
                                '<option value="' + group.GroupId + '">' +
                                _.escape(group.GroupName) + ' (' + tagCountText + ')' +
                                '</option>'
                            );
                        });
                    }
                });
            },

            // Predefined Group dropdown change handler
            onPredefinedGroupChange: function (e) {
                var groupId = this.$('#predefined-groups-dropdown').val();
                var $cloneBtn = this.$('#btn-clone-predefined');

                if (!groupId) {
                    // No predefined group selected - exit predefined view mode
                    $cloneBtn.prop('disabled', true);
                    this.exitPredefinedView();
                    return;
                }

                // Enable clone button
                $cloneBtn.prop('disabled', false);

                // Find the predefined group
                var predefinedGroup = null;
                if (this._predefinedGroups) {
                    for (var i = 0; i < this._predefinedGroups.length; i++) {
                        if (this._predefinedGroups[i].GroupId === parseInt(groupId, 10)) {
                            predefinedGroup = this._predefinedGroups[i];
                            break;
                        }
                    }
                }

                if (predefinedGroup) {
                    this.viewPredefinedGroup(predefinedGroup);
                }
            },

            // View predefined group tags (read-only mode)
            viewPredefinedGroup: function (group) {
                var that = this;

                // Store current state so we can restore it later
                if (!this._viewingPredefinedGroup) {
                    this._savedTags = this.itemsColl.toJSON();
                    this._savedActiveGroupId = this.activeGroupId;
                }

                this._viewingPredefinedGroup = true;
                this._currentPredefinedGroup = group;

                // Stop polling while viewing predefined group (read-only view)
                this.stopPolling();

                // Hide the Actions column
                this.setActionsColumnVisible(false);

                // Show loading message
                app.views.topMessages.showMessage('Loading predefined group tags...', { stay: 1500 });

                // Fetch tags from the predefined group
                TAG_GROUPS_API.getTagIds(group.GroupId, function (error, tagIds) {
                    if (error) {
                        app.views.topMessages.showMessage('Failed to load tags: ' + error, { stay: 3000 });
                        return;
                    }

                    // Clear current collection and add predefined group tags
                    that.itemsColl.reset([], { silent: true });

                    if (tagIds && tagIds.length > 0) {
                        tagIds.forEach(function (tagId) {
                            var tag = new Screen.Models.Tag({
                                nodeId: tagId,
                                displayName: tagId,
                                dataType: null,
                                value: null,
                                timestamp: null,
                                quality: '',
                                statusCode: 0
                            });
                            that.itemsColl.add(tag, { silent: true });
                            // Fetch tag value for display
                            that.updateTagValue(tag);
                        });
                    }

                    that.model.set('selectedTagsCount', that.itemsColl.length);
                    that.updateGrid();

                    app.views.topMessages.showMessage(
                        'Viewing "' + group.GroupName + '" (' + tagIds.length + ' tags) - Read only',
                        { stay: 2000 }
                    );
                });
            },

            // Exit predefined view mode and restore previous state
            exitPredefinedView: function () {
                if (!this._viewingPredefinedGroup) return;

                this._viewingPredefinedGroup = false;
                this._currentPredefinedGroup = null;

                // Show the Actions column again
                this.setActionsColumnVisible(true);

                // Restore previous tags
                if (this._savedTags) {
                    this.itemsColl.reset([], { silent: true });
                    this._savedTags.forEach(function (tagData) {
                        var tag = new Screen.Models.Tag(tagData);
                        this.itemsColl.add(tag, { silent: true });
                    }, this);
                    this._savedTags = null;
                }

                // Restore active group
                if (this._savedActiveGroupId) {
                    this.activeGroupId = this._savedActiveGroupId;
                    this._savedActiveGroupId = null;
                }

                this.model.set('selectedTagsCount', this.itemsColl.length);
                this.updateGrid();

                // Resume polling
                this.startPolling();

                // Reset the predefined dropdown
                this.$('#predefined-groups-dropdown').val('');
                this.$('#btn-clone-predefined').prop('disabled', true);

                app.views.topMessages.showMessage('Returned to normal view', { stay: 1500 });
            },

            // Show/hide Actions column in grid
            setActionsColumnVisible: function (visible) {
                if (this.agGridModule && this.agGridModule.gridApi) {
                    this.agGridModule.gridApi.setColumnsVisible(['actions'], visible);
                }
            },

            // Clone predefined button click handler
            onClonePredefinedClick: function (e) {
                e.preventDefault();

                var groupId = this.$('#predefined-groups-dropdown').val();
                if (!groupId) {
                    app.views.topMessages.showMessage('Please select a predefined group first', { stay: 2000 });
                    return;
                }

                // Find the predefined group name
                var groupName = '';
                if (this._predefinedGroups) {
                    for (var i = 0; i < this._predefinedGroups.length; i++) {
                        if (this._predefinedGroups[i].GroupId === parseInt(groupId, 10)) {
                            groupName = this._predefinedGroups[i].GroupName;
                            break;
                        }
                    }
                }

                // Use the existing clone function
                this.clonePredefinedGroup(parseInt(groupId, 10), groupName);
            },

            // Load Group Button Click
            onLoadGroupClick: function (e) {
                e.preventDefault();
                var groupId = this.$('#groups-dropdown').val();

                if (!groupId) {
                    app.views.topMessages.showMessage('Please select a group to load', { stay: 2000 });
                    return;
                }

                // Exit predefined view mode if active (clear saved state since we're loading a new group)
                if (this._viewingPredefinedGroup) {
                    this._viewingPredefinedGroup = false;
                    this._currentPredefinedGroup = null;
                    this._savedTags = null;
                    this._savedActiveGroupId = null;
                    this.setActionsColumnVisible(true);
                    this.$('#predefined-groups-dropdown').val('');
                    this.$('#btn-clone-predefined').prop('disabled', true);
                }

                // Handle default localStorage group
                if (groupId === this.DEFAULT_GROUP_ID) {
                    this.$('#load-group-name').text('Real Time (default)');
                    this.selectedGroupIdToLoad = this.DEFAULT_GROUP_ID;
                    this.showModal('#load-group-modal');
                    return;
                }

                var group = this.tagGroupsColl.findByGroupId(parseInt(groupId, 10));
                if (group) {
                    this.$('#load-group-name').text(group.get('GroupName'));
                    this.selectedGroupIdToLoad = parseInt(groupId, 10);
                    this.showModal('#load-group-modal');
                }
            },

            // Load Group Confirm
            onLoadGroupConfirm: function (e) {
                e.preventDefault();
                var that = this;
                var groupId = this.selectedGroupIdToLoad;
                var loadMode = this.$('input[name="load-mode"]:checked').val();

                if (!groupId) {
                    this.closeModal();
                    return;
                }

                // Handle default localStorage group
                if (groupId === this.DEFAULT_GROUP_ID) {
                    // Track active group BEFORE modifying collection
                    that.activeGroupId = that.DEFAULT_GROUP_ID;

                    var savedTags = TagStorage.loadTags() || [];

                    if (loadMode === 'replace') {
                        that.itemsColl.reset([], { silent: true });
                    }

                    var addedCount = 0;
                    var skippedCount = 0;

                    savedTags.forEach(function (tagData) {
                        if (that.itemsColl.findByNodeId(tagData.nodeId)) {
                            skippedCount++;
                            return;
                        }
                        if (that.itemsColl.length >= that.model.get('maxTags')) return;

                        var tag = new Screen.Models.Tag({
                            nodeId: tagData.nodeId,
                            displayName: tagData.displayName || tagData.nodeId,
                            dataType: tagData.dataType || null,
                            value: null,
                            timestamp: null,
                            quality: '',
                            statusCode: 0
                        });

                        that.itemsColl.add(tag, { silent: true });
                        addedCount++;
                        that.updateTagValue(tag);
                    });

                    that.model.set('selectedTagsCount', that.itemsColl.length);
                    that.updateGrid();
                    that.saveTagsToStorage();

                    var message = addedCount > 0
                        ? 'Loaded ' + addedCount + ' tag(s) from localStorage'
                        : 'Real Time (default) loaded. Add tags from the tree.';
                    if (skippedCount > 0) message += ' (' + skippedCount + ' already in panel)';
                    app.views.topMessages.showMessage(message, { stay: 3000 });

                    this.closeModal();
                    return;
                }

                // Disable button during load
                var $btn = this.$('#btn-load-group-confirm');
                $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Loading...');

                TAG_GROUPS_API.getTagIds(groupId, function (error, tagIds) {
                    $btn.prop('disabled', false).html('Load Group');

                    if (error) {
                        app.views.topMessages.showMessage(error, { stay: 3000 });
                        return;
                    }

                    // Track active group BEFORE modifying collection
                    // so the listener doesn't wipe localStorage
                    that.activeGroupId = groupId;

                    // Clear current tags if replace mode
                    if (loadMode === 'replace') {
                        that.itemsColl.reset([]);
                    }

                    // Add tags from group (if any)
                    var addedCount = 0;
                    var skippedCount = 0;

                    if (tagIds && tagIds.length > 0) {
                        tagIds.forEach(function (tagId) {
                            // Check if already exists
                            if (that.itemsColl.findByNodeId(tagId)) {
                                skippedCount++;
                                return;
                            }

                            // Check max tags limit
                            if (that.itemsColl.length >= that.model.get('maxTags')) {
                                return;
                            }

                            // Create and add tag
                            var tag = new Screen.Models.Tag({
                                nodeId: tagId,
                                displayName: tagId, // Will be updated when value is fetched
                                dataType: null,
                                value: null,
                                timestamp: null,
                                quality: '',
                                statusCode: 0
                            });

                            that.itemsColl.add(tag, { silent: true });
                            addedCount++;

                            // Fetch initial value
                            that.updateTagValue(tag);
                        });
                    }

                    // Update UI
                    that.model.set('selectedTagsCount', that.itemsColl.length);
                    that.updateGrid();
                    that.saveTagsToStorage();

                    // Show message
                    var message = addedCount > 0
                        ? 'Loaded ' + addedCount + ' tag(s)'
                        : 'Group loaded (empty). Add tags from the tree.';
                    if (skippedCount > 0) {
                        message += ' (' + skippedCount + ' already in panel)';
                    }
                    app.views.topMessages.showMessage(message, { stay: 3000 });

                    that.closeModal();
                });
            },

            // Save Group Button Click
            onSaveGroupClick: function (e) {
                e.preventDefault();

                // If viewing predefined group, redirect to clone functionality
                if (this._viewingPredefinedGroup && this._currentPredefinedGroup) {
                    app.views.topMessages.showMessage('Use the Clone button to copy this predefined group', { stay: 2000 });
                    return;
                }

                if (this.itemsColl.length === 0) {
                    app.views.topMessages.showMessage('No tags to save. Add tags to the monitoring panel first.', { stay: 3000 });
                    return;
                }

                // Clear cloning state (this is a regular save, not a clone)
                this._isCloning = false;
                this._cloneTagIds = null;

                // Reset form
                this.$('#save-group-name').val('');
                this.$('#save-group-description').val('');
                this.$('#save-group-tag-count').text(this.itemsColl.length);

                this.showModal('#save-group-modal');

                // Focus on name input
                setTimeout(function () {
                    this.$('#save-group-name').focus();
                }.bind(this), 100);
            },

            // Save Group Confirm
            onSaveGroupConfirm: function (e) {
                e.preventDefault();
                var that = this;

                var groupName = this.$('#save-group-name').val().trim();
                var description = this.$('#save-group-description').val().trim();

                if (!groupName) {
                    app.views.topMessages.showMessage('Please enter a group name', { stay: 2000 });
                    this.$('#save-group-name').focus();
                    return;
                }

                // Get tag IDs - use cloned tags if cloning, otherwise current panel tags
                var tagIds;
                var isCloning = this._isCloning;
                if (isCloning && this._cloneTagIds && this._cloneTagIds.length > 0) {
                    tagIds = this._cloneTagIds;
                } else {
                    tagIds = this.itemsColl.models.map(function (tag) {
                        return tag.get('nodeId');
                    });
                }

                if (tagIds.length === 0) {
                    app.views.topMessages.showMessage('No tags to save', { stay: 2000 });
                    return;
                }

                // Disable button during save
                var $btn = this.$('#btn-save-group-confirm');
                $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Saving...');

                // Create group first, then add tags
                TAG_GROUPS_API.create(groupName, description, function (error, result) {
                    if (error) {
                        $btn.prop('disabled', false).html('Save Group');
                        app.views.topMessages.showMessage(error, { stay: 3000 });
                        return;
                    }

                    var groupId = result.GroupId;

                    // Add tags to the group
                    TAG_GROUPS_API.addTagsBulk(groupId, tagIds, function (tagError, tagResult) {
                        $btn.prop('disabled', false).html('Save Group');

                        // Clear cloning state
                        var wasCloning = that._isCloning;
                        var clonedTagIds = that._cloneTagIds;
                        that._isCloning = false;
                        that._cloneTagIds = null;

                        // Exit predefined view if active
                        if (that._viewingPredefinedGroup) {
                            that._viewingPredefinedGroup = false;
                            that._currentPredefinedGroup = null;
                            that._savedTags = null;
                            that._savedActiveGroupId = null;
                            that.setActionsColumnVisible(true);
                            that.$('#predefined-groups-dropdown').val('');
                            that.$('#btn-clone-predefined').prop('disabled', true);
                        }

                        if (tagError) {
                            app.views.topMessages.showMessage('Group created but failed to add tags: ' + tagError, { stay: 4000 });
                            // Still refresh and select the group even if tags failed
                        }

                        // Set the new group as active
                        that.activeGroupId = groupId;

                        // If this was a clone, load the cloned tags into the grid
                        if (wasCloning && clonedTagIds && clonedTagIds.length > 0) {
                            that.itemsColl.reset([], { silent: true });
                            clonedTagIds.forEach(function (tagId) {
                                var tag = new Screen.Models.Tag({
                                    nodeId: tagId,
                                    displayName: tagId,
                                    dataType: null,
                                    value: null,
                                    timestamp: null,
                                    quality: '',
                                    statusCode: 0
                                });
                                that.itemsColl.add(tag, { silent: true });
                                that.updateTagValue(tag);
                            });
                            that.model.set('selectedTagsCount', that.itemsColl.length);
                            that.updateGrid();
                        }

                        // Refresh groups dropdown and select the new group
                        that.loadTagGroups();

                        // Ensure polling is running
                        that.startPolling();

                        var message = wasCloning
                            ? 'Group "' + groupName + '" cloned and loaded with ' + tagIds.length + ' tag(s)'
                            : 'Group "' + groupName + '" saved and loaded with ' + tagIds.length + ' tag(s)';
                        app.views.topMessages.showMessage(message, { stay: 3000 });

                        that.closeModal();
                    });
                });
            },

            // Manage Groups Button Click
            onManageGroupsClick: function (e) {
                e.preventDefault();
                this.renderManageGroupsList();
                this.showModal('#manage-groups-modal');
            },

            // Render the groups list in manage modal
            renderManageGroupsList: function () {
                var that = this;
                var $list = this.$('#manage-groups-list');
                var $empty = this.$('#manage-groups-empty');

                $list.html('<div class="groups-loading"><i class="fa fa-spinner fa-spin"></i> Loading groups...</div>');
                $empty.hide();

                TAG_GROUPS_API.getAll(function (error, result) {
                    if (error) {
                        $list.html('<div class="tree-error">' + _.escape(error) + '</div>');
                        return;
                    }

                    var groups = result && result.groups ? result.groups : [];

                    // Render user groups
                    if (groups.length === 0) {
                        $list.empty();
                        $empty.show();
                    } else {
                        $empty.hide();
                        $list.empty();

                        groups.forEach(function (group) {
                            var tagCountText = group.TagCount === 1 ? '1 tag' : group.TagCount + ' tags';
                            var createdDate = group.CreatedDate ? moment(group.CreatedDate).format('MMM D, YYYY') : '';

                            var $item = $('<div class="groups-list-item" data-group-id="' + group.GroupId + '">' +
                                '<div class="groups-list-info">' +
                                '<div class="groups-list-name">' + _.escape(group.GroupName) + '</div>' +
                                '<div class="groups-list-meta">' +
                                '<span><i class="fa fa-tags"></i> ' + tagCountText + '</span>' +
                                '<span><i class="fa fa-calendar"></i> ' + createdDate + '</span>' +
                                '<span><i class="fa fa-user"></i> ' + _.escape(group.CreatedBy || 'Unknown') + '</span>' +
                                '</div>' +
                                (group.Description ? '<div class="groups-list-description">' + _.escape(group.Description) + '</div>' : '') +
                                '</div>' +
                                '<div class="groups-list-actions">' +
                                '<button type="button" class="groups-btn groups-btn-primary groups-btn-sm btn-edit-group" title="Edit group">' +
                                '<i class="fa fa-pencil"></i>' +
                                '</button>' +
                                '<button type="button" class="groups-btn groups-btn-danger groups-btn-sm btn-delete-group" title="Delete group">' +
                                '<i class="fa fa-trash-o"></i>' +
                                '</button>' +
                                '</div>' +
                                '</div>');

                            // Bind edit button
                            $item.find('.btn-edit-group').on('click', function (evt) {
                                evt.stopPropagation();
                                that.openEditGroupModal(group.GroupId, group.GroupName, group.Description);
                            });

                            // Bind delete button
                            $item.find('.btn-delete-group').on('click', function (evt) {
                                evt.stopPropagation();
                                that.deleteGroup(group.GroupId, group.GroupName);
                            });

                            $list.append($item);
                        });
                    }
                });
            },

            // Clone a predefined group - opens Save Group modal with prefilled data
            clonePredefinedGroup: function (groupId, groupName) {
                var that = this;

                // Show loading indicator
                app.views.topMessages.showMessage('Loading group tags...', { stay: 2000 });

                // Get the tags from the predefined group
                TAG_GROUPS_API.getTagIds(groupId, function (error, tagIds) {
                    if (error) {
                        app.views.topMessages.showMessage('Failed to load group tags: ' + error, { stay: 3000 });
                        return;
                    }

                    // Store the tags to be cloned
                    that._cloneTagIds = tagIds || [];
                    that._isCloning = true;

                    // Close manage modal
                    that.$('#manage-groups-modal').hide();

                    // Prefill the save group form
                    that.$('#save-group-name').val(groupName + ' (clone)');
                    that.$('#save-group-description').val('');
                    that.$('#save-group-tag-count').text(that._cloneTagIds.length);

                    // Show the save group modal
                    that.showModal('#save-group-modal');

                    // Focus on name input
                    setTimeout(function () {
                        that.$('#save-group-name').focus().select();
                    }, 100);
                });
            },

            // Open edit group modal
            openEditGroupModal: function (groupId, groupName, description) {
                this.$('#edit-group-id').val(groupId);
                this.$('#edit-group-name').val(groupName);
                this.$('#edit-group-description').val(description || '');

                // Hide manage modal, show edit modal
                this.$('#manage-groups-modal').hide();
                this.showModal('#edit-group-modal');

                setTimeout(function () {
                    this.$('#edit-group-name').focus();
                }.bind(this), 100);
            },

            // Edit Group Confirm
            onEditGroupConfirm: function (e) {
                e.preventDefault();
                var that = this;

                var groupId = parseInt(this.$('#edit-group-id').val(), 10);
                var groupName = this.$('#edit-group-name').val().trim();
                var description = this.$('#edit-group-description').val().trim();

                if (!groupName) {
                    app.views.topMessages.showMessage('Please enter a group name', { stay: 2000 });
                    this.$('#edit-group-name').focus();
                    return;
                }

                var $btn = this.$('#btn-edit-group-confirm');
                $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Updating...');

                TAG_GROUPS_API.update(groupId, groupName, description, function (error, result) {
                    $btn.prop('disabled', false).html('Update Group');

                    if (error) {
                        app.views.topMessages.showMessage(error, { stay: 3000 });
                        return;
                    }

                    app.views.topMessages.showMessage('Group updated successfully', { stay: 2000 });

                    // Close edit modal and refresh manage modal
                    that.$('#edit-group-modal').hide();
                    that.$('#manage-groups-modal').show();
                    that.renderManageGroupsList();

                    // Also refresh the dropdown
                    that.loadTagGroups();
                });
            },

            // Delete group with confirmation
            deleteGroup: function (groupId, groupName) {
                var that = this;

                if (!confirm('Are you sure you want to delete the group "' + groupName + '"?\n\nThis action cannot be undone.')) {
                    return;
                }

                TAG_GROUPS_API.delete(groupId, function (error, result) {
                    if (error) {
                        app.views.topMessages.showMessage(error, { stay: 3000 });
                        return;
                    }

                    app.views.topMessages.showMessage('Group "' + groupName + '" deleted', { stay: 2000 });

                    // Refresh the list and dropdown
                    that.renderManageGroupsList();
                    that.loadTagGroups();
                });
            },

            // Show modal helper
            showModal: function (selector) {
                this.$(selector).show();
            },

            // Close modal helper
            closeModal: function (e) {
                if (e) {
                    e.preventDefault();
                    // Find the closest modal
                    var $modal = $(e.target).closest('.groups-modal');
                    if ($modal.length) {
                        // Clear cloning state if closing save-group-modal
                        if ($modal.attr('id') === 'save-group-modal') {
                            this._isCloning = false;
                            this._cloneTagIds = null;
                        }
                        $modal.hide();
                        return;
                    }
                }
                // Clear cloning state when closing all modals
                this._isCloning = false;
                this._cloneTagIds = null;
                // Close all modals
                this.$('.groups-modal').hide();
            },
        });

        // Required, return the module for AMD compliance.
        return Screen;
    });
