using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandController : MonoBehaviour
{
    [SerializeField] private Animator _animator;
    [SerializeField] private Transform _rightGrabPoint;
    [SerializeField] private Transform _leftGrabPoint;
    [SerializeField] private float _interactDistance = 3f;
    [SerializeField] private float _reachSpeed = 6f;
    [SerializeField] private float _reachOffset = 0.15f;
    [SerializeField] private LayerMask _interactMask = ~0;
    [SerializeField] private float _liquidReturnDistance = 3f;
    [SerializeField] private float _holdScale = 0.6f;
    [SerializeField] private Vector3 _liquidLocalOffset = new Vector3(0f, 0.0005f, 0f);

    private Camera _camera;
    private Transform _rightHeldObject;
    private Transform _leftHeldObject;
    private Vector3 _rightGrabStartLocalPos;
    private Vector3 _leftGrabStartLocalPos;
    private bool _isAnimating;

    private Dictionary<Transform, Vector3> _originalPositions = new Dictionary<Transform, Vector3>();
    private Dictionary<Transform, Quaternion> _originalRotations = new Dictionary<Transform, Quaternion>();
    private Dictionary<Transform, Vector3> _originalScales = new Dictionary<Transform, Vector3>();

    public Transform RightHeldObject => _rightHeldObject;
    public Transform LeftHeldObject => _leftHeldObject;

    void Start()
    {
        _camera = Camera.main;
        _rightGrabStartLocalPos = _rightGrabPoint.localPosition;
        _leftGrabStartLocalPos = _leftGrabPoint.localPosition;
    }

    void Update()
    {
        if (_isAnimating) return;
        if (Input.GetKeyDown(KeyCode.R)) HandleRight();
        if (Input.GetKeyDown(KeyCode.L)) HandleLeft();
    }

    void HandleRight()
    {
        if (_rightHeldObject != null)
        {
            Release(_rightHeldObject, _rightGrabPoint, _rightGrabStartLocalPos, "RightOpen");
            _rightHeldObject = null;
            return;
        }
        TryGrab(_rightGrabPoint, _rightGrabStartLocalPos, "RightGrab", true);
    }

    void HandleLeft()
    {
        if (_leftHeldObject != null)
        {
            Release(_leftHeldObject, _leftGrabPoint, _leftGrabStartLocalPos, "LeftOpen");
            _leftHeldObject = null;
            return;
        }
        TryGrab(_leftGrabPoint, _leftGrabStartLocalPos, "LeftGrab", false);
    }

    void TryGrab(Transform grabPoint, Vector3 grabStartLocalPos, string animName, bool isRight)
    {
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit[] hits = Physics.RaycastAll(ray, _interactDistance, _interactMask);

        foreach (var hit in hits)
        {
            Transform target = FindTarget(hit.collider.transform);
            if (target == null) continue;
            StartCoroutine(ReachAndGrab(target, grabPoint, grabStartLocalPos, animName, isRight));
            return;
        }
    }

    Transform FindTarget(Transform current)
    {
        while (current != null)
        {
            if (current.CompareTag("Target")) return current;
            if (current.CompareTag("Liquid")) return current;
            if (current.CompareTag("Breakable")) return current;
            current = current.parent;
        }
        return null;
    }


    IEnumerator ReachAndGrab(Transform target, Transform grabPoint, Vector3 grabStartLocalPos, string animName, bool isRight)
    {
        _isAnimating = true;
        NotifyScaleRemoved(target);

        _originalPositions[target] = target.position;
        _originalRotations[target] = target.rotation;
        _originalScales[target] = target.lossyScale;

        Vector3 direction = (target.position - grabPoint.position).normalized;
        Vector3 reachTargetPos = grabPoint.position + direction * _reachOffset;
        Vector3 startPos = grabPoint.position;
        float t = 0f;

        while (t < 1f)
        {
            t += Time.deltaTime * _reachSpeed;
            grabPoint.position = Vector3.Lerp(startPos, reachTargetPos, t);
            yield return null;
        }

        Vector3 originalScale = target.lossyScale;
        SetupPhysics(target, true);
        target.SetParent(grabPoint, true);

        Vector3 parentScale = grabPoint.lossyScale;
        Vector3 holdScale = originalScale * _holdScale;
        target.localScale = new Vector3(
            holdScale.x / parentScale.x,
            holdScale.y / parentScale.y,
            holdScale.z / parentScale.z
        );

        target.rotation = Quaternion.Euler(0f, grabPoint.rotation.eulerAngles.y, 0f);

        if (target.CompareTag("Liquid"))
            target.localPosition = _liquidLocalOffset;
        else
            target.localPosition = Vector3.zero;

        if (isRight) _rightHeldObject = target;
        else _leftHeldObject = target;

        _animator.Play(animName);
        yield return ReturnGrabPoint(grabPoint, grabStartLocalPos);
        _isAnimating = false;
    }

    IEnumerator ReturnGrabPoint(Transform grabPoint, Vector3 startLocalPos)
    {
        Vector3 startPos = grabPoint.localPosition;
        float t = 0f;
        while (t < 1f)
        {
            t += Time.deltaTime * _reachSpeed;
            grabPoint.localPosition = Vector3.Lerp(startPos, startLocalPos, t);
            yield return null;
        }
    }

    void Release(Transform obj, Transform grabPoint, Vector3 grabStartLocalPos, string animName)
    {
        Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
        bool placedOnScale = false;

        RaycastHit[] hits = Physics.RaycastAll(ray, _interactDistance);
        foreach (var hit in hits)
        {
            ScaleSurface scale = hit.collider.GetComponentInParent<ScaleSurface>();
            if (scale != null)
            {
                obj.SetParent(null, true);
                SetupPhysics(obj, false);
                if (_originalScales.ContainsKey(obj))
                    obj.localScale = _originalScales[obj];
                scale.PlaceObject(obj);
                placedOnScale = true;
                break;
            }

            BalanceScale balance = hit.collider.GetComponentInParent<BalanceScale>();
            if (balance != null)
            {
                obj.SetParent(null, true);
                SetupPhysics(obj, false);
                if (_originalScales.ContainsKey(obj))
                    obj.localScale = _originalScales[obj];
                bool isLeft = Vector3.Dot(
                    hit.point - balance.transform.position,
                    balance.transform.right) < 0;
                if (isLeft) balance.PlaceLeft(obj);
                else balance.PlaceRight(obj);
                placedOnScale = true;
                break;
            }
        }

        if (!placedOnScale)
        {
            obj.SetParent(null, true);
            SetupPhysics(obj, false);
            if (_originalScales.ContainsKey(obj))
                obj.localScale = _originalScales[obj];
            if (obj.CompareTag("Liquid") && _originalPositions.ContainsKey(obj))
            {
                float dist = Vector3.Distance(obj.position, _originalPositions[obj]);
                if (dist <= _liquidReturnDistance)
                {
                    obj.position = _originalPositions[obj];
                    obj.rotation = _originalRotations[obj];
                    Rigidbody rb = obj.GetComponent<Rigidbody>();
                    if (rb != null) StartCoroutine(FreezeOnPlace(rb));
                }
            }
        }

        _originalPositions.Remove(obj);
        _originalRotations.Remove(obj);
        _originalScales.Remove(obj);

        StartCoroutine(ReturnGrabPoint(grabPoint, grabStartLocalPos));
        _animator.Play(animName);
    }

    private IEnumerator FreezeOnPlace(Rigidbody rb)
    {
        rb.isKinematic = true;
        yield return new WaitForSeconds(0.5f);
        rb.isKinematic = false;
    }

    void NotifyScaleRemoved(Transform obj)
    {
        MassController[] allMass = FindObjectsOfType<MassController>();
        foreach (var mc in allMass)
            mc.RemoveObject(obj.GetComponent<Rigidbody>());

        ScaleSurface[] allSurfaces = FindObjectsOfType<ScaleSurface>();
        foreach (var surface in allSurfaces)
            surface.RemoveObject(obj);

        BalanceScale[] allBalances = FindObjectsOfType<BalanceScale>();
        foreach (var balance in allBalances)
            balance.RemoveObject(obj);
    }

    void SetupPhysics(Transform obj, bool disable)
    {
        Rigidbody rb = obj.GetComponent<Rigidbody>();
        Collider[] colliders = obj.GetComponentsInChildren<Collider>();

        if (rb != null)
        {
            rb.isKinematic = disable;
            rb.useGravity = !disable;
        }

        foreach (var col in colliders)
            col.enabled = !disable;
    }

    public bool IsHoldingAny()
    {
        return _rightHeldObject != null || _leftHeldObject != null;
    }
}
