// 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;
using System.Text;

namespace Microsoft.Maui.Platform.Linux.Services;

/// <summary>
/// IBus Input Method service using D-Bus interface.
/// Provides modern IME support on Linux desktops.
/// </summary>
public class IBusInputMethodService : IInputMethodService, IDisposable
{
    private nint _bus;
    private nint _context;
    private IInputContext? _currentContext;
    private string _preEditText = string.Empty;
    private int _preEditCursorPosition;
    private bool _isActive;
    private bool _disposed;

    // Callback delegates (prevent GC)
    private IBusCommitTextCallback? _commitCallback;
    private IBusUpdatePreeditTextCallback? _preeditCallback;
    private IBusShowPreeditTextCallback? _showPreeditCallback;
    private IBusHidePreeditTextCallback? _hidePreeditCallback;

    public bool IsActive => _isActive;
    public string PreEditText => _preEditText;
    public int PreEditCursorPosition => _preEditCursorPosition;

    public event EventHandler<TextCommittedEventArgs>? TextCommitted;
    public event EventHandler<PreEditChangedEventArgs>? PreEditChanged;
    public event EventHandler? PreEditEnded;

    public void Initialize(nint windowHandle)
    {
        try
        {
            // Initialize IBus
            ibus_init();

            // Get the IBus bus connection
            _bus = ibus_bus_new();
            if (_bus == IntPtr.Zero)
            {
                DiagnosticLog.Error("IBusInputMethodService", "Failed to connect to IBus");
                return;
            }

            // Check if IBus is connected
            if (!ibus_bus_is_connected(_bus))
            {
                DiagnosticLog.Error("IBusInputMethodService", "IBus not connected");
                return;
            }

            // Create input context
            _context = ibus_bus_create_input_context(_bus, "maui-linux");
            if (_context == IntPtr.Zero)
            {
                DiagnosticLog.Error("IBusInputMethodService", "Failed to create input context");
                return;
            }

            // Set capabilities
            uint capabilities = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS | IBUS_CAP_SURROUNDING_TEXT;
            ibus_input_context_set_capabilities(_context, capabilities);

            // Connect signals
            ConnectSignals();

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

    private void ConnectSignals()
    {
        if (_context == IntPtr.Zero) return;

        // Set up callbacks for IBus signals
        _commitCallback = OnCommitText;
        _preeditCallback = OnUpdatePreeditText;
        _showPreeditCallback = OnShowPreeditText;
        _hidePreeditCallback = OnHidePreeditText;

        // Connect to commit-text signal
        g_signal_connect(_context, "commit-text",
            Marshal.GetFunctionPointerForDelegate(_commitCallback), IntPtr.Zero);

        // Connect to update-preedit-text signal
        g_signal_connect(_context, "update-preedit-text",
            Marshal.GetFunctionPointerForDelegate(_preeditCallback), IntPtr.Zero);

        // Connect to show-preedit-text signal
        g_signal_connect(_context, "show-preedit-text",
            Marshal.GetFunctionPointerForDelegate(_showPreeditCallback), IntPtr.Zero);

        // Connect to hide-preedit-text signal
        g_signal_connect(_context, "hide-preedit-text",
            Marshal.GetFunctionPointerForDelegate(_hidePreeditCallback), IntPtr.Zero);
    }

    private void OnCommitText(nint context, nint text, nint userData)
    {
        if (text == IntPtr.Zero) return;

        string committedText = GetIBusTextString(text);
        if (!string.IsNullOrEmpty(committedText))
        {
            _preEditText = string.Empty;
            _preEditCursorPosition = 0;
            _isActive = false;

            TextCommitted?.Invoke(this, new TextCommittedEventArgs(committedText));
            _currentContext?.OnTextCommitted(committedText);
        }
    }

    private void OnUpdatePreeditText(nint context, nint text, uint cursorPos, bool visible, nint userData)
    {
        if (!visible)
        {
            OnHidePreeditText(context, userData);
            return;
        }

        _isActive = true;
        _preEditText = text != IntPtr.Zero ? GetIBusTextString(text) : string.Empty;
        _preEditCursorPosition = (int)cursorPos;

        var attributes = GetPreeditAttributes(text);
        PreEditChanged?.Invoke(this, new PreEditChangedEventArgs(_preEditText, _preEditCursorPosition, attributes));
        _currentContext?.OnPreEditChanged(_preEditText, _preEditCursorPosition);
    }

    private void OnShowPreeditText(nint context, nint userData)
    {
        _isActive = true;
    }

    private void OnHidePreeditText(nint context, nint userData)
    {
        _isActive = false;
        _preEditText = string.Empty;
        _preEditCursorPosition = 0;

        PreEditEnded?.Invoke(this, EventArgs.Empty);
        _currentContext?.OnPreEditEnded();
    }

    private string GetIBusTextString(nint ibusText)
    {
        if (ibusText == IntPtr.Zero) return string.Empty;

        nint textPtr = ibus_text_get_text(ibusText);
        if (textPtr == IntPtr.Zero) return string.Empty;

        return Marshal.PtrToStringUTF8(textPtr) ?? string.Empty;
    }

    private List<PreEditAttribute> GetPreeditAttributes(nint ibusText)
    {
        var attributes = new List<PreEditAttribute>();
        if (ibusText == IntPtr.Zero) return attributes;

        nint attrList = ibus_text_get_attributes(ibusText);
        if (attrList == IntPtr.Zero) return attributes;

        uint count = ibus_attr_list_size(attrList);

        for (uint i = 0; i < count; i++)
        {
            nint attr = ibus_attr_list_get(attrList, i);
            if (attr == IntPtr.Zero) continue;

            var type = ibus_attribute_get_attr_type(attr);
            var start = ibus_attribute_get_start_index(attr);
            var end = ibus_attribute_get_end_index(attr);

            attributes.Add(new PreEditAttribute
            {
                Start = (int)start,
                Length = (int)(end - start),
                Type = ConvertAttributeType(type)
            });
        }

        return attributes;
    }

    private PreEditAttributeType ConvertAttributeType(uint ibusType)
    {
        return ibusType switch
        {
            IBUS_ATTR_TYPE_UNDERLINE => PreEditAttributeType.Underline,
            IBUS_ATTR_TYPE_FOREGROUND => PreEditAttributeType.Highlighted,
            IBUS_ATTR_TYPE_BACKGROUND => PreEditAttributeType.Reverse,
            _ => PreEditAttributeType.None
        };
    }

    public void SetFocus(IInputContext? context)
    {
        _currentContext = context;

        if (_context != IntPtr.Zero)
        {
            if (context != null)
            {
                ibus_input_context_focus_in(_context);
            }
            else
            {
                ibus_input_context_focus_out(_context);
            }
        }
    }

    public void SetCursorLocation(int x, int y, int width, int height)
    {
        if (_context == IntPtr.Zero) return;

        ibus_input_context_set_cursor_location(_context, x, y, width, height);
    }

    public bool ProcessKeyEvent(uint keyCode, KeyModifiers modifiers, bool isKeyDown)
    {
        if (_context == IntPtr.Zero) return false;

        uint state = ConvertModifiers(modifiers);
        if (!isKeyDown)
        {
            state |= IBUS_RELEASE_MASK;
        }

        return ibus_input_context_process_key_event(_context, keyCode, keyCode, state);
    }

    private uint ConvertModifiers(KeyModifiers modifiers)
    {
        uint state = 0;
        if (modifiers.HasFlag(KeyModifiers.Shift)) state |= IBUS_SHIFT_MASK;
        if (modifiers.HasFlag(KeyModifiers.Control)) state |= IBUS_CONTROL_MASK;
        if (modifiers.HasFlag(KeyModifiers.Alt)) state |= IBUS_MOD1_MASK;
        if (modifiers.HasFlag(KeyModifiers.Super)) state |= IBUS_SUPER_MASK;
        if (modifiers.HasFlag(KeyModifiers.CapsLock)) state |= IBUS_LOCK_MASK;
        return state;
    }

    public void Reset()
    {
        if (_context != IntPtr.Zero)
        {
            ibus_input_context_reset(_context);
        }

        _preEditText = string.Empty;
        _preEditCursorPosition = 0;
        _isActive = false;

        PreEditEnded?.Invoke(this, EventArgs.Empty);
        _currentContext?.OnPreEditEnded();
    }

    public void Shutdown()
    {
        Dispose();
    }

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

        if (_context != IntPtr.Zero)
        {
            ibus_input_context_focus_out(_context);
            g_object_unref(_context);
            _context = IntPtr.Zero;
        }

        if (_bus != IntPtr.Zero)
        {
            g_object_unref(_bus);
            _bus = IntPtr.Zero;
        }
    }

    #region IBus Constants

    private const uint IBUS_CAP_PREEDIT_TEXT = 1 << 0;
    private const uint IBUS_CAP_FOCUS = 1 << 3;
    private const uint IBUS_CAP_SURROUNDING_TEXT = 1 << 5;

    private const uint IBUS_SHIFT_MASK = 1 << 0;
    private const uint IBUS_LOCK_MASK = 1 << 1;
    private const uint IBUS_CONTROL_MASK = 1 << 2;
    private const uint IBUS_MOD1_MASK = 1 << 3;
    private const uint IBUS_SUPER_MASK = 1 << 26;
    private const uint IBUS_RELEASE_MASK = 1 << 30;

    private const uint IBUS_ATTR_TYPE_UNDERLINE = 1;
    private const uint IBUS_ATTR_TYPE_FOREGROUND = 2;
    private const uint IBUS_ATTR_TYPE_BACKGROUND = 3;

    #endregion

    #region IBus Interop

    private delegate void IBusCommitTextCallback(nint context, nint text, nint userData);
    private delegate void IBusUpdatePreeditTextCallback(nint context, nint text, uint cursorPos, bool visible, nint userData);
    private delegate void IBusShowPreeditTextCallback(nint context, nint userData);
    private delegate void IBusHidePreeditTextCallback(nint context, nint userData);

    [DllImport("libibus-1.0.so.5")]
    private static extern void ibus_init();

    [DllImport("libibus-1.0.so.5")]
    private static extern nint ibus_bus_new();

    [DllImport("libibus-1.0.so.5")]
    private static extern bool ibus_bus_is_connected(nint bus);

    [DllImport("libibus-1.0.so.5")]
    private static extern nint ibus_bus_create_input_context(nint bus, string clientName);

    [DllImport("libibus-1.0.so.5")]
    private static extern void ibus_input_context_set_capabilities(nint context, uint capabilities);

    [DllImport("libibus-1.0.so.5")]
    private static extern void ibus_input_context_focus_in(nint context);

    [DllImport("libibus-1.0.so.5")]
    private static extern void ibus_input_context_focus_out(nint context);

    [DllImport("libibus-1.0.so.5")]
    private static extern void ibus_input_context_reset(nint context);

    [DllImport("libibus-1.0.so.5")]
    private static extern void ibus_input_context_set_cursor_location(nint context, int x, int y, int w, int h);

    [DllImport("libibus-1.0.so.5")]
    private static extern bool ibus_input_context_process_key_event(nint context, uint keyval, uint keycode, uint state);

    [DllImport("libibus-1.0.so.5")]
    private static extern nint ibus_text_get_text(nint text);

    [DllImport("libibus-1.0.so.5")]
    private static extern nint ibus_text_get_attributes(nint text);

    [DllImport("libibus-1.0.so.5")]
    private static extern uint ibus_attr_list_size(nint attrList);

    [DllImport("libibus-1.0.so.5")]
    private static extern nint ibus_attr_list_get(nint attrList, uint index);

    [DllImport("libibus-1.0.so.5")]
    private static extern uint ibus_attribute_get_attr_type(nint attr);

    [DllImport("libibus-1.0.so.5")]
    private static extern uint ibus_attribute_get_start_index(nint attr);

    [DllImport("libibus-1.0.so.5")]
    private static extern uint ibus_attribute_get_end_index(nint attr);

    [DllImport("libgobject-2.0.so.0")]
    private static extern void g_object_unref(nint obj);

    [DllImport("libgobject-2.0.so.0")]
    private static extern ulong g_signal_connect(nint instance, string signal, nint handler, nint data);

    #endregion
}
