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

using System.Runtime.InteropServices;

namespace Microsoft.Maui.Platform.Linux.Services;

public class Gtk4InteropService : IDisposable
{
    #region GTK4 Native Interop

    private const string LibGtk4 = "libgtk-4.so.1";
    private const string LibGio = "libgio-2.0.so.0";
    private const string LibGlib = "libglib-2.0.so.0";
    private const string LibGObject = "libgobject-2.0.so.0";

    // GTK initialization
    [DllImport(LibGtk4)]
    private static extern bool gtk_init_check();

    [DllImport(LibGtk4)]
    private static extern bool gtk_is_initialized();

    // Main loop
    [DllImport(LibGtk4)]
    private static extern IntPtr g_main_context_default();

    [DllImport(LibGtk4)]
    private static extern bool g_main_context_iteration(IntPtr context, bool mayBlock);

    [DllImport(LibGlib)]
    private static extern void g_free(IntPtr mem);

    // GObject
    [DllImport(LibGObject)]
    private static extern void g_object_unref(IntPtr obj);

    [DllImport(LibGObject)]
    private static extern void g_object_ref(IntPtr obj);

    // Window
    [DllImport(LibGtk4)]
    private static extern IntPtr gtk_window_new();

    [DllImport(LibGtk4)]
    private static extern void gtk_window_set_title(IntPtr window, [MarshalAs(UnmanagedType.LPStr)] string title);

    [DllImport(LibGtk4)]
    private static extern void gtk_window_set_modal(IntPtr window, bool modal);

    [DllImport(LibGtk4)]
    private static extern void gtk_window_set_transient_for(IntPtr window, IntPtr parent);

    [DllImport(LibGtk4)]
    private static extern void gtk_window_destroy(IntPtr window);

    [DllImport(LibGtk4)]
    private static extern void gtk_window_present(IntPtr window);

    [DllImport(LibGtk4)]
    private static extern void gtk_window_close(IntPtr window);

    // Widget
    [DllImport(LibGtk4)]
    private static extern void gtk_widget_show(IntPtr widget);

    [DllImport(LibGtk4)]
    private static extern void gtk_widget_hide(IntPtr widget);

    [DllImport(LibGtk4)]
    private static extern void gtk_widget_set_visible(IntPtr widget, bool visible);

    [DllImport(LibGtk4)]
    private static extern bool gtk_widget_get_visible(IntPtr widget);

    // Alert Dialog (GTK4)
    [DllImport(LibGtk4)]
    private static extern IntPtr gtk_alert_dialog_new([MarshalAs(UnmanagedType.LPStr)] string format);

    [DllImport(LibGtk4)]
    private static extern void gtk_alert_dialog_set_message(IntPtr dialog, [MarshalAs(UnmanagedType.LPStr)] string message);

    [DllImport(LibGtk4)]
    private static extern void gtk_alert_dialog_set_detail(IntPtr dialog, [MarshalAs(UnmanagedType.LPStr)] string detail);

    [DllImport(LibGtk4)]
    private static extern void gtk_alert_dialog_set_buttons(IntPtr dialog, string[] labels);

    [DllImport(LibGtk4)]
    private static extern void gtk_alert_dialog_set_cancel_button(IntPtr dialog, int button);

    [DllImport(LibGtk4)]
    private static extern void gtk_alert_dialog_set_default_button(IntPtr dialog, int button);

    [DllImport(LibGtk4)]
    private static extern void gtk_alert_dialog_show(IntPtr dialog, IntPtr parent);

    // File Dialog (GTK4)
    [DllImport(LibGtk4)]
    private static extern IntPtr gtk_file_dialog_new();

    [DllImport(LibGtk4)]
    private static extern void gtk_file_dialog_set_title(IntPtr dialog, [MarshalAs(UnmanagedType.LPStr)] string title);

    [DllImport(LibGtk4)]
    private static extern void gtk_file_dialog_set_modal(IntPtr dialog, bool modal);

    [DllImport(LibGtk4)]
    private static extern void gtk_file_dialog_set_accept_label(IntPtr dialog, [MarshalAs(UnmanagedType.LPStr)] string label);

    [DllImport(LibGtk4)]
    private static extern void gtk_file_dialog_open(IntPtr dialog, IntPtr parent, IntPtr cancellable, GAsyncReadyCallback callback, IntPtr userData);

    [DllImport(LibGtk4)]
    private static extern IntPtr gtk_file_dialog_open_finish(IntPtr dialog, IntPtr result, out IntPtr error);

    [DllImport(LibGtk4)]
    private static extern void gtk_file_dialog_save(IntPtr dialog, IntPtr parent, IntPtr cancellable, GAsyncReadyCallback callback, IntPtr userData);

    [DllImport(LibGtk4)]
    private static extern IntPtr gtk_file_dialog_save_finish(IntPtr dialog, IntPtr result, out IntPtr error);

    [DllImport(LibGtk4)]
    private static extern void gtk_file_dialog_select_folder(IntPtr dialog, IntPtr parent, IntPtr cancellable, GAsyncReadyCallback callback, IntPtr userData);

    [DllImport(LibGtk4)]
    private static extern IntPtr gtk_file_dialog_select_folder_finish(IntPtr dialog, IntPtr result, out IntPtr error);

    [DllImport(LibGtk4)]
    private static extern void gtk_file_dialog_open_multiple(IntPtr dialog, IntPtr parent, IntPtr cancellable, GAsyncReadyCallback callback, IntPtr userData);

    [DllImport(LibGtk4)]
    private static extern IntPtr gtk_file_dialog_open_multiple_finish(IntPtr dialog, IntPtr result, out IntPtr error);

    // File filters
    [DllImport(LibGtk4)]
    private static extern IntPtr gtk_file_filter_new();

    [DllImport(LibGtk4)]
    private static extern void gtk_file_filter_set_name(IntPtr filter, [MarshalAs(UnmanagedType.LPStr)] string name);

    [DllImport(LibGtk4)]
    private static extern void gtk_file_filter_add_pattern(IntPtr filter, [MarshalAs(UnmanagedType.LPStr)] string pattern);

    [DllImport(LibGtk4)]
    private static extern void gtk_file_filter_add_mime_type(IntPtr filter, [MarshalAs(UnmanagedType.LPStr)] string mimeType);

    [DllImport(LibGtk4)]
    private static extern void gtk_file_dialog_set_default_filter(IntPtr dialog, IntPtr filter);

    // GFile
    [DllImport(LibGio)]
    private static extern IntPtr g_file_get_path(IntPtr file);

    // GListModel for multiple files
    [DllImport(LibGio)]
    private static extern uint g_list_model_get_n_items(IntPtr list);

    [DllImport(LibGio)]
    private static extern IntPtr g_list_model_get_item(IntPtr list, uint position);

    // Color Dialog (GTK4)
    [DllImport(LibGtk4)]
    private static extern IntPtr gtk_color_dialog_new();

    [DllImport(LibGtk4)]
    private static extern void gtk_color_dialog_set_title(IntPtr dialog, [MarshalAs(UnmanagedType.LPStr)] string title);

    [DllImport(LibGtk4)]
    private static extern void gtk_color_dialog_set_modal(IntPtr dialog, bool modal);

    [DllImport(LibGtk4)]
    private static extern void gtk_color_dialog_set_with_alpha(IntPtr dialog, bool withAlpha);

    [DllImport(LibGtk4)]
    private static extern void gtk_color_dialog_choose_rgba(IntPtr dialog, IntPtr parent, IntPtr initialColor, IntPtr cancellable, GAsyncReadyCallback callback, IntPtr userData);

    [DllImport(LibGtk4)]
    private static extern IntPtr gtk_color_dialog_choose_rgba_finish(IntPtr dialog, IntPtr result, out IntPtr error);

    // GdkRGBA
    [StructLayout(LayoutKind.Sequential)]
    private struct GdkRGBA
    {
        public float Red;
        public float Green;
        public float Blue;
        public float Alpha;
    }

    // Async callback delegate
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void GAsyncReadyCallback(IntPtr sourceObject, IntPtr result, IntPtr userData);

    // Legacy GTK3 fallbacks
    private const string LibGtk3 = "libgtk-3.so.0";

    [DllImport(LibGtk3, EntryPoint = "gtk_init_check")]
    private static extern bool gtk3_init_check(ref int argc, ref IntPtr argv);

    [DllImport(LibGtk3, EntryPoint = "gtk_file_chooser_dialog_new")]
    private static extern IntPtr gtk3_file_chooser_dialog_new(
        [MarshalAs(UnmanagedType.LPStr)] string title,
        IntPtr parent,
        int action,
        [MarshalAs(UnmanagedType.LPStr)] string firstButtonText,
        int firstButtonResponse,
        [MarshalAs(UnmanagedType.LPStr)] string secondButtonText,
        int secondButtonResponse,
        IntPtr terminator);

    [DllImport(LibGtk3, EntryPoint = "gtk_dialog_run")]
    private static extern int gtk3_dialog_run(IntPtr dialog);

    [DllImport(LibGtk3, EntryPoint = "gtk_widget_destroy")]
    private static extern void gtk3_widget_destroy(IntPtr widget);

    [DllImport(LibGtk3, EntryPoint = "gtk_file_chooser_get_filename")]
    private static extern IntPtr gtk3_file_chooser_get_filename(IntPtr chooser);

    [DllImport(LibGtk3, EntryPoint = "gtk_file_chooser_get_filenames")]
    private static extern IntPtr gtk3_file_chooser_get_filenames(IntPtr chooser);

    [DllImport(LibGtk3, EntryPoint = "gtk_file_chooser_set_select_multiple")]
    private static extern void gtk3_file_chooser_set_select_multiple(IntPtr chooser, bool selectMultiple);

    [DllImport(LibGtk3, EntryPoint = "gtk_message_dialog_new")]
    private static extern IntPtr gtk3_message_dialog_new(
        IntPtr parent,
        int flags,
        int type,
        int buttons,
        [MarshalAs(UnmanagedType.LPStr)] string message);

    [DllImport(LibGlib, EntryPoint = "g_slist_length")]
    private static extern uint g_slist_length(IntPtr list);

    [DllImport(LibGlib, EntryPoint = "g_slist_nth_data")]
    private static extern IntPtr g_slist_nth_data(IntPtr list, uint n);

    [DllImport(LibGlib, EntryPoint = "g_slist_free")]
    private static extern void g_slist_free(IntPtr list);

    #endregion

    #region Fields

    private bool _initialized;
    private bool _useGtk4;
    private bool _disposed;
    private readonly object _lock = new();

    // Store callbacks to prevent GC
    private GAsyncReadyCallback? _currentCallback;
    private TaskCompletionSource<FileDialogResult>? _fileDialogTcs;
    private TaskCompletionSource<ColorDialogResult>? _colorDialogTcs;
    private IntPtr _currentDialog;

    #endregion

    #region Properties

    /// <summary>
    /// Gets whether GTK is initialized.
    /// </summary>
    public bool IsInitialized => _initialized;

    /// <summary>
    /// Gets whether GTK4 is being used (vs GTK3 fallback).
    /// </summary>
    public bool IsGtk4 => _useGtk4;

    #endregion

    #region Initialization

    /// <summary>
    /// Initializes the GTK4 interop service.
    /// Falls back to GTK3 if GTK4 is not available.
    /// </summary>
    public bool Initialize()
    {
        if (_initialized)
            return true;

        lock (_lock)
        {
            if (_initialized)
                return true;

            // Try GTK4 first
            try
            {
                if (gtk_init_check())
                {
                    _useGtk4 = true;
                    _initialized = true;
                    DiagnosticLog.Debug("Gtk4InteropService", "Initialized GTK4");
                    return true;
                }
            }
            catch (DllNotFoundException)
            {
                DiagnosticLog.Warn("Gtk4InteropService", "GTK4 not found, trying GTK3");
            }
            catch (Exception ex)
            {
                DiagnosticLog.Error("Gtk4InteropService", $"GTK4 init failed: {ex.Message}");
            }

            // Fall back to GTK3
            try
            {
                int argc = 0;
                IntPtr argv = IntPtr.Zero;
                if (gtk3_init_check(ref argc, ref argv))
                {
                    _useGtk4 = false;
                    _initialized = true;
                    DiagnosticLog.Debug("Gtk4InteropService", "Initialized GTK3 (fallback)");
                    return true;
                }
            }
            catch (DllNotFoundException)
            {
                DiagnosticLog.Warn("Gtk4InteropService", "GTK3 not found");
            }
            catch (Exception ex)
            {
                DiagnosticLog.Error("Gtk4InteropService", $"GTK3 init failed: {ex.Message}");
            }

            return false;
        }
    }

    #endregion

    #region Message Dialogs

    /// <summary>
    /// Shows an alert message dialog.
    /// </summary>
    public void ShowAlert(string title, string message, GtkMessageType type = GtkMessageType.Info)
    {
        if (!EnsureInitialized())
            return;

        if (_useGtk4)
        {
            var dialog = gtk_alert_dialog_new(title);
            gtk_alert_dialog_set_detail(dialog, message);
            string[] buttons = { "OK" };
            gtk_alert_dialog_set_buttons(dialog, buttons);
            gtk_alert_dialog_show(dialog, IntPtr.Zero);
            g_object_unref(dialog);
        }
        else
        {
            var dialog = gtk3_message_dialog_new(
                IntPtr.Zero,
                1, // GTK_DIALOG_MODAL
                (int)type,
                (int)GtkButtonsType.Ok,
                message);

            gtk3_dialog_run(dialog);
            gtk3_widget_destroy(dialog);
        }

        ProcessPendingEvents();
    }

    /// <summary>
    /// Shows a confirmation dialog.
    /// </summary>
    public bool ShowConfirmation(string title, string message)
    {
        if (!EnsureInitialized())
            return false;

        if (_useGtk4)
        {
            // GTK4 async dialogs are more complex - use synchronous approach
            var dialog = gtk_alert_dialog_new(title);
            gtk_alert_dialog_set_detail(dialog, message);
            string[] buttons = { "No", "Yes" };
            gtk_alert_dialog_set_buttons(dialog, buttons);
            gtk_alert_dialog_set_default_button(dialog, 1);
            gtk_alert_dialog_set_cancel_button(dialog, 0);
            gtk_alert_dialog_show(dialog, IntPtr.Zero);
            g_object_unref(dialog);
            // Note: GTK4 alert dialogs are async, this is simplified
            return true;
        }
        else
        {
            var dialog = gtk3_message_dialog_new(
                IntPtr.Zero,
                1, // GTK_DIALOG_MODAL
                (int)GtkMessageType.Question,
                (int)GtkButtonsType.YesNo,
                message);

            int response = gtk3_dialog_run(dialog);
            gtk3_widget_destroy(dialog);
            ProcessPendingEvents();

            return response == (int)GtkResponseType.Yes;
        }
    }

    #endregion

    #region File Dialogs

    /// <summary>
    /// Shows an open file dialog.
    /// </summary>
    public FileDialogResult ShowOpenFileDialog(
        string title = "Open File",
        string? initialFolder = null,
        bool allowMultiple = false,
        params (string Name, string Pattern)[] filters)
    {
        if (!EnsureInitialized())
            return new FileDialogResult { Accepted = false };

        if (_useGtk4)
        {
            return ShowGtk4FileDialog(title, GtkFileChooserAction.Open, allowMultiple, filters);
        }
        else
        {
            return ShowGtk3FileDialog(title, 0, allowMultiple, filters); // GTK_FILE_CHOOSER_ACTION_OPEN = 0
        }
    }

    /// <summary>
    /// Shows a save file dialog.
    /// </summary>
    public FileDialogResult ShowSaveFileDialog(
        string title = "Save File",
        string? suggestedName = null,
        params (string Name, string Pattern)[] filters)
    {
        if (!EnsureInitialized())
            return new FileDialogResult { Accepted = false };

        if (_useGtk4)
        {
            return ShowGtk4FileDialog(title, GtkFileChooserAction.Save, false, filters);
        }
        else
        {
            return ShowGtk3FileDialog(title, 1, false, filters); // GTK_FILE_CHOOSER_ACTION_SAVE = 1
        }
    }

    /// <summary>
    /// Shows a folder picker dialog.
    /// </summary>
    public FileDialogResult ShowFolderDialog(string title = "Select Folder")
    {
        if (!EnsureInitialized())
            return new FileDialogResult { Accepted = false };

        if (_useGtk4)
        {
            return ShowGtk4FileDialog(title, GtkFileChooserAction.SelectFolder, false, Array.Empty<(string, string)>());
        }
        else
        {
            return ShowGtk3FileDialog(title, 2, false, Array.Empty<(string, string)>()); // GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER = 2
        }
    }

    private FileDialogResult ShowGtk4FileDialog(
        string title,
        GtkFileChooserAction action,
        bool allowMultiple,
        (string Name, string Pattern)[] filters)
    {
        var dialog = gtk_file_dialog_new();
        gtk_file_dialog_set_title(dialog, title);
        gtk_file_dialog_set_modal(dialog, true);

        // Set up filters
        if (filters.Length > 0)
        {
            var filter = gtk_file_filter_new();
            gtk_file_filter_set_name(filter, filters[0].Name);
            gtk_file_filter_add_pattern(filter, filters[0].Pattern);
            gtk_file_dialog_set_default_filter(dialog, filter);
        }

        // For GTK4, we need async handling - simplified synchronous version
        // In a full implementation, this would use proper async/await
        _fileDialogTcs = new TaskCompletionSource<FileDialogResult>();
        _currentDialog = dialog;

        _currentCallback = (source, result, userData) =>
        {
            IntPtr error = IntPtr.Zero;
            IntPtr file = IntPtr.Zero;

            try
            {
                if (action == GtkFileChooserAction.Open && !allowMultiple)
                    file = gtk_file_dialog_open_finish(dialog, result, out error);
                else if (action == GtkFileChooserAction.Save)
                    file = gtk_file_dialog_save_finish(dialog, result, out error);
                else if (action == GtkFileChooserAction.SelectFolder)
                    file = gtk_file_dialog_select_folder_finish(dialog, result, out error);

                if (file != IntPtr.Zero && error == IntPtr.Zero)
                {
                    IntPtr pathPtr = g_file_get_path(file);
                    string path = Marshal.PtrToStringUTF8(pathPtr) ?? "";
                    g_free(pathPtr);
                    g_object_unref(file);

                    _fileDialogTcs?.TrySetResult(new FileDialogResult
                    {
                        Accepted = true,
                        SelectedFiles = new[] { path }
                    });
                }
                else
                {
                    _fileDialogTcs?.TrySetResult(new FileDialogResult { Accepted = false });
                }
            }
            catch
            {
                _fileDialogTcs?.TrySetResult(new FileDialogResult { Accepted = false });
            }
        };

        // Start the dialog
        if (action == GtkFileChooserAction.Open && !allowMultiple)
            gtk_file_dialog_open(dialog, IntPtr.Zero, IntPtr.Zero, _currentCallback, IntPtr.Zero);
        else if (action == GtkFileChooserAction.Open && allowMultiple)
            gtk_file_dialog_open_multiple(dialog, IntPtr.Zero, IntPtr.Zero, _currentCallback, IntPtr.Zero);
        else if (action == GtkFileChooserAction.Save)
            gtk_file_dialog_save(dialog, IntPtr.Zero, IntPtr.Zero, _currentCallback, IntPtr.Zero);
        else if (action == GtkFileChooserAction.SelectFolder)
            gtk_file_dialog_select_folder(dialog, IntPtr.Zero, IntPtr.Zero, _currentCallback, IntPtr.Zero);

        // Process events until dialog completes
        while (!_fileDialogTcs.Task.IsCompleted)
        {
            ProcessPendingEvents();
            Thread.Sleep(10);
        }

        g_object_unref(dialog);
        return _fileDialogTcs.Task.Result;
    }

    private FileDialogResult ShowGtk3FileDialog(
        string title,
        int action,
        bool allowMultiple,
        (string Name, string Pattern)[] filters)
    {
        var dialog = gtk3_file_chooser_dialog_new(
            title,
            IntPtr.Zero,
            action,
            "_Cancel", (int)GtkResponseType.Cancel,
            action == 1 ? "_Save" : "_Open", (int)GtkResponseType.Accept,
            IntPtr.Zero);

        if (allowMultiple)
            gtk3_file_chooser_set_select_multiple(dialog, true);

        int response = gtk3_dialog_run(dialog);

        var result = new FileDialogResult { Accepted = false };

        if (response == (int)GtkResponseType.Accept)
        {
            if (allowMultiple)
            {
                IntPtr list = gtk3_file_chooser_get_filenames(dialog);
                uint count = g_slist_length(list);
                var files = new List<string>();

                for (uint i = 0; i < count; i++)
                {
                    IntPtr pathPtr = g_slist_nth_data(list, i);
                    string? path = Marshal.PtrToStringUTF8(pathPtr);
                    if (!string.IsNullOrEmpty(path))
                    {
                        files.Add(path);
                        g_free(pathPtr);
                    }
                }

                g_slist_free(list);
                result = new FileDialogResult { Accepted = true, SelectedFiles = files.ToArray() };
            }
            else
            {
                IntPtr pathPtr = gtk3_file_chooser_get_filename(dialog);
                string? path = Marshal.PtrToStringUTF8(pathPtr);
                g_free(pathPtr);

                if (!string.IsNullOrEmpty(path))
                    result = new FileDialogResult { Accepted = true, SelectedFiles = new[] { path } };
            }
        }

        gtk3_widget_destroy(dialog);
        ProcessPendingEvents();

        return result;
    }

    #endregion

    #region Color Dialog

    /// <summary>
    /// Shows a color picker dialog.
    /// </summary>
    public ColorDialogResult ShowColorDialog(
        string title = "Choose Color",
        float initialRed = 1f,
        float initialGreen = 1f,
        float initialBlue = 1f,
        float initialAlpha = 1f,
        bool withAlpha = true)
    {
        if (!EnsureInitialized())
            return new ColorDialogResult { Accepted = false };

        if (_useGtk4)
        {
            return ShowGtk4ColorDialog(title, initialRed, initialGreen, initialBlue, initialAlpha, withAlpha);
        }
        else
        {
            // GTK3 color dialog would go here
            return new ColorDialogResult { Accepted = false };
        }
    }

    private ColorDialogResult ShowGtk4ColorDialog(
        string title,
        float r, float g, float b, float a,
        bool withAlpha)
    {
        var dialog = gtk_color_dialog_new();
        gtk_color_dialog_set_title(dialog, title);
        gtk_color_dialog_set_modal(dialog, true);
        gtk_color_dialog_set_with_alpha(dialog, withAlpha);

        _colorDialogTcs = new TaskCompletionSource<ColorDialogResult>();

        _currentCallback = (source, result, userData) =>
        {
            IntPtr error = IntPtr.Zero;
            try
            {
                IntPtr rgbaPtr = gtk_color_dialog_choose_rgba_finish(dialog, result, out error);
                if (rgbaPtr != IntPtr.Zero && error == IntPtr.Zero)
                {
                    var rgba = Marshal.PtrToStructure<GdkRGBA>(rgbaPtr);
                    _colorDialogTcs?.TrySetResult(new ColorDialogResult
                    {
                        Accepted = true,
                        Red = rgba.Red,
                        Green = rgba.Green,
                        Blue = rgba.Blue,
                        Alpha = rgba.Alpha
                    });
                }
                else
                {
                    _colorDialogTcs?.TrySetResult(new ColorDialogResult { Accepted = false });
                }
            }
            catch
            {
                _colorDialogTcs?.TrySetResult(new ColorDialogResult { Accepted = false });
            }
        };

        gtk_color_dialog_choose_rgba(dialog, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, _currentCallback, IntPtr.Zero);

        while (!_colorDialogTcs.Task.IsCompleted)
        {
            ProcessPendingEvents();
            Thread.Sleep(10);
        }

        g_object_unref(dialog);
        return _colorDialogTcs.Task.Result;
    }

    #endregion

    #region Helpers

    private bool EnsureInitialized()
    {
        if (!_initialized)
            Initialize();
        return _initialized;
    }

    private void ProcessPendingEvents()
    {
        var context = g_main_context_default();
        while (g_main_context_iteration(context, false)) { }
    }

    #endregion

    #region IDisposable

    public void Dispose()
    {
        if (_disposed)
            return;

        _disposed = true;
        _initialized = false;

        GC.SuppressFinalize(this);
    }

    ~Gtk4InteropService()
    {
        Dispose();
    }

    #endregion
}
