// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Dispatching;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform.Linux.Dispatching;
using Microsoft.Maui.Platform.Linux.Hosting;
using Microsoft.Maui.Platform.Linux.Native;
using Microsoft.Maui.Platform.Linux.Rendering;
using Microsoft.Maui.Platform.Linux.Services;
using Microsoft.Maui.Platform.Linux.Window;
using Microsoft.Maui.Platform;
using SkiaSharp;

namespace Microsoft.Maui.Platform.Linux;

public partial class LinuxApplication
{
    /// <summary>
    /// Runs a MAUI application on Linux.
    /// This is the main entry point for Linux apps.
    /// </summary>
    /// <param name="app">The MauiApp to run.</param>
    /// <param name="args">Command line arguments.</param>
    public static void Run(MauiApp app, string[] args)
    {
        Run(app, args, null);
    }

    /// <summary>
    /// Runs a MAUI application on Linux with options.
    /// </summary>
    /// <param name="app">The MauiApp to run.</param>
    /// <param name="args">Command line arguments.</param>
    /// <param name="configure">Optional configuration action.</param>
    public static void Run(MauiApp app, string[] args, Action<LinuxApplicationOptions>? configure)
    {
        // Force X11 backend for GTK/WebKitGTK - MUST be set before any GTK code runs
        Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");

        // Pre-initialize GTK for WebView compatibility (even when using X11 mode)
        int argc = 0;
        IntPtr argv = IntPtr.Zero;
        if (!GtkNative.gtk_init_check(ref argc, ref argv))
        {
            DiagnosticLog.Warn("LinuxApplication", "GTK initialization failed - WebView may not work");
        }
        else
        {
            DiagnosticLog.Debug("LinuxApplication", "GTK pre-initialized for WebView support");
        }

        // Set application name for desktop integration (taskbar, etc.)
        // Try to get the name from environment or use executable name
        string? appName = Environment.GetEnvironmentVariable("APPIMAGE_NAME");
        if (string.IsNullOrEmpty(appName))
        {
            appName = Path.GetFileNameWithoutExtension(Environment.ProcessPath ?? "MauiApp");
        }
        string prgName = appName.Replace(" ", "");
        GtkNative.g_set_prgname(prgName);
        GtkNative.g_set_application_name(appName);
        DiagnosticLog.Debug("LinuxApplication", $"Set application name: {appName} (prgname: {prgName})");

        // Initialize dispatcher
        LinuxDispatcher.Initialize();
        DispatcherProvider.SetCurrent(LinuxDispatcherProvider.Instance);
        DiagnosticLog.Debug("LinuxApplication", "Dispatcher initialized");

        var options = app.Services.GetService<LinuxApplicationOptions>()
                      ?? new LinuxApplicationOptions();
        configure?.Invoke(options);
        ParseCommandLineOptions(args, options);

        var linuxApp = new LinuxApplication();
        try
        {
            linuxApp.Initialize(options);

            // Create MAUI context
            var mauiContext = new LinuxMauiContext(app.Services, linuxApp);

            // Get the application and render it
            var application = app.Services.GetService<IApplication>();
            SkiaView? rootView = null;

            if (application is Application mauiApplication)
            {
                // Force Application.Current to be this instance
                var currentProperty = typeof(Application).GetProperty("Current");
                if (currentProperty != null && currentProperty.CanWrite)
                {
                    currentProperty.SetValue(null, mauiApplication);
                }

                // Set initial theme based on system theme
                var systemTheme = SystemThemeService.Instance.CurrentTheme;
                DiagnosticLog.Debug("LinuxApplication", $"System theme detected at startup: {systemTheme}");
                if (systemTheme == SystemTheme.Dark)
                {
                    mauiApplication.UserAppTheme = AppTheme.Dark;
                    DiagnosticLog.Debug("LinuxApplication", "Set initial UserAppTheme to Dark based on system theme");
                }
                else
                {
                    mauiApplication.UserAppTheme = AppTheme.Light;
                    DiagnosticLog.Debug("LinuxApplication", "Set initial UserAppTheme to Light based on system theme");
                }

                // Initialize GTK theme service and apply initial CSS
                GtkThemeService.ApplyTheme();

                // Handle user-initiated theme changes
                ((BindableObject)mauiApplication).PropertyChanged += (s, e) =>
                {
                    if (e.PropertyName == "UserAppTheme")
                    {
                        DiagnosticLog.Debug("LinuxApplication", $"User theme changed to: {mauiApplication.UserAppTheme}");

                        // Apply GTK CSS for dialogs, menus, and window decorations
                        GtkThemeService.ApplyTheme();

                        LinuxViewRenderer.CurrentSkiaShell?.RefreshTheme();

                        // Force re-render the entire page to pick up theme changes
                        linuxApp.RefreshPageForThemeChange();

                        // Invalidate to redraw - use correct method based on mode
                        if (linuxApp._useGtk)
                        {
                            linuxApp._gtkWindow?.RequestRedraw();
                        }
                        else
                        {
                            linuxApp._renderingEngine?.InvalidateAll();
                        }
                    }
                };

                // Handle system theme changes (e.g., GNOME/KDE dark mode toggle)
                SystemThemeService.Instance.ThemeChanged += (s, e) =>
                {
                    DiagnosticLog.Debug("LinuxApplication", $"System theme changed to: {e.NewTheme}");

                    // Update MAUI's UserAppTheme to match system theme
                    // This will trigger the PropertyChanged handler which does the refresh
                    var newAppTheme = e.NewTheme == SystemTheme.Dark ? AppTheme.Dark : AppTheme.Light;
                    if (mauiApplication.UserAppTheme != newAppTheme)
                    {
                        DiagnosticLog.Debug("LinuxApplication", $"Setting UserAppTheme to {newAppTheme} to match system");
                        mauiApplication.UserAppTheme = newAppTheme;
                    }
                    else
                    {
                        // If UserAppTheme didn't change (user manually set it), still refresh
                        LinuxViewRenderer.CurrentSkiaShell?.RefreshTheme();
                        linuxApp.RefreshPageForThemeChange();
                        if (linuxApp._useGtk)
                        {
                            linuxApp._gtkWindow?.RequestRedraw();
                        }
                        else
                        {
                            linuxApp._renderingEngine?.InvalidateAll();
                        }
                    }
                };

                // Get the main page - prefer CreateWindow() over deprecated MainPage
                Page? mainPage = null;

                // Try CreateWindow() first (the modern MAUI pattern)
                try
                {
                    // CreateWindow is protected, use reflection
                    var createWindowMethod = typeof(Application).GetMethod("CreateWindow",
                        BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
                        null, new[] { typeof(IActivationState) }, null);

                    if (createWindowMethod != null)
                    {
                        var mauiWindow = createWindowMethod.Invoke(mauiApplication, new object?[] { null }) as Microsoft.Maui.Controls.Window;
                        if (mauiWindow != null)
                        {
                            DiagnosticLog.Debug("LinuxApplication", $"Got Window from CreateWindow: {mauiWindow.GetType().Name}");
                            mainPage = mauiWindow.Page;
                            DiagnosticLog.Debug("LinuxApplication", $"Window.Page: {mainPage?.GetType().Name}");

                            // Add to windows list
                            var windowsField = typeof(Application).GetField("_windows",
                                BindingFlags.NonPublic | BindingFlags.Instance);
                            var windowsList = windowsField?.GetValue(mauiApplication) as List<Microsoft.Maui.Controls.Window>;
                            if (windowsList != null && !windowsList.Contains(mauiWindow))
                            {
                                windowsList.Add(mauiWindow);
                                mauiWindow.Parent = mauiApplication;
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    DiagnosticLog.Error("LinuxApplication", $"CreateWindow failed: {ex.Message}");
                }

                // Fall back to deprecated MainPage if CreateWindow didn't work
                if (mainPage == null && mauiApplication.MainPage != null)
                {
                    DiagnosticLog.Debug("LinuxApplication", $"Falling back to MainPage: {mauiApplication.MainPage.GetType().Name}");
                    mainPage = mauiApplication.MainPage;

                    var windowsField = typeof(Application).GetField("_windows",
                        BindingFlags.NonPublic | BindingFlags.Instance);
                    var windowsList = windowsField?.GetValue(mauiApplication) as List<Microsoft.Maui.Controls.Window>;

                    if (windowsList != null && windowsList.Count == 0)
                    {
                        var mauiWindow = new Microsoft.Maui.Controls.Window(mainPage);
                        windowsList.Add(mauiWindow);
                        mauiWindow.Parent = mauiApplication;
                    }
                    else if (windowsList != null && windowsList.Count > 0 && windowsList[0].Page == null)
                    {
                        windowsList[0].Page = mainPage;
                    }
                }

                if (mainPage != null)
                {
                    var renderer = new LinuxViewRenderer(mauiContext);
                    rootView = renderer.RenderPage(mainPage);

                    string windowTitle = "OpenMaui App";
                    if (mainPage is NavigationPage navPage)
                    {
                        windowTitle = navPage.Title ?? windowTitle;
                    }
                    else if (mainPage is Shell shell)
                    {
                        windowTitle = shell.Title ?? windowTitle;
                    }
                    else
                    {
                        windowTitle = mainPage.Title ?? windowTitle;
                    }
                    linuxApp.SetWindowTitle(windowTitle);
                }
            }

            if (rootView == null)
            {
                rootView = LinuxProgramHost.CreateDemoView();
            }

            linuxApp.RootView = rootView;
            linuxApp.Run();
        }
        finally
        {
            linuxApp?.Dispose();
        }
    }

    private static void ParseCommandLineOptions(string[] args, LinuxApplicationOptions options)
    {
        for (int i = 0; i < args.Length; i++)
        {
            switch (args[i].ToLowerInvariant())
            {
                case "--title" when i + 1 < args.Length:
                    options.Title = args[++i];
                    break;
                case "--width" when i + 1 < args.Length && int.TryParse(args[i + 1], out var w):
                    options.Width = w;
                    i++;
                    break;
                case "--height" when i + 1 < args.Length && int.TryParse(args[i + 1], out var h):
                    options.Height = h;
                    i++;
                    break;
            }
        }
    }

    /// <summary>
    /// Shows the main window and runs the event loop.
    /// </summary>
    public void Run()
    {
        if (_useGtk)
        {
            RunGtk();
        }
        else
        {
            RunX11();
        }
    }

    private void RunX11()
    {
        if (_mainWindow == null)
            throw new InvalidOperationException("Application not initialized");

        _mainWindow.Show();
        Render();

        DiagnosticLog.Debug("LinuxApplication", "Starting event loop");
        while (_mainWindow.IsRunning)
        {
            _loopCounter++;
            if (_loopCounter % 1000 == 0)
            {
                DiagnosticLog.Debug("LinuxApplication", $"Loop iteration {_loopCounter}");
            }

            _mainWindow.ProcessEvents();
            SkiaWebView.ProcessGtkEvents();
            UpdateAnimations();
            Render();
            Thread.Sleep(1);
        }
        DiagnosticLog.Debug("LinuxApplication", "Event loop ended");
    }

    private void RunGtk()
    {
        if (_gtkWindow == null)
            throw new InvalidOperationException("Application not initialized");

        StartHeartbeat();
        PerformGtkLayout(_gtkWindow.Width, _gtkWindow.Height);
        _gtkWindow.RequestRedraw();
        _gtkWindow.Run();
        GtkHostService.Instance.Shutdown();
    }

    private void PerformGtkLayout(int width, int height)
    {
        if (_rootView != null)
        {
            _rootView.Measure(new Microsoft.Maui.Graphics.Size(width, height));
            _rootView.Arrange(new Microsoft.Maui.Graphics.Rect(0, 0, width, height));
        }
    }

    /// <summary>
    /// Forces all views to refresh their theme-dependent properties.
    /// This is needed because AppThemeBinding may not automatically trigger
    /// property mappers on all platforms.
    /// </summary>
    private void RefreshPageForThemeChange()
    {
        DiagnosticLog.Debug("LinuxApplication", "RefreshPageForThemeChange - forcing property updates");

        // First, try to trigger MAUI's RequestedThemeChanged event using reflection
        // This ensures AppThemeBinding bindings re-evaluate
        TriggerMauiThemeChanged();

        if (_rootView == null) return;

        // Traverse the visual tree and force theme-dependent properties to update
        RefreshViewTheme(_rootView);
    }

    /// <summary>
    /// Called after theme change to refresh views.
    /// Note: MAUI's Application.UserAppTheme setter automatically triggers RequestedThemeChanged
    /// via WeakEventManager, which AppThemeBinding subscribes to. This method handles
    /// any additional platform-specific refresh needed.
    /// </summary>
    private void TriggerMauiThemeChanged()
    {
        var app = Application.Current;
        if (app == null) return;

        DiagnosticLog.Debug("LinuxApplication", $"Theme is now: {app.UserAppTheme}, RequestedTheme: {app.RequestedTheme}");
    }

    private void RefreshViewTheme(SkiaView view)
    {
        // Get the associated MAUI view and handler
        var mauiView = view.MauiView;
        var handler = mauiView?.Handler;

        if (handler != null && mauiView != null)
        {
            // Force key properties to be re-mapped
            // This ensures theme-dependent bindings are re-evaluated
            try
            {
                // Background/BackgroundColor - both need updating for AppThemeBinding
                handler.UpdateValue(nameof(IView.Background));
                handler.UpdateValue("BackgroundColor");

                // For ImageButton, force Source to be re-mapped
                if (mauiView is Microsoft.Maui.Controls.ImageButton)
                {
                    handler.UpdateValue(nameof(IImageSourcePart.Source));
                }

                // For Image, force Source to be re-mapped
                if (mauiView is Microsoft.Maui.Controls.Image)
                {
                    handler.UpdateValue(nameof(IImageSourcePart.Source));
                }

                // For views with text colors
                if (mauiView is ITextStyle)
                {
                    handler.UpdateValue(nameof(ITextStyle.TextColor));
                }

                // For Entry/Editor placeholder colors
                if (mauiView is IPlaceholder)
                {
                    handler.UpdateValue(nameof(IPlaceholder.PlaceholderColor));
                }

                // For Border stroke
                if (mauiView is IBorderStroke)
                {
                    handler.UpdateValue(nameof(IBorderStroke.Stroke));
                }
            }
            catch (Exception ex)
            {
                DiagnosticLog.Error("LinuxApplication", $"Error refreshing theme for {mauiView.GetType().Name}: {ex.Message}");
            }
        }

        // Special handling for ItemsViews (CollectionView, ListView)
        // Their item views are cached separately and need to be refreshed
        if (view is SkiaItemsView itemsView)
        {
            itemsView.RefreshTheme();
        }

        // Special handling for NavigationPage - it stores content in _currentPage
        if (view is SkiaNavigationPage navPage && navPage.CurrentPage != null)
        {
            RefreshViewTheme(navPage.CurrentPage);
            navPage.Invalidate(); // Force redraw of navigation page
        }

        // Special handling for SkiaPage - refresh via MauiPage handler and process Content
        if (view is SkiaPage page)
        {
            // Refresh page properties via handler if MauiPage is set
            var pageHandler = page.MauiPage?.Handler;
            if (pageHandler != null)
            {
                try
                {
                    DiagnosticLog.Debug("LinuxApplication", $"Refreshing page theme: {page.MauiPage?.GetType().Name}");
                    pageHandler.UpdateValue(nameof(IView.Background));
                    pageHandler.UpdateValue("BackgroundColor");
                }
                catch (Exception ex)
                {
                    DiagnosticLog.Error("LinuxApplication", $"Error refreshing page theme: {ex.Message}");
                }
            }

            page.Invalidate(); // Force redraw to pick up theme-aware background
            if (page.Content != null)
            {
                RefreshViewTheme(page.Content);
            }
        }

        // Recursively process children
        // Note: SkiaLayoutView hides SkiaView.Children with 'new', so we need to cast
        IReadOnlyList<SkiaView> children = view is SkiaLayoutView layout ? layout.Children : view.Children;
        foreach (var child in children)
        {
            RefreshViewTheme(child);
        }
    }

    private void Render()
    {
        if (_renderingEngine != null && _rootView != null)
        {
            _renderingEngine.Render(_rootView);
        }
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _renderingEngine?.Dispose();
            _mainWindow?.Dispose();

            if (Current == this)
                Current = null;

            _disposed = true;
        }
    }
}
