using Content.Shared.Hands.Components;
using Content.Shared.Interaction;
using Robust.Shared.Containers;
using Robust.Shared.Map;

namespace Content.Shared.Hands.EntitySystems;

public abstract partial class SharedHandsSystem : EntitySystem
{
    /// <summary>
    ///     Checks if the contents of a hand is able to be removed from its container.
    /// </summary>
    public bool CanDropHeld(EntityUid uid, Hand hand, bool checkActionBlocker = true)
    {
        if (hand.HeldEntity == null)
            return false;

        if (!hand.Container!.CanRemove(hand.HeldEntity.Value, EntityManager))
            return false;

        if (checkActionBlocker && !_actionBlocker.CanDrop(uid))
            return false;

        return true;
    }

    /// <summary>
    ///     Attempts to drop the item in the currently active hand.
    /// </summary>
    public bool TryDrop(EntityUid uid, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, SharedHandsComponent? handsComp = null)
    {
        if (!Resolve(uid, ref handsComp))
            return false;

        if (handsComp.ActiveHand == null)
            return false;

        return TryDrop(uid, handsComp.ActiveHand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp);
    }

    /// <summary>
    ///     Drops an item at the target location.
    /// </summary>
    public bool TryDrop(EntityUid uid, EntityUid entity, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, SharedHandsComponent? handsComp = null)
    {
        if (!Resolve(uid, ref handsComp))
            return false;

        if (!IsHolding(uid, entity, out var hand, handsComp))
            return false;

        return TryDrop(uid, hand, targetDropLocation, checkActionBlocker, doDropInteraction, handsComp);
    }

    /// <summary>
    ///     Drops a hands contents at the target location.
    /// </summary>
    public bool TryDrop(EntityUid uid, Hand hand, EntityCoordinates? targetDropLocation = null, bool checkActionBlocker = true, bool doDropInteraction = true, SharedHandsComponent? handsComp = null)
    {
        if (!Resolve(uid, ref handsComp))
            return false;

        if (!CanDropHeld(uid, hand, checkActionBlocker))
            return false;

        var entity = hand.HeldEntity!.Value;
        DoDrop(uid, hand, doDropInteraction: doDropInteraction, handsComp);

        var xform = Transform(uid);

        if (targetDropLocation == null)
        {
            // TODO recursively check upwards for containers
            Transform(entity).AttachParentToContainerOrGrid(EntityManager);
            return true;
        }

        var target = targetDropLocation.Value.ToMap(EntityManager);
        Transform(entity).WorldPosition = GetFinalDropCoordinates(uid, xform.MapPosition, target);
        return true;
    }

    /// <summary>
    ///     Attempts to move a held item from a hand into a container that is not another hand, without dropping it on the floor in-between.
    /// </summary>
    public bool TryDropIntoContainer(EntityUid uid, EntityUid entity, BaseContainer targetContainer, bool checkActionBlocker = true, SharedHandsComponent? handsComp = null)
    {
        if (!Resolve(uid, ref handsComp))
            return false;

        if (!IsHolding(uid, entity, out var hand, handsComp))
            return false;

        if (!CanDropHeld(uid, hand, checkActionBlocker))
            return false;

        if (!targetContainer.CanInsert(entity, EntityManager))
            return false;

        DoDrop(uid, hand, false, handsComp);
        targetContainer.Insert(entity);
        return true;
    }

    /// <summary>
    ///     Calculates the final location a dropped item will end up at, accounting for max drop range and collision along the targeted drop path.
    /// </summary>
    private Vector2 GetFinalDropCoordinates(EntityUid user, MapCoordinates origin, MapCoordinates target)
    {
        var dropVector = target.Position - origin.Position;
        var requestedDropDistance = dropVector.Length;

        if (dropVector.Length > SharedInteractionSystem.InteractionRange)
        {
            dropVector = dropVector.Normalized * SharedInteractionSystem.InteractionRange;
            target = new MapCoordinates(origin.Position + dropVector, target.MapId);
        }

        var dropLength = _interactionSystem.UnobstructedDistance(origin, target, predicate: e => e == user);

        if (dropLength < requestedDropDistance)
            return origin.Position + dropVector.Normalized * dropLength;
        return target.Position;
    }

    /// <summary>
    ///     Removes the contents of a hand from its container. Assumes that the removal is allowed. In general, you should not be calling this directly.
    /// </summary>
    public virtual void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, SharedHandsComponent? handsComp = null)
    {
        if (!Resolve(uid, ref handsComp))
            return;

        if (hand.Container?.ContainedEntity == null)
            return;

        var entity = hand.Container.ContainedEntity.Value;

        if (!hand.Container!.Remove(entity, EntityManager))
        {
            Logger.Error($"{nameof(SharedHandsComponent)} on {uid} could not remove {entity} from {hand.Container}.");
            return;
        }

        Dirty(handsComp);

        if (doDropInteraction)
            _interactionSystem.DroppedInteraction(uid, entity);

        var gotUnequipped = new GotUnequippedHandEvent(uid, entity, hand);
        RaiseLocalEvent(entity, gotUnequipped, false);

        var didUnequip = new DidUnequipHandEvent(uid, entity, hand);
        RaiseLocalEvent(uid, didUnequip);

        if (hand == handsComp.ActiveHand)
            RaiseLocalEvent(entity, new HandDeselectedEvent(uid), false);
    }
}
