using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Tumble.Games
{
    public static class Groups
    {
        public static int GetWidth()
        {
            return Console.WindowWidth - 1;
        }
        public static int GetHeight()
        {
            return Console.WindowHeight - 1;
        }
        [StructLayout(LayoutKind.Sequential)]
        public struct COORD
        {
            public short X;
            public short Y;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct CONSOLE_SCREEN_BUFFER_INFO
        {
            public COORD dwSize;
            public COORD dwCursorPosition;
            public ushort wAttributes;
            public SMALL_RECT srWindow;
            public COORD dwMaximumWindowSize;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SMALL_RECT
        {
            public short Left;
            public short Top;
            public short Right;
            public short Bottom;
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr GetStdHandle(int nStdHandle);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool GetConsoleScreenBufferInfo(IntPtr hConsoleOutput, out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo);

        [DllImport("user32.dll")]
        public static extern bool GetCursorPos(out POINT lpPoint);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool GetConsoleWindow(out IntPtr consoleWindow);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int X;
            public int Y;
        }

        public const int STD_OUTPUT_HANDLE = -11;
        public static char[] Chars = ['@'];
        public static List<Particle> Particles = new();
        public static int TickDelay;
        public static bool Dragging;
        public static int DragRadius;
        public static int MaxParticles;
        static ConsoleColor GetColorFromValue(int value)
        {
            return value switch
            {
                < 10 => ConsoleColor.Cyan,
                < 100 => ConsoleColor.Green,
                < 1000 => ConsoleColor.Yellow,
                < 5000 => ConsoleColor.Red,
                < 10000 => ConsoleColor.Magenta,
                _ => ConsoleColor.White
            };
        }
        public static (int x, int y) GetAccurateCursorPosition()
        {
            // Get console window handle
            IntPtr consoleWindow;
            GetConsoleWindow(out consoleWindow);

            // Get cursor position
            POINT point;
            GetCursorPos(out point);

            // Convert screen coordinates to client coordinates
            ScreenToClient(consoleWindow, ref point);

            // Get console buffer info
            var handle = GetStdHandle(STD_OUTPUT_HANDLE);
            CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
            GetConsoleScreenBufferInfo(handle, out bufferInfo);

            // Calculate character size
            var fontSize = new
            {
                X = (bufferInfo.srWindow.Right - bufferInfo.srWindow.Left + 1) / (float)Console.WindowWidth,
                Y = (bufferInfo.srWindow.Bottom - bufferInfo.srWindow.Top + 1) / (float)Console.WindowHeight
            };

            // Convert pixel coordinates to character coordinates
            int charX = (int)Math.Floor(point.X / fontSize.X);
            int charY = (int)Math.Floor(point.Y / fontSize.Y);

            // Ensure coordinates are within bounds
            charX = Math.Max(0, Math.Min(charX, Console.WindowWidth - 1));
            charY = Math.Max(0, Math.Min(charY, Console.WindowHeight - 1));

            return (charX, charY);
        }
        static void Reset()
        {
            Particles.Clear();
            Dragging = true;
            TickDelay = 10;
            DragRadius = 15;
            MaxParticles = 250;
        }
        public static void Start()
        {
            Reset();
            var tempTitle = Console.Title;
            Console.Title = "Groups";
            var tempVisible = Console.CursorVisible;
            Console.CursorVisible = false;
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.BackgroundColor = ConsoleColor.Black;
            while (true)
            {
                UI.Wipe();
                var particlesToAdd = new List<Particle>();
                var particlesToRemove = new List<Particle>();
                // merge particles with relatively the same position
                var grouped = Particles.GroupBy(p => (p.X / 2, p.Y));
                foreach (var group in grouped)
                {
                    if (group.Count() > 1)
                    {
                        Particle merged = group.Aggregate((p1, p2) => Particle.Merge(p1, p2));
                        particlesToRemove.AddRange(group);
                        particlesToAdd.Add(merged);
                    }
                }
                foreach (Particle p in particlesToRemove)
                {
                    Particles.Remove(p);
                }
                particlesToRemove.Clear();
                foreach (Particle p in particlesToAdd)
                {
                    Particles.Add(p);
                }
                foreach (Particle p in Particles)
                {
                    p.Move();
                    if (Dragging)
                    {
                        (int x, int y) = GetAccurateCursorPosition();
                        if (Math.Abs(x - p.X) < DragRadius && Math.Abs(y - p.Y) < DragRadius)
                        {
                            p.GravitateTowards(x, y);
                        }
                    }
                    try
                    {
                        Console.SetCursorPosition(p.X, p.Y);
                        Console.ForegroundColor = GetColorFromValue(p.Value);
                        Console.Write(p.Value == 1 ? p.Symbol : p.Value.ToString());
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        if (p.Value == 1)
                        {
                            particlesToRemove.Add(p);
                        }
                    }
                }
                foreach (Particle p in particlesToRemove)
                {
                    Particles.Remove(p);
                }
                if (Particles.Count < MaxParticles)
                {
                    AddParticle();
                }
                Thread.Sleep(TickDelay);
                if (Console.KeyAvailable)
                {
                    ConsoleKeyInfo ki = Console.ReadKey(true);
                    var key = ki.Key;
                    bool ctrl = ki.Modifiers.HasFlag(ConsoleModifiers.Control);
                    bool shift = ki.Modifiers.HasFlag(ConsoleModifiers.Shift);
                    bool alt = ki.Modifiers.HasFlag(ConsoleModifiers.Alt);
                    switch (key)
                    {
                        case ConsoleKey.Spacebar:
                            Dragging = !Dragging;
                            break;
                        case ConsoleKey.R when ctrl:
                            Particles.Clear();
                            break;
                        case ConsoleKey.Escape:
                            Console.CursorVisible = tempVisible;
                            Console.Title = tempTitle;
                            UI.Wipe();
                            return;
                    }
                }
            }
        }
        public static void AddParticle()
        {
            const int LEFT = 1;
            const int RIGHT = 2;
            const int TOP = 3;
            const int BOTTOM = 4;
            var comeFromSide = Random.Shared.Next(1, 5);
            int x = 0;
            int y = 0;
            int vx = 0;
            int vy = 0;
            switch (comeFromSide)
            {
                case LEFT:
                    x = 0;
                    y = Random.Shared.Next(0, GetHeight());
                    vx = 1;
                    vy = Random.Shared.Next(-1, 2);
                    break;
                case RIGHT:
                    x = GetWidth();
                    y = Random.Shared.Next(0, GetHeight());
                    vx = -1;
                    vy = Random.Shared.Next(-1, 2);
                    break;
                case TOP:
                    x = Random.Shared.Next(0, GetWidth());
                    y = 0;
                    vx = Random.Shared.Next(-1, 2);
                    vy = 1;
                    break;
                case BOTTOM:
                    x = Random.Shared.Next(0, GetWidth());
                    y = GetHeight();
                    vx = Random.Shared.Next(-1, 2);
                    vy = -1;
                    break;
            }
            Particles.Add(new Particle(x, y, '.')
            {
                VelocityX = vx,
                VelocityY = vy,
                Value = 1,
            });
        }
        public class Particle
        {
            public int X { get; set; }
            public int Y { get; set; }
            public char Symbol { get; set; }
            public int VelocityX { get; set; }
            public int VelocityY { get; set; }
            public int Value { get; set; }
            public Particle(int x, int y, char symbol)
            {
                X = x;
                Y = y;
                Symbol = symbol;
                VelocityX = 0;
                VelocityY = 1;
                Value = 1;
            }
            public Particle()
            {
            }
            public static Particle Merge(Particle p1, Particle p2)
            {
                return new Particle()
                {
                    X = p1.Value > p2.Value ? p1.X : p2.X,
                    Y = p1.Value > p2.Value ? p1.Y : p2.Y,
                    Symbol = p1.Value > p2.Value ? p1.Symbol : p2.Symbol,
                    VelocityX = p1.Value > p2.Value ? p1.VelocityX : p2.VelocityX,
                    VelocityY = p1.Value > p2.Value ? p1.VelocityY : p2.VelocityY,
                    Value = p1.Value + p2.Value
                };
            }
            public void GravitateTowards(int x, int y)
            {
                if (x > X)
                {
                    VelocityX = 1;
                }
                else if (x < X)
                {
                    VelocityX = -1;
                }
                else
                {
                    VelocityX = 0;
                }
                if (y > Y)
                {
                    VelocityY = 1;
                }
                else if (y < Y)
                {
                    VelocityY = -1;
                }
                else
                {
                    VelocityY = 0;
                }
            }
            public void Move()
            {
                if (X + VelocityX > GetWidth() || X + VelocityX < 1)
                {
                    VelocityX *= -1;
                }
                if (Y + VelocityY > GetHeight() || Y + VelocityY < 1)
                {
                    VelocityY *= -1;
                }
                X += VelocityX * 2;
                Y += VelocityY;
            }
        }
    }
}

the mouse position calculation is not correct, it should translate the mouse position on the screen to the position of the cursor on the terminal, like in vim. fix it and reply with the full fixed code.\