using System;
using Decal.Adapter;
using Decal.Adapter.Wrappers;
using System.Numerics;
using Mag.Shared.Constants;
using System.Linq;

namespace MosswartMassacre
{
    /// <summary>
    ///  Misc. helpers shared across the plugin.
    ///  Add new utilities here as your project grows.
    /// </summary>
    public static class Utils
    {
        /* ----------------------------------------------------------
         * 1) Direct-memory physics helpers
         * -------------------------------------------------------- */

        /// <summary>
        ///  Return the player’s raw world position by reading the
        ///  physics-object pointer (same offsets UB uses: +0x84/88/8C).
        /// </summary>
        public static unsafe Vector3 GetPlayerPosition()
        {
            try
            {
                int id = CoreManager.Current.CharacterFilter.Id;
                if (!CoreManager.Current.Actions.IsValidObject(id))
                    return new Vector3();

                byte* p = (byte*)CoreManager.Current.Actions.Underlying.GetPhysicsObjectPtr(id);
                return new Vector3(
                    *(float*)(p + 0x84),   // X
                    *(float*)(p + 0x88),   // Y
                    *(float*)(p + 0x8C));  // Z
            }
            catch
            {
                return new Vector3();
            }
        }

        /// <summary>
        ///  Return any WorldObject's raw world position by reading the
        ///  physics-object pointer (same offsets: +0x84/88/8C).
        /// </summary>
        public static unsafe Vector3 GetWorldObjectPosition(int objectId)
        {
            try
            {
                if (!CoreManager.Current.Actions.IsValidObject(objectId))
                    return new Vector3();

                byte* p = (byte*)CoreManager.Current.Actions.Underlying.GetPhysicsObjectPtr(objectId);
                return new Vector3(
                    *(float*)(p + 0x84),   // X
                    *(float*)(p + 0x88),   // Y
                    *(float*)(p + 0x8C));  // Z
            }
            catch
            {
                return new Vector3();
            }
        }

        /// <summary>
        ///  Convenience: returns the current landcell (upper 16 bits of landblock).
        /// </summary>
        public static uint GetPlayerLandcell() =>
            (uint)CoreManager.Current.Actions.Landcell;

        /* ----------------------------------------------------------
         * 2) High-level wrappers around Geometry / Coordinates
         * -------------------------------------------------------- */

        /// <summary>
        ///  Get AC-style coordinates (EW/NS) for the player in one call.
        /// </summary>
        public static Coordinates GetPlayerCoordinates()
        {
            Vector3 pos = GetPlayerPosition();
            uint landcell = GetPlayerLandcell();

            double ew = Geometry.LandblockToEW(landcell, pos.X);
            double ns = Geometry.LandblockToNS(landcell, pos.Y);

            return new Coordinates(ew, ns, pos.Z);
        }

        /// <summary>
        ///  Get AC-style coordinates (EW/NS/Z) for any WorldObject.
        /// </summary>
        public static Coordinates GetWorldObjectCoordinates(WorldObject wo)
        {
            if (wo == null) return new Coordinates();

            Vector3 pos = GetWorldObjectPosition(wo.Id);
            
            // Get landcell from the object's coordinates
            var coordsObj = wo.Coordinates();
            if (coordsObj == null) return new Coordinates();
            
            // Convert DECAL coords to our Coordinates with Z
            double ew = coordsObj.EastWest;
            double ns = coordsObj.NorthSouth;
            
            return new Coordinates(ew, ns, pos.Z);
        }

        /* ----------------------------------------------------------
         * 3) Generic math helpers you may want later
         * -------------------------------------------------------- */

        public static double Clamp(double value, double min, double max) =>
            value < min ? min : (value > max ? max : value);

        public static double DegToRad(double deg) => deg * Math.PI / 180.0;
        public static double RadToDeg(double rad) => rad * 180.0 / Math.PI;

        /* ----------------------------------------------------------
         * 4) Generic item property access
         * -------------------------------------------------------- */

        /// <summary>
        /// Find a WorldObject item by name in inventory
        /// </summary>
        /// <param name="itemName">Name of the item to find</param>
        /// <returns>WorldObject or null if not found</returns>
        public static WorldObject FindItemByName(string itemName)
        {
            try
            {
                //var worldFilter = CoreManager.Current.WorldFilter;
                //var playerInv = CoreManager.Current.CharacterFilter.Id;

                // Search inventory
              
                foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
                {
                    if (string.Equals(wo.Name, itemName, StringComparison.OrdinalIgnoreCase))
                        return wo;
                }
                
                return null;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Get the stack size/quantity of a specific item by name
        /// </summary>
        /// <param name="itemName">Name of the item to find</param>
        /// <returns>Stack size or 0 if not found</returns>
        /// <summary>
        /// Return the total quantity of an item in the character’s inventory,
        /// adding up every stack that shares <paramref name="itemName"/>.
        /// </summary>
        public static int GetItemStackSize(string itemName)
        {
            try
            {
                // 1. Pull every WorldObject in bags + containers
                var inv = CoreManager.Current.WorldFilter.GetInventory();

                // 2. Keep only those whose display name matches (case-insensitive)
                // 3. For each one, use StackCount if it exists, otherwise treat as 1
                return inv.Where(wo =>
                            string.Equals(wo.Name, itemName,
                                          StringComparison.OrdinalIgnoreCase))
                          .Sum(wo =>
                          {
                              // Some items (weapons, armor) aren’t stackable;
                              // Values(LongValueKey.StackCount) throws if the key is absent.
                              try
                              {
                                  return wo.Values(LongValueKey.StackCount);
                              }
                              catch
                              {
                                  return 1;   // non-stackable item = quantity 1
                              }
                          });
            }
            catch
            {
                return 0;
            }
        }


        /// <summary>
        /// Get the icon ID of a specific item by name
        /// </summary>
        /// <param name="itemName">Name of the item to find</param>
        /// <returns>Icon ID or 0 if not found</returns>
        public static int GetItemIcon(string itemName)
        {
            try
            {
                var item = FindItemByName(itemName);
                return item?.Icon ?? 0;
            }
            catch
            {
                return 0;
            }
        }

        /// <summary>
        /// Get the display icon ID (with 0x6000000 offset) for an item by name
        /// </summary>
        /// <param name="itemName">Name of the item to find</param>
        /// <returns>Display icon ID or 0x6002D14 (default icon) if not found</returns>
        public static int GetItemDisplayIcon(string itemName)
        {
            int rawIcon = GetItemIcon(itemName);
            return rawIcon != 0 ? rawIcon + 0x6000000 : 0x6002D14;
        }

        /* ----------------------------------------------------------
         * 5) Chest Looter helper methods
         * -------------------------------------------------------- */

        /// <summary>
        /// Calculate 3D distance from player to a world object
        /// </summary>
        /// <param name="objectId">World object ID</param>
        /// <returns>Distance in meters, or float.MaxValue if object is invalid</returns>
        public static float GetDistanceToWorldObject(int objectId)
        {
            try
            {
                if (!CoreManager.Current.Actions.IsValidObject(objectId))
                    return float.MaxValue;

                Vector3 playerPos = GetPlayerPosition();
                Vector3 objectPos = GetWorldObjectPosition(objectId);

                return Vector3.Distance(playerPos, objectPos);
            }
            catch
            {
                return float.MaxValue;
            }
        }

        /// <summary>
        /// Find the closest chest with the specified name in the game world
        /// </summary>
        /// <param name="chestName">Name of the chest to find</param>
        /// <returns>WorldObject of the closest chest, or null if not found</returns>
        public static WorldObject FindClosestChestByName(string chestName)
        {
            try
            {
                WorldObject closestChest = null;
                float closestDistance = float.MaxValue;

                // Search all objects in WorldFilter
                using (var objects = CoreManager.Current.WorldFilter.GetAll())
                {
                    foreach (WorldObject wo in objects)
                    {
                        // Check if this is a container (chest)
                        if (wo.ObjectClass != ObjectClass.Container)
                            continue;

                        // Check if name matches (case-insensitive, partial match allowed)
                        if (!wo.Name.Contains(chestName) &&
                            !string.Equals(wo.Name, chestName, StringComparison.OrdinalIgnoreCase))
                            continue;

                        // Calculate distance
                        float distance = GetDistanceToWorldObject(wo.Id);

                        // Update closest if this is nearer
                        if (distance < closestDistance)
                        {
                            closestDistance = distance;
                            closestChest = wo;
                        }
                    }
                }

                return closestChest;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Find a key in the player's inventory by name
        /// </summary>
        /// <param name="keyName">Name of the key to find</param>
        /// <returns>WorldObject of the key, or null if not found</returns>
        public static WorldObject FindKeyInInventory(string keyName)
        {
            try
            {
                foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
                {
                    // Check if this is a key
                    if (wo.ObjectClass != ObjectClass.Key)
                        continue;

                    // Check if name matches (case-insensitive, partial match allowed)
                    if (wo.Name.Contains(keyName) ||
                        string.Equals(wo.Name, keyName, StringComparison.OrdinalIgnoreCase))
                    {
                        return wo;
                    }
                }

                return null;
            }
            catch
            {
                return null;
            }
        }
    }
}
