using DesktopMagic.Api;
using DesktopMagic.Api.Drawing;
using DesktopMagic.Api.Settings;
using DesktopMagic.DataContexts;
using DesktopMagic.Helpers;
using DesktopMagic.Plugins;
using DesktopMagic.Settings;

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace DesktopMagic;

public partial class PluginWindow : Window, IPluginWindow
{
    public event Action? PluginLoaded;

    public event Action? OnExit;

    private readonly PluginSettings settings;
    private Thread? pluginThread;
    private System.Timers.Timer? updateTimer;
    private Plugin? pluginClassInstance;
    private AssemblyLoadContext assemblyLoadContext;

    private CancellationTokenSource? pluginCancellationTokenSource;
    private FileSystemWatcher? pluginFileWatcher;
    private System.Timers.Timer? reloadDebounceTimer;
    private bool isReloading = false;

    public bool IsRunning { get; private set; } = true;
    public PluginMetadata PluginMetadata { get; private set; }
    public string PluginFolderPath { get; private set; }

    public PluginWindow(PluginMetadata pluginMetadata, PluginSettings settings, string pluginFolderPath)
    {
        InitializeComponent();

        Window w = new()
        {
            Top = -100,
            Left = -100,
            Width = 0,
            Height = 0,

            WindowStyle = WindowStyle.ToolWindow,
            ShowInTaskbar = false
        };

        WindowInteropHelper helper = new WindowInteropHelper(w);
        _ = helper.EnsureHandle();

        Owner = w;

        settings.PropertyChanged += (e, s) =>
        {
            if (s.PropertyName == nameof(PluginSettings.CurrentThemeName))
            {
                settings.Theme.PropertyChanged += (se, ev) =>
                {
                    ThemeChanged();
                };
                ThemeChanged();
            }
        };

        settings.Theme.PropertyChanged += (se, ev) =>
        {
            ThemeChanged();
        };

        PluginMetadata = pluginMetadata;
        this.settings = settings;

        Left = settings.Position.X;
        Top = settings.Position.Y;
        Width = settings.Size.X;
        Height = settings.Size.Y;

        PluginFolderPath = pluginFolderPath;

        assemblyLoadContext = CreateAssemblyLoadContext();

        // Initialize hot reload watcher if plugin supports unloading and is external
        if (pluginMetadata.SupportsUnloading && !string.IsNullOrEmpty(pluginFolderPath))
        {
            InitializeHotReload();
        }
    }

    public PluginWindow(Plugin pluginClassInstance, PluginMetadata pluginMetadata, PluginSettings settings) : this(pluginMetadata, settings, string.Empty)
    {
        this.pluginClassInstance = pluginClassInstance;
    }

    private AssemblyLoadContext CreateAssemblyLoadContext()
    {
        AssemblyLoadContext context = new(PluginMetadata.Name, isCollectible: true);
        context.Resolving += (ctx, assemblyName) =>
        {
            string assemblyPath = Path.Combine(PluginFolderPath, assemblyName.Name + ".dll");
            if (File.Exists(assemblyPath))
            {
                return ctx.LoadFromAssemblyPath(assemblyPath);
            }
            else
            {
                _ = ctx.LoadFromAssemblyName(assemblyName);
            }
            return null;
        };
        return context;
    }

    private void InitializeHotReload()
    {
        try
        {
            pluginFileWatcher = new FileSystemWatcher(PluginFolderPath)
            {
                Filter = "main.dll",
                NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,
                EnableRaisingEvents = false
            };

            pluginFileWatcher.Changed += OnPluginFileChanged;

            reloadDebounceTimer = new System.Timers.Timer(500); // 500ms debounce
            reloadDebounceTimer.Elapsed += async (s, e) =>
            {
                reloadDebounceTimer!.Stop();
                await ReloadPlugin();
            };

            App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Hot reload enabled", source: "Plugin");
        }
        catch (Exception ex)
        {
            App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to initialize hot reload: {ex.Message}", source: "Plugin");
        }
    }

    private void OnPluginFileChanged(object sender, FileSystemEventArgs e)
    {
        if (isReloading)
        {
            return;
        }

        App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Plugin file changed, scheduling reload", source: "Plugin");

        // Debounce multiple file change events
        reloadDebounceTimer?.Stop();
        reloadDebounceTimer?.Start();
    }

    private async Task StopPlugin(bool unloadAssembly = true)
    {
        App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Stopping plugin", source: "Plugin");

        try
        {
            pluginCancellationTokenSource?.Cancel();

            if (pluginClassInstance is AsyncPlugin asyncPluginStop)
            {
                try
                {
                    await asyncPluginStop.StopAsync(pluginCancellationTokenSource?.Token ?? CancellationToken.None);
                }
                catch (Exception ex)
                {
                    App.Logger.LogError($"\"{PluginMetadata.Name}\" - Error stopping async plugin: {ex.Message}", source: "Plugin");
                }
            }

            if (pluginClassInstance is not null)
            {
                SaveState(pluginClassInstance);
                pluginClassInstance.Stop();
            }
        }
        catch (Exception ex)
        {
            App.Logger.LogError($"\"{PluginMetadata.Name}\" - Error stopping plugin: {ex}", source: "Plugin");
        }

        if (unloadAssembly)
        {
            assemblyLoadContext.Unload();
        }

        pluginCancellationTokenSource?.Dispose();
        pluginCancellationTokenSource = null;
    }

    private async Task ReloadPlugin()
    {
        if (isReloading || !IsRunning)
        {
            return;
        }

        isReloading = true;

        try
        {
            App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Reloading plugin", source: "Plugin");

            // Stop the file watcher during reload
            if (pluginFileWatcher != null)
            {
                pluginFileWatcher.EnableRaisingEvents = false;
            }

            updateTimer?.Stop();

            await StopPlugin(unloadAssembly: true);

            // Wait for file to be fully written and released
            await Task.Delay(200);

            // Clear the current instance
            pluginClassInstance = null;

            // Create a new AssemblyLoadContext since the old one was unloaded
            assemblyLoadContext = CreateAssemblyLoadContext();

            // Show busy indicator
            await Dispatcher.InvokeAsync(() => busyMask.IsBusy = true);

            // Reload the plugin
            await ExecuteSource();

            // Hide busy indicator
            await Dispatcher.InvokeAsync(() => busyMask.IsBusy = false);

            App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Plugin reloaded successfully", source: "Plugin");
        }
        catch (Exception ex)
        {
            App.Logger.LogError($"\"{PluginMetadata.Name}\" - Failed to reload plugin: {ex}", source: "Plugin");
            
            await Dispatcher.InvokeAsync(async () =>
            {
                Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
                {
                    Title = $"Reload Error \"{PluginMetadata.Name}\"",
                    Content = $"Failed to reload plugin:\n{ex.Message}",
                    CloseButtonText = "Ok"
                };
                _ = await messageBox.ShowDialogAsync();
            });
        }
        finally
        {
            isReloading = false;

            // Re-enable file watcher
            if (pluginFileWatcher != null && IsRunning)
            {
                pluginFileWatcher.EnableRaisingEvents = true;
            }
        }
    }

    public void UpdatePluginWindow()
    {
        UpdateTimer_Elapsed(updateTimer, null);
    }

    public void SavePluginState()
    {
        if (pluginClassInstance is not null)
        {
            SaveState(pluginClassInstance);
        }
    }

    public void Exit()
    {
        IsRunning = false;

        Dispatcher.Invoke(() =>
        {
            OnExit?.Invoke();
        });
    }

    public void SetEditMode(bool enabled)
    {
        if (enabled)
        {
            Topmost = true;
            panel.Visibility = Visibility.Visible;
            imageBorder.BorderThickness = new Thickness(3);
            image.Margin = new(-3);
            WindowPos.SetIsLocked(this, false);
            tileBar.CaptionHeight = tileBar.CaptionHeight = ActualHeight - 10 < 0 ? 0 : ActualHeight - 10;
            ResizeMode = ResizeMode.CanResize;
        }
        else
        {
            Topmost = false;
            panel.Visibility = Visibility.Collapsed;
            imageBorder.BorderThickness = new Thickness(0);
            image.Margin = new Thickness(0);
            WindowPos.SendWpfWindowBack(this);
            WindowPos.SendWpfWindowBack(this);
            WindowPos.SetIsLocked(this, true);
            tileBar.CaptionHeight = 0;
            ResizeMode = ResizeMode.NoResize;
        }
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        //Set the window style to noactivate.
        WindowInteropHelper helper = new(this);
        _ = WindowPos.SetWindowLong(helper.Handle, WindowPos.GWL_EXSTYLE,
        WindowPos.GetWindowLong(helper.Handle, WindowPos.GWL_EXSTYLE) | WindowPos.WS_EX_NOACTIVATE);
    }

    private static BitmapSource BitmapToImageSource(Bitmap bitmap)
    {
        BitmapData bitmapData = bitmap.LockBits(
            new Rectangle(0, 0, bitmap.Width, bitmap.Height),
            ImageLockMode.ReadOnly, bitmap.PixelFormat);

        BitmapSource bitmapSource = BitmapSource.Create(
            bitmapData.Width, bitmapData.Height,
            bitmap.HorizontalResolution, bitmap.VerticalResolution,
            PixelFormats.Bgra32, null,
            bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);

        bitmap.UnlockBits(bitmapData);

        bitmapSource.Freeze();
        return bitmapSource;
    }

    private void Window_ContentRendered(object? sender, EventArgs e)
    {
        App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Starting plugin thread", source: "Plugin");
        pluginThread = new Thread(async () => await LoadPlugin());
        pluginThread.Start();
    }

    private async Task LoadPlugin()
    {
        App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Loading plugin", source: "Plugin");

        if (pluginClassInstance is null && !File.Exists($"{PluginFolderPath}\\main.dll"))
        {
            App.Logger.LogError($"\"{PluginMetadata.Name}\" - File \"main.dll\" does not exist", source: "Plugin");
            _ = await Dispatcher.InvokeAsync(async () =>
            {
                Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
                {
                    Title = $"Error \"{PluginMetadata.Name}\"",
                    Content = "File \"main.dll\" does not exist!",
                    CloseButtonText = "Ok"
                };
                _ = await messageBox.ShowDialogAsync();
            });

            Exit();
            return;
        }

        try
        {
            await ExecuteSource();
        }
        catch (Exception ex)
        {
            App.Logger.LogError($"\"{PluginMetadata.Name}\" - {ex}", source: "Plugin");
            _ = await Dispatcher.InvokeAsync(async () =>
            {
                Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
                {
                    Title = $"Error \"{PluginMetadata.Name}\"",
                    Content = "File execution error:\n" + ex,
                    CloseButtonText = "Ok"
                };
                _ = await messageBox.ShowDialogAsync();
            });
            Exit();
            return;
        }
        PluginLoaded?.Invoke();

        _ = await Dispatcher.InvokeAsync(() => busyMask.IsBusy = false);

        // Enable hot reload after initial load
        if (pluginFileWatcher != null)
        {
            pluginFileWatcher.EnableRaisingEvents = true;
            App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Hot reload monitoring started", source: "Plugin");
        }
    }

    private void ThemeChanged()
    {
        viewBox.Margin = new Thickness(settings.Theme.Margin);
        border.Background = new SolidColorBrush(MultiColorConverter.ConvertToMediaColor(settings.Theme.BackgroundColor));
        border.CornerRadius = new CornerRadius(settings.Theme.CornerRadius);

        pluginClassInstance?.OnThemeChanged();

        if (pluginClassInstance?.UpdateInterval is 0 or > 500)
        {
            pluginClassInstance.Application.UpdateWindow();
        }
    }

    private async Task ExecuteSource()
    {
        object? instance = pluginClassInstance;
        if (instance is null)
        {
            Assembly dll;

            if (PluginMetadata.SupportsUnloading)
            {
                byte[] assemblyData = await File.ReadAllBytesAsync($"{PluginFolderPath}\\main.dll");
                using MemoryStream assemblyStream = new(assemblyData);

                dll = assemblyLoadContext.LoadFromStream(assemblyStream);
            }
            else
            {
                dll = Assembly.LoadFrom($"{PluginFolderPath}\\main.dll");
            }

            Type? instanceType = Array.Find(dll.GetTypes(), type => type.GetTypeInfo().BaseType == typeof(Plugin));

            if (instanceType is null)
            {
                App.Logger.LogError($"\"{PluginMetadata.Name}\" - The \"Plugin\" class could not be found! It has to inherit from \"{typeof(Plugin).FullName}\"", source: "Plugin");
                _ = await Dispatcher.InvokeAsync(async () =>
                {
                    Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
                    {
                        Title = $"Error \"{PluginMetadata.Name}\"",
                        Content = $"The \"Plugin\" class could not be found! It has to inherit from \"{typeof(Plugin).FullName}\"",
                        CloseButtonText = "Ok"
                    };
                    _ = await messageBox.ShowDialogAsync();
                });
                Exit();
                return;
            }

            instance = Activator.CreateInstance(instanceType);
        }
        if (instance is Plugin plugin)
        {
            pluginClassInstance = plugin;
            pluginClassInstance.Application = new PluginData(this, settings);
        }
        else
        {
            App.Logger.LogError($"\"{PluginMetadata.Name}\" - The \"Plugin\" class could not be found! It has to inherit from \"{typeof(Plugin).FullName}\"", source: "Plugin");
            _ = await Dispatcher.InvokeAsync(async () =>
            {
                Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
                {
                    Title = $"Error \"{PluginMetadata.Name}\"",
                    Content = $"The \"Plugin\" class has to inherit from \"{typeof(Plugin).FullName}\"",
                    CloseButtonText = "Ok"
                };
                _ = await messageBox.ShowDialogAsync();
            });
            Exit();
            return;
        }

        LoadState(pluginClassInstance);
        await LoadOptions(pluginClassInstance);
        BindDefaultSettings(pluginClassInstance);

        updateTimer = new System.Timers.Timer
        {
            Interval = 1000
        };
        updateTimer.Elapsed += UpdateTimer_Elapsed;

        pluginClassInstance.Start();
        if (pluginClassInstance is AsyncPlugin asyncPluginStart)
        {
            pluginCancellationTokenSource?.Dispose();
            pluginCancellationTokenSource = new CancellationTokenSource();
            await asyncPluginStart.StartAsync(pluginCancellationTokenSource.Token);
        }
        UpdatePluginWindow();
        await Dispatcher.InvokeAsync(ThemeChanged);

        if (pluginClassInstance.UpdateInterval > 0)
        {
            updateTimer.Interval = pluginClassInstance.UpdateInterval;
            updateTimer.Start();
        }
    }

    private void BindDefaultSettings(Plugin pluginClassInstance)
    {
        Dispatcher.Invoke(() =>
        {
            SetHorizontalAlignment();
            SetVerticalAlignment();
            SetThemeOverride();
            SetThemeOverrideItems();

            pluginClassInstance.horizontalAlignment.OnValueChanged += SetHorizontalAlignment;
            pluginClassInstance.verticalAlignment.OnValueChanged += SetVerticalAlignment;
            pluginClassInstance.themeOverride.OnValueChanged += SetThemeOverride;

            DesktopMagicSettings desktopMagicSettings = MainWindowDataContext.GetSettings();
            desktopMagicSettings.Themes.CollectionChanged += (s, e) => SetThemeOverrideItems();
        });

        void SetVerticalAlignment()
        {
            VerticalAlignment verticalAlignment = pluginClassInstance.verticalAlignment.Value switch
            {
                "Top" => VerticalAlignment.Top,
                "Center" => VerticalAlignment.Center,
                "Bottom" => VerticalAlignment.Bottom,
                _ => VerticalAlignment.Center
            };

            viewBox.VerticalAlignment = verticalAlignment;
            border.VerticalAlignment = verticalAlignment;
        }

        void SetHorizontalAlignment()
        {
            HorizontalAlignment horizontalAlignment = pluginClassInstance.horizontalAlignment.Value switch
            {
                "Left" => HorizontalAlignment.Left,
                "Center" => HorizontalAlignment.Center,
                "Right" => HorizontalAlignment.Right,
                _ => HorizontalAlignment.Center
            };

            viewBox.HorizontalAlignment = horizontalAlignment;
            border.HorizontalAlignment = horizontalAlignment;
        }

        void SetThemeOverride()
        {
            if (pluginClassInstance.themeOverride.Value == "<None>")
            {
                settings.CurrentThemeName = null;
            }
            else
            {
                settings.CurrentThemeName = pluginClassInstance.themeOverride.Value;
            }
        }

        void SetThemeOverrideItems()
        {
            DesktopMagicSettings desktopMagicSettings = MainWindowDataContext.GetSettings();

            pluginClassInstance.themeOverride.Items.Clear();
            pluginClassInstance.themeOverride.Items.Add("<None>");

            foreach (Theme theme in desktopMagicSettings.Themes)
            {
                pluginClassInstance.themeOverride.Items.Add(theme.Name);
            }
        }
    }

    private async Task LoadOptions(object instance)
    {
        App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Loading plugin options", source: "Plugin");

        try
        {
            FieldInfo[] props = instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.GetField);
            List<SettingElement> settingElements = [];
            foreach (FieldInfo prop in props)
            {
                if (prop.GetValue(instance) is Setting element)
                {
                    object[] attributes = prop.GetCustomAttributes(true);
                    foreach (object attribute in attributes)
                    {
                        if (attribute is SettingAttribute elementAttribute)
                        {
                            SettingElement settingElement = new SettingElement(element, elementAttribute.Id, elementAttribute.Name, elementAttribute.OrderIndex);

                            if (settings.Settings.Exists(e => e.Id == elementAttribute.Id))
                            {
                                SettingElement settingsSettingElement = settings.Settings.First(e => e.Id == elementAttribute.Id);
                                settingElement.JsonValue = settingsSettingElement.JsonValue;
                            }

                            element.OnValueChanged += () =>
                            {
                                pluginClassInstance?.OnSettingsChanged();

                                if (pluginClassInstance?.UpdateInterval is 0 or > 500)
                                {
                                    pluginClassInstance.Application.UpdateWindow();
                                }
                            };

                            settingElements.Add(settingElement);
                            break;
                        }
                    }
                }
            }

            settings.Settings = [.. settingElements.OrderBy(x => x.OrderIndex)];
        }
        catch (Exception ex)
        {
            IsRunning = false;
            App.Logger.LogError($"\"{PluginMetadata.Name}\" - {ex}", source: "Plugin");
            _ = await Dispatcher.InvokeAsync(async () =>
            {
                Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
                {
                    Title = $"Error \"{PluginMetadata.Name}\"",
                    Content = "File execution error:\n" + ex,
                    CloseButtonText = "Ok"
                };
                _ = await messageBox.ShowDialogAsync();
            });
            Exit();
        }
    }

    private void LoadState(object instance)
    {
        App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Loading plugin state", source: "Plugin");

        try
        {
            if (settings.State.Count == 0)
            {
                App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - No state found, using defaults", source: "Plugin");
                return;
            }

            FieldInfo[] fields = instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
            PropertyInfo[] properties = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

            // Load fields
            foreach (FieldInfo field in fields)
            {
                PersistStateAttribute? persistAttribute = field.GetCustomAttribute<PersistStateAttribute>();
                if (persistAttribute is null)
                {
                    continue;
                }

                if (settings.State.TryGetValue(field.Name, out JsonElement jsonElement))
                {
                    try
                    {
                        object? value = jsonElement.Deserialize(field.FieldType);
                        field.SetValue(instance, value);
                        App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Restored state field: {field.Name}", source: "Plugin");
                    }
                    catch (Exception ex)
                    {
                        App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to restore field {field.Name}: {ex.Message}", source: "Plugin");
                    }
                }
            }

            // Load properties
            foreach (PropertyInfo property in properties)
            {
                PersistStateAttribute? persistAttribute = property.GetCustomAttribute<PersistStateAttribute>();
                if (persistAttribute is null || !property.CanWrite)
                {
                    continue;
                }

                if (settings.State.TryGetValue(property.Name, out JsonElement jsonElement))
                {
                    try
                    {
                        object? value = jsonElement.Deserialize(property.PropertyType);
                        property.SetValue(instance, value);
                        App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Restored state property: {property.Name}", source: "Plugin");
                    }
                    catch (Exception ex)
                    {
                        App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to restore property {property.Name}: {ex.Message}", source: "Plugin");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            App.Logger.LogError($"\"{PluginMetadata.Name}\" - Error loading state: {ex.Message}", source: "Plugin");
        }
    }

    private void SaveState(object instance)
    {
        App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Saving plugin state", source: "Plugin");

        try
        {
            FieldInfo[] fields = instance.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
            PropertyInfo[] properties = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

            // Save fields
            foreach (FieldInfo field in fields)
            {
                PersistStateAttribute? persistAttribute = field.GetCustomAttribute<PersistStateAttribute>();
                if (persistAttribute is null)
                {
                    continue;
                }

                try
                {
                    object? value = field.GetValue(instance);
                    JsonElement jsonElement = JsonSerializer.SerializeToElement(value, field.FieldType);
                    settings.State[field.Name] = jsonElement;
                    App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Saved state field: {field.Name}", source: "Plugin");
                }
                catch (Exception ex)
                {
                    App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to save field {field.Name}: {ex.Message}", source: "Plugin");
                }
            }

            // Save properties
            foreach (PropertyInfo property in properties)
            {
                PersistStateAttribute? persistAttribute = property.GetCustomAttribute<PersistStateAttribute>();
                if (persistAttribute is null || !property.CanRead)
                {
                    continue;
                }

                try
                {
                    object? value = property.GetValue(instance);
                    JsonElement jsonElement = JsonSerializer.SerializeToElement(value, property.PropertyType);
                    settings.State[property.Name] = jsonElement;
                    App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Saved state property: {property.Name}", source: "Plugin");
                }
                catch (Exception ex)
                {
                    App.Logger.LogWarn($"\"{PluginMetadata.Name}\" - Failed to save property {property.Name}: {ex.Message}", source: "Plugin");
                }
            }

            App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - State saved successfully ({settings.State.Count} items)", source: "Plugin");
        }
        catch (Exception ex)
        {
            App.Logger.LogError($"\"{PluginMetadata.Name}\" - Error saving state: {ex.Message}", source: "Plugin");
        }
    }

    private async void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs? e)
    {
        try
        {
            if (IsRunning && pluginClassInstance is not null)
            {
                Bitmap? result;

                if (pluginClassInstance is AsyncPlugin asyncPlugin)
                {
                    CancellationToken token = pluginCancellationTokenSource?.Token ?? CancellationToken.None;
                    result = await asyncPlugin.MainAsync(token);
                }
                else
                {
                    result = pluginClassInstance.Main();
                }

                if (pluginClassInstance.UpdateInterval > 0)
                {
                    updateTimer!.Interval = pluginClassInstance.UpdateInterval;
                }
                else
                {
                    updateTimer!.Stop();
                }

                if (result is not null)
                {
                    BitmapScalingMode renderOptions = pluginClassInstance.RenderQuality switch
                    {
                        RenderQuality.High => BitmapScalingMode.HighQuality,
                        RenderQuality.Low => BitmapScalingMode.LowQuality,
                        RenderQuality.Performance => BitmapScalingMode.NearestNeighbor,
                        _ => BitmapScalingMode.Unspecified
                    };

                    // Update Image
                    BitmapSource frozenSource = BitmapToImageSource(result);

                    _ = Dispatcher.BeginInvoke(() =>
                    {
                        RenderOptions.SetBitmapScalingMode(image, renderOptions);
                        image.Source = frozenSource;
                    });
                }
            }
        }
        catch (Exception ex)
        {
            IsRunning = false;
            App.Logger.LogError($"\"{PluginMetadata.Name}\" - {ex}", source: "Plugin");
            _ = await Dispatcher.InvokeAsync(async () =>
            {
                Wpf.Ui.Controls.MessageBox messageBox = new Wpf.Ui.Controls.MessageBox
                {
                    Title = $"Error \"{PluginMetadata.Name}\"",
                    Content = "File execution error:\n" + ex,
                    CloseButtonText = "Ok"
                };
                _ = await messageBox.ShowDialogAsync();
            });
            Exit();
            return;
        }

        if (!IsRunning)
        {
            updateTimer!.Stop();
        }
    }

    private void Border_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        rectangleGeometry.Rect = new Rect(-settings.Theme.Margin, -settings.Theme.Margin, e.NewSize.Width, e.NewSize.Height);
    }

    private void ViewBox_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        border.Width = e.NewSize.Width + (settings.Theme.Margin * 2);
        border.Height = e.NewSize.Height + (settings.Theme.Margin * 2);
    }

    private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        IsRunning = false;

        // Cleanup hot reload resources
        if (pluginFileWatcher != null)
        {
            pluginFileWatcher.EnableRaisingEvents = false;
            pluginFileWatcher.Changed -= OnPluginFileChanged;
            pluginFileWatcher.Dispose();
            pluginFileWatcher = null;
        }

        reloadDebounceTimer?.Stop();
        reloadDebounceTimer?.Dispose();
        reloadDebounceTimer = null;

        // Stop plugin using the shared method (no need to await in synchronous event handler)
        _ = StopPlugin(unloadAssembly: true);
    }

    #region Window Events

    private void Window_LocationChanged(object sender, EventArgs e)
    {
        settings.Position = new System.Windows.Point(Left, Top);
    }

    private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        settings.Size = new System.Windows.Point(Width, Height);

        tileBar.CaptionHeight = ActualHeight - 10;
    }

    private void Window_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        ImageSource imageSource = image.Source;
        BitmapSource bitmapImage = (BitmapSource)imageSource;
        double pixelMousePositionX = e.GetPosition(image).X * bitmapImage.PixelWidth / image.ActualHeight;
        double pixelMousePositionY = e.GetPosition(image).Y * bitmapImage.PixelHeight / image.ActualHeight;

        MouseButton mouseButton;

        switch (e.ChangedButton)
        {
            case System.Windows.Input.MouseButton.Left:
                mouseButton = MouseButton.Left;
                break;

            case System.Windows.Input.MouseButton.Middle:
                mouseButton = MouseButton.Middle;
                break;

            case System.Windows.Input.MouseButton.Right:
                mouseButton = MouseButton.Right;
                break;

            default:
                return;
        }

        System.Drawing.Point point = new System.Drawing.Point((int)pixelMousePositionX, (int)pixelMousePositionY);
        pluginClassInstance?.OnMouseClick(point, mouseButton);
    }

    private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
    {
        ImageSource imageSource = image.Source;
        BitmapSource bitmapImage = (BitmapSource)imageSource;
        double pixelMousePositionX = e.GetPosition(image).X * bitmapImage.PixelWidth / image.ActualHeight;
        double pixelMousePositionY = e.GetPosition(image).Y * bitmapImage.PixelHeight / image.ActualHeight;

        System.Drawing.Point point = new System.Drawing.Point((int)pixelMousePositionX, (int)pixelMousePositionY);
        pluginClassInstance?.OnMouseMove(point);
    }

    private void Window_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
    {
        ImageSource imageSource = image.Source;
        BitmapSource bitmapImage = (BitmapSource)imageSource;
        double pixelMousePositionX = e.GetPosition(image).X * bitmapImage.PixelWidth / image.ActualHeight;
        double pixelMousePositionY = e.GetPosition(image).Y * bitmapImage.PixelHeight / image.ActualHeight;

        System.Drawing.Point point = new System.Drawing.Point((int)pixelMousePositionX, (int)pixelMousePositionY);
        pluginClassInstance?.OnMouseWheel(point, e.Delta);
    }

    #endregion Window Events
}