// 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.Net;
using System.Runtime.InteropServices;
using Microsoft.Maui.Platform.Linux.Services;
using SkiaSharp;

namespace Microsoft.Maui.Platform;

/// <summary>
/// WebView implementation using WebKitGTK for Linux.
/// Renders web content in a native GTK window embedded via X11.
/// </summary>
public class SkiaWebView : SkiaView
{
    #region Delegates

    private delegate void LoadChangedCallback(IntPtr webView, int loadEvent, IntPtr userData);
    private delegate IntPtr WebKitWebViewNewDelegate();
    private delegate void WebKitWebViewLoadUriDelegate(IntPtr webView, [MarshalAs(UnmanagedType.LPStr)] string uri);
    private delegate void WebKitWebViewLoadHtmlDelegate(IntPtr webView, [MarshalAs(UnmanagedType.LPStr)] string html, [MarshalAs(UnmanagedType.LPStr)] string? baseUri);
    private delegate IntPtr WebKitWebViewGetUriDelegate(IntPtr webView);
    private delegate IntPtr WebKitWebViewGetTitleDelegate(IntPtr webView);
    private delegate void WebKitWebViewGoBackDelegate(IntPtr webView);
    private delegate void WebKitWebViewGoForwardDelegate(IntPtr webView);
    private delegate bool WebKitWebViewCanGoBackDelegate(IntPtr webView);
    private delegate bool WebKitWebViewCanGoForwardDelegate(IntPtr webView);
    private delegate void WebKitWebViewReloadDelegate(IntPtr webView);
    private delegate void WebKitWebViewStopLoadingDelegate(IntPtr webView);
    private delegate double WebKitWebViewGetEstimatedLoadProgressDelegate(IntPtr webView);
    private delegate IntPtr WebKitWebViewGetSettingsDelegate(IntPtr webView);
    private delegate void WebKitSettingsSetEnableJavascriptDelegate(IntPtr settings, bool enabled);
    private delegate void WebKitSettingsSetHardwareAccelerationPolicyDelegate(IntPtr settings, int policy);
    private delegate void WebKitSettingsSetEnableWebglDelegate(IntPtr settings, bool enabled);
    private delegate void WebKitSettingsSetUserAgentDelegate(IntPtr settings, [MarshalAs(UnmanagedType.LPStr)] string userAgent);
    private delegate IntPtr WebKitSettingsGetUserAgentDelegate(IntPtr settings);
    private delegate void WebKitWebViewRunJavascriptDelegate(IntPtr webView, [MarshalAs(UnmanagedType.LPStr)] string script, IntPtr cancellable, IntPtr callback, IntPtr userData);

    #endregion

    #region X11 Structures

    private struct XWindowAttributes
    {
        public int x;
        public int y;
        public int width;
        public int height;
        public int border_width;
        public int depth;
        public IntPtr visual;
        public IntPtr root;
        public int c_class;
        public int bit_gravity;
        public int win_gravity;
        public int backing_store;
        public ulong backing_planes;
        public ulong backing_pixel;
        public int save_under;
        public IntPtr colormap;
        public int map_installed;
        public int map_state;
        public long all_event_masks;
        public long your_event_mask;
        public long do_not_propagate_mask;
        public int override_redirect;
        public IntPtr screen;
    }

    #endregion

    #region Constants

    private const string LibGtk4 = "libgtk-4.so.1";
    private const string LibGtk3 = "libgtk-3.so.0";
    private const string LibWebKit2Gtk4 = "libwebkitgtk-6.0.so.4";
    private const string LibWebKit2Gtk3 = "libwebkit2gtk-4.1.so.0";
    private const string LibGObject = "libgobject-2.0.so.0";
    private const string LibGLib = "libglib-2.0.so.0";
    private const string LibGdk4 = "libgtk-4.so.1";
    private const string LibGdk3 = "libgdk-3.so.0";
    private const string LibX11 = "libX11.so.6";
    private const int RTLD_NOW = 2;
    private const int RTLD_GLOBAL = 256;

    #endregion

    #region Static Fields

    private static bool _useGtk4;
    private static bool _gtkInitialized;
    private static string _webkitLib = LibWebKit2Gtk3;
    private static LoadChangedCallback? _loadChangedCallback;
    private static IntPtr _webkitHandle;
    private static IntPtr _mainDisplay;
    private static IntPtr _mainWindow;

    private static WebKitWebViewNewDelegate? _webkitWebViewNew;
    private static WebKitWebViewLoadUriDelegate? _webkitLoadUri;
    private static WebKitWebViewLoadHtmlDelegate? _webkitLoadHtml;
    private static WebKitWebViewGetUriDelegate? _webkitGetUri;
    private static WebKitWebViewGetTitleDelegate? _webkitGetTitle;
    private static WebKitWebViewGoBackDelegate? _webkitGoBack;
    private static WebKitWebViewGoForwardDelegate? _webkitGoForward;
    private static WebKitWebViewCanGoBackDelegate? _webkitCanGoBack;
    private static WebKitWebViewCanGoForwardDelegate? _webkitCanGoForward;
    private static WebKitWebViewReloadDelegate? _webkitReload;
    private static WebKitWebViewStopLoadingDelegate? _webkitStopLoading;
    private static WebKitWebViewGetEstimatedLoadProgressDelegate? _webkitGetProgress;
    private static WebKitWebViewGetSettingsDelegate? _webkitGetSettings;
    private static WebKitSettingsSetEnableJavascriptDelegate? _webkitSetJavascript;
    private static WebKitSettingsSetHardwareAccelerationPolicyDelegate? _webkitSetHardwareAcceleration;
    private static WebKitSettingsSetEnableWebglDelegate? _webkitSetWebgl;
    private static WebKitSettingsSetUserAgentDelegate? _webkitSetUserAgent;
    private static WebKitSettingsGetUserAgentDelegate? _webkitGetUserAgent;
    private static WebKitWebViewRunJavascriptDelegate? _webkitRunJavascript;

    private static readonly List<SkiaWebView> _activeWebViews = new();
    private static readonly Dictionary<IntPtr, SkiaWebView> _webViewInstances = new();

    #endregion

    #region Instance Fields

    private IntPtr _gtkWindow;
    private IntPtr _webView;
    private IntPtr _gtkX11Window;
    private IntPtr _x11Container;
    private string _source = "";
    private string _html = "";
    private bool _isInitialized;
    private bool _isEmbedded;
    private bool _isProperlyReparented;
    private bool _javascriptEnabled = true;
    private string? _userAgent;
    private CookieContainer _cookies = new();
    private double _loadProgress;
    private Rect _lastBounds;
    private int _lastMainX;
    private int _lastMainY;
    private int _lastPosX;
    private int _lastPosY;
    private int _lastWidth;
    private int _lastHeight;

    #endregion

    #region GTK Native Imports

    [DllImport(LibGtk4, EntryPoint = "gtk_init")]
    private static extern void gtk4_init();

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

    [DllImport(LibGtk4, EntryPoint = "gtk_window_new")]
    private static extern IntPtr gtk4_window_new();

    [DllImport(LibGtk3, EntryPoint = "gtk_window_new")]
    private static extern IntPtr gtk3_window_new(int type);

    [DllImport(LibGtk4, EntryPoint = "gtk_window_set_default_size")]
    private static extern void gtk4_window_set_default_size(IntPtr window, int width, int height);

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

    [DllImport(LibGtk3, EntryPoint = "gtk_window_set_default_size")]
    private static extern void gtk3_window_set_default_size(IntPtr window, int width, int height);

    [DllImport(LibGtk3, EntryPoint = "gtk_window_set_title")]
    private static extern void gtk3_window_set_title(IntPtr window, [MarshalAs(UnmanagedType.LPStr)] string title);

    [DllImport(LibGtk4, EntryPoint = "gtk_window_set_child")]
    private static extern void gtk4_window_set_child(IntPtr window, IntPtr child);

    [DllImport(LibGtk3, EntryPoint = "gtk_container_add")]
    private static extern void gtk3_container_add(IntPtr container, IntPtr widget);

    [DllImport(LibGtk4, EntryPoint = "gtk_widget_show")]
    private static extern void gtk4_widget_show(IntPtr widget);

    [DllImport(LibGtk4, EntryPoint = "gtk_window_present")]
    private static extern void gtk4_window_present(IntPtr window);

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

    [DllImport(LibGtk4, EntryPoint = "gtk_widget_hide")]
    private static extern void gtk4_widget_hide(IntPtr widget);

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

    [DllImport(LibGtk4, EntryPoint = "gtk_widget_get_width")]
    private static extern int gtk4_widget_get_width(IntPtr widget);

    [DllImport(LibGtk4, EntryPoint = "gtk_widget_get_height")]
    private static extern int gtk4_widget_get_height(IntPtr widget);

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

    [DllImport(LibGObject)]
    private static extern ulong g_signal_connect_data(IntPtr instance, [MarshalAs(UnmanagedType.LPStr)] string signal, IntPtr handler, IntPtr data, IntPtr destroyData, int flags);

    [DllImport(LibGtk4, EntryPoint = "gtk_native_get_surface")]
    private static extern IntPtr gtk4_native_get_surface(IntPtr native);

    [DllImport(LibGtk4, EntryPoint = "gdk_x11_surface_get_xid")]
    private static extern IntPtr gdk4_x11_surface_get_xid(IntPtr surface);

    [DllImport(LibGtk3, EntryPoint = "gtk_widget_get_window")]
    private static extern IntPtr gtk3_widget_get_window(IntPtr widget);

    [DllImport(LibGdk3, EntryPoint = "gdk_x11_window_get_xid")]
    private static extern IntPtr gdk3_x11_window_get_xid(IntPtr gdkWindow);

    [DllImport(LibGtk4, EntryPoint = "gtk_window_set_decorated")]
    private static extern void gtk4_window_set_decorated(IntPtr window, bool decorated);

    [DllImport(LibGtk3, EntryPoint = "gtk_window_set_decorated")]
    private static extern void gtk3_window_set_decorated(IntPtr window, bool decorated);

    [DllImport(LibGtk3, EntryPoint = "gtk_window_move")]
    private static extern void gtk3_window_move(IntPtr window, int x, int y);

    [DllImport(LibGtk3, EntryPoint = "gtk_window_resize")]
    private static extern void gtk3_window_resize(IntPtr window, int width, int height);

    [DllImport(LibGdk3, EntryPoint = "gdk_window_move_resize")]
    private static extern void gdk3_window_move_resize(IntPtr window, int x, int y, int width, int height);

    [DllImport(LibGdk3, EntryPoint = "gdk_window_move")]
    private static extern void gdk3_gdk_window_move(IntPtr window, int x, int y);

    [DllImport(LibGdk3, EntryPoint = "gdk_window_set_override_redirect")]
    private static extern void gdk3_window_set_override_redirect(IntPtr window, bool override_redirect);

    [DllImport(LibGdk3, EntryPoint = "gdk_window_set_transient_for")]
    private static extern void gdk3_window_set_transient_for(IntPtr window, IntPtr parent);

    [DllImport(LibGdk3, EntryPoint = "gdk_window_raise")]
    private static extern void gdk3_window_raise(IntPtr window);

    [DllImport(LibGdk3, EntryPoint = "gdk_x11_window_foreign_new_for_display")]
    private static extern IntPtr gdk3_x11_window_foreign_new_for_display(IntPtr display, IntPtr window);

    [DllImport(LibGdk3, EntryPoint = "gdk_display_get_default")]
    private static extern IntPtr gdk3_display_get_default();

    [DllImport(LibGtk3, EntryPoint = "gtk_widget_set_parent_window")]
    private static extern void gtk3_widget_set_parent_window(IntPtr widget, IntPtr parentWindow);

    [DllImport(LibGtk3, EntryPoint = "gtk_socket_new")]
    private static extern IntPtr gtk3_socket_new();

    [DllImport(LibGtk3, EntryPoint = "gtk_socket_add_id")]
    private static extern void gtk3_socket_add_id(IntPtr socket, IntPtr windowId);

    [DllImport(LibGtk3, EntryPoint = "gtk_socket_get_id")]
    private static extern IntPtr gtk3_socket_get_id(IntPtr socket);

    [DllImport(LibGtk3, EntryPoint = "gtk_plug_new")]
    private static extern IntPtr gtk3_plug_new(IntPtr socketId);

    [DllImport(LibGtk3, EntryPoint = "gtk_plug_get_id")]
    private static extern IntPtr gtk3_plug_get_id(IntPtr plug);

    [DllImport(LibGtk3, EntryPoint = "gtk_window_set_skip_taskbar_hint")]
    private static extern void gtk3_window_set_skip_taskbar_hint(IntPtr window, bool setting);

    [DllImport(LibGtk3, EntryPoint = "gtk_window_set_skip_pager_hint")]
    private static extern void gtk3_window_set_skip_pager_hint(IntPtr window, bool setting);

    [DllImport(LibGtk3, EntryPoint = "gtk_window_set_type_hint")]
    private static extern void gtk3_window_set_type_hint(IntPtr window, int hint);

    [DllImport(LibGtk3, EntryPoint = "gtk_window_present")]
    private static extern void gtk3_window_present(IntPtr window);

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

    [DllImport(LibGtk3, EntryPoint = "gtk_window_set_keep_above")]
    private static extern void gtk3_window_set_keep_above(IntPtr window, bool setting);

    [DllImport(LibGtk3, EntryPoint = "gtk_widget_set_hexpand")]
    private static extern void gtk3_widget_set_hexpand(IntPtr widget, bool expand);

    [DllImport(LibGtk3, EntryPoint = "gtk_widget_set_vexpand")]
    private static extern void gtk3_widget_set_vexpand(IntPtr widget, bool expand);

    [DllImport(LibGtk3, EntryPoint = "gtk_widget_set_size_request")]
    private static extern void gtk3_widget_set_size_request(IntPtr widget, int width, int height);

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

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

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

    [DllImport(LibGtk3, EntryPoint = "gtk_events_pending")]
    private static extern bool gtk3_events_pending();

    [DllImport(LibGtk3, EntryPoint = "gtk_main_iteration")]
    private static extern void gtk3_main_iteration();

    [DllImport(LibGtk3, EntryPoint = "gtk_main_iteration_do")]
    private static extern bool gtk3_main_iteration_do(bool blocking);

    #endregion

    #region X11 Native Imports

    [DllImport(LibX11)]
    private static extern int XReparentWindow(IntPtr display, IntPtr window, IntPtr parent, int x, int y);

    [DllImport(LibX11)]
    private static extern int XMoveResizeWindow(IntPtr display, IntPtr window, int x, int y, uint width, uint height);

    [DllImport(LibX11)]
    private static extern int XMapWindow(IntPtr display, IntPtr window);

    [DllImport(LibX11)]
    private static extern int XUnmapWindow(IntPtr display, IntPtr window);

    [DllImport(LibX11)]
    private static extern int XFlush(IntPtr display);

    [DllImport(LibX11)]
    private static extern int XRaiseWindow(IntPtr display, IntPtr window);

    [DllImport(LibX11)]
    private static extern IntPtr XCreateSimpleWindow(IntPtr display, IntPtr parent, int x, int y, uint width, uint height, uint borderWidth, ulong border, ulong background);

    [DllImport(LibX11)]
    private static extern int XDestroyWindow(IntPtr display, IntPtr window);

    [DllImport(LibX11)]
    private static extern int XSelectInput(IntPtr display, IntPtr window, long eventMask);

    [DllImport(LibX11)]
    private static extern int XSync(IntPtr display, bool discard);

    [DllImport(LibX11)]
    private static extern bool XQueryTree(IntPtr display, IntPtr window, ref IntPtr root, ref IntPtr parent, ref IntPtr children, ref uint nchildren);

    [DllImport(LibX11)]
    private static extern int XFree(IntPtr data);

    [DllImport(LibX11)]
    private static extern int XMapRaised(IntPtr display, IntPtr window);

    [DllImport(LibX11)]
    private static extern int XGetWindowAttributes(IntPtr display, IntPtr window, out XWindowAttributes attributes);

    [DllImport(LibX11)]
    private static extern bool XTranslateCoordinates(IntPtr display, IntPtr src, IntPtr dest, int srcX, int srcY, out int destX, out int destY, out IntPtr child);

    [DllImport(LibX11)]
    private static extern IntPtr XDefaultRootWindow(IntPtr display);

    [DllImport(LibX11)]
    private static extern IntPtr XInternAtom(IntPtr display, string atomName, bool onlyIfExists);

    [DllImport(LibX11)]
    private static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, int format, int mode, IntPtr data, int nelements);

    [DllImport(LibX11)]
    private static extern int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, int format, int mode, IntPtr[] data, int nelements);

    [DllImport("libdl.so.2")]
    private static extern IntPtr dlopen([MarshalAs(UnmanagedType.LPStr)] string? filename, int flags);

    [DllImport("libdl.so.2")]
    private static extern IntPtr dlsym(IntPtr handle, [MarshalAs(UnmanagedType.LPStr)] string symbol);

    [DllImport("libdl.so.2")]
    private static extern IntPtr dlerror();

    #endregion

    #region Properties

    public string Source
    {
        get => _source;
        set
        {
            if (_source == value) return;
            _source = value;
            if (!string.IsNullOrEmpty(value))
            {
                if (!_isInitialized) Initialize();
                if (_isInitialized) LoadUrl(value);
            }
            Invalidate();
        }
    }

    public string Html
    {
        get => _html;
        set
        {
            if (_html == value) return;
            _html = value;
            if (!string.IsNullOrEmpty(value))
            {
                if (!_isInitialized) Initialize();
                if (_isInitialized) LoadHtml(value);
            }
            Invalidate();
        }
    }

    public bool CanGoBack => _webView != IntPtr.Zero && (_webkitCanGoBack?.Invoke(_webView) ?? false);

    public bool CanGoForward => _webView != IntPtr.Zero && (_webkitCanGoForward?.Invoke(_webView) ?? false);

    public string? CurrentUrl
    {
        get
        {
            if (_webView == IntPtr.Zero || _webkitGetUri == null) return null;
            var ptr = _webkitGetUri(_webView);
            return ptr != IntPtr.Zero ? Marshal.PtrToStringAnsi(ptr) : null;
        }
    }

    public string? Title
    {
        get
        {
            if (_webView == IntPtr.Zero || _webkitGetTitle == null) return null;
            var ptr = _webkitGetTitle(_webView);
            return ptr != IntPtr.Zero ? Marshal.PtrToStringAnsi(ptr) : null;
        }
    }

    public bool JavaScriptEnabled
    {
        get => _javascriptEnabled;
        set
        {
            _javascriptEnabled = value;
            UpdateJavaScriptSetting();
        }
    }

    public double LoadProgress => _loadProgress;

    public string? UserAgent
    {
        get
        {
            if (!string.IsNullOrEmpty(_userAgent))
                return _userAgent;
            if (_webView == IntPtr.Zero || _webkitGetSettings == null || _webkitGetUserAgent == null)
                return null;
            var settings = _webkitGetSettings(_webView);
            if (settings == IntPtr.Zero) return null;
            var ptr = _webkitGetUserAgent(settings);
            return ptr != IntPtr.Zero ? Marshal.PtrToStringAnsi(ptr) : null;
        }
        set
        {
            _userAgent = value;
            UpdateUserAgentSetting();
        }
    }

    /// <summary>
    /// Gets or sets the cookie container for this WebView.
    /// Note: Cookies set here are available for .NET code but are not automatically
    /// synchronized with WebKitGTK's internal cookie storage. For full cookie
    /// integration, use JavaScript-based cookie operations.
    /// </summary>
    public CookieContainer Cookies
    {
        get => _cookies;
        set => _cookies = value ?? new CookieContainer();
    }

    public static bool IsSupported => InitializeWebKit();

    #endregion

    #region Events

    public event EventHandler<WebNavigatingEventArgs>? Navigating;
    public event EventHandler<WebNavigatedEventArgs>? Navigated;
    public event EventHandler<string>? TitleChanged;
    public event EventHandler<double>? LoadProgressChanged;

    #endregion

    #region Static Methods

    public static void SetMainWindow(IntPtr display, IntPtr window)
    {
        _mainDisplay = display;
        _mainWindow = window;
        DiagnosticLog.Debug("SkiaWebView", $"Main window set: display={display}, window={window}");
    }

    public static void ProcessGtkEvents()
    {
        bool hasActive;
        lock (_activeWebViews)
        {
            hasActive = _activeWebViews.Count > 0;
        }
        if (hasActive && _gtkInitialized)
        {
            while (g_main_context_iteration(IntPtr.Zero, mayBlock: false)) { }
        }
    }

    private static bool InitializeWebKit()
    {
        if (_webkitHandle != IntPtr.Zero) return true;

        _webkitHandle = dlopen("libwebkit2gtk-4.1.so.0", RTLD_NOW | RTLD_GLOBAL);
        if (_webkitHandle != IntPtr.Zero)
        {
            _useGtk4 = false;
            _webkitLib = "libwebkit2gtk-4.1.so.0";
        }
        else
        {
            _webkitHandle = dlopen("libwebkit2gtk-4.0.so.37", RTLD_NOW | RTLD_GLOBAL);
            if (_webkitHandle != IntPtr.Zero)
            {
                _useGtk4 = false;
                _webkitLib = "libwebkit2gtk-4.0.so.37";
            }
            else
            {
                _webkitHandle = dlopen("libwebkitgtk-6.0.so.4", RTLD_NOW | RTLD_GLOBAL);
                if (_webkitHandle != IntPtr.Zero)
                {
                    _useGtk4 = true;
                    _webkitLib = "libwebkitgtk-6.0.so.4";
                    DiagnosticLog.Warn("SkiaWebView", "Using GTK4 WebKitGTK - embedding may be limited");
                }
            }
        }

        if (_webkitHandle == IntPtr.Zero)
        {
            DiagnosticLog.Error("SkiaWebView", "WebKitGTK not found. Install with: sudo apt install libwebkit2gtk-4.1-0");
            return false;
        }

        _webkitWebViewNew = LoadFunction<WebKitWebViewNewDelegate>("webkit_web_view_new");
        _webkitLoadUri = LoadFunction<WebKitWebViewLoadUriDelegate>("webkit_web_view_load_uri");
        _webkitLoadHtml = LoadFunction<WebKitWebViewLoadHtmlDelegate>("webkit_web_view_load_html");
        _webkitGetUri = LoadFunction<WebKitWebViewGetUriDelegate>("webkit_web_view_get_uri");
        _webkitGetTitle = LoadFunction<WebKitWebViewGetTitleDelegate>("webkit_web_view_get_title");
        _webkitGoBack = LoadFunction<WebKitWebViewGoBackDelegate>("webkit_web_view_go_back");
        _webkitGoForward = LoadFunction<WebKitWebViewGoForwardDelegate>("webkit_web_view_go_forward");
        _webkitCanGoBack = LoadFunction<WebKitWebViewCanGoBackDelegate>("webkit_web_view_can_go_back");
        _webkitCanGoForward = LoadFunction<WebKitWebViewCanGoForwardDelegate>("webkit_web_view_can_go_forward");
        _webkitReload = LoadFunction<WebKitWebViewReloadDelegate>("webkit_web_view_reload");
        _webkitStopLoading = LoadFunction<WebKitWebViewStopLoadingDelegate>("webkit_web_view_stop_loading");
        _webkitGetProgress = LoadFunction<WebKitWebViewGetEstimatedLoadProgressDelegate>("webkit_web_view_get_estimated_load_progress");
        _webkitGetSettings = LoadFunction<WebKitWebViewGetSettingsDelegate>("webkit_web_view_get_settings");
        _webkitSetJavascript = LoadFunction<WebKitSettingsSetEnableJavascriptDelegate>("webkit_settings_set_enable_javascript");
        _webkitSetHardwareAcceleration = LoadFunction<WebKitSettingsSetHardwareAccelerationPolicyDelegate>("webkit_settings_set_hardware_acceleration_policy");
        _webkitSetWebgl = LoadFunction<WebKitSettingsSetEnableWebglDelegate>("webkit_settings_set_enable_webgl");
        _webkitSetUserAgent = LoadFunction<WebKitSettingsSetUserAgentDelegate>("webkit_settings_set_user_agent");
        _webkitGetUserAgent = LoadFunction<WebKitSettingsGetUserAgentDelegate>("webkit_settings_get_user_agent");
        _webkitRunJavascript = LoadFunction<WebKitWebViewRunJavascriptDelegate>("webkit_web_view_run_javascript");

        DiagnosticLog.Debug("SkiaWebView", $"Using {_webkitLib}");
        return _webkitWebViewNew != null;
    }

    private static T? LoadFunction<T>(string name) where T : Delegate
    {
        var ptr = dlsym(_webkitHandle, name);
        return ptr == IntPtr.Zero ? null : Marshal.GetDelegateForFunctionPointer<T>(ptr);
    }

    private static void OnLoadChanged(IntPtr webView, int loadEvent, IntPtr userData)
    {
        string[] events = { "STARTED", "REDIRECTED", "COMMITTED", "FINISHED" };
        string eventName = loadEvent >= 0 && loadEvent < events.Length ? events[loadEvent] : loadEvent.ToString();
        DiagnosticLog.Debug("SkiaWebView", $"Load event: {eventName}");

        if (!_webViewInstances.TryGetValue(webView, out var instance)) return;

        string url = instance.Source ?? "";
        if (_webkitGetUri != null)
        {
            var ptr = _webkitGetUri(webView);
            if (ptr != IntPtr.Zero)
            {
                url = Marshal.PtrToStringAnsi(ptr) ?? "";
            }
        }

        switch (loadEvent)
        {
            case 0: // STARTED
                instance.Navigating?.Invoke(instance, new WebNavigatingEventArgs(url));
                break;
            case 3: // FINISHED
                instance.Navigated?.Invoke(instance, new WebNavigatedEventArgs(url, true));
                break;
        }
    }

    #endregion

    #region Constructor

    public SkiaWebView()
    {
        RequestedWidth = 400.0;
        RequestedHeight = 300.0;
        BackgroundColor = Colors.White;
    }

    #endregion

    #region Initialization

    private void Initialize()
    {
        if (_isInitialized || !InitializeWebKit()) return;

        try
        {
            if (!_gtkInitialized)
            {
                Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");
                Environment.SetEnvironmentVariable("LIBGL_ALWAYS_SOFTWARE", "1");
                Environment.SetEnvironmentVariable("WEBKIT_DISABLE_COMPOSITING_MODE", "1");
                DiagnosticLog.Debug("SkiaWebView", "Using X11 backend with software rendering for proper positioning");

                var waylandDisplay = Environment.GetEnvironmentVariable("WAYLAND_DISPLAY");
                DiagnosticLog.Debug("SkiaWebView", $"XDG_RUNTIME_DIR: {Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR")}");
                DiagnosticLog.Debug("SkiaWebView", $"Forcing X11: GDK_BACKEND=x11, WAYLAND_DISPLAY={waylandDisplay}, XDG_SESSION_TYPE=x11");

                if (_useGtk4)
                {
                    gtk4_init();
                }
                else
                {
                    int argc = 0;
                    IntPtr argv = IntPtr.Zero;
                    if (!gtk3_init_check(ref argc, ref argv))
                    {
                        DiagnosticLog.Error("SkiaWebView", "gtk3_init_check failed!");
                    }
                }
                _gtkInitialized = true;

                var gdkDisplay = gdk3_display_get_default();
                DiagnosticLog.Debug("SkiaWebView", $"GDK display: {gdkDisplay}");
            }

            _webView = _webkitWebViewNew!();
            if (_webView == IntPtr.Zero)
            {
                DiagnosticLog.Error("SkiaWebView", "Failed to create WebKit view");
                return;
            }

            _webViewInstances[_webView] = this;
            _loadChangedCallback = OnLoadChanged;
            var callbackPtr = Marshal.GetFunctionPointerForDelegate(_loadChangedCallback);
            g_signal_connect_data(_webView, "load-changed", callbackPtr, IntPtr.Zero, IntPtr.Zero, 0);
            DiagnosticLog.Debug("SkiaWebView", "Connected to load-changed signal");

            int width = Math.Max(800, (int)RequestedWidth);
            int height = Math.Max(600, (int)RequestedHeight);

            if (_useGtk4)
            {
                _gtkWindow = gtk4_window_new();
                gtk4_window_set_title(_gtkWindow, "OpenMaui WebView");
                gtk4_window_set_default_size(_gtkWindow, width, height);
                gtk4_window_set_child(_gtkWindow, _webView);
                DiagnosticLog.Debug("SkiaWebView", $"GTK4 window created: {width}x{height}");
            }
            else
            {
                _gtkWindow = gtk3_window_new(0);
                gtk3_window_set_default_size(_gtkWindow, width, height);
                gtk3_window_set_title(_gtkWindow, "WebViewDemo");
                gtk3_widget_set_hexpand(_webView, true);
                gtk3_widget_set_vexpand(_webView, true);
                gtk3_widget_set_size_request(_webView, width, height);
                gtk3_container_add(_gtkWindow, _webView);
                DiagnosticLog.Debug("SkiaWebView", $"GTK3 TOPLEVEL window created: {width}x{height}");
            }

            ConfigureWebKitSettings();
            UpdateJavaScriptSetting();
            UpdateUserAgentSetting();
            _isInitialized = true;

            lock (_activeWebViews)
            {
                if (!_activeWebViews.Contains(this))
                {
                    _activeWebViews.Add(this);
                }
            }

            if (!string.IsNullOrEmpty(_source))
            {
                LoadUrl(_source);
            }
            else if (!string.IsNullOrEmpty(_html))
            {
                LoadHtml(_html);
            }

            DiagnosticLog.Debug("SkiaWebView", "Initialized successfully");
        }
        catch (Exception ex)
        {
            DiagnosticLog.Error("SkiaWebView", $"Initialization failed: {ex.Message}", ex);
        }
    }

    private void ConfigureWebKitSettings()
    {
        if (_webView == IntPtr.Zero) return;

        try
        {
            if (_webkitGetSettings == null) return;

            var settings = _webkitGetSettings(_webView);
            if (settings == IntPtr.Zero)
            {
                DiagnosticLog.Warn("SkiaWebView", "Could not get WebKit settings");
                return;
            }

            if (_webkitSetHardwareAcceleration != null)
            {
                _webkitSetHardwareAcceleration(settings, 2); // NEVER
                DiagnosticLog.Debug("SkiaWebView", "Set hardware acceleration to NEVER (software rendering)");
            }
            else
            {
                DiagnosticLog.Warn("SkiaWebView", "Could not set hardware acceleration policy");
            }

            if (_webkitSetWebgl != null)
            {
                _webkitSetWebgl(settings, false);
                DiagnosticLog.Debug("SkiaWebView", "Disabled WebGL");
            }

            DiagnosticLog.Debug("SkiaWebView", "WebKit settings configured successfully");
        }
        catch (Exception ex)
        {
            DiagnosticLog.Error("SkiaWebView", $"Failed to configure settings: {ex.Message}", ex);
        }
    }

    private void UpdateJavaScriptSetting()
    {
        if (_webView == IntPtr.Zero || _webkitGetSettings == null || _webkitSetJavascript == null) return;

        var settings = _webkitGetSettings(_webView);
        if (settings != IntPtr.Zero)
        {
            _webkitSetJavascript(settings, _javascriptEnabled);
        }
    }

    private void UpdateUserAgentSetting()
    {
        if (_webView == IntPtr.Zero || _webkitGetSettings == null || _webkitSetUserAgent == null) return;
        if (string.IsNullOrEmpty(_userAgent)) return;

        var settings = _webkitGetSettings(_webView);
        if (settings != IntPtr.Zero)
        {
            _webkitSetUserAgent(settings, _userAgent);
        }
    }

    #endregion

    #region Navigation

    public void LoadUrl(string url)
    {
        if (!_isInitialized) Initialize();
        if (_webView != IntPtr.Zero && _webkitLoadUri != null)
        {
            Navigating?.Invoke(this, new WebNavigatingEventArgs(url));
            _webkitLoadUri(_webView, url);
            DiagnosticLog.Debug("SkiaWebView", $"URL loaded: {url}");
            ShowNativeWindow();
        }
    }

    public void LoadHtml(string html, string? baseUrl = null)
    {
        DiagnosticLog.Debug("SkiaWebView", $"LoadHtml called, html length: {html?.Length ?? 0}");
        if (string.IsNullOrEmpty(html))
        {
            DiagnosticLog.Warn("SkiaWebView", "Cannot load HTML - html is null or empty");
            return;
        }
        if (!_isInitialized) Initialize();
        if (_webView == IntPtr.Zero || _webkitLoadHtml == null)
        {
            DiagnosticLog.Warn("SkiaWebView", "Cannot load HTML - not initialized or no webkit function");
            return;
        }
        DiagnosticLog.Debug("SkiaWebView", "Calling webkit_web_view_load_html...");
        _webkitLoadHtml(_webView, html, baseUrl);
        DiagnosticLog.Debug("SkiaWebView", "HTML loaded to WebKit");
        ShowNativeWindow();
    }

    public void GoBack()
    {
        if (_webView != IntPtr.Zero && CanGoBack)
        {
            _webkitGoBack?.Invoke(_webView);
        }
    }

    public void GoForward()
    {
        if (_webView != IntPtr.Zero && CanGoForward)
        {
            _webkitGoForward?.Invoke(_webView);
        }
    }

    public void Reload()
    {
        _webkitReload?.Invoke(_webView);
    }

    public void Stop()
    {
        _webkitStopLoading?.Invoke(_webView);
    }

    /// <summary>
    /// Evaluates JavaScript in the WebView. This is a fire-and-forget operation.
    /// For MAUI compatibility - use EvaluateJavaScriptAsync for async results.
    /// </summary>
    public void Eval(string script)
    {
        if (_webView == IntPtr.Zero || _webkitRunJavascript == null || string.IsNullOrEmpty(script)) return;
        _webkitRunJavascript(_webView, script, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
    }

    /// <summary>
    /// Evaluates JavaScript in the WebView asynchronously.
    /// Note: WebKitGTK async result handling is complex - this implementation
    /// executes the script but returns null. Full async result support would
    /// require GAsyncReadyCallback integration.
    /// </summary>
    public System.Threading.Tasks.Task<string?> EvaluateJavaScriptAsync(string script)
    {
        if (_webView == IntPtr.Zero || _webkitRunJavascript == null || string.IsNullOrEmpty(script))
        {
            return System.Threading.Tasks.Task.FromResult<string?>(null);
        }

        // Execute the JavaScript
        _webkitRunJavascript(_webView, script, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

        // Note: Full async result handling would require GAsyncReadyCallback
        // For now, we execute and return null (script side effects still work)
        return System.Threading.Tasks.Task.FromResult<string?>(null);
    }

    #endregion

    #region Event Processing

    public void ProcessEvents()
    {
        if (!_isInitialized) return;

        g_main_context_iteration(IntPtr.Zero, mayBlock: false);

        if (_webView != IntPtr.Zero && _webkitGetProgress != null)
        {
            double progress = _webkitGetProgress(_webView);
            if (Math.Abs(progress - _loadProgress) > 0.01)
            {
                _loadProgress = progress;
                LoadProgressChanged?.Invoke(this, progress);
            }
        }
    }

    #endregion

    #region Window Management

    private bool CreateX11Container()
    {
        if (_mainDisplay == IntPtr.Zero || _mainWindow == IntPtr.Zero)
        {
            DiagnosticLog.Warn("SkiaWebView", "Cannot create X11 container - main window not set");
            return false;
        }

        if (_x11Container != IntPtr.Zero)
        {
            DiagnosticLog.Debug("SkiaWebView", "X11 container already exists");
            return true;
        }

        try
        {
            int x = (int)Bounds.Left;
            int y = (int)Bounds.Top;
            uint width = Math.Max(100u, (uint)Bounds.Width);
            uint height = Math.Max(100u, (uint)Bounds.Height);

            if (width < 100) width = 780;
            if (height < 100) height = 300;

            DiagnosticLog.Debug("SkiaWebView", $"Creating X11 container at ({x}, {y}), size ({width}x{height})");

            _x11Container = XCreateSimpleWindow(_mainDisplay, _mainWindow, x, y, width, height, 0, 0, 0xFFFFFF);
            if (_x11Container == IntPtr.Zero)
            {
                DiagnosticLog.Error("SkiaWebView", "Failed to create X11 container window");
                return false;
            }

            DiagnosticLog.Debug("SkiaWebView", $"Created X11 container: {_x11Container.ToInt64()}");
            XMapWindow(_mainDisplay, _x11Container);
            XFlush(_mainDisplay);
            return true;
        }
        catch (Exception ex)
        {
            DiagnosticLog.Error("SkiaWebView", $"Error creating X11 container: {ex.Message}", ex);
            return false;
        }
    }

    public void ShowNativeWindow()
    {
        if (_isEmbedded) return; // Already embedded
        if (!_isInitialized) Initialize();
        if (_gtkWindow == IntPtr.Zero) return;

        DiagnosticLog.Debug("SkiaWebView", "Showing native GTK window...");

        if (!_useGtk4)
        {
            gtk3_window_set_decorated(_gtkWindow, false);
            gtk3_window_set_skip_taskbar_hint(_gtkWindow, true);
            gtk3_window_set_skip_pager_hint(_gtkWindow, true);
            gtk3_window_set_keep_above(_gtkWindow, true);
            gtk3_window_set_type_hint(_gtkWindow, 5); // UTILITY
        }

        if (_useGtk4)
        {
            gtk4_widget_show(_gtkWindow);
            gtk4_window_present(_gtkWindow);
        }
        else
        {
            gtk3_widget_show_all(_gtkWindow);
        }

        for (int i = 0; i < 100; i++)
        {
            while (gtk3_events_pending())
            {
                gtk3_main_iteration_do(false);
            }
        }

        TryReparentIntoMainWindow();
        _isEmbedded = true;
        DiagnosticLog.Debug("SkiaWebView", "Native window shown");
    }

    private void TryReparentIntoMainWindow()
    {
        if (_mainDisplay == IntPtr.Zero || _mainWindow == IntPtr.Zero)
        {
            DiagnosticLog.Warn("SkiaWebView", "Cannot setup - main window not set");
            return;
        }

        var gdkWindow = gtk3_widget_get_window(_gtkWindow);
        if (gdkWindow != IntPtr.Zero)
        {
            _gtkX11Window = gdk3_x11_window_get_xid(gdkWindow);
            if (_gtkX11Window != IntPtr.Zero)
            {
                _isProperlyReparented = true;
                DiagnosticLog.Debug("SkiaWebView", $"GTK X11 window: {_gtkX11Window} (reparented successfully)");
            }
            else
            {
                DiagnosticLog.Warn("SkiaWebView", "GTK X11 window: failed to get XID");
            }
        }

        PositionUsingGtk();
    }

    private void PositionUsingGtk()
    {
        if (_gtkWindow == IntPtr.Zero || _mainDisplay == IntPtr.Zero) return;

        int destX = 0, destY = 0;
        try
        {
            var root = XDefaultRootWindow(_mainDisplay);
            XTranslateCoordinates(_mainDisplay, _mainWindow, root, 0, 0, out destX, out destY, out _);
        }
        catch
        {
            destX = 0;
            destY = 0;
        }

        int screenX = destX + (int)Bounds.Left;
        int screenY = destY + (int)Bounds.Top;
        int width = Math.Max(100, (int)Bounds.Width);
        int height = Math.Max(100, (int)Bounds.Height);

        DiagnosticLog.Debug("SkiaWebView", $"Position: screen=({screenX}, {screenY}), size ({width}x{height}), bounds=({Bounds.Left},{Bounds.Top})");

        if (!_useGtk4)
        {
            gtk3_window_move(_gtkWindow, screenX, screenY);
            gtk3_window_resize(_gtkWindow, width, height);

            while (gtk3_events_pending())
            {
                gtk3_main_iteration_do(false);
            }

            if (_gtkX11Window != IntPtr.Zero)
            {
                XRaiseWindow(_mainDisplay, _gtkX11Window);
                SetWindowAlwaysOnTop(_gtkX11Window);
                XFlush(_mainDisplay);
            }
        }
        else
        {
            gtk4_window_set_default_size(_gtkWindow, width, height);
        }
    }

    private void PositionWithX11()
    {
        if (_gtkX11Window == IntPtr.Zero || _mainDisplay == IntPtr.Zero) return;

        int destX = 0, destY = 0;
        try
        {
            var root = XDefaultRootWindow(_mainDisplay);
            XTranslateCoordinates(_mainDisplay, _mainWindow, root, 0, 0, out destX, out destY, out _);
        }
        catch (Exception ex) { DiagnosticLog.Debug("SkiaWebView", "X11 coordinate translation failed", ex); }

        int x = destX + (int)Bounds.Left;
        int y = destY + (int)Bounds.Top;
        uint width = (uint)Math.Max(100f, Bounds.Width > 10f ? Bounds.Width : 780f);
        uint height = (uint)Math.Max(100f, Bounds.Height > 10f ? Bounds.Height : 300f);

        XMoveResizeWindow(_mainDisplay, _gtkX11Window, x, y, width, height);
        XRaiseWindow(_mainDisplay, _gtkX11Window);
        SetWindowAlwaysOnTop(_gtkX11Window);
        XFlush(_mainDisplay);

        gtk3_widget_queue_draw(_webView);

        for (int i = 0; i < 5; i++)
        {
            g_main_context_iteration(IntPtr.Zero, mayBlock: false);
        }
    }

    private void SetWindowAlwaysOnTop(IntPtr window)
    {
        try
        {
            var wmState = XInternAtom(_mainDisplay, "_NET_WM_STATE", false);
            var wmStateAbove = XInternAtom(_mainDisplay, "_NET_WM_STATE_ABOVE", false);
            var atomType = XInternAtom(_mainDisplay, "ATOM", false);
            IntPtr[] data = { wmStateAbove };
            XChangeProperty(_mainDisplay, window, wmState, atomType, 32, 0, data, 1);
        }
        catch (Exception ex) { DiagnosticLog.Debug("SkiaWebView", "Window state change failed", ex); }
    }

    private void EnableOverlayMode()
    {
        if (_gtkWindow == IntPtr.Zero || _useGtk4) return;

        try
        {
            gtk3_window_set_type_hint(_gtkWindow, 5); // UTILITY
            gtk3_window_set_skip_taskbar_hint(_gtkWindow, true);
            gtk3_window_set_skip_pager_hint(_gtkWindow, true);
            gtk3_window_set_keep_above(_gtkWindow, true);
            gtk3_window_set_decorated(_gtkWindow, false);
            DiagnosticLog.Debug("SkiaWebView", "Overlay mode enabled with UTILITY hint");
        }
        catch (Exception ex)
        {
            DiagnosticLog.Error("SkiaWebView", $"Failed to enable overlay mode: {ex.Message}", ex);
        }
    }

    private void SetupEmbedding()
    {
        if (_mainDisplay == IntPtr.Zero || _mainWindow == IntPtr.Zero)
        {
            DiagnosticLog.Warn("SkiaWebView", "Cannot setup embedding - main window not set");
            return;
        }

        GetWindowPosition(_mainDisplay, _mainWindow, out int x, out int y);
        int screenX = x + (int)Bounds.Left;
        int screenY = y + (int)Bounds.Top;
        int width = Math.Max(100, (int)Bounds.Width);
        int height = Math.Max(100, (int)Bounds.Height);

        DiagnosticLog.Debug("SkiaWebView", $"Initial position: ({screenX}, {screenY}), size ({width}x{height})");

        if (!_useGtk4)
        {
            gtk3_window_move(_gtkWindow, screenX, screenY);
            gtk3_window_resize(_gtkWindow, width, height);
        }
        else
        {
            gtk4_window_set_default_size(_gtkWindow, width, height);
        }

        _lastBounds = Bounds;
    }

    private void PositionAtScreenCoordinates()
    {
        if (_gtkWindow == IntPtr.Zero || _mainDisplay == IntPtr.Zero) return;

        int destX = 0, destY = 0;
        try
        {
            var root = XDefaultRootWindow(_mainDisplay);
            XTranslateCoordinates(_mainDisplay, _mainWindow, root, 0, 0, out destX, out destY, out _);
        }
        catch (Exception ex) { DiagnosticLog.Debug("SkiaWebView", "X11 coordinate translation failed", ex); }

        // Track main window position changes
        bool mainWindowMoved = destX != _lastMainX || destY != _lastMainY;
        if (mainWindowMoved)
        {
            _lastMainX = destX;
            _lastMainY = destY;
        }

        int screenX = destX + (int)Bounds.Left;
        int screenY = destY + (int)Bounds.Top;
        int width = Math.Max(100, (int)Bounds.Width);
        int height = Math.Max(100, (int)Bounds.Height);

        if (mainWindowMoved || Math.Abs(screenX - _lastPosX) > 2 || Math.Abs(screenY - _lastPosY) > 2 ||
            Math.Abs(width - _lastWidth) > 2 || Math.Abs(height - _lastHeight) > 2)
        {
            DiagnosticLog.Debug("SkiaWebView", $"Move to ({screenX}, {screenY}), size ({width}x{height}), mainWin=({destX},{destY}), bounds=({Bounds.Left},{Bounds.Top})");
            _lastPosX = screenX;
            _lastPosY = screenY;
            _lastWidth = width;
            _lastHeight = height;
            _lastBounds = Bounds;
        }

        if (!_useGtk4)
        {
            gtk3_window_move(_gtkWindow, screenX, screenY);
            gtk3_window_resize(_gtkWindow, width, height);

            var gdkWindow = gtk3_widget_get_window(_gtkWindow);
            if (gdkWindow != IntPtr.Zero)
            {
                var xid = gdk3_x11_window_get_xid(gdkWindow);
                if (xid != IntPtr.Zero)
                {
                    XRaiseWindow(_mainDisplay, xid);
                    XFlush(_mainDisplay);
                }
            }

            while (gtk3_events_pending())
            {
                gtk3_main_iteration_do(false);
            }
        }
        else
        {
            gtk4_window_set_default_size(_gtkWindow, width, height);
        }
    }

    private IntPtr GetGtkX11Window()
    {
        if (_gtkWindow == IntPtr.Zero) return IntPtr.Zero;

        for (int i = 0; i < 50; i++)
        {
            g_main_context_iteration(IntPtr.Zero, mayBlock: false);
        }

        if (_useGtk4)
        {
            var surface = gtk4_native_get_surface(_gtkWindow);
            if (surface != IntPtr.Zero)
            {
                try { return gdk4_x11_surface_get_xid(surface); }
                catch (Exception ex) { DiagnosticLog.Debug("SkiaWebView", "GTK4 XID lookup failed", ex); }
            }
        }
        else
        {
            var gdkWindow = gtk3_widget_get_window(_gtkWindow);
            if (gdkWindow != IntPtr.Zero)
            {
                try { return gdk3_x11_window_get_xid(gdkWindow); }
                catch (Exception ex) { DiagnosticLog.Debug("SkiaWebView", "GTK3 XID lookup failed", ex); }
            }
        }

        return IntPtr.Zero;
    }

    private void GetWindowPosition(IntPtr display, IntPtr window, out int x, out int y)
    {
        x = 0;
        y = 0;
        try
        {
            var root = XDefaultRootWindow(display);
            if (XTranslateCoordinates(display, window, root, 0, 0, out x, out y, out _))
            {
                DiagnosticLog.Debug("SkiaWebView", $"Main window at screen ({x}, {y})");
            }
        }
        catch (Exception ex)
        {
            DiagnosticLog.Error("SkiaWebView", $"Failed to get window position: {ex.Message}", ex);
        }
    }

    public void UpdateEmbeddedPosition()
    {
        if (_mainDisplay == IntPtr.Zero) return;
        if (Bounds.Width < 10f || Bounds.Height < 10f) return;

        bool boundsChanged = Math.Abs(Bounds.Left - _lastBounds.Left) > 1f ||
                             Math.Abs(Bounds.Top - _lastBounds.Top) > 1f ||
                             Math.Abs(Bounds.Width - _lastBounds.Width) > 1f ||
                             Math.Abs(Bounds.Height - _lastBounds.Height) > 1f;

        if (!boundsChanged) return;

        _lastBounds = Bounds;
        int x = (int)Bounds.Left;
        int y = (int)Bounds.Top;
        uint width = (uint)Math.Max(10f, Bounds.Width);
        uint height = (uint)Math.Max(10f, Bounds.Height);

        if (_isProperlyReparented && _gtkX11Window != IntPtr.Zero)
        {
            DiagnosticLog.Debug("SkiaWebView", $"UpdateEmbedded (reparented): ({x}, {y}), size ({width}x{height})");
            XMoveResizeWindow(_mainDisplay, _gtkX11Window, x, y, width, height);
            XFlush(_mainDisplay);
        }
        else if (_x11Container != IntPtr.Zero)
        {
            DiagnosticLog.Debug("SkiaWebView", $"UpdateEmbedded (container): ({x}, {y}), size ({width}x{height})");
            XMoveResizeWindow(_mainDisplay, _x11Container, x, y, width, height);
            if (_gtkX11Window != IntPtr.Zero && _isProperlyReparented)
            {
                XMoveResizeWindow(_mainDisplay, _gtkX11Window, 0, 0, width, height);
            }
            else if (_gtkWindow != IntPtr.Zero)
            {
                PositionAtScreenCoordinates();
            }
            XFlush(_mainDisplay);
        }
        else if (_gtkWindow != IntPtr.Zero)
        {
            PositionAtScreenCoordinates();
        }
    }

    public void HideNativeWindow()
    {
        if (_gtkWindow == IntPtr.Zero) return;

        if (_useGtk4)
        {
            gtk4_widget_hide(_gtkWindow);
        }
        else
        {
            gtk3_widget_hide(_gtkWindow);
        }
    }

    #endregion

    #region Rendering

    protected override void OnDraw(SKCanvas canvas, SKRect bounds)
    {
        base.OnDraw(canvas, bounds);
        Bounds = new Rect(bounds.Left, bounds.Top, bounds.Width, bounds.Height);

        if (_isInitialized)
        {
            while (gtk3_events_pending())
            {
                gtk3_main_iteration_do(false);
            }

            if (_gtkWindow != IntPtr.Zero && _mainDisplay != IntPtr.Zero)
            {
                bool needsUpdate = Math.Abs(bounds.Left - _lastBounds.Left) > 1f ||
                                   Math.Abs(bounds.Top - _lastBounds.Top) > 1f ||
                                   Math.Abs(bounds.Width - _lastBounds.Width) > 1f ||
                                   Math.Abs(bounds.Height - _lastBounds.Height) > 1f;

                if (!needsUpdate && _lastBounds.Width < 150f && bounds.Width > 150f)
                {
                    needsUpdate = true;
                }

                if (needsUpdate && bounds.Width > 50f && bounds.Height > 50f)
                {
                    PositionUsingGtk();
                    _lastBounds = new Rect(bounds.Left, bounds.Top, bounds.Width, bounds.Height);
                }
            }
        }

        if (_isInitialized && _gtkWindow != IntPtr.Zero) return;

        // Draw placeholder when not initialized
        using var bgPaint = new SKPaint { Color = GetEffectiveBackgroundColor(), Style = SKPaintStyle.Fill };
        canvas.DrawRect(bounds, bgPaint);

        using var borderPaint = new SKPaint
        {
            Color = SkiaTheme.BorderMediumSK,
            Style = SKPaintStyle.Stroke,
            StrokeWidth = 1
        };
        canvas.DrawRect(bounds, borderPaint);

        float midX = bounds.MidX;
        float midY = bounds.MidY;

        using var iconPaint = new SKPaint
        {
            Color = SkiaTheme.Gray600SK,
            Style = SKPaintStyle.Stroke,
            StrokeWidth = 2,
            IsAntialias = true
        };
        canvas.DrawCircle(midX, midY - 20, 25, iconPaint);
        canvas.DrawLine(midX - 25, midY - 20, midX + 25, midY - 20, iconPaint);
        canvas.DrawArc(new SKRect(midX - 15, midY - 45, midX + 15, midY + 5), 0, 180, false, iconPaint);

        using var textPaint = new SKPaint
        {
            Color = SkiaTheme.Gray700SK,
            IsAntialias = true,
            TextSize = 14
        };

        string statusText;
        if (!IsSupported)
        {
            statusText = "WebKitGTK not installed";
        }
        else if (_isInitialized)
        {
            statusText = string.IsNullOrEmpty(_source) ? "No URL loaded" : $"Loading: {_source}";
            if (_loadProgress > 0 && _loadProgress < 1)
            {
                statusText = $"Loading: {(int)(_loadProgress * 100)}%";
            }
        }
        else
        {
            statusText = "WebView (click to open)";
        }

        float textWidth = textPaint.MeasureText(statusText);
        canvas.DrawText(statusText, midX - textWidth / 2, midY + 30, textPaint);

        if (!IsSupported)
        {
            using var hintPaint = new SKPaint
            {
                Color = SkiaTheme.Gray600SK,
                IsAntialias = true,
                TextSize = 11
            };
            string hint = "Install: sudo apt install libwebkit2gtk-4.1-0";
            float hintWidth = hintPaint.MeasureText(hint);
            canvas.DrawText(hint, midX - hintWidth / 2, midY + 50, hintPaint);
        }

        if (_loadProgress > 0 && _loadProgress < 1)
        {
            var progressRect = new SKRect(bounds.Left + 20, bounds.Bottom - 30, bounds.Right - 20, bounds.Bottom - 20);
            using var progressBgPaint = new SKPaint { Color = SkiaTheme.Gray200SK, Style = SKPaintStyle.Fill };
            canvas.DrawRoundRect(new SKRoundRect(progressRect, 5), progressBgPaint);

            float filledWidth = progressRect.Width * (float)_loadProgress;
            var filledRect = new SKRect(progressRect.Left, progressRect.Top, progressRect.Left + filledWidth, progressRect.Bottom);
            using var progressPaint = new SKPaint { Color = SkiaTheme.PrimarySK, Style = SKPaintStyle.Fill };
            canvas.DrawRoundRect(new SKRoundRect(filledRect, 5), progressPaint);
        }
    }

    public override void OnPointerPressed(PointerEventArgs e)
    {
        base.OnPointerPressed(e);

        if (!_isInitialized && IsSupported)
        {
            Initialize();
            ShowNativeWindow();
        }
        else if (_isInitialized)
        {
            ShowNativeWindow();
        }
    }

    #endregion

    #region Cleanup

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            lock (_activeWebViews)
            {
                _activeWebViews.Remove(this);
            }

            if (_webView != IntPtr.Zero)
            {
                _webViewInstances.Remove(_webView);
            }

            if (_gtkWindow != IntPtr.Zero)
            {
                if (_useGtk4)
                {
                    gtk4_widget_hide(_gtkWindow);
                }
                else
                {
                    gtk3_widget_hide(_gtkWindow);
                }
                g_object_unref(_gtkWindow);
                _gtkWindow = IntPtr.Zero;
            }

            if (_x11Container != IntPtr.Zero && _mainDisplay != IntPtr.Zero)
            {
                XDestroyWindow(_mainDisplay, _x11Container);
                _x11Container = IntPtr.Zero;
            }

            _webView = IntPtr.Zero;
            _gtkX11Window = IntPtr.Zero;
            _isInitialized = false;
        }

        base.Dispose(disposing);
    }

    #endregion
}

#region Event Args

public class WebNavigatingEventArgs : EventArgs
{
    public string Url { get; }
    public bool Cancel { get; set; }

    public WebNavigatingEventArgs(string url)
    {
        Url = url;
    }
}

public class WebNavigatedEventArgs : EventArgs
{
    public string Url { get; }
    public bool Success { get; }
    public string? Error { get; }

    public WebNavigatedEventArgs(string url, bool success, string? error = null)
    {
        Url = url;
        Success = success;
        Error = error;
    }
}

#endregion
