// 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 Microsoft.Maui.Controls;
using Microsoft.Maui.Platform.Linux.Rendering;
using Microsoft.Maui.Platform.Linux.Services;
using Microsoft.Maui.Platform.Linux.Window;
using Microsoft.Maui.Platform;
using SkiaSharp;

namespace Microsoft.Maui.Platform.Linux;

public partial class LinuxApplication
{
    private void UpdateAnimations()
    {
        // Update cursor blink for text input controls
        if (_focusedView is SkiaEntry entry)
        {
            entry.UpdateCursorBlink();
        }
        else if (_focusedView is SkiaEditor editor)
        {
            editor.UpdateCursorBlink();
        }
    }

    private void OnWindowResized(object? sender, (int Width, int Height) size)
    {
        if (_rootView != null)
        {
            // Re-measure with new available size, then arrange
            var availableSize = new Microsoft.Maui.Graphics.Size(size.Width, size.Height);
            _rootView.Measure(availableSize);
            _rootView.Arrange(new Microsoft.Maui.Graphics.Rect(0, 0, size.Width, size.Height));
        }
        _renderingEngine?.InvalidateAll();
    }

    private void OnWindowExposed(object? sender, EventArgs e)
    {
        Render();
    }

    private void OnKeyDown(object? sender, KeyEventArgs e)
    {
        // Route to dialog if one is active
        if (LinuxDialogService.HasActiveDialog)
        {
            LinuxDialogService.TopDialog?.OnKeyDown(e);
            return;
        }

        if (_focusedView != null)
        {
            _focusedView.OnKeyDown(e);
        }
    }

    private void OnKeyUp(object? sender, KeyEventArgs e)
    {
        // Route to dialog if one is active
        if (LinuxDialogService.HasActiveDialog)
        {
            LinuxDialogService.TopDialog?.OnKeyUp(e);
            return;
        }

        if (_focusedView != null)
        {
            _focusedView.OnKeyUp(e);
        }
    }

    private void OnTextInput(object? sender, TextInputEventArgs e)
    {
        if (_focusedView != null)
        {
            _focusedView.OnTextInput(e);
        }
    }

    private void OnPointerMoved(object? sender, PointerEventArgs e)
    {
        // Route to context menu if one is active
        if (LinuxDialogService.HasContextMenu)
        {
            LinuxDialogService.ActiveContextMenu?.OnPointerMoved(e);
            return;
        }

        // Route to dialog if one is active
        if (LinuxDialogService.HasActiveDialog)
        {
            LinuxDialogService.TopDialog?.OnPointerMoved(e);
            return;
        }

        if (_rootView != null)
        {
            // If a view has captured the pointer, send all events to it
            if (_capturedView != null)
            {
                _capturedView.OnPointerMoved(e);
                return;
            }

            // Check for popup overlay first
            var popupOwner = SkiaView.GetPopupOwnerAt(e.X, e.Y);
            var hitView = popupOwner ?? _rootView.HitTest(e.X, e.Y);

            // Track hover state changes
            if (hitView != _hoveredView)
            {
                _hoveredView?.OnPointerExited(e);
                _hoveredView = hitView;
                _hoveredView?.OnPointerEntered(e);

                // Update cursor based on view's cursor type
                CursorType cursor = hitView?.CursorType ?? CursorType.Arrow;
                _mainWindow?.SetCursor(cursor);
            }

            hitView?.OnPointerMoved(e);
        }
    }

    private void OnPointerPressed(object? sender, PointerEventArgs e)
    {
        DiagnosticLog.Debug("LinuxApplication", $"OnPointerPressed at ({e.X}, {e.Y}), Button={e.Button}");

        // Route to context menu if one is active
        if (LinuxDialogService.HasContextMenu)
        {
            LinuxDialogService.ActiveContextMenu?.OnPointerPressed(e);
            return;
        }

        // Route to dialog if one is active
        if (LinuxDialogService.HasActiveDialog)
        {
            LinuxDialogService.TopDialog?.OnPointerPressed(e);
            return;
        }

        if (_rootView != null)
        {
            // Check for popup overlay first
            var popupOwner = SkiaView.GetPopupOwnerAt(e.X, e.Y);
            var hitView = popupOwner ?? _rootView.HitTest(e.X, e.Y);
            DiagnosticLog.Debug("LinuxApplication", $"HitView: {hitView?.GetType().Name ?? "null"}, rootView: {_rootView.GetType().Name}");

            if (hitView != null)
            {
                // Capture pointer to this view for drag operations
                _capturedView = hitView;

                // Update focus
                if (hitView.IsFocusable)
                {
                    FocusedView = hitView;
                }

                DiagnosticLog.Debug("LinuxApplication", $"Calling OnPointerPressed on {hitView.GetType().Name}");
                hitView.OnPointerPressed(e);
            }
            else
            {
                // Close any open popups when clicking outside
                if (SkiaView.HasActivePopup && _focusedView != null)
                {
                    _focusedView.OnFocusLost();
                }
                FocusedView = null;
            }
        }
    }

    private void OnPointerReleased(object? sender, PointerEventArgs e)
    {
        // Route to dialog if one is active
        if (LinuxDialogService.HasActiveDialog)
        {
            LinuxDialogService.TopDialog?.OnPointerReleased(e);
            return;
        }

        if (_rootView != null)
        {
            // If a view has captured the pointer, send release to it
            if (_capturedView != null)
            {
                _capturedView.OnPointerReleased(e);
                _capturedView = null; // Release capture
                return;
            }

            // Check for popup overlay first
            var popupOwner = SkiaView.GetPopupOwnerAt(e.X, e.Y);
            var hitView = popupOwner ?? _rootView.HitTest(e.X, e.Y);
            hitView?.OnPointerReleased(e);
        }
    }

    private void OnScroll(object? sender, ScrollEventArgs e)
    {
        DiagnosticLog.Debug("LinuxApplication", $"OnScroll - X={e.X}, Y={e.Y}, DeltaX={e.DeltaX}, DeltaY={e.DeltaY}");
        if (_rootView != null)
        {
            var hitView = _rootView.HitTest(e.X, e.Y);
            DiagnosticLog.Debug("LinuxApplication", $"HitView: {hitView?.GetType().Name ?? "null"}");
            // Bubble scroll events up to find a ScrollView
            var view = hitView;
            while (view != null)
            {
                DiagnosticLog.Debug("LinuxApplication", $"Bubbling to: {view.GetType().Name}");
                if (view is SkiaScrollView scrollView)
                {
                    scrollView.OnScroll(e);
                    return;
                }
                view.OnScroll(e);
                if (e.Handled) return;
                view = view.Parent;
            }
        }
    }

    private void OnCloseRequested(object? sender, EventArgs e)
    {
        _mainWindow?.Stop();
    }

    // GTK Event Handlers
    private void OnGtkDrawRequested(object? sender, EventArgs e)
    {
        DiagnosticLog.Debug("LinuxApplication", ">>> OnGtkDrawRequested ENTER");
        LogDraw();
        var surface = _gtkWindow?.SkiaSurface;
        if (surface?.Canvas != null && _rootView != null)
        {
            var bgColor = Application.Current?.UserAppTheme == AppTheme.Dark
                ? new SKColor(32, 33, 36)
                : SKColors.White;
            surface.Canvas.Clear(bgColor);
            DiagnosticLog.Debug("LinuxApplication", "Drawing rootView...");
            _rootView.Draw(surface.Canvas);
            DiagnosticLog.Debug("LinuxApplication", "Drawing dialogs...");
            var bounds = new SKRect(0, 0, surface.Width, surface.Height);
            LinuxDialogService.DrawDialogs(surface.Canvas, bounds);
            DiagnosticLog.Debug("LinuxApplication", "<<< OnGtkDrawRequested EXIT");
        }
    }

    private void OnGtkResized(object? sender, (int Width, int Height) size)
    {
        PerformGtkLayout(size.Width, size.Height);
        _gtkWindow?.RequestRedraw();
    }

    private void OnGtkPointerPressed(object? sender, (double X, double Y, int Button) e)
    {
        string buttonName = e.Button == 1 ? "Left" : e.Button == 2 ? "Middle" : e.Button == 3 ? "Right" : $"Unknown({e.Button})";
        DiagnosticLog.Debug("LinuxApplication", $"GTK PointerPressed at ({e.X:F1}, {e.Y:F1}), Button={e.Button} ({buttonName})");

        // Route to dialog if one is active
        if (LinuxDialogService.HasActiveDialog)
        {
            var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
            var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
            LinuxDialogService.TopDialog?.OnPointerPressed(args);
            _gtkWindow?.RequestRedraw();
            return;
        }

        if (LinuxDialogService.HasContextMenu)
        {
            var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
            var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
            LinuxDialogService.ActiveContextMenu?.OnPointerPressed(args);
            _gtkWindow?.RequestRedraw();
            return;
        }

        if (_rootView == null)
        {
            DiagnosticLog.Warn("LinuxApplication", "GTK _rootView is null!");
            return;
        }

        var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
        DiagnosticLog.Debug("LinuxApplication", $"GTK HitView: {hitView?.GetType().Name ?? "null"}");

        if (hitView != null)
        {
            if (hitView.IsFocusable && _focusedView != hitView)
            {
                _focusedView?.OnFocusLost();
                _focusedView = hitView;
                _focusedView.OnFocusGained();
            }
            _capturedView = hitView;
            var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
            var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
            DiagnosticLog.Debug("LinuxApplication", ">>> Before OnPointerPressed");
            hitView.OnPointerPressed(args);
            DiagnosticLog.Debug("LinuxApplication", "<<< After OnPointerPressed, calling RequestRedraw");
            _gtkWindow?.RequestRedraw();
            DiagnosticLog.Debug("LinuxApplication", "<<< After RequestRedraw, returning from handler");
        }
    }

    private void OnGtkPointerReleased(object? sender, (double X, double Y, int Button) e)
    {
        DiagnosticLog.Debug("LinuxApplication", ">>> OnGtkPointerReleased ENTER");

        // Route to dialog if one is active
        if (LinuxDialogService.HasActiveDialog)
        {
            var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
            var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
            LinuxDialogService.TopDialog?.OnPointerReleased(args);
            _gtkWindow?.RequestRedraw();
            return;
        }

        if (_rootView == null) return;

        if (_capturedView != null)
        {
            var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
            var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
            DiagnosticLog.Debug("LinuxApplication", $"Calling OnPointerReleased on {_capturedView.GetType().Name}");
            _capturedView.OnPointerReleased(args);
            DiagnosticLog.Debug("LinuxApplication", "OnPointerReleased returned");
            _capturedView = null;
            _gtkWindow?.RequestRedraw();
            DiagnosticLog.Debug("LinuxApplication", "<<< OnGtkPointerReleased EXIT (captured path)");
        }
        else
        {
            var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
            if (hitView != null)
            {
                var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
                var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
                hitView.OnPointerReleased(args);
                _gtkWindow?.RequestRedraw();
            }
        }
    }

    private void OnGtkPointerMoved(object? sender, (double X, double Y) e)
    {
        // Route to dialog if one is active
        if (LinuxDialogService.HasActiveDialog)
        {
            var args = new PointerEventArgs((float)e.X, (float)e.Y);
            LinuxDialogService.TopDialog?.OnPointerMoved(args);
            _gtkWindow?.RequestRedraw();
            return;
        }

        if (LinuxDialogService.HasContextMenu)
        {
            var args = new PointerEventArgs((float)e.X, (float)e.Y);
            LinuxDialogService.ActiveContextMenu?.OnPointerMoved(args);
            _gtkWindow?.RequestRedraw();
            return;
        }

        if (_rootView == null) return;

        if (_capturedView != null)
        {
            var args = new PointerEventArgs((float)e.X, (float)e.Y);
            _capturedView.OnPointerMoved(args);
            _gtkWindow?.RequestRedraw();
            return;
        }

        var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
        if (hitView != _hoveredView)
        {
            var args = new PointerEventArgs((float)e.X, (float)e.Y);
            _hoveredView?.OnPointerExited(args);
            _hoveredView = hitView;
            _hoveredView?.OnPointerEntered(args);
            _gtkWindow?.RequestRedraw();
        }

        if (hitView != null)
        {
            var args = new PointerEventArgs((float)e.X, (float)e.Y);
            hitView.OnPointerMoved(args);
        }
    }

    private void OnGtkKeyPressed(object? sender, (uint KeyVal, uint KeyCode, uint State) e)
    {
        var key = ConvertGdkKey(e.KeyVal);
        var modifiers = ConvertGdkModifiers(e.State);
        var args = new KeyEventArgs(key, modifiers);

        // Route to dialog if one is active
        if (LinuxDialogService.HasActiveDialog)
        {
            LinuxDialogService.TopDialog?.OnKeyDown(args);
            _gtkWindow?.RequestRedraw();
            return;
        }

        if (_focusedView != null)
        {
            _focusedView.OnKeyDown(args);
            _gtkWindow?.RequestRedraw();
        }
    }

    private void OnGtkKeyReleased(object? sender, (uint KeyVal, uint KeyCode, uint State) e)
    {
        var key = ConvertGdkKey(e.KeyVal);
        var modifiers = ConvertGdkModifiers(e.State);
        var args = new KeyEventArgs(key, modifiers);

        // Route to dialog if one is active
        if (LinuxDialogService.HasActiveDialog)
        {
            LinuxDialogService.TopDialog?.OnKeyUp(args);
            _gtkWindow?.RequestRedraw();
            return;
        }

        if (_focusedView != null)
        {
            _focusedView.OnKeyUp(args);
            _gtkWindow?.RequestRedraw();
        }
    }

    private void OnGtkScrolled(object? sender, (double X, double Y, double DeltaX, double DeltaY, uint State) e)
    {
        if (_rootView == null) return;

        // Convert GDK state to KeyModifiers
        var modifiers = ConvertGdkStateToModifiers(e.State);
        bool isCtrlPressed = (modifiers & KeyModifiers.Control) != 0;

        var hitView = _rootView.HitTest((float)e.X, (float)e.Y);

        // Check for pinch gesture (Ctrl+Scroll) first
        if (isCtrlPressed && hitView?.MauiView != null)
        {
            if (Handlers.GestureManager.ProcessScrollAsPinch(hitView.MauiView, e.X, e.Y, e.DeltaY, true))
            {
                _gtkWindow?.RequestRedraw();
                return;
            }
        }

        while (hitView != null)
        {
            if (hitView is SkiaScrollView scrollView)
            {
                var args = new ScrollEventArgs((float)e.X, (float)e.Y, (float)e.DeltaX, (float)e.DeltaY, modifiers);
                scrollView.OnScroll(args);
                _gtkWindow?.RequestRedraw();
                break;
            }
            hitView = hitView.Parent;
        }
    }

    private static KeyModifiers ConvertGdkStateToModifiers(uint state)
    {
        var modifiers = KeyModifiers.None;
        // GDK modifier masks
        const uint GDK_SHIFT_MASK = 1 << 0;
        const uint GDK_CONTROL_MASK = 1 << 2;
        const uint GDK_MOD1_MASK = 1 << 3;  // Alt
        const uint GDK_SUPER_MASK = 1 << 26;
        const uint GDK_LOCK_MASK = 1 << 1;  // Caps Lock

        if ((state & GDK_SHIFT_MASK) != 0) modifiers |= KeyModifiers.Shift;
        if ((state & GDK_CONTROL_MASK) != 0) modifiers |= KeyModifiers.Control;
        if ((state & GDK_MOD1_MASK) != 0) modifiers |= KeyModifiers.Alt;
        if ((state & GDK_SUPER_MASK) != 0) modifiers |= KeyModifiers.Super;
        if ((state & GDK_LOCK_MASK) != 0) modifiers |= KeyModifiers.CapsLock;

        return modifiers;
    }

    private void OnGtkTextInput(object? sender, string text)
    {
        if (_focusedView != null)
        {
            var args = new TextInputEventArgs(text);
            _focusedView.OnTextInput(args);
            _gtkWindow?.RequestRedraw();
        }
    }

    private static Key ConvertGdkKey(uint keyval)
    {
        return keyval switch
        {
            65288 => Key.Backspace,
            65289 => Key.Tab,
            65293 => Key.Enter,
            65307 => Key.Escape,
            65360 => Key.Home,
            65361 => Key.Left,
            65362 => Key.Up,
            65363 => Key.Right,
            65364 => Key.Down,
            65365 => Key.PageUp,
            65366 => Key.PageDown,
            65367 => Key.End,
            65535 => Key.Delete,
            >= 32 and <= 126 => (Key)keyval,
            _ => Key.Unknown
        };
    }

    private static KeyModifiers ConvertGdkModifiers(uint state)
    {
        var modifiers = KeyModifiers.None;
        if ((state & 1) != 0) modifiers |= KeyModifiers.Shift;
        if ((state & 4) != 0) modifiers |= KeyModifiers.Control;
        if ((state & 8) != 0) modifiers |= KeyModifiers.Alt;
        return modifiers;
    }
}
