// ============================================================================
// mb_clouseau - Plugin Monitor
// Uncovering clues with MusicBee Clouseau
// ============================================================================

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using MusicBeePlugin.Clouseau.Core;

namespace MusicBeePlugin.Clouseau.Introspection
{
    /// <summary>
    /// Monitors MusicBee plugin resource usage including memory footprint,
    /// thread counts, and panel/menu registrations.
    /// Provides historical data tracking and excessive resource detection.
    /// </summary>
    public class PluginMonitor : IDisposable
    {
        // ====================================================================
        // Constants and Thresholds
        // ====================================================================

        /// <summary>
        /// Memory threshold (in MB) above which a plugin is flagged as high memory usage.
        /// </summary>
        private const long HIGH_MEMORY_THRESHOLD_MB = 100;

        /// <summary>
        /// Thread count threshold above which a plugin is flagged for excessive threads.
        /// </summary>
        private const int HIGH_THREAD_COUNT_THRESHOLD = 10;

        /// <summary>
        /// Type count threshold above which a plugin is flagged for excessive type loading.
        /// </summary>
        private const int HIGH_TYPE_COUNT_THRESHOLD = 500;

        /// <summary>
        /// Maximum number of historical snapshots to retain.
        /// </summary>
        private const int MAX_HISTORY_SIZE = 100;

        /// <summary>
        /// Memory growth rate (bytes per snapshot) that triggers a leak warning.
        /// </summary>
        private const long MEMORY_LEAK_THRESHOLD_BYTES_PER_SAMPLE = 1024 * 1024; // 1MB

        // ====================================================================
        // Fields
        // ====================================================================

        private readonly PluginDiscovery _pluginDiscovery;
        private readonly ConcurrentDictionary<string, PluginStats> _pluginStats;
        private readonly ConcurrentQueue<PluginMonitorSnapshot> _history;
        private readonly object _monitorLock = new object();
        private Timer _monitorTimer;
        private bool _disposed;
        private int _snapshotCount;
        private DateTime _lastSnapshotTime;
        private int _baselineThreadCount;

        // Registration tracking - which plugins have registered panels, menus, events
        private readonly ConcurrentDictionary<string, PluginRegistrations> _registrations;

        // ====================================================================
        // Constructor
        // ====================================================================

        /// <summary>
        /// Creates a new PluginMonitor instance.
        /// </summary>
        /// <param name="pluginsPath">Path to the MusicBee Plugins folder.</param>
        public PluginMonitor(string pluginsPath)
        {
            if (string.IsNullOrEmpty(pluginsPath))
            {
                throw new ArgumentNullException(nameof(pluginsPath));
            }

            _pluginDiscovery = new PluginDiscovery(pluginsPath);
            _pluginStats = new ConcurrentDictionary<string, PluginStats>(StringComparer.OrdinalIgnoreCase);
            _history = new ConcurrentQueue<PluginMonitorSnapshot>();
            _registrations = new ConcurrentDictionary<string, PluginRegistrations>(StringComparer.OrdinalIgnoreCase);
            _lastSnapshotTime = DateTime.MinValue;

            // Capture baseline thread count
            try
            {
                _baselineThreadCount = Process.GetCurrentProcess().Threads.Count;
            }
            catch
            {
                _baselineThreadCount = 0;
            }

            LogManager.Core.Info($"PluginMonitor initialized for path: {pluginsPath}");
        }

        /// <summary>
        /// Creates a PluginMonitor using the MusicBee application path.
        /// </summary>
        /// <param name="musicBeeAppPath">Path returned by MB_GetApplicationPath().</param>
        /// <returns>A new PluginMonitor instance.</returns>
        public static PluginMonitor FromMusicBeePath(string musicBeeAppPath)
        {
            if (string.IsNullOrEmpty(musicBeeAppPath))
            {
                throw new ArgumentNullException(nameof(musicBeeAppPath));
            }

            var pluginsPath = Path.Combine(musicBeeAppPath, "Plugins");
            return new PluginMonitor(pluginsPath);
        }

        /// <summary>
        /// Creates a PluginMonitor from a list of already-discovered plugins.
        /// </summary>
        /// <param name="discoveredPlugins">List of discovered plugins to monitor.</param>
        /// <param name="pluginsPath">Path to the Plugins folder (for future discovery).</param>
        /// <returns>A new PluginMonitor instance.</returns>
        public static PluginMonitor FromDiscoveredPlugins(IEnumerable<DiscoveredPlugin> discoveredPlugins, string pluginsPath)
        {
            if (discoveredPlugins == null)
            {
                throw new ArgumentNullException(nameof(discoveredPlugins));
            }
            if (string.IsNullOrEmpty(pluginsPath))
            {
                throw new ArgumentNullException(nameof(pluginsPath));
            }

            var monitor = new PluginMonitor(pluginsPath);

            // Pre-populate stats from discovered plugins
            foreach (var plugin in discoveredPlugins.Where(p => p.IsValidPlugin))
            {
                var stats = monitor.CollectPluginStats(plugin);
                monitor._pluginStats.TryAdd(plugin.FileName, stats);
            }

            return monitor;
        }

        // ====================================================================
        // Public Properties
        // ====================================================================

        /// <summary>
        /// Gets the number of snapshots captured.
        /// </summary>
        public int SnapshotCount => _snapshotCount;

        /// <summary>
        /// Gets the time of the last snapshot.
        /// </summary>
        public DateTime LastSnapshotTime => _lastSnapshotTime;

        /// <summary>
        /// Gets whether automatic monitoring is running.
        /// </summary>
        public bool IsMonitoring => _monitorTimer != null;

        // ====================================================================
        // Public Methods - Monitoring Control
        // ====================================================================

        /// <summary>
        /// Starts automatic monitoring at the specified interval.
        /// </summary>
        /// <param name="intervalMs">Monitoring interval in milliseconds (minimum 1000ms).</param>
        public void StartMonitoring(int intervalMs = 30000)
        {
            if (_disposed) throw new ObjectDisposedException(nameof(PluginMonitor));
            if (intervalMs < 1000) intervalMs = 1000;

            lock (_monitorLock)
            {
                StopMonitoring();
                _monitorTimer = new Timer(MonitorCallback, null, 0, intervalMs);
                LogManager.Core.Info($"Plugin monitoring started with {intervalMs}ms interval");
            }
        }

        /// <summary>
        /// Stops automatic monitoring.
        /// </summary>
        public void StopMonitoring()
        {
            lock (_monitorLock)
            {
                if (_monitorTimer != null)
                {
                    _monitorTimer.Dispose();
                    _monitorTimer = null;
                    LogManager.Core.Info("Plugin monitoring stopped");
                }
            }
        }

        /// <summary>
        /// Captures a single snapshot of all plugin resource usage.
        /// </summary>
        /// <returns>The captured snapshot.</returns>
        public PluginMonitorSnapshot CaptureSnapshot()
        {
            if (_disposed) throw new ObjectDisposedException(nameof(PluginMonitor));

            var stopwatch = Stopwatch.StartNew();
            var snapshot = new PluginMonitorSnapshot
            {
                Timestamp = DateTime.Now,
                SnapshotId = Interlocked.Increment(ref _snapshotCount)
            };

            try
            {
                // Discover all plugins
                var discoveredPlugins = _pluginDiscovery.DiscoverPlugins();
                snapshot.TotalPluginsFound = discoveredPlugins.Count;
                snapshot.ValidPluginsFound = discoveredPlugins.Count(p => p.IsValidPlugin);

                // Collect stats for each plugin
                foreach (var plugin in discoveredPlugins.Where(p => p.IsValidPlugin))
                {
                    var stats = CollectPluginStats(plugin);
                    snapshot.PluginStats.Add(stats);

                    // Store/update in tracking dictionary
                    _pluginStats.AddOrUpdate(
                        plugin.FileName,
                        stats,
                        (key, existing) =>
                        {
                            // Update with new stats but preserve historical data
                            stats.PreviousMemoryBytes = existing.EstimatedMemoryBytes;
                            stats.PreviousTypeCount = existing.LoadedTypeCount;
                            stats.FirstSeenTime = existing.FirstSeenTime;
                            stats.SampleCount = existing.SampleCount + 1;
                            stats.TotalMemoryGrowth = existing.TotalMemoryGrowth +
                                (stats.EstimatedMemoryBytes - existing.EstimatedMemoryBytes);
                            return stats;
                        });
                }

                // Capture process-level thread info
                snapshot.TotalProcessThreads = GetCurrentThreadCount();
                snapshot.ThreadCountSinceBaseline = snapshot.TotalProcessThreads - _baselineThreadCount;

                // Detect excessive resource usage
                DetectExcessiveUsage(snapshot);

                stopwatch.Stop();
                snapshot.CaptureTimeMs = stopwatch.ElapsedMilliseconds;
                _lastSnapshotTime = snapshot.Timestamp;

                // Add to history
                _history.Enqueue(snapshot);
                while (_history.Count > MAX_HISTORY_SIZE)
                {
                    _history.TryDequeue(out _);
                }

                LogManager.Core.Debug($"Plugin snapshot captured: {snapshot.ValidPluginsFound} plugins, " +
                    $"{snapshot.PluginsWithWarnings} warnings, {snapshot.CaptureTimeMs}ms");
            }
            catch (Exception ex)
            {
                LogManager.Core.Error(ex, "Error capturing plugin snapshot");
                snapshot.CaptureError = ex.Message;
            }

            return snapshot;
        }

        // ====================================================================
        // Public Methods - Data Access
        // ====================================================================

        /// <summary>
        /// Gets the current stats for all monitored plugins.
        /// </summary>
        /// <returns>Dictionary of plugin filename to stats.</returns>
        public Dictionary<string, PluginStats> GetPluginStats()
        {
            return _pluginStats.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        }

        /// <summary>
        /// Gets stats for a specific plugin by filename.
        /// </summary>
        /// <param name="fileName">Plugin DLL filename.</param>
        /// <returns>Plugin stats or null if not found.</returns>
        public PluginStats GetPluginStats(string fileName)
        {
            return _pluginStats.TryGetValue(fileName, out var stats) ? stats : null;
        }

        /// <summary>
        /// Gets the most recent snapshot.
        /// </summary>
        /// <returns>Most recent snapshot or null if none captured.</returns>
        public PluginMonitorSnapshot GetLatestSnapshot()
        {
            return _history.LastOrDefault();
        }

        /// <summary>
        /// Gets all historical snapshots.
        /// </summary>
        /// <returns>List of snapshots in chronological order.</returns>
        public List<PluginMonitorSnapshot> GetHistory()
        {
            return _history.ToList();
        }

        /// <summary>
        /// Gets plugins flagged with warnings for excessive resource usage.
        /// </summary>
        /// <returns>List of plugin stats with active warnings.</returns>
        public List<PluginStats> GetPluginsWithWarnings()
        {
            return _pluginStats.Values
                .Where(s => s.HasWarnings)
                .OrderByDescending(s => s.WarningFlags.Count)
                .ToList();
        }

        /// <summary>
        /// Compares two snapshots and returns the differences.
        /// </summary>
        /// <param name="baseline">Baseline snapshot.</param>
        /// <param name="current">Current snapshot to compare.</param>
        /// <returns>Comparison result with deltas.</returns>
        public PluginSnapshotComparison CompareSnapshots(PluginMonitorSnapshot baseline, PluginMonitorSnapshot current)
        {
            if (baseline == null) throw new ArgumentNullException(nameof(baseline));
            if (current == null) throw new ArgumentNullException(nameof(current));

            var comparison = new PluginSnapshotComparison
            {
                BaselineTime = baseline.Timestamp,
                CurrentTime = current.Timestamp,
                TimeDeltaMs = (long)(current.Timestamp - baseline.Timestamp).TotalMilliseconds
            };

            // Compare overall metrics
            comparison.PluginCountDelta = current.ValidPluginsFound - baseline.ValidPluginsFound;
            comparison.TotalThreadsDelta = current.TotalProcessThreads - baseline.TotalProcessThreads;
            comparison.WarningCountDelta = current.PluginsWithWarnings - baseline.PluginsWithWarnings;

            // Per-plugin comparison
            var baselinePlugins = baseline.PluginStats.ToDictionary(p => p.FileName, StringComparer.OrdinalIgnoreCase);
            var currentPlugins = current.PluginStats.ToDictionary(p => p.FileName, StringComparer.OrdinalIgnoreCase);

            // Find new plugins
            comparison.NewPlugins = currentPlugins.Keys
                .Except(baselinePlugins.Keys, StringComparer.OrdinalIgnoreCase)
                .ToList();

            // Find removed plugins
            comparison.RemovedPlugins = baselinePlugins.Keys
                .Except(currentPlugins.Keys, StringComparer.OrdinalIgnoreCase)
                .ToList();

            // Compare existing plugins
            foreach (var fileName in currentPlugins.Keys.Intersect(baselinePlugins.Keys, StringComparer.OrdinalIgnoreCase))
            {
                var baseStats = baselinePlugins[fileName];
                var currStats = currentPlugins[fileName];

                var delta = new PluginStatsDelta
                {
                    FileName = fileName,
                    PluginName = currStats.PluginName,
                    MemoryDeltaBytes = currStats.EstimatedMemoryBytes - baseStats.EstimatedMemoryBytes,
                    TypeCountDelta = currStats.LoadedTypeCount - baseStats.LoadedTypeCount,
                    NewWarnings = currStats.WarningFlags.Except(baseStats.WarningFlags).ToList(),
                    ResolvedWarnings = baseStats.WarningFlags.Except(currStats.WarningFlags).ToList()
                };

                if (delta.HasChanges)
                {
                    comparison.PluginDeltas.Add(delta);
                }
            }

            // Calculate total memory delta
            comparison.TotalMemoryDeltaBytes = comparison.PluginDeltas.Sum(d => d.MemoryDeltaBytes);

            return comparison;
        }

        /// <summary>
        /// Gets a summary string of current plugin resource usage.
        /// </summary>
        /// <returns>Formatted summary string.</returns>
        public string GetSummary()
        {
            var latest = GetLatestSnapshot();
            if (latest == null)
            {
                return "No plugin snapshots captured yet.";
            }

            var sb = new System.Text.StringBuilder();
            sb.AppendLine($"=== Plugin Monitor Summary ({latest.Timestamp:yyyy-MM-dd HH:mm:ss}) ===");
            sb.AppendLine($"Total Plugins: {latest.ValidPluginsFound} valid / {latest.TotalPluginsFound} DLLs");
            sb.AppendLine($"Process Threads: {latest.TotalProcessThreads} ({latest.ThreadCountSinceBaseline:+#;-#;0} since baseline)");
            sb.AppendLine($"Plugins with Warnings: {latest.PluginsWithWarnings}");
            sb.AppendLine($"Snapshots Captured: {_snapshotCount}");
            sb.AppendLine();

            // Registration totals
            var totalPanels = _registrations.Values.Sum(r => r.PanelCount);
            var totalMenus = _registrations.Values.Sum(r => r.MenuCount);
            var totalEvents = _registrations.Values.Sum(r => r.EventCount);
            var totalPluginThreads = _registrations.Values.Sum(r => r.ThreadIds.Count);
            sb.AppendLine($"Total Registrations: Panels={totalPanels}, Menus={totalMenus}, Events={totalEvents}, Plugin Threads={totalPluginThreads}");
            sb.AppendLine();

            if (latest.PluginStats.Any())
            {
                sb.AppendLine("--- Plugin Resource Usage ---");
                foreach (var stats in latest.PluginStats.OrderByDescending(s => s.EstimatedMemoryBytes))
                {
                    var warningIndicator = stats.HasWarnings ? " [!]" : "";
                    sb.AppendLine($"  {stats.PluginName ?? stats.FileName}{warningIndicator}");
                    sb.AppendLine($"    Memory: {FormatBytes(stats.EstimatedMemoryBytes)} | " +
                        $"Types: {stats.LoadedTypeCount} | " +
                        $"File: {FormatBytes(stats.FileSizeBytes)}");

                    // Include registration info if available
                    if (_registrations.TryGetValue(stats.FileName, out var regs) && regs.HasAnyRegistrations)
                    {
                        sb.AppendLine($"    Registrations: {regs}");
                        if (regs.MenuPaths.Count > 0)
                        {
                            sb.AppendLine($"      Menus: {string.Join(", ", regs.MenuPaths)}");
                        }
                    }

                    if (stats.HasWarnings)
                    {
                        sb.AppendLine($"    Warnings: {string.Join(", ", stats.WarningFlags)}");
                    }
                }
            }

            var warnings = GetPluginsWithWarnings();
            if (warnings.Any())
            {
                sb.AppendLine();
                sb.AppendLine("--- Warning Details ---");
                foreach (var plugin in warnings)
                {
                    sb.AppendLine($"  {plugin.PluginName ?? plugin.FileName}:");
                    foreach (var flag in plugin.WarningFlags)
                    {
                        sb.AppendLine($"    - {flag}");
                    }
                }
            }

            return sb.ToString();
        }

        /// <summary>
        /// Generates a comprehensive report of all plugin resource usage.
        /// Alias for GetSummary() to match PLAN.md interface.
        /// </summary>
        /// <returns>Formatted report string suitable for display or logging.</returns>
        public string GetReport()
        {
            return GetSummary();
        }

        // ====================================================================
        // Registration Tracking Methods
        // ====================================================================

        /// <summary>
        /// Records that a plugin has registered a dockable panel.
        /// </summary>
        /// <param name="pluginFileName">Plugin DLL filename (e.g., "mb_myplugin.dll").</param>
        /// <param name="panelId">Optional panel identifier.</param>
        public void RecordPanelRegistration(string pluginFileName, string panelId = null)
        {
            if (string.IsNullOrEmpty(pluginFileName)) return;

            var regs = _registrations.GetOrAdd(pluginFileName, _ => new PluginRegistrations());
            regs.PanelCount++;
            if (!string.IsNullOrEmpty(panelId))
            {
                regs.PanelIds.Add(panelId);
            }
            LogManager.Core.Debug($"Panel registration recorded for {pluginFileName}: {panelId ?? "(unnamed)"}");
        }

        /// <summary>
        /// Records that a plugin has registered a menu item.
        /// </summary>
        /// <param name="pluginFileName">Plugin DLL filename.</param>
        /// <param name="menuPath">Menu path of the registered item (e.g., "mnuTools/MyPlugin...").</param>
        public void RecordMenuRegistration(string pluginFileName, string menuPath)
        {
            if (string.IsNullOrEmpty(pluginFileName)) return;

            var regs = _registrations.GetOrAdd(pluginFileName, _ => new PluginRegistrations());
            regs.MenuCount++;
            if (!string.IsNullOrEmpty(menuPath))
            {
                regs.MenuPaths.Add(menuPath);
            }
            LogManager.Core.Debug($"Menu registration recorded for {pluginFileName}: {menuPath}");
        }

        /// <summary>
        /// Records that a plugin has registered for a notification/event.
        /// </summary>
        /// <param name="pluginFileName">Plugin DLL filename.</param>
        /// <param name="eventName">Name of the event/notification.</param>
        public void RecordEventRegistration(string pluginFileName, string eventName)
        {
            if (string.IsNullOrEmpty(pluginFileName)) return;

            var regs = _registrations.GetOrAdd(pluginFileName, _ => new PluginRegistrations());
            regs.EventCount++;
            if (!string.IsNullOrEmpty(eventName))
            {
                regs.EventNames.Add(eventName);
            }
            LogManager.Core.Debug($"Event registration recorded for {pluginFileName}: {eventName}");
        }

        /// <summary>
        /// Records that a plugin has created a thread.
        /// </summary>
        /// <param name="pluginFileName">Plugin DLL filename.</param>
        /// <param name="threadId">Managed thread ID.</param>
        /// <param name="threadName">Optional thread name.</param>
        public void RecordThreadCreation(string pluginFileName, int threadId, string threadName = null)
        {
            if (string.IsNullOrEmpty(pluginFileName)) return;

            var regs = _registrations.GetOrAdd(pluginFileName, _ => new PluginRegistrations());
            regs.ThreadIds.TryAdd(threadId, true);
            if (!string.IsNullOrEmpty(threadName))
            {
                regs.ThreadNames[threadId] = threadName;
            }
            LogManager.Core.Debug($"Thread creation recorded for {pluginFileName}: Thread {threadId} ({threadName ?? "unnamed"})");
        }

        /// <summary>
        /// Records that a thread has exited.
        /// </summary>
        /// <param name="threadId">Managed thread ID that has exited.</param>
        public void RecordThreadExit(int threadId)
        {
            foreach (var kvp in _registrations)
            {
                if (kvp.Value.ThreadIds.TryRemove(threadId, out _))
                {
                    kvp.Value.ThreadNames.TryRemove(threadId, out _);
                    LogManager.Core.Debug($"Thread {threadId} exited from plugin {kvp.Key}");
                    break;
                }
            }
        }

        /// <summary>
        /// Gets the registration info for a specific plugin.
        /// </summary>
        /// <param name="pluginFileName">Plugin DLL filename.</param>
        /// <returns>Registration info or null if not found.</returns>
        public PluginRegistrations GetPluginRegistrations(string pluginFileName)
        {
            return _registrations.TryGetValue(pluginFileName, out var regs) ? regs : null;
        }

        /// <summary>
        /// Gets registration info for all plugins.
        /// </summary>
        /// <returns>Dictionary of plugin filename to registration info.</returns>
        public Dictionary<string, PluginRegistrations> GetAllRegistrations()
        {
            return _registrations.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        }

        /// <summary>
        /// Gets a compact summary line for logging.
        /// </summary>
        /// <returns>Single-line summary suitable for log output.</returns>
        public string GetSummaryLine()
        {
            var latest = GetLatestSnapshot();
            if (latest == null)
            {
                return "PLUGINS|NoData";
            }

            var totalPanels = _registrations.Values.Sum(r => r.PanelCount);
            var totalMenus = _registrations.Values.Sum(r => r.MenuCount);
            var totalEvents = _registrations.Values.Sum(r => r.EventCount);
            var totalPluginThreads = _registrations.Values.Sum(r => r.ThreadIds.Count);

            return $"PLUGINS|Count={latest.ValidPluginsFound}|Threads={latest.TotalProcessThreads}(+{latest.ThreadCountSinceBaseline})|" +
                $"Panels={totalPanels}|Menus={totalMenus}|Events={totalEvents}|PluginThreads={totalPluginThreads}|" +
                $"Warnings={latest.PluginsWithWarnings}";
        }

        // ====================================================================
        // Private Methods
        // ====================================================================

        /// <summary>
        /// Timer callback for automatic monitoring.
        /// </summary>
        private void MonitorCallback(object state)
        {
            try
            {
                CaptureSnapshot();
            }
            catch (Exception ex)
            {
                LogManager.Core.Error(ex, "Error in plugin monitor callback");
            }
        }

        /// <summary>
        /// Collects resource usage statistics for a single plugin.
        /// </summary>
        private PluginStats CollectPluginStats(DiscoveredPlugin plugin)
        {
            var stats = new PluginStats
            {
                FileName = plugin.FileName,
                FilePath = plugin.FilePath,
                PluginName = plugin.PluginName ?? plugin.AssemblyName ?? plugin.FileName,
                PluginType = plugin.PluginType,
                Version = $"{plugin.VersionMajor}.{plugin.VersionMinor}.{plugin.Revision}",
                FileSizeBytes = plugin.FileSizeBytes,
                LastModified = plugin.LastModified,
                FirstSeenTime = DateTime.Now,
                LastSampleTime = DateTime.Now,
                SampleCount = 1
            };

            // Try to get loaded assembly information
            try
            {
                var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies()
                    .FirstOrDefault(a => !a.IsDynamic &&
                        Path.GetFileName(a.Location).Equals(plugin.FileName, StringComparison.OrdinalIgnoreCase));

                if (loadedAssembly != null)
                {
                    stats.IsLoaded = true;
                    stats.AssemblyFullName = loadedAssembly.FullName;

                    // Count types in the assembly
                    try
                    {
                        stats.LoadedTypeCount = loadedAssembly.GetTypes().Length;
                    }
                    catch (ReflectionTypeLoadException ex)
                    {
                        stats.LoadedTypeCount = ex.Types.Count(t => t != null);
                    }

                    // Estimate memory footprint based on file size and loaded types
                    // This is a rough estimate since we can't directly measure per-assembly memory
                    stats.EstimatedMemoryBytes = EstimateMemoryFootprint(loadedAssembly, plugin.FileSizeBytes, stats.LoadedTypeCount);

                    // Count methods (indicates complexity)
                    try
                    {
                        stats.MethodCount = loadedAssembly.GetTypes()
                            .SelectMany(t =>
                            {
                                try { return t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); }
                                catch { return Enumerable.Empty<MethodInfo>(); }
                            })
                            .Count();
                    }
                    catch
                    {
                        stats.MethodCount = 0;
                    }

                    // Count fields (indicates data storage)
                    try
                    {
                        stats.FieldCount = loadedAssembly.GetTypes()
                            .SelectMany(t =>
                            {
                                try { return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); }
                                catch { return Enumerable.Empty<FieldInfo>(); }
                            })
                            .Count();
                    }
                    catch
                    {
                        stats.FieldCount = 0;
                    }

                    // Check for referenced assemblies
                    stats.ReferencedAssemblyCount = loadedAssembly.GetReferencedAssemblies().Length;
                }
                else
                {
                    stats.IsLoaded = false;
                    // Use file size as estimate for non-loaded assemblies
                    stats.EstimatedMemoryBytes = plugin.FileSizeBytes;
                }
            }
            catch (Exception ex)
            {
                LogManager.Core.Trace($"Error collecting stats for {plugin.FileName}: {ex.Message}");
                stats.CollectionError = ex.Message;
            }

            return stats;
        }

        /// <summary>
        /// Estimates memory footprint for a loaded assembly.
        /// </summary>
        private long EstimateMemoryFootprint(Assembly assembly, long fileSizeBytes, int typeCount)
        {
            // Base: file size (code in memory)
            long estimate = fileSizeBytes;

            // Add estimate for type metadata (rough: ~1KB per type)
            estimate += typeCount * 1024;

            // Add estimate for JIT compiled code (varies wildly, estimate 2x IL size)
            // Typically only a portion of code is JIT'd at any time
            estimate += (long)(fileSizeBytes * 0.5);

            return estimate;
        }

        /// <summary>
        /// Gets the current process thread count.
        /// </summary>
        private int GetCurrentThreadCount()
        {
            try
            {
                return Process.GetCurrentProcess().Threads.Count;
            }
            catch
            {
                return 0;
            }
        }

        /// <summary>
        /// Detects and flags plugins with excessive resource usage.
        /// </summary>
        private void DetectExcessiveUsage(PluginMonitorSnapshot snapshot)
        {
            foreach (var stats in snapshot.PluginStats)
            {
                stats.WarningFlags.Clear();

                // Check memory usage
                long memoryMB = stats.EstimatedMemoryBytes / (1024 * 1024);
                if (memoryMB >= HIGH_MEMORY_THRESHOLD_MB)
                {
                    stats.WarningFlags.Add($"HighMemory:{memoryMB}MB");
                }

                // Check type count
                if (stats.LoadedTypeCount >= HIGH_TYPE_COUNT_THRESHOLD)
                {
                    stats.WarningFlags.Add($"HighTypeCount:{stats.LoadedTypeCount}");
                }

                // Check for memory growth (potential leak)
                if (stats.SampleCount > 5 && stats.TotalMemoryGrowth > 0)
                {
                    long avgGrowthPerSample = stats.TotalMemoryGrowth / stats.SampleCount;
                    if (avgGrowthPerSample > MEMORY_LEAK_THRESHOLD_BYTES_PER_SAMPLE)
                    {
                        stats.WarningFlags.Add($"MemoryGrowth:{FormatBytes(stats.TotalMemoryGrowth)}/session");
                    }
                }

                // Check referenced assembly count (indicates complexity/dependencies)
                if (stats.ReferencedAssemblyCount > 20)
                {
                    stats.WarningFlags.Add($"HighDependencies:{stats.ReferencedAssemblyCount}");
                }
            }

            // Update warning count in snapshot
            snapshot.PluginsWithWarnings = snapshot.PluginStats.Count(s => s.HasWarnings);

            // Check overall thread growth
            if (snapshot.ThreadCountSinceBaseline > HIGH_THREAD_COUNT_THRESHOLD)
            {
                snapshot.HasThreadGrowthWarning = true;
            }
        }

        /// <summary>
        /// Formats bytes as human-readable string.
        /// </summary>
        private static string FormatBytes(long bytes)
        {
            if (bytes < 0) bytes = Math.Abs(bytes);

            if (bytes >= 1024 * 1024 * 1024)
                return $"{bytes / (1024.0 * 1024 * 1024):F1}GB";
            if (bytes >= 1024 * 1024)
                return $"{bytes / (1024.0 * 1024):F1}MB";
            if (bytes >= 1024)
                return $"{bytes / 1024.0:F1}KB";
            return $"{bytes}B";
        }

        // ====================================================================
        // IDisposable
        // ====================================================================

        /// <summary>
        /// Disposes the PluginMonitor and stops monitoring.
        /// </summary>
        public void Dispose()
        {
            if (_disposed) return;

            StopMonitoring();
            _disposed = true;
            LogManager.Core.Info("PluginMonitor disposed");
        }
    }

    // ========================================================================
    // Data Models
    // ========================================================================

    /// <summary>
    /// Resource usage statistics for a single plugin.
    /// </summary>
    public class PluginStats
    {
        /// <summary>
        /// Plugin DLL filename.
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// Full path to the plugin DLL.
        /// </summary>
        public string FilePath { get; set; }

        /// <summary>
        /// Display name of the plugin.
        /// </summary>
        public string PluginName { get; set; }

        /// <summary>
        /// Plugin type (General, LyricsRetrieval, etc.).
        /// </summary>
        public string PluginType { get; set; }

        /// <summary>
        /// Plugin version string.
        /// </summary>
        public string Version { get; set; }

        /// <summary>
        /// Plugin DLL file size in bytes.
        /// </summary>
        public long FileSizeBytes { get; set; }

        /// <summary>
        /// Last modification time of the plugin file.
        /// </summary>
        public DateTime LastModified { get; set; }

        /// <summary>
        /// Whether the assembly is currently loaded in the AppDomain.
        /// </summary>
        public bool IsLoaded { get; set; }

        /// <summary>
        /// Full name of the loaded assembly.
        /// </summary>
        public string AssemblyFullName { get; set; }

        /// <summary>
        /// Estimated memory footprint in bytes.
        /// </summary>
        public long EstimatedMemoryBytes { get; set; }

        /// <summary>
        /// Previous memory reading for delta calculation.
        /// </summary>
        public long PreviousMemoryBytes { get; set; }

        /// <summary>
        /// Number of types loaded from this assembly.
        /// </summary>
        public int LoadedTypeCount { get; set; }

        /// <summary>
        /// Previous type count for delta calculation.
        /// </summary>
        public int PreviousTypeCount { get; set; }

        /// <summary>
        /// Number of methods in the assembly.
        /// </summary>
        public int MethodCount { get; set; }

        /// <summary>
        /// Number of fields in the assembly.
        /// </summary>
        public int FieldCount { get; set; }

        /// <summary>
        /// Number of assemblies referenced by this plugin.
        /// </summary>
        public int ReferencedAssemblyCount { get; set; }

        /// <summary>
        /// When this plugin was first observed.
        /// </summary>
        public DateTime FirstSeenTime { get; set; }

        /// <summary>
        /// When the last sample was taken.
        /// </summary>
        public DateTime LastSampleTime { get; set; }

        /// <summary>
        /// Number of samples collected for this plugin.
        /// </summary>
        public int SampleCount { get; set; }

        /// <summary>
        /// Total memory growth since first observation (bytes).
        /// </summary>
        public long TotalMemoryGrowth { get; set; }

        /// <summary>
        /// Warning flags for excessive resource usage.
        /// </summary>
        public List<string> WarningFlags { get; set; } = new List<string>();

        /// <summary>
        /// Error message if stats collection failed.
        /// </summary>
        public string CollectionError { get; set; }

        /// <summary>
        /// Gets whether this plugin has any active warnings.
        /// </summary>
        public bool HasWarnings => WarningFlags != null && WarningFlags.Count > 0;

        /// <summary>
        /// Gets the memory delta since previous sample.
        /// </summary>
        public long MemoryDelta => EstimatedMemoryBytes - PreviousMemoryBytes;

        /// <summary>
        /// Gets the type count delta since previous sample.
        /// </summary>
        public int TypeCountDelta => LoadedTypeCount - PreviousTypeCount;

        /// <summary>
        /// Returns a summary string for this plugin.
        /// </summary>
        public override string ToString()
        {
            return $"{PluginName ?? FileName}: {EstimatedMemoryBytes / (1024 * 1024)}MB, {LoadedTypeCount} types" +
                (HasWarnings ? $" [!{WarningFlags.Count} warnings]" : "");
        }
    }

    /// <summary>
    /// Point-in-time snapshot of all plugin resource usage.
    /// </summary>
    public class PluginMonitorSnapshot
    {
        /// <summary>
        /// When this snapshot was captured.
        /// </summary>
        public DateTime Timestamp { get; set; }

        /// <summary>
        /// Sequential snapshot ID.
        /// </summary>
        public int SnapshotId { get; set; }

        /// <summary>
        /// Time taken to capture this snapshot in milliseconds.
        /// </summary>
        public long CaptureTimeMs { get; set; }

        /// <summary>
        /// Total DLL files found in the Plugins folder.
        /// </summary>
        public int TotalPluginsFound { get; set; }

        /// <summary>
        /// Number of valid MusicBee plugins found.
        /// </summary>
        public int ValidPluginsFound { get; set; }

        /// <summary>
        /// Total threads in the MusicBee process.
        /// </summary>
        public int TotalProcessThreads { get; set; }

        /// <summary>
        /// Thread count change since monitoring baseline.
        /// </summary>
        public int ThreadCountSinceBaseline { get; set; }

        /// <summary>
        /// Number of plugins with resource warnings.
        /// </summary>
        public int PluginsWithWarnings { get; set; }

        /// <summary>
        /// Whether thread growth exceeds threshold.
        /// </summary>
        public bool HasThreadGrowthWarning { get; set; }

        /// <summary>
        /// Error message if capture failed.
        /// </summary>
        public string CaptureError { get; set; }

        /// <summary>
        /// Stats for each discovered plugin.
        /// </summary>
        public List<PluginStats> PluginStats { get; set; } = new List<PluginStats>();

        /// <summary>
        /// Returns a summary string for this snapshot.
        /// </summary>
        public override string ToString()
        {
            return $"Snapshot #{SnapshotId} at {Timestamp:HH:mm:ss}: " +
                $"{ValidPluginsFound} plugins, {TotalProcessThreads} threads, " +
                $"{PluginsWithWarnings} warnings";
        }

        /// <summary>
        /// Formats as a compact log line.
        /// </summary>
        public string ToLogLine()
        {
            return $"PLUGINS|Count={ValidPluginsFound}|Threads={TotalProcessThreads}(+{ThreadCountSinceBaseline})|" +
                $"Warnings={PluginsWithWarnings}|CaptureMs={CaptureTimeMs}";
        }
    }

    /// <summary>
    /// Comparison between two plugin monitor snapshots.
    /// </summary>
    public class PluginSnapshotComparison
    {
        /// <summary>
        /// Baseline snapshot timestamp.
        /// </summary>
        public DateTime BaselineTime { get; set; }

        /// <summary>
        /// Current snapshot timestamp.
        /// </summary>
        public DateTime CurrentTime { get; set; }

        /// <summary>
        /// Time difference in milliseconds.
        /// </summary>
        public long TimeDeltaMs { get; set; }

        /// <summary>
        /// Change in number of valid plugins.
        /// </summary>
        public int PluginCountDelta { get; set; }

        /// <summary>
        /// Change in total thread count.
        /// </summary>
        public int TotalThreadsDelta { get; set; }

        /// <summary>
        /// Change in total memory across all plugins.
        /// </summary>
        public long TotalMemoryDeltaBytes { get; set; }

        /// <summary>
        /// Change in warning count.
        /// </summary>
        public int WarningCountDelta { get; set; }

        /// <summary>
        /// Plugins added since baseline.
        /// </summary>
        public List<string> NewPlugins { get; set; } = new List<string>();

        /// <summary>
        /// Plugins removed since baseline.
        /// </summary>
        public List<string> RemovedPlugins { get; set; } = new List<string>();

        /// <summary>
        /// Per-plugin changes.
        /// </summary>
        public List<PluginStatsDelta> PluginDeltas { get; set; } = new List<PluginStatsDelta>();

        /// <summary>
        /// Gets whether there are any significant changes.
        /// </summary>
        public bool HasChanges => PluginCountDelta != 0 ||
            TotalThreadsDelta != 0 ||
            NewPlugins.Count > 0 ||
            RemovedPlugins.Count > 0 ||
            PluginDeltas.Any(d => d.HasChanges);

        /// <summary>
        /// Returns a summary of the comparison.
        /// </summary>
        public override string ToString()
        {
            var duration = TimeSpan.FromMilliseconds(TimeDeltaMs);
            return $"Comparison over {duration.TotalMinutes:F1} minutes: " +
                $"Plugins {PluginCountDelta:+#;-#;0}, Threads {TotalThreadsDelta:+#;-#;0}, " +
                $"Memory {TotalMemoryDeltaBytes / (1024 * 1024):+#;-#;0}MB";
        }
    }

    /// <summary>
    /// Changes to a single plugin between snapshots.
    /// </summary>
    public class PluginStatsDelta
    {
        /// <summary>
        /// Plugin filename.
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// Plugin display name.
        /// </summary>
        public string PluginName { get; set; }

        /// <summary>
        /// Memory change in bytes.
        /// </summary>
        public long MemoryDeltaBytes { get; set; }

        /// <summary>
        /// Type count change.
        /// </summary>
        public int TypeCountDelta { get; set; }

        /// <summary>
        /// New warnings since baseline.
        /// </summary>
        public List<string> NewWarnings { get; set; } = new List<string>();

        /// <summary>
        /// Warnings resolved since baseline.
        /// </summary>
        public List<string> ResolvedWarnings { get; set; } = new List<string>();

        /// <summary>
        /// Gets whether there are any significant changes.
        /// </summary>
        public bool HasChanges => MemoryDeltaBytes != 0 ||
            TypeCountDelta != 0 ||
            NewWarnings.Count > 0 ||
            ResolvedWarnings.Count > 0;

        /// <summary>
        /// Returns a summary of changes.
        /// </summary>
        public override string ToString()
        {
            return $"{PluginName ?? FileName}: Memory {MemoryDeltaBytes / 1024:+#;-#;0}KB, " +
                $"Types {TypeCountDelta:+#;-#;0}";
        }
    }

    /// <summary>
    /// Tracks registrations (panels, menus, events, threads) made by a plugin.
    /// Used to monitor plugin resource usage and detect potential issues.
    /// </summary>
    public class PluginRegistrations
    {
        /// <summary>
        /// Number of dockable panels registered.
        /// </summary>
        public int PanelCount { get; set; }

        /// <summary>
        /// Number of menu items registered.
        /// </summary>
        public int MenuCount { get; set; }

        /// <summary>
        /// Number of events/notifications registered for.
        /// </summary>
        public int EventCount { get; set; }

        /// <summary>
        /// List of panel IDs registered by this plugin.
        /// </summary>
        public List<string> PanelIds { get; } = new List<string>();

        /// <summary>
        /// List of menu paths registered by this plugin.
        /// </summary>
        public List<string> MenuPaths { get; } = new List<string>();

        /// <summary>
        /// List of event names registered by this plugin.
        /// </summary>
        public List<string> EventNames { get; } = new List<string>();

        /// <summary>
        /// Dictionary of thread IDs created by this plugin.
        /// Uses concurrent collection for thread-safety. Value is true for active threads.
        /// </summary>
        public ConcurrentDictionary<int, bool> ThreadIds { get; } = new ConcurrentDictionary<int, bool>();

        /// <summary>
        /// Map of thread ID to thread name (for named threads).
        /// </summary>
        public ConcurrentDictionary<int, string> ThreadNames { get; } = new ConcurrentDictionary<int, string>();

        /// <summary>
        /// Gets the total number of active threads for this plugin.
        /// </summary>
        public int ActiveThreadCount => ThreadIds.Count;

        /// <summary>
        /// Gets whether the plugin has any registrations.
        /// </summary>
        public bool HasAnyRegistrations => PanelCount > 0 || MenuCount > 0 || EventCount > 0 || ThreadIds.Count > 0;

        /// <summary>
        /// Returns a summary string.
        /// </summary>
        public override string ToString()
        {
            return $"Panels={PanelCount}, Menus={MenuCount}, Events={EventCount}, Threads={ThreadIds.Count}";
        }
    }
}
