using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Timers;
using Decal.Adapter;
using Decal.Adapter.Wrappers;
using MosswartMassacre.Views;
using Mag.Shared.Constants;

namespace MosswartMassacre
{
    [FriendlyName("Mosswart Massacre")]
    public class PluginCore : PluginBase
    {
        // Hot Reload Support Properties
        private static string _assemblyDirectory = null;
        public static string AssemblyDirectory 
        {
            get 
            {
                if (_assemblyDirectory == null) 
                {
                    try 
                    {
                        _assemblyDirectory = System.IO.Path.GetDirectoryName(typeof(PluginCore).Assembly.Location);
                    }
                    catch 
                    {
                        _assemblyDirectory = Environment.CurrentDirectory;
                    }
                }
                return _assemblyDirectory;
            }
            set 
            {
                _assemblyDirectory = value;
            }
        }
        public static bool IsHotReload { get; set; }
        
        internal static PluginHost MyHost;
        internal static int totalKills = 0;
        internal static int rareCount = 0;
        internal static int sessionDeaths = 0;  // Deaths this session
        internal static int totalDeaths = 0;    // Total deaths from character
        internal static int cachedPrismaticCount = 0;  // Cached Prismatic Taper count
        internal static int lastPrismaticCount = 0;    // For delta calculation
        
        // Track taper items and their containers for accurate release detection
        private static readonly Dictionary<int, int> trackedTaperContainers = new Dictionary<int, int>();
        private static readonly Dictionary<int, int> lastKnownStackSizes = new Dictionary<int, int>();
        internal static DateTime lastKillTime = DateTime.Now;
        internal static double killsPer5Min = 0;
        internal static double killsPerHour = 0;
        internal static DateTime statsStartTime = DateTime.Now;
        internal static Timer updateTimer;
        private static Timer vitalsTimer;
        private static System.Windows.Forms.Timer commandTimer;
        private static Timer characterStatsTimer;
        private static readonly Queue<string> pendingCommands = new Queue<string>();
        public static bool RareMetaEnabled { get; set; } = true;

        // VVS View Management
        private static class ViewManager
        {
            public static void ViewInit()
            {
                Views.VVSTabbedMainView.ViewInit();
            }

            public static void ViewDestroy()
            {
                Views.VVSTabbedMainView.ViewDestroy();
            }

            public static void UpdateKillStats(int totalKills, double killsPer5Min, double killsPerHour)
            {
                Views.VVSTabbedMainView.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);
            }

            public static void UpdateElapsedTime(TimeSpan elapsed)
            {
                Views.VVSTabbedMainView.UpdateElapsedTime(elapsed);
            }

            public static void UpdateRareCount(int rareCount)
            {
                Views.VVSTabbedMainView.UpdateRareCount(rareCount);
            }

            public static void SetRareMetaToggleState(bool enabled)
            {
                Views.VVSTabbedMainView.SetRareMetaToggleState(enabled);
            }

            public static void RefreshSettingsFromConfig()
            {
                Views.VVSTabbedMainView.RefreshSettingsFromConfig();
            }

            public static void RefreshUpdateStatus()
            {
                Views.VVSTabbedMainView.RefreshUpdateStatus();
            }
        }
        public static bool RemoteCommandsEnabled { get; set; } = false;
        public static bool HttpServerEnabled { get; set; } = false;
        public static string CharTag { get; set; } = "";
        public static bool TelemetryEnabled { get; set; } = false;
        public static bool WebSocketEnabled { get; set; } = false;
        public bool InventoryLogEnabled { get; set; } = false;
        public static bool AggressiveChatStreamingEnabled { get; set; } = true;
        private MossyInventory _inventoryLogger;
        public static NavVisualization navVisualization;
        public static ChestLooter chestLooter;

        // Quest Management for always-on quest streaming
        public static QuestManager questManager;
        private static Timer questStreamingTimer;

        private static Queue<string> rareMessageQueue = new Queue<string>();
        private static DateTime _lastSent = DateTime.MinValue;
        private static readonly Queue<string> _chatQueue = new Queue<string>();

        protected override void Startup()
        {
            try
            {
                // Set MyHost - for hot reload scenarios, Host might be null
                if (Host != null)
                {
                    MyHost = Host;
                }
                else if (MyHost == null)
                {
                    // Hot reload fallback - this is okay, WriteToChat will handle it
                    MyHost = null;
                }
                
                // Check if this is a hot reload
                var isCharacterLoaded = CoreManager.Current.CharacterFilter.LoginStatus == 3;
                if (IsHotReload || isCharacterLoaded)
                {
                    // Hot reload detected - reinitialize connections and state
                    WriteToChat("[INFO] Hot reload detected - reinitializing plugin");
                    
                    // Reload settings if character is already logged in
                    if (isCharacterLoaded)
                    {
                        try
                        {
                            WriteToChat("Hot reload - reinitializing character-dependent systems");
                            // Don't call LoginComplete - create hot reload specific initialization
                            InitializeForHotReload();
                            WriteToChat("[INFO] Hot reload initialization complete");
                        }
                        catch (Exception ex)
                        {
                            WriteToChat($"[ERROR] Hot reload initialization failed: {ex.Message}");
                        }
                    }
                }
                
                // Note: Startup messages will appear after character login
                // Subscribe to chat message event
                CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
                CoreManager.Current.ChatBoxMessage += new EventHandler<ChatTextInterceptEventArgs>(AllChatText);
                CoreManager.Current.CommandLineText += OnChatCommand;
                CoreManager.Current.CharacterFilter.LoginComplete += CharacterFilter_LoginComplete;
                CoreManager.Current.CharacterFilter.Death += OnCharacterDeath;
                CoreManager.Current.WorldFilter.CreateObject += OnSpawn;
                CoreManager.Current.WorldFilter.CreateObject += OnPortalDetected;
                CoreManager.Current.WorldFilter.ReleaseObject += OnDespawn;
                // Subscribe to inventory change events for taper tracking
                CoreManager.Current.WorldFilter.CreateObject += OnInventoryCreate;
                CoreManager.Current.WorldFilter.ReleaseObject += OnInventoryRelease;
                CoreManager.Current.WorldFilter.ChangeObject += OnInventoryChange;
                // Initialize VVS view after character login
                ViewManager.ViewInit();

                // Initialize the timer
                updateTimer = new Timer(1000); // Update every second
                updateTimer.Elapsed += UpdateStats;
                updateTimer.Start();

                // Initialize vitals streaming timer
                vitalsTimer = new Timer(5000); // Send vitals every 5 seconds
                vitalsTimer.Elapsed += SendVitalsUpdate;
                vitalsTimer.Start();

                // Initialize command processing timer (Windows Forms timer for main thread)
                commandTimer = new System.Windows.Forms.Timer();
                commandTimer.Interval = 10; // Process commands every 10ms
                commandTimer.Tick += ProcessPendingCommands;
                commandTimer.Start();

                // Note: View initialization moved to LoginComplete for VVS compatibility

                // Initialize character stats and hook ServerDispatch early
                // 0x0013 (character properties with luminance) fires DURING login,
                // BEFORE LoginComplete — must hook here to catch it
                CharacterStats.Init();
                CoreManager.Current.EchoFilter.ServerDispatch += EchoFilter_ServerDispatch;

                // Enable TLS1.2
                ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
                //Enable vTank interface
                vTank.Enable();
                //lyssna på commands
                WebSocket.OnServerCommand += HandleServerCommand;
                //starta inventory. Hanterar subscriptions  i den med

                _inventoryLogger = new MossyInventory();

                // Initialize navigation visualization system
                navVisualization = new NavVisualization();

                // Note: ChestLooter is initialized in LoginComplete after PluginSettings.Initialize()

                // Note: DECAL Harmony patches will be initialized in LoginComplete event
                // where the chat system is available for error messages

            }
            catch (Exception ex)
            {
                WriteToChat("Error during startup: " + ex.Message);
            }
        }

        protected override void Shutdown()
        {
            try
            {
                PluginSettings.Save();
                if (TelemetryEnabled)
                    Telemetry.Stop();   // ensure no dangling timer / HttpClient
                WriteToChat("Mosswart Massacre is shutting down. Bye!");



                // Unsubscribe from chat message event
                CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(OnChatText);
                CoreManager.Current.CommandLineText -= OnChatCommand;
                CoreManager.Current.ChatBoxMessage -= new EventHandler<ChatTextInterceptEventArgs>(AllChatText);
                CoreManager.Current.CharacterFilter.Death -= OnCharacterDeath;
                CoreManager.Current.WorldFilter.CreateObject -= OnSpawn;
                CoreManager.Current.WorldFilter.CreateObject -= OnPortalDetected;
                CoreManager.Current.WorldFilter.ReleaseObject -= OnDespawn;
                // Unsubscribe from inventory change events
                CoreManager.Current.WorldFilter.CreateObject -= OnInventoryCreate;
                CoreManager.Current.WorldFilter.ReleaseObject -= OnInventoryRelease;
                CoreManager.Current.WorldFilter.ChangeObject -= OnInventoryChange;
                // Unsubscribe from server dispatch
                CoreManager.Current.EchoFilter.ServerDispatch -= EchoFilter_ServerDispatch;

                // Stop and dispose of the timers
                if (updateTimer != null)
                {
                    updateTimer.Stop();
                    updateTimer.Dispose();
                    updateTimer = null;
                }

                if (vitalsTimer != null)
                {
                    vitalsTimer.Stop();
                    vitalsTimer.Dispose();
                    vitalsTimer = null;
                }

                if (commandTimer != null)
                {
                    commandTimer.Stop();
                    commandTimer.Dispose();
                    commandTimer = null;
                }

                // Stop and dispose quest streaming timer
                if (questStreamingTimer != null)
                {
                    questStreamingTimer.Stop();
                    questStreamingTimer.Elapsed -= OnQuestStreamingUpdate;
                    questStreamingTimer.Dispose();
                    questStreamingTimer = null;
                }

                // Stop and dispose character stats timer
                if (characterStatsTimer != null)
                {
                    characterStatsTimer.Stop();
                    characterStatsTimer.Elapsed -= OnCharacterStatsUpdate;
                    characterStatsTimer.Dispose();
                    characterStatsTimer = null;
                }

                // Dispose quest manager
                if (questManager != null)
                {
                    questManager.Dispose();
                    questManager = null;
                }

                // Clean up the view
                ViewManager.ViewDestroy();
                //Disable vtank interface
                vTank.Disable();
                // sluta lyssna på commands
                WebSocket.OnServerCommand -= HandleServerCommand;
                WebSocket.Stop();
                //shutdown inv
                _inventoryLogger.Dispose();

                // Clean up navigation visualization
                if (navVisualization != null)
                {
                    navVisualization.Dispose();
                    navVisualization = null;
                }

                // Clean up taper tracking
                trackedTaperContainers.Clear();
                lastKnownStackSizes.Clear();

                // Clean up Harmony patches
                DecalHarmonyClean.Cleanup();

                MyHost = null;
            }
            catch (Exception ex)
            {
                WriteToChat("Error during shutdown: " + ex.Message);
            }
        }
        private void CharacterFilter_LoginComplete(object sender, EventArgs e)
        {
            CoreManager.Current.CharacterFilter.LoginComplete -= CharacterFilter_LoginComplete;

            WriteToChat("Mosswart Massacre has started!");



            PluginSettings.Initialize(); // Safe to call now

            // Initialize chest looter system (needs PluginSettings to be ready)
            try
            {
                chestLooter = new ChestLooter(CoreManager.Current, PluginSettings.Instance.ChestLooterSettings);
                chestLooter.Initialize();
                chestLooter.StatusChanged += (sender, status) =>
                {
                    VVSTabbedMainView.UpdateChestLooterStatus(status);
                };
            }
            catch (Exception ex)
            {
                WriteToChat($"[ChestLooter] Initialization failed: {ex.Message}");
            }

            // Apply the values
            RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
            WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled;
            RemoteCommandsEnabled = PluginSettings.Instance.RemoteCommandsEnabled;
            HttpServerEnabled = PluginSettings.Instance.HttpServerEnabled;
            TelemetryEnabled = PluginSettings.Instance.TelemetryEnabled;
            CharTag = PluginSettings.Instance.CharTag;
            ViewManager.SetRareMetaToggleState(RareMetaEnabled);
            ViewManager.RefreshSettingsFromConfig(); // Refresh all UI settings after loading
            if (TelemetryEnabled)
                Telemetry.Start();
            if (WebSocketEnabled)
                WebSocket.Start();

            // Initialize Harmony patches using UtilityBelt's loaded DLL
            try
            {
                bool success = DecalHarmonyClean.Initialize();
                if (success)
                    WriteToChat("[OK] Plugin message interception active");
                else
                    WriteToChat("[FAIL] Could not initialize message interception");
            }
            catch (Exception ex)
            {
                WriteToChat($"[ERROR] Harmony initialization failed: {ex.Message}");
            }

            // Initialize death tracking
            totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
            sessionDeaths = 0;

            // Initialize cached Prismatic Taper count
            InitializePrismaticTaperCount();

            // Initialize quest manager for always-on quest streaming
            try
            {
                questManager = new QuestManager();
                
                // Trigger full quest data refresh (same as clicking refresh button)
                Views.FlagTrackerView.RefreshQuestData();
                
                // Initialize quest streaming timer (30 seconds)
                questStreamingTimer = new Timer(30000);
                questStreamingTimer.Elapsed += OnQuestStreamingUpdate;
                questStreamingTimer.AutoReset = true;
                questStreamingTimer.Start();
                
                WriteToChat("[OK] Quest streaming initialized with full data refresh");
            }
            catch (Exception ex)
            {
                WriteToChat($"[ERROR] Quest streaming initialization failed: {ex.Message}");
            }

            // Start character stats streaming
            // Note: Init() and ServerDispatch hook are in Startup() so we catch
            // 0x0013 (luminance/properties) which fires BEFORE LoginComplete
            try
            {
                // Start 10-minute character stats timer
                characterStatsTimer = new Timer(600000); // 10 minutes
                characterStatsTimer.Elapsed += OnCharacterStatsUpdate;
                characterStatsTimer.AutoReset = true;
                characterStatsTimer.Start();

                // Send initial stats after 5-second delay (let CharacterFilter populate)
                var initialDelay = new Timer(5000);
                initialDelay.AutoReset = false;
                initialDelay.Elapsed += (s, args) =>
                {
                    CharacterStats.CollectAndSend();
                    ((Timer)s).Dispose();
                };
                initialDelay.Start();

                WriteToChat("[OK] Character stats streaming initialized (10-min interval)");
            }
            catch (Exception ex)
            {
                WriteToChat($"[ERROR] Character stats initialization failed: {ex.Message}");
            }

        }

        #region Quest Streaming Methods
        private static void OnQuestStreamingUpdate(object sender, ElapsedEventArgs e)
        {
            try
            {
                // Debug: Log when timer fires
                if (PluginSettings.Instance?.VerboseLogging == true)
                {
                    WriteToChat("[QUEST-STREAM] Timer fired, checking conditions...");
                }
                
                // Stream high priority quest data via WebSocket
                if (!WebSocketEnabled)
                {
                    if (PluginSettings.Instance?.VerboseLogging == true)
                    {
                        WriteToChat("[QUEST-STREAM] WebSocket not enabled, skipping");
                    }
                    return;
                }
                
                if (questManager?.QuestList == null || questManager.QuestList.Count == 0)
                {
                    if (PluginSettings.Instance?.VerboseLogging == true)
                    {
                        WriteToChat($"[QUEST-STREAM] No quest data available (null: {questManager?.QuestList == null}, count: {questManager?.QuestList?.Count ?? 0})");
                    }
                    return;
                }
                
                var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
                
                // Find and stream priority quests (deduplicated by quest ID)
                var priorityQuests = questManager.QuestList
                    .Where(q => IsHighPriorityQuest(q.Id))
                    .GroupBy(q => q.Id)
                    .Select(g => g.First()) // Take first occurrence of each quest ID
                    .ToList();
                
                if (PluginSettings.Instance?.VerboseLogging == true)
                {
                    WriteToChat($"[QUEST-STREAM] Found {priorityQuests.Count} priority quests to stream");
                }
                
                foreach (var quest in priorityQuests)
                {
                    try
                    {
                        string questName = questManager.GetFriendlyQuestName(quest.Id);
                        long timeRemaining = quest.ExpireTime - currentTime;
                        string countdown = FormatCountdown(timeRemaining);
                        
                        if (PluginSettings.Instance?.VerboseLogging == true)
                        {
                            WriteToChat($"[QUEST-STREAM] Sending: {questName} - {countdown}");
                        }
                        
                        // Stream quest data
                        System.Threading.Tasks.Task.Run(() => WebSocket.SendQuestDataAsync(questName, countdown));
                    }
                    catch (Exception ex)
                    {
                        WriteToChat($"[QUEST-STREAM] Error streaming quest {quest.Id}: {ex.Message}");
                    }
                }
            }
            catch (Exception ex)
            {
                WriteToChat($"[QUEST-STREAM] Error in timer handler: {ex.Message}");
            }
        }
        
        private static bool IsHighPriorityQuest(string questId)
        {
            return questId == "stipendtimer_0812" ||  // Changed from stipendtimer_monthly to stipendtimer_0812
                   questId == "augmentationblankgemacquired" || 
                   questId == "insatiableeaterjaw";
        }
        
        private static string FormatCountdown(long seconds)
        {
            if (seconds <= 0)
                return "READY";
                
            var timeSpan = TimeSpan.FromSeconds(seconds);
            
            if (timeSpan.TotalDays >= 1)
                return $"{(int)timeSpan.TotalDays}d {timeSpan.Hours:D2}h";
            else if (timeSpan.TotalHours >= 1)
                return $"{timeSpan.Hours}h {timeSpan.Minutes:D2}m";
            else if (timeSpan.TotalMinutes >= 1)
                return $"{timeSpan.Minutes}m {timeSpan.Seconds:D2}s";
            else
                return $"{timeSpan.Seconds}s";
        }
        #endregion

        private void InitializeForHotReload()
        {
            // This method handles initialization that depends on character being logged in
            // Similar to LoginComplete but designed for hot reload scenarios
            
            WriteToChat("Mosswart Massacre hot reload initialization started!");

            // 1. Initialize settings - CRITICAL first step
            PluginSettings.Initialize();

            // 1b. Initialize chest looter system (needs PluginSettings to be ready)
            try
            {
                chestLooter = new ChestLooter(CoreManager.Current, PluginSettings.Instance.ChestLooterSettings);
                chestLooter.Initialize();
                chestLooter.StatusChanged += (sender, status) =>
                {
                    VVSTabbedMainView.UpdateChestLooterStatus(status);
                };
            }
            catch (Exception ex)
            {
                WriteToChat($"[ChestLooter] Initialization failed: {ex.Message}");
            }

            // 2. Apply the values from settings
            RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
            WebSocketEnabled = PluginSettings.Instance.WebSocketEnabled;
            RemoteCommandsEnabled = PluginSettings.Instance.RemoteCommandsEnabled;
            HttpServerEnabled = PluginSettings.Instance.HttpServerEnabled;
            TelemetryEnabled = PluginSettings.Instance.TelemetryEnabled;
            CharTag = PluginSettings.Instance.CharTag;

            // 3. Update UI with current settings
            ViewManager.SetRareMetaToggleState(RareMetaEnabled);
            ViewManager.RefreshSettingsFromConfig();

            // 4. Restart services if they were enabled (stop first, then start)
            if (TelemetryEnabled)
            {
                Telemetry.Stop();   // Stop existing
                Telemetry.Start();  // Restart
            }
            
            if (WebSocketEnabled)
            {
                WebSocket.Stop();   // Stop existing  
                WebSocket.Start();  // Restart
            }

            if (HttpServerEnabled)
            {
                HttpCommandServer.Stop();  // Stop existing
                HttpCommandServer.Start(); // Restart
            }

            // 5. Initialize Harmony patches (only if not already done)
            // Note: Harmony patches are global and don't need reinitialization
            if (!DecalHarmonyClean.IsActive())
            {
                try
                {
                    bool success = DecalHarmonyClean.Initialize();
                    if (success)
                        WriteToChat("[OK] Plugin message interception active");
                    else
                        WriteToChat("[FAIL] Could not initialize message interception");
                }
                catch (Exception ex)
                {
                    WriteToChat($"[ERROR] Harmony initialization failed: {ex.Message}");
                }
            }

            // 6. Reinitialize death tracking
            totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
            // Don't reset sessionDeaths - keep the current session count

            // 7. Reinitialize cached Prismatic Taper count
            InitializePrismaticTaperCount();

            // 8. Reinitialize quest manager for hot reload
            try
            {
                if (questManager == null)
                {
                    questManager = new QuestManager();
                    WriteToChat("[OK] Quest manager reinitialized");
                }
                else
                {
                    WriteToChat("[INFO] Quest manager already active");
                }
                
                // Trigger full quest data refresh (same as clicking refresh button)
                Views.FlagTrackerView.RefreshQuestData();
                WriteToChat("[INFO] Quest data refresh triggered for hot reload");
            }
            catch (Exception ex)
            {
                WriteToChat($"[ERROR] Quest manager hot reload failed: {ex.Message}");
            }

            // 9. Reinitialize quest streaming timer for hot reload
            try
            {
                // Stop existing timer if any
                if (questStreamingTimer != null)
                {
                    questStreamingTimer.Stop();
                    questStreamingTimer.Elapsed -= OnQuestStreamingUpdate;
                    questStreamingTimer.Dispose();
                    questStreamingTimer = null;
                }
                
                // Create new timer
                questStreamingTimer = new Timer(30000); // 30 seconds
                questStreamingTimer.Elapsed += OnQuestStreamingUpdate;
                questStreamingTimer.AutoReset = true;
                questStreamingTimer.Start();
                
                WriteToChat("[OK] Quest streaming timer reinitialized (30s interval)");
            }
            catch (Exception ex)
            {
                WriteToChat($"[ERROR] Quest streaming timer hot reload failed: {ex.Message}");
            }

            WriteToChat("Hot reload initialization completed!");
        }

        private void InitializePrismaticTaperCount()
        {
            try
            {
                lastPrismaticCount = cachedPrismaticCount;
                cachedPrismaticCount = Utils.GetItemStackSize("Prismatic Taper");
                
                // Initialize tracking for existing tapers
                trackedTaperContainers.Clear();
                lastKnownStackSizes.Clear();
                
                foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
                {
                    if (wo.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase) &&
                        IsPlayerOwnedContainer(wo.Container))
                    {
                        int stackCount = wo.Values(LongValueKey.StackCount, 1);
                        trackedTaperContainers[wo.Id] = wo.Container;
                        lastKnownStackSizes[wo.Id] = stackCount;
                    }
                }
                
            }
            catch (Exception ex)
            {
                WriteToChat($"[TAPER] Error initializing count: {ex.Message}");
                cachedPrismaticCount = 0;
                lastPrismaticCount = 0;
                trackedTaperContainers.Clear();
                lastKnownStackSizes.Clear();
            }
        }

        private bool IsPlayerOwnedContainer(int containerId)
        {
            try
            {
                // Check if it's the player's main inventory
                if (containerId == CoreManager.Current.CharacterFilter.Id)
                    return true;

                // Check if it's one of the player's containers (side packs)
                // Get the container object to verify it belongs to the player
                WorldObject container = CoreManager.Current.WorldFilter[containerId];
                if (container != null && 
                    container.ObjectClass == ObjectClass.Container &&
                    container.Container == CoreManager.Current.CharacterFilter.Id)
                {
                    return true;
                }

                return false;
            }
            catch
            {
                return false;
            }
        }

        private void OnInventoryCreate(object sender, CreateObjectEventArgs e)
        {
            try
            {
                var item = e.New;
                if (IsPlayerOwnedContainer(item.Container) && 
                    item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
                {
                    lastPrismaticCount = cachedPrismaticCount;
                    int stackCount = item.Values(LongValueKey.StackCount, 1);
                    cachedPrismaticCount += stackCount;
                    int delta = cachedPrismaticCount - lastPrismaticCount;
                    
                    // Initialize tracking for this new taper
                    trackedTaperContainers[item.Id] = item.Container;
                    lastKnownStackSizes[item.Id] = stackCount;
                    
                }
            }
            catch (Exception ex)
            {
                WriteToChat($"[TAPER] Error in OnInventoryCreate: {ex.Message}");
            }
        }

        private void OnInventoryRelease(object sender, ReleaseObjectEventArgs e)
        {
            try
            {
                var item = e.Released;
                if (item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
                {
                    // Check where this taper WAS before being released (not where it's going)
                    if (trackedTaperContainers.TryGetValue(item.Id, out int previousContainer))
                    {
                        if (IsPlayerOwnedContainer(previousContainer))
                        {
                            // This taper was in our inventory and is now being released
                            lastPrismaticCount = cachedPrismaticCount;
                            int stackCount = item.Values(LongValueKey.StackCount, 1);
                            cachedPrismaticCount -= stackCount;
                        }
                        
                        // Clean up tracking
                        trackedTaperContainers.Remove(item.Id);
                        lastKnownStackSizes.Remove(item.Id);
                    }
                    else
                    {
                        // Fallback: recalculate total count when untracked taper is released
                        lastPrismaticCount = cachedPrismaticCount;
                        cachedPrismaticCount = Utils.GetItemStackSize("Prismatic Taper");
                    }
                }
            }
            catch (Exception ex)
            {
                WriteToChat($"[TAPER] Error in OnInventoryRelease: {ex.Message}");
            }
        }

        private void OnInventoryChange(object sender, ChangeObjectEventArgs e)
        {
            try
            {
                var item = e.Changed;
                if (item.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
                {
                    bool isInPlayerContainer = IsPlayerOwnedContainer(item.Container);
                    
                    // Track container location for release detection
                    if (isInPlayerContainer)
                    {
                        bool wasAlreadyTracked = trackedTaperContainers.ContainsKey(item.Id);
                        trackedTaperContainers[item.Id] = item.Container;
                        
                        // Handle stack size changes with pure delta math
                        int currentStack = item.Values(LongValueKey.StackCount, 1);
                        
                        // Check if this is a pickup from ground (item not previously tracked)
                        if (!wasAlreadyTracked)
                        {
                            // This is likely a pickup from ground - increment count
                            lastPrismaticCount = cachedPrismaticCount;
                            cachedPrismaticCount += currentStack;
                        }
                        else if (lastKnownStackSizes.TryGetValue(item.Id, out int previousStack))
                        {
                            int stackDelta = currentStack - previousStack;
                            if (stackDelta != 0)
                            {
                                lastPrismaticCount = cachedPrismaticCount;
                                cachedPrismaticCount += stackDelta;
                            }
                        }
                        
                        lastKnownStackSizes[item.Id] = currentStack;
                    }
                    // Item is no longer in player containers
                    // DON'T clean up tracking here - let OnInventoryRelease handle cleanup
                    // This ensures tracking data is available for the Release event
                }
            }
            catch (Exception ex)
            {
                WriteToChat($"[TAPER] Error in OnInventoryChange: {ex.Message}");
            }
        }

        private async void OnSpawn(object sender, CreateObjectEventArgs e)
        {
            var mob = e.New;
            if (mob.ObjectClass != ObjectClass.Monster) return;

            try
            {
                // Get DECAL coordinates
                var decalCoords = mob.Coordinates();
                if (decalCoords == null) return;
                
                const string fmt = "F7";
                string ns = decalCoords.NorthSouth.ToString(fmt, CultureInfo.InvariantCulture);
                string ew = decalCoords.EastWest.ToString(fmt, CultureInfo.InvariantCulture);

                // Get Z coordinate using RawCoordinates() for accurate world Z position
                string zCoord = "0";
                try
                {
                    var rawCoords = mob.RawCoordinates();
                    if (rawCoords != null)
                    {
                        zCoord = rawCoords.Z.ToString("F2", CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        // Fallback to player Z approximation if RawCoordinates fails
                        var playerCoords = Coordinates.Me;
                        if (Math.Abs(playerCoords.Z) > 0.1)
                        {
                            zCoord = playerCoords.Z.ToString("F2", CultureInfo.InvariantCulture);
                        }
                    }
                }
                catch
                {
                    // Fallback to player Z approximation on error
                    try
                    {
                        var playerCoords = Coordinates.Me;
                        if (Math.Abs(playerCoords.Z) > 0.1)
                        {
                            zCoord = playerCoords.Z.ToString("F2", CultureInfo.InvariantCulture);
                        }
                    }
                    catch
                    {
                        zCoord = "0";
                    }
                }

                await WebSocket.SendSpawnAsync(ns, ew, zCoord, mob.Name);
            }
            catch (Exception ex)
            {
                PluginCore.WriteToChat($"[WS] Spawn send failed: {ex}");
            }
        }

        private async void OnPortalDetected(object sender, CreateObjectEventArgs e)
        {
            var portal = e.New;
            if (portal.ObjectClass != ObjectClass.Portal) return;

            try
            {
                // Get portal coordinates from DECAL
                var decalCoords = portal.Coordinates();
                if (decalCoords == null) return;

                const string fmt = "F7";
                string ns = decalCoords.NorthSouth.ToString(fmt, CultureInfo.InvariantCulture);
                string ew = decalCoords.EastWest.ToString(fmt, CultureInfo.InvariantCulture);

                // Get Z coordinate using RawCoordinates() for accurate world Z position
                string zCoord = "0";
                try
                {
                    var rawCoords = portal.RawCoordinates();
                    if (rawCoords != null)
                    {
                        zCoord = rawCoords.Z.ToString("F2", CultureInfo.InvariantCulture);
                    }
                    else
                    {
                        // Fallback to player Z approximation if RawCoordinates fails
                        var playerCoords = Coordinates.Me;
                        if (Math.Abs(playerCoords.Z) > 0.1)
                        {
                            zCoord = playerCoords.Z.ToString("F2", CultureInfo.InvariantCulture);
                        }
                    }
                }
                catch
                {
                    // Fallback to player Z approximation on error
                    try
                    {
                        var playerCoords = Coordinates.Me;
                        if (Math.Abs(playerCoords.Z) > 0.1)
                        {
                            zCoord = playerCoords.Z.ToString("F2", CultureInfo.InvariantCulture);
                        }
                    }
                    catch
                    {
                        zCoord = "0";
                    }
                }

                await WebSocket.SendPortalAsync(ns, ew, zCoord, portal.Name);
            }
            catch (Exception ex)
            {
                PluginCore.WriteToChat($"[PORTAL ERROR] {ex.Message}");
                PluginCore.WriteToChat($"[WS] Portal send failed: {ex}");
            }
        }

 
        private void OnDespawn(object sender, ReleaseObjectEventArgs e)
        {
            var mob = e.Released;
            if (mob.ObjectClass != ObjectClass.Monster) return;

        
            // var c = mob.Coordinates();
            // PluginCore.WriteToChat(
            //     $"[Despawn] {mob.Name} @ (NS={c.NorthSouth:F1}, EW={c.EastWest:F1})");
        }

        private async void AllChatText(object sender, ChatTextInterceptEventArgs e)
        {
            try
            {
                string cleaned = NormalizeChatLine(e.Text);
                
                // Send to WebSocket
                await WebSocket.SendChatTextAsync(e.Color, cleaned);
                
                // Note: Plugin message analysis is now handled by Harmony patches
            }
            catch (Exception ex)
            {
                PluginCore.WriteToChat($"[WS] Chat send failed: {ex}");
            }
        }

        private static string NormalizeChatLine(string raw)
        {
            if (string.IsNullOrEmpty(raw))
                return raw;

            // 1) Remove all <…> tags
            var noTags = Regex.Replace(raw, "<[^>]+>", "");

            // 2) Trim trailing newline or carriage-return
            var trimmed = noTags.TrimEnd('\r', '\n');

            // 3) Collapse multiple spaces into one
            var collapsed = Regex.Replace(trimmed, @"[ ]{2,}", " ");

            return collapsed;
        }

        private void OnCharacterDeath(object sender, Decal.Adapter.Wrappers.DeathEventArgs e)
        {
            sessionDeaths++;
            totalDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
        }

        private void HandleServerCommand(CommandEnvelope env)
        {
            // This is called from WebSocket thread - queue for main thread execution
            lock (pendingCommands)
            {
                pendingCommands.Enqueue(env.Command);
            }
        }

        private void ProcessPendingCommands(object sender, EventArgs e)
        {
            // This runs on the main UI thread via Windows Forms timer
            string command = null;
            
            lock (pendingCommands)
            {
                if (pendingCommands.Count > 0)
                    command = pendingCommands.Dequeue();
            }
            
            if (command != null)
            {
                try
                {
                    // Execute ALL WebSocket commands on main thread - fast and reliable
                    DispatchChatToBoxWithPluginIntercept(command);
                }
                catch (Exception ex)
                {
                    WriteToChat($"[WS] Command execution error: {ex.Message}");
                }
            }
        }

        private void OnChatText(object sender, ChatTextInterceptEventArgs e)
        {
            try
            {

                if (IsKilledByMeMessage(e.Text))
                {
                    totalKills++;
                    lastKillTime = DateTime.Now;
                    CalculateKillsPerInterval();
                    ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);
                }

                if (IsRareDiscoveryMessage(e.Text, out string rareText))
                {
                    rareCount++;
                    ViewManager.UpdateRareCount(rareCount);

                    if (RareMetaEnabled)
                    {
                        Decal_DispatchOnChatCommand("/vt setmetastate loot_rare");
                    }
                    
                    DelayedCommandManager.AddDelayedCommand($"/a {rareText}", 3000);
                    // Fire and forget: we don't await, since sending is not critical and we don't want to block.
                    _ = WebSocket.SendRareAsync(rareText);
                }

                if (e.Color == 18 && e.Text.EndsWith("!report\""))
                {
                    TimeSpan elapsed = DateTime.Now - statsStartTime;
                    string reportMessage = $"Total Kills: {totalKills}, Kills per Hour: {killsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {rareCount}";
                    WriteToChat($"[Mosswart Massacre] Reporting to allegiance: {reportMessage}");
                    MyHost.Actions.InvokeChatParser($"/a {reportMessage}");
                }
                if (RemoteCommandsEnabled && e.Color == 18)
                {
                    string characterName = Regex.Escape(CoreManager.Current.CharacterFilter.Name);
                    string pattern = $@"^\[Allegiance\].*Dunking Rares.*say[s]?, \""!do {characterName} (?<command>.+)\""$";
                    string tag = Regex.Escape(PluginCore.CharTag);
                    string patterntag = $@"^\[Allegiance\].*Dunking Rares.*say[s]?, \""!dot {tag} (?<command>.+)\""$";


                    var match = Regex.Match(e.Text, pattern);
                    var matchtag = Regex.Match(e.Text, patterntag);

                    if (match.Success)
                    {
                        string command = match.Groups["command"].Value;
                        DispatchChatToBoxWithPluginIntercept(command);
                        DelayedCommandManager.AddDelayedCommand($"/a [Remote] Executing: {command}", 2000);
                    }
                    else if (matchtag.Success)
                    {
                        string command = matchtag.Groups["command"].Value;
                        DispatchChatToBoxWithPluginIntercept(command);
                        DelayedCommandManager.AddDelayedCommand($"/a [Remote] Executing: {command}", 2000);
                    }

                }








            }
            catch (Exception ex)
            {
                WriteToChat("Error processing chat message: " + ex.Message);
            }
        }
        private void OnChatCommand(object sender, ChatParserInterceptEventArgs e)
        {
            try
            {
                if (e.Text.StartsWith("/mm", StringComparison.OrdinalIgnoreCase))
                {
                    e.Eat = true; // Prevent the message from showing in chat
                    HandleMmCommand(e.Text);
                }
            }
            catch (Exception ex)
            {
                PluginCore.WriteToChat($"[Error] Failed to process /mm command: {ex.Message}");
            }
        }

        private void UpdateStats(object sender, ElapsedEventArgs e)
        {
            try
            {
                // Update the elapsed time
                TimeSpan elapsed = DateTime.Now - statsStartTime;
                ViewManager.UpdateElapsedTime(elapsed);

                // Recalculate kill rates
                CalculateKillsPerInterval();
                ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);

            }
            catch (Exception ex)
            {
                WriteToChat("Error updating stats: " + ex.Message);
            }
        }

        private static void SendVitalsUpdate(object sender, ElapsedEventArgs e)
        {
            try
            {
                // Only send if WebSocket is enabled
                if (!WebSocketEnabled)
                    return;

                // Collect vitals data
                int currentHealth = CoreManager.Current.Actions.Vital[VitalType.CurrentHealth];
                int currentStamina = CoreManager.Current.Actions.Vital[VitalType.CurrentStamina];
                int currentMana = CoreManager.Current.Actions.Vital[VitalType.CurrentMana];
                
                int maxHealth = CoreManager.Current.Actions.Vital[VitalType.MaximumHealth];
                int maxStamina = CoreManager.Current.Actions.Vital[VitalType.MaximumStamina];
                int maxMana = CoreManager.Current.Actions.Vital[VitalType.MaximumMana];
                
                int vitae = CoreManager.Current.CharacterFilter.Vitae;

                // Create vitals data structure
                var vitalsData = new
                {
                    type = "vitals",
                    timestamp = DateTime.UtcNow.ToString("o"),
                    character_name = CoreManager.Current.CharacterFilter.Name,
                    health_current = currentHealth,
                    health_max = maxHealth,
                    health_percentage = maxHealth > 0 ? Math.Round((double)currentHealth / maxHealth * 100, 1) : 0,
                    stamina_current = currentStamina,
                    stamina_max = maxStamina,
                    stamina_percentage = maxStamina > 0 ? Math.Round((double)currentStamina / maxStamina * 100, 1) : 0,
                    mana_current = currentMana,
                    mana_max = maxMana,
                    mana_percentage = maxMana > 0 ? Math.Round((double)currentMana / maxMana * 100, 1) : 0,
                    vitae = vitae
                };

                // Send via WebSocket
                _ = WebSocket.SendVitalsAsync(vitalsData);
            }
            catch (Exception ex)
            {
                WriteToChat($"Error sending vitals: {ex.Message}");
            }
        }

        private static void OnCharacterStatsUpdate(object sender, ElapsedEventArgs e)
        {
            try
            {
                CharacterStats.CollectAndSend();
            }
            catch (Exception ex)
            {
                WriteToChat($"[CharStats] Timer error: {ex.Message}");
            }
        }

        private void EchoFilter_ServerDispatch(object sender, NetworkMessageEventArgs e)
        {
            try
            {
                if (e.Message.Type == 0xF7B0) // Game Event
                {
                    int eventId = (int)e.Message["event"];

                    if (eventId == 0x0020) // Allegiance info
                    {
                        CharacterStats.ProcessAllegianceInfoMessage(e);
                    }
                    else if (eventId == 0x0013) // Login Character (properties)
                    {
                        CharacterStats.ProcessCharacterPropertyData(e);
                    }
                    else if (eventId == 0x0029) // Titles list
                    {
                        CharacterStats.ProcessTitlesMessage(e);
                    }
                    else if (eventId == 0x002b) // Set title
                    {
                        CharacterStats.ProcessSetTitleMessage(e);
                    }
                }
                else if (e.Message.Type == 0x02CF) // PrivateUpdatePropertyInt64 (runtime luminance changes)
                {
                    CharacterStats.ProcessPropertyInt64Update(e);
                }
            }
            catch (Exception ex)
            {
                WriteToChat($"[CharStats] ServerDispatch error: {ex.Message}");
            }
        }

        private void CalculateKillsPerInterval()
        {
            double minutesElapsed = (DateTime.Now - statsStartTime).TotalMinutes;

            if (minutesElapsed > 0)
            {
                killsPer5Min = (totalKills / minutesElapsed) * 5;
                killsPerHour = (totalKills / minutesElapsed) * 60;
            }
        }

        private bool IsKilledByMeMessage(string text)
        {
            string[] killPatterns = new string[]
            {
                @"^You flatten (?<targetname>.+)'s body with the force of your assault!$",
                @"^You bring (?<targetname>.+) to a fiery end!$",
                @"^You beat (?<targetname>.+) to a lifeless pulp!$",
                @"^You smite (?<targetname>.+) mightily!$",
                @"^You obliterate (?<targetname>.+)!$",
                @"^You run (?<targetname>.+) through!$",
                @"^You reduce (?<targetname>.+) to a sizzling, oozing mass!$",
                @"^You knock (?<targetname>.+) into next Morningthaw!$",
                @"^You split (?<targetname>.+) apart!$",
                @"^You cleave (?<targetname>.+) in twain!$",
                @"^You slay (?<targetname>.+) viciously enough to impart death several times over!$",
                @"^You reduce (?<targetname>.+) to a drained, twisted corpse!$",
                @"^Your killing blow nearly turns (?<targetname>.+) inside-out!$",
                @"^Your attack stops (?<targetname>.+) cold!$",
                @"^Your lightning coruscates over (?<targetname>.+)'s mortal remains!$",
                @"^Your assault sends (?<targetname>.+) to an icy death!$",
                @"^You killed (?<targetname>.+)!$",
                @"^The thunder of crushing (?<targetname>.+) is followed by the deafening silence of death!$",
                @"^The deadly force of your attack is so strong that (?<targetname>.+)'s ancestors feel it!$",
                @"^(?<targetname>.+)'s seared corpse smolders before you!$",
                @"^(?<targetname>.+) is reduced to cinders!$",
                @"^(?<targetname>.+) is shattered by your assault!$",
                @"^(?<targetname>.+) catches your attack, with dire consequences!$",
                @"^(?<targetname>.+) is utterly destroyed by your attack!$",
                @"^(?<targetname>.+) suffers a frozen fate!$",
                @"^(?<targetname>.+)'s perforated corpse falls before you!$",
                @"^(?<targetname>.+) is fatally punctured!$",
                @"^(?<targetname>.+)'s death is preceded by a sharp, stabbing pain!$",
                @"^(?<targetname>.+) is torn to ribbons by your assault!$",
                @"^(?<targetname>.+) is liquified by your attack!$",
                @"^(?<targetname>.+)'s last strength dissolves before you!$",
                @"^Electricity tears (?<targetname>.+) apart!$",
                @"^Blistered by lightning, (?<targetname>.+) falls!$",
                @"^(?<targetname>.+)'s last strength withers before you!$",
                @"^(?<targetname>.+) is dessicated by your attack!$",
                @"^(?<targetname>.+) is incinerated by your assault!$"
            };

            foreach (string pattern in killPatterns)
            {
                if (Regex.IsMatch(text, pattern))
                    return true;
            }

            return false;
        }
        private bool IsRareDiscoveryMessage(string text, out string rareTextOnly)
        {
            rareTextOnly = null;

            // Match pattern: "<name> has discovered the <something>!"
            string pattern = @"^(?<name>['A-Za-z ]+)\shas discovered the (?<item>.*?)!$";
            Match match = Regex.Match(text, pattern);

            if (match.Success && match.Groups["name"].Value == CoreManager.Current.CharacterFilter.Name)
            {
                rareTextOnly = match.Groups["item"].Value; // just "Ancient Pickle"
                return true;
            }

            return false;
        }
        public static void WriteToChat(string message)
        {
            try
            {
                // For hot reload scenarios where MyHost might be null, use CoreManager directly
                if (MyHost != null)
                {
                    MyHost.Actions.AddChatText("[Mosswart Massacre] " + message, 0, 1);
                }
                else
                {
                    // Hot reload fallback1 - use CoreManager directly like the original template
                    CoreManager.Current.Actions.AddChatText("[Mosswart Massacre] " + message, 1);
                }
            }
            catch (Exception ex)
            {
                // Last resort fallback - try CoreManager even if MyHost was supposed to work
                try
                {
                    CoreManager.Current.Actions.AddChatText($"[Mosswart Massacre] {message} (WriteToChat error: {ex.Message})", 1);
                }
                catch
                {
                    // Give up - can't write to chat at all
                }
            }
        }
        public static void RestartStats()
        {
            totalKills = 0;
            rareCount = 0;
            sessionDeaths = 0;  // Reset session deaths only
            statsStartTime = DateTime.Now;
            killsPer5Min = 0;
            killsPerHour = 0;

            WriteToChat($"Stats have been reset. Session deaths: {sessionDeaths}, Total deaths: {totalDeaths}");
            ViewManager.UpdateKillStats(totalKills, killsPer5Min, killsPerHour);
            ViewManager.UpdateRareCount(rareCount);
        }
        public static void ToggleRareMeta()
        {
            PluginSettings.Instance.RareMetaEnabled = !PluginSettings.Instance.RareMetaEnabled;
            RareMetaEnabled = PluginSettings.Instance.RareMetaEnabled;
            ViewManager.SetRareMetaToggleState(RareMetaEnabled);
        }

        [DllImport("Decal.dll")]
        private static extern int DispatchOnChatCommand(ref IntPtr str, [MarshalAs(UnmanagedType.U4)] int target);

        public static bool Decal_DispatchOnChatCommand(string cmd)
        {
            IntPtr bstr = Marshal.StringToBSTR(cmd);

            try
            {
                bool eaten = (DispatchOnChatCommand(ref bstr, 1) & 0x1) > 0;
                return eaten;
            }
            finally
            {
                Marshal.FreeBSTR(bstr);
            }
        }
        public static void DispatchChatToBoxWithPluginIntercept(string cmd)
        {
            if (!Decal_DispatchOnChatCommand(cmd))
                CoreManager.Current.Actions.InvokeChatParser(cmd);
        }
        private void HandleMmCommand(string text)
        {
            // Remove the /mm prefix and trim extra whitespace test
            string[] args = text.Substring(3).Trim().Split(' ');

            if (args.Length == 0 || string.IsNullOrEmpty(args[0]))
            {
                WriteToChat("Usage: /mm <command>. Try /mm help");
                return;
            }

            string subCommand = args[0].ToLower();

            switch (subCommand)
            {
                case "telemetry":
                    if (args.Length > 1)
                    {
                        if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
                        {
                            TelemetryEnabled = true;
                            Telemetry.Start();
                            PluginSettings.Instance.TelemetryEnabled = true;
                            WriteToChat("Telemetry streaming ENABLED.");
                        }
                        else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
                        {
                            TelemetryEnabled = false;
                            Telemetry.Stop();
                            PluginSettings.Instance.TelemetryEnabled = false;
                            WriteToChat("Telemetry streaming DISABLED.");
                        }
                        else
                        {
                            WriteToChat("Usage: /mm telemetry <enable|disable>");
                        }
                    }
                    else
                    {
                        WriteToChat("Usage: /mm telemetry <enable|disable>");
                    }
                    break;
                case "ws":
                    if (args.Length > 1)
                    {
                        if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
                        {
                            WebSocketEnabled = true;
                            WebSocket.Start();
                            PluginSettings.Instance.WebSocketEnabled = true;
                            WriteToChat("WS streaming ENABLED.");
                        }
                        else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
                        {
                            WebSocketEnabled = false;
                            WebSocket.Stop();
                            PluginSettings.Instance.WebSocketEnabled = false;
                            WriteToChat("WS streaming DISABLED.");
                        }
                        else
                        {
                            WriteToChat("Usage: /mm ws <enable|disable>");
                        }
                    }

                    else
                    {
                        WriteToChat("Usage: /mm ws <enable|disable>");
                    }
                    break;
                case "help":
                    WriteToChat("Mosswart Massacre Commands:");
                    WriteToChat("/mm report        - Show current stats");
                    WriteToChat("/mm loc           - Show current location");
                    WriteToChat("/mm telemetry     - Telemetry streaming enable|disable");
                    WriteToChat("/mm ws            - Websocket streaming enable|disable");
                    WriteToChat("/mm reset         - Reset all counters");
                    WriteToChat("/mm meta          - Toggle rare meta state!!");
                    WriteToChat("/mm http          - Local http-command server enable|disable");
                    WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable");
                    WriteToChat("/mm getmetastate  - Gets the current metastate");
                    WriteToChat("/mm setchest <name> - Set chest name for looter");
                    WriteToChat("/mm setkey <name>   - Set key name for looter");
                    WriteToChat("/mm lootchest      - Start chest looting");
                    WriteToChat("/mm stoploot       - Stop chest looting");
                    WriteToChat("/mm nextwp        - Advance VTank to next waypoint");
                    WriteToChat("/mm decalstatus   - Check Harmony patch status (UtilityBelt version)");
                    WriteToChat("/mm decaldebug    - Enable/disable plugin message debug output + WebSocket streaming");
                    WriteToChat("/mm harmonyraw    - Show raw intercepted messages (debug output)");
                    WriteToChat("/mm testprismatic - Test Prismatic Taper detection and icon lookup");
                    WriteToChat("/mm deathstats    - Show current death tracking statistics");
                    WriteToChat("/mm testtaper     - Test cached Prismatic Taper tracking");
                    WriteToChat("/mm debugtaper    - Show detailed taper tracking debug info");
                    WriteToChat("/mm gui           - Manually initialize/reinitialize GUI!!!");
                    WriteToChat("/mm checkforupdate - Check for plugin updates");
                    WriteToChat("/mm update        - Download and install update (if available)");
                    WriteToChat("/mm debugupdate   - Debug update UI controls");
                    WriteToChat("/mm sendinventory - Force inventory upload with ID requests");
                    WriteToChat("/mm refreshquests - Force quest data refresh for Flag Tracker");
                    WriteToChat("/mm queststatus   - Show quest streaming status and diagnostics");
                    WriteToChat("/mm verbose       - Toggle verbose debug logging");
                    break;
                case "report":
                    TimeSpan elapsed = DateTime.Now - statsStartTime;
                    string reportMessage = $"Total Kills: {totalKills}, Kills per Hour: {killsPerHour:F2}, Elapsed Time: {elapsed:dd\\.hh\\:mm\\:ss}, Rares Found: {rareCount}, Session Deaths: {sessionDeaths}, Total Deaths: {totalDeaths}";
                    WriteToChat(reportMessage);
                    break;
                case "getmetastate":
                    string metaState = VtankControl.VtGetMetaState();
                    WriteToChat(metaState);
                    break;

                case "loc":
                    Coordinates here = Coordinates.Me;
                    var pos = Utils.GetPlayerPosition();
                    WriteToChat($"Location: {here}  (X={pos.X:F1}, Y={pos.Y:F1}, Z={pos.Z:F1})");
                    break;
                case "reset":
                    RestartStats();
                    break;
                case "meta":
                    RareMetaEnabled = !RareMetaEnabled;
                    WriteToChat($"Rare meta state is now {(RareMetaEnabled ? "ON" : "OFF")}");
                    ViewManager.SetRareMetaToggleState(RareMetaEnabled);  // <-- sync the UI
                    break;
                case "http":
                    if (args.Length > 1)
                    {
                        if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
                        {
                            PluginSettings.Instance.HttpServerEnabled = true;
                            HttpServerEnabled = true;
                            HttpCommandServer.Start();
                        }
                        else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
                        {
                            PluginSettings.Instance.HttpServerEnabled = false;
                            HttpServerEnabled = false;
                            HttpCommandServer.Stop();
                        }
                        else
                        {
                            WriteToChat("Usage: /mm http <enable|disable>");
                        }
                    }
                    else
                    {
                        WriteToChat("Usage: /mm http <enable|disable>");
                    }
                    break;

                case "remotecommands":
                    if (args.Length > 1)
                    {
                        if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
                        {
                            PluginSettings.Instance.RemoteCommandsEnabled = true;
                            RemoteCommandsEnabled = true;
                            WriteToChat("Remote command listening is now ENABLED.");
                        }
                        else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
                        {
                            PluginSettings.Instance.RemoteCommandsEnabled = false;
                            RemoteCommandsEnabled = false;
                            WriteToChat("Remote command listening is now DISABLED.");
                        }
                        else
                        {
                            WriteToChat("Invalid remotecommands argument. Use 'enable' or 'disable'.");
                        }
                    }
                    else
                    {
                        WriteToChat("Usage: /mm remotecommands <enable|disable>");
                    }
                    break;

                case "nextwp":
                    double result = VtankControl.VtAdvanceWaypoint();
                    if (result == 1)
                    {
                        WriteToChat("Advanced VTank to next waypoint.");
                    }
                    else
                    {
                        WriteToChat("Failed to advance VTank waypoint. Is VTank running?");
                    }
                    break;

                case "setchest":
                    if (args.Length < 2)
                    {
                        WriteToChat("[ChestLooter] Usage: /mm setchest <chest name>");
                        return;
                    }
                    string chestName = string.Join(" ", args.Skip(1));
                    if (chestLooter != null)
                    {
                        chestLooter.SetChestName(chestName);
                        if (PluginSettings.Instance?.ChestLooterSettings != null)
                        {
                            PluginSettings.Instance.ChestLooterSettings.ChestName = chestName;
                            PluginSettings.Save();
                        }
                        Views.VVSTabbedMainView.RefreshChestLooterUI();
                    }
                    break;

                case "setkey":
                    if (args.Length < 2)
                    {
                        WriteToChat("[ChestLooter] Usage: /mm setkey <key name>");
                        return;
                    }
                    string keyName = string.Join(" ", args.Skip(1));
                    if (chestLooter != null)
                    {
                        chestLooter.SetKeyName(keyName);
                        if (PluginSettings.Instance?.ChestLooterSettings != null)
                        {
                            PluginSettings.Instance.ChestLooterSettings.KeyName = keyName;
                            PluginSettings.Save();
                        }
                        Views.VVSTabbedMainView.RefreshChestLooterUI();
                    }
                    break;

                case "lootchest":
                    if (chestLooter != null)
                    {
                        if (!chestLooter.StartByName())
                        {
                            WriteToChat("[ChestLooter] Failed to start. Check chest/key names are set.");
                        }
                    }
                    else
                    {
                        WriteToChat("[ChestLooter] Chest looter not initialized");
                    }
                    break;

                case "stoploot":
                    if (chestLooter != null)
                    {
                        chestLooter.Stop();
                    }
                    else
                    {
                        WriteToChat("[ChestLooter] Chest looter not initialized");
                    }
                    break;

                case "vtanktest":
                    try
                    {
                        WriteToChat("Testing VTank interface...");
                        WriteToChat($"VTank Instance: {(vTank.Instance != null ? "Found" : "NULL")}");
                        WriteToChat($"VTank Type: {vTank.Instance?.GetType()?.Name ?? "NULL"}");
                        WriteToChat($"NavCurrent: {vTank.Instance?.NavCurrent ?? -1}");
                        WriteToChat($"NavNumPoints: {vTank.Instance?.NavNumPoints ?? -1}");
                        WriteToChat($"NavType: {vTank.Instance?.NavType}");
                        WriteToChat($"MacroEnabled: {vTank.Instance?.MacroEnabled}");
                    }
                    catch (Exception ex)
                    {
                        WriteToChat($"VTank test error: {ex.Message}");
                    }
                    break;

                case "decalstatus":
                    try
                    {
                        WriteToChat("=== Harmony Patch Status (UtilityBelt Pattern) ===");
                        WriteToChat($"Patches Active: {DecalHarmonyClean.IsActive()}");
                        WriteToChat($"Messages Intercepted: {DecalHarmonyClean.GetMessagesIntercepted()}");
                        WriteToChat($"WebSocket Streaming: {(AggressiveChatStreamingEnabled && WebSocketEnabled ? "ACTIVE" : "INACTIVE")}");
                        
                        // Test Harmony availability
                        WriteToChat("=== Harmony Version Status ===");
                        try
                        {
                            var harmonyTest = Harmony.HarmonyInstance.Create("test.version.check");
                            WriteToChat($"[OK] Harmony Available (ID: {harmonyTest.Id})");
                            
                            // Check Harmony assembly version
                            var harmonyAssembly = typeof(Harmony.HarmonyInstance).Assembly;
                            WriteToChat($"[OK] Harmony Version: {harmonyAssembly.GetName().Version}");
                            WriteToChat($"[OK] Harmony Location: {harmonyAssembly.Location}");
                        }
                        catch (Exception harmonyEx)
                        {
                            WriteToChat($"[FAIL] Harmony Test Failed: {harmonyEx.Message}");
                        }
                    }
                    catch (Exception ex)
                    {
                        WriteToChat($"Status check error: {ex.Message}");
                    }
                    break;

                case "decaldebug":
                    if (args.Length > 1)
                    {
                        if (args[1].Equals("enable", StringComparison.OrdinalIgnoreCase))
                        {
                            AggressiveChatStreamingEnabled = true;
                            WriteToChat("[OK] DECAL debug streaming ENABLED - will show captured messages + stream via WebSocket");
                        }
                        else if (args[1].Equals("disable", StringComparison.OrdinalIgnoreCase))
                        {
                            AggressiveChatStreamingEnabled = false;
                            WriteToChat("[FAIL] DECAL debug streaming DISABLED - WebSocket streaming also disabled");
                        }
                        else
                        {
                            WriteToChat("Usage: /mm decaldebug <enable|disable>");
                        }
                    }
                    else
                    {
                        WriteToChat("Usage: /mm decaldebug <enable|disable>");
                    }
                    break;


                case "harmonyraw":
                    // Debug functionality removed
                    break;

                case "initgui":
                case "gui":
                    try
                    {
                        WriteToChat("Attempting to manually initialize GUI...");
                        ViewManager.ViewDestroy(); // Clean up any existing view
                        ViewManager.ViewInit();     // Reinitialize
                        WriteToChat("GUI initialization attempt completed.");
                    }
                    catch (Exception ex)
                    {
                        WriteToChat($"GUI initialization error: {ex.Message}");
                    }
                    break;

                case "testprismatic":
                    try
                    {
                        WriteToChat("=== FULL INVENTORY DUMP ===");
                        var worldFilter = CoreManager.Current.WorldFilter;
                        var playerInv = CoreManager.Current.CharacterFilter.Id;
                        
                        WriteToChat("Listing ALL items in your main inventory:");
                        int itemNum = 1;
                        
                        foreach (WorldObject item in worldFilter.GetByContainer(playerInv))
                        {
                            if (!string.IsNullOrEmpty(item.Name))
                            {
                                int stackCount = item.Values(LongValueKey.StackCount, 0);
                                WriteToChat($"{itemNum:D2}: '{item.Name}' (count: {stackCount}, icon: 0x{item.Icon:X}, class: {item.ObjectClass})");
                                itemNum++;
                                
                                // Highlight anything that might be a taper
                                string nameLower = item.Name.ToLower();
                                if (nameLower.Contains("taper") || nameLower.Contains("prismatic") || 
                                    nameLower.Contains("prism") || nameLower.Contains("component"))
                                {
                                    WriteToChat($"    *** POSSIBLE MATCH: '{item.Name}' ***");
                                }
                            }
                        }
                        
                        WriteToChat($"=== Total items listed: {itemNum - 1} ===");
                        
                        // Now test our utility functions on the found Prismatic Taper
                        WriteToChat("=== Testing Utility Functions on Prismatic Taper ===");
                        var foundItem = Utils.FindItemByName("Prismatic Taper");
                        if (foundItem != null)
                        {
                            WriteToChat($"SUCCESS! Found: '{foundItem.Name}'");
                            WriteToChat($"Utils.GetItemStackSize: {Utils.GetItemStackSize("Prismatic Taper")}");
                            WriteToChat($"Utils.GetItemIcon: 0x{Utils.GetItemIcon("Prismatic Taper"):X}");
                            WriteToChat($"Utils.GetItemDisplayIcon: 0x{Utils.GetItemDisplayIcon("Prismatic Taper"):X}");
                            WriteToChat("=== TELEMETRY WILL NOW WORK! ===");
                        }
                        else
                        {
                            WriteToChat("ERROR: Still can't find Prismatic Taper with utility functions!");
                        }
                    }
                    catch (Exception ex)
                    {
                        WriteToChat($"Search error: {ex.Message}");
                    }
                    break;

                case "deathstats":
                    try
                    {
                        WriteToChat("=== Death Tracking Statistics ===");
                        WriteToChat($"Session Deaths: {sessionDeaths}");
                        WriteToChat($"Total Deaths: {totalDeaths}");
                        
                        // Get current character death count to verify sync
                        int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
                        WriteToChat($"Character Property NumDeaths: {currentCharDeaths}");
                        
                        if (currentCharDeaths != totalDeaths)
                        {
                            WriteToChat($"[WARNING] Death count sync issue detected!");
                            WriteToChat($"Updating totalDeaths from {totalDeaths} to {currentCharDeaths}");
                            totalDeaths = currentCharDeaths;
                        }
                        
                        WriteToChat("Death tracking is active and will increment on character death.");
                    }
                    catch (Exception ex)
                    {
                        WriteToChat($"Death stats error: {ex.Message}");
                    }
                    break;

                case "testdeath":
                    try
                    {
                        WriteToChat("=== Manual Death Test ===");
                        WriteToChat($"Current sessionDeaths variable: {sessionDeaths}");
                        WriteToChat($"Current totalDeaths variable: {totalDeaths}");
                        
                        // Read directly from character property
                        int currentCharDeaths = CoreManager.Current.CharacterFilter.GetCharProperty((int)IntValueKey.NumDeaths);
                        WriteToChat($"Character Property NumDeaths (43): {currentCharDeaths}");
                        
                        // Manually increment session deaths for testing
                        sessionDeaths++;
                        WriteToChat($"Manually incremented sessionDeaths to: {sessionDeaths}");
                        WriteToChat("Note: This doesn't simulate a real death, just tests the tracking variables.");
                        
                        // Check if death event is properly subscribed
                        WriteToChat($"Death event subscription check:");
                        var deathEvent = typeof(Decal.Adapter.Wrappers.CharacterFilter).GetEvent("Death");
                        WriteToChat($"Death event exists: {deathEvent != null}");
                    }
                    catch (Exception ex)
                    {
                        WriteToChat($"Test death error: {ex.Message}");
                    }
                    break;

                case "testtaper":
                    try
                    {
                        WriteToChat("=== Cached Taper Tracking Test ===");
                        WriteToChat($"Cached Count: {cachedPrismaticCount}");
                        WriteToChat($"Last Count: {lastPrismaticCount}");
                        
                        // Compare with Utils function
                        int utilsCount = Utils.GetItemStackSize("Prismatic Taper");
                        WriteToChat($"Utils Count: {utilsCount}");
                        
                        if (cachedPrismaticCount == utilsCount)
                        {
                            WriteToChat("[OK] Cached count matches Utils count");
                        }
                        else
                        {
                            WriteToChat($"[WARNING] Count mismatch! Cached: {cachedPrismaticCount}, Utils: {utilsCount}");
                            WriteToChat("Refreshing cached count...");
                            InitializePrismaticTaperCount();
                        }
                        
                        WriteToChat("=== Container Analysis ===");
                        int mainPackCount = 0;
                        int sidePackCount = 0;
                        int playerId = CoreManager.Current.CharacterFilter.Id;
                        
                        foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
                        {
                            if (wo.Name.Equals("Prismatic Taper", StringComparison.OrdinalIgnoreCase))
                            {
                                int stackCount = wo.Values(LongValueKey.StackCount, 1);
                                if (wo.Container == playerId)
                                {
                                    mainPackCount += stackCount;
                                }
                                else
                                {
                                    sidePackCount += stackCount;
                                }
                            }
                        }
                        
                        WriteToChat($"Main Pack Tapers: {mainPackCount}");
                        WriteToChat($"Side Pack Tapers: {sidePackCount}");
                        WriteToChat($"Total: {mainPackCount + sidePackCount}");
                        
                        WriteToChat("=== Event System Status ===");
                        WriteToChat($"Tracking {trackedTaperContainers.Count} taper stacks for delta detection");
                        WriteToChat($"Known stack sizes: {lastKnownStackSizes.Count} items");
                        WriteToChat("Pure delta tracking - NO expensive inventory scans during events!");
                        WriteToChat("Now tracks: consumption, drops, trades, container moves");
                        WriteToChat("Try moving tapers between containers and casting spells!");
                    }
                    catch (Exception ex)
                    {
                        WriteToChat($"Taper test error: {ex.Message}");
                    }
                    break;

                case "debugtaper":
                    // Debug functionality removed
                    break;

                case "finditem":
                    if (args.Length > 1)
                    {
                        string itemName = string.Join(" ", args, 1, args.Length - 1).Trim('"');
                        WriteToChat($"=== Searching for: '{itemName}' ===");
                        
                        var foundItem = Utils.FindItemByName(itemName);
                        if (foundItem != null)
                        {
                            WriteToChat($"FOUND: '{foundItem.Name}'");
                            WriteToChat($"Count: {foundItem.Values(LongValueKey.StackCount, 0)}");
                            WriteToChat($"Icon: 0x{foundItem.Icon:X}");
                            WriteToChat($"Display Icon: 0x{(foundItem.Icon + 0x6000000):X}");
                            WriteToChat($"Object Class: {foundItem.ObjectClass}");
                        }
                        else
                        {
                            WriteToChat($"NOT FOUND: '{itemName}'");
                            WriteToChat("Make sure the name is exactly as it appears in-game.");
                        }
                    }
                    else
                    {
                        WriteToChat("Usage: /mm finditem \"Item Name\"");
                        WriteToChat("Example: /mm finditem \"Prismatic Taper\"");
                    }
                    break;

                case "checkforupdate":
                    // Run the update check asynchronously
                    Task.Run(async () =>
                    {
                        await UpdateManager.CheckForUpdateAsync();
                        // Update UI if available
                        try
                        {
                            ViewManager.RefreshUpdateStatus();
                        }
                        catch (Exception ex)
                        {
                            WriteToChat($"Error refreshing UI: {ex.Message}");
                        }
                    });
                    break;

                case "update":
                    // Run the update installation asynchronously
                    Task.Run(async () =>
                    {
                        await UpdateManager.DownloadAndInstallUpdateAsync();
                    });
                    break;

                case "debugupdate":
                    Views.VVSTabbedMainView.DebugUpdateControls();
                    break;

                case "sendinventory":
                    // Force inventory upload with ID requests
                    if (_inventoryLogger != null)
                    {
                        _inventoryLogger.ForceInventoryUpload();
                    }
                    else
                    {
                        WriteToChat("[INV] Inventory system not initialized");
                    }
                    break;

                case "refreshquests":
                    // Force quest data refresh (same as clicking refresh button)
                    try
                    {
                        WriteToChat("[QUEST] Refreshing quest data...");
                        Views.FlagTrackerView.RefreshQuestData();
                    }
                    catch (Exception ex)
                    {
                        WriteToChat($"[QUEST] Refresh failed: {ex.Message}");
                    }
                    break;

                case "queststatus":
                    // Show quest streaming status
                    try
                    {
                        WriteToChat("=== Quest Streaming Status ===");
                        WriteToChat($"Timer Active: {questStreamingTimer != null && questStreamingTimer.Enabled}");
                        WriteToChat($"WebSocket Enabled: {WebSocketEnabled}");
                        WriteToChat($"Quest Manager: {(questManager != null ? "Active" : "Not Active")}");
                        WriteToChat($"Quest Count: {questManager?.QuestList?.Count ?? 0}");
                        
                        if (questManager?.QuestList != null)
                        {
                            var priorityQuests = questManager.QuestList
                                .Where(q => IsHighPriorityQuest(q.Id))
                                .GroupBy(q => q.Id)
                                .Select(g => g.First())
                                .ToList();
                            WriteToChat($"Priority Quests Found: {priorityQuests.Count}");
                            foreach (var quest in priorityQuests)
                            {
                                string questName = questManager.GetFriendlyQuestName(quest.Id);
                                WriteToChat($"  - {questName} ({quest.Id})");
                            }
                        }
                        
                        WriteToChat($"Verbose Logging: {PluginSettings.Instance?.VerboseLogging ?? false}");
                        WriteToChat("Use '/mm verbose' to toggle debug logging");
                    }
                    catch (Exception ex)
                    {
                        WriteToChat($"[QUEST] Status check failed: {ex.Message}");
                    }
                    break;

                case "verbose":
                    // Toggle verbose logging
                    if (PluginSettings.Instance != null)
                    {
                        PluginSettings.Instance.VerboseLogging = !PluginSettings.Instance.VerboseLogging;
                        WriteToChat($"Verbose logging: {(PluginSettings.Instance.VerboseLogging ? "ENABLED" : "DISABLED")}");
                    }
                    else
                    {
                        WriteToChat("Settings not initialized");
                    }
                    break;

                default:
                    WriteToChat($"Unknown /mm command: {subCommand}. Try /mm help");
                    break;
            }
        }


    }
}

    
