using Darkmatter.Core.Contracts.Features.History;
using Darkmatter.Core.Contracts.Services.Audio;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Core.Data.Static.Features.ShapeBuilder;
using Darkmatter.Core.Enums.Services.Audio;
using Darkmatter.Features.ShapeBuilder.Commands;
using Darkmatter.Libs.Observer;
using PrimeTween;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace Darkmatter.Features.ShapeBuilder.UI
{
    /// <summary>
    /// Single-MB shape piece. Handles drag, reactive preview lerp, snap,
    /// and return-to-tray. Snap is wrapped in a <see cref="SnapPieceCommand"/>
    /// pushed onto the shared <see cref="IUndoStack"/> so it participates in
    /// the same undo/redo flow as coloring.
    /// </summary>
    [RequireComponent(typeof(RectTransform))]
    public sealed class ShapePiece : MonoBehaviour,
        IBeginDragHandler, IDragHandler, IEndDragHandler
    {
        [SerializeField] private Image image;

        // Bound by Setup
        private ShapeSO _shape;
        private SlotMarker _slot;
        private ShapeBuilderConfig _cfg;
        private ISfxPlayer _sfx;
        private IEventBus _bus;
        private IUndoStack _undo;
        private Vector2 _trayPos;
        private Vector2 _traySize;

        // Per-drag state
        private RectTransform _rt;
        private RectTransform _parentRect;
        private Camera _eventCam;
        private Vector2 _grabOffset;
        private bool _locked;
        private bool _inPreview;

        public ShapeSO Shape => _shape;
        public string PieceId => _shape != null ? _shape.Id : null;
        public bool IsLocked => _locked;
        public RectTransform RectTransform => _rt != null ? _rt : (_rt = (RectTransform)transform);

        public void Setup(
            ShapeSO shape,
            SlotMarker slot,
            ShapeBuilderConfig cfg,
            ISfxPlayer sfx,
            IEventBus bus,
            IUndoStack undo,
            Vector2 trayPos,
            bool preSnapped)
        {
            _shape    = shape;
            _slot     = slot;
            _cfg      = cfg;
            _sfx      = sfx;
            _bus      = bus;
            _undo     = undo;
            _trayPos  = trayPos;
            _traySize = shape.DefaultSizeDelta;

            image.sprite = shape.Sprite;
            ApplyTrayPose();

            if (preSnapped) SnapInstantly();
        }

        public void OnBeginDrag(PointerEventData e)
        {
            if (_locked) return;
            _parentRect = (RectTransform)RectTransform.parent;
            _eventCam   = e.pressEventCamera;
            _grabOffset = RectTransform.anchoredPosition - ScreenToLocal(e.position);
            _inPreview  = false;
        }

        public void OnDrag(PointerEventData e)
        {
            if (_locked) return;

            var pointerLocal = ScreenToLocal(e.position) + _grabOffset;
            var slotPos = _slot.RectTransform.anchoredPosition;
            float dist = Vector2.Distance(pointerLocal, slotPos);

            if (dist <= _cfg.PreviewRadius)
            {
                if (!_inPreview)
                {
                    _inPreview = true;
                    _sfx.Play(SfxId.ShapeHover);
                }
                ApplyPreviewLerp(pointerLocal, dist);
            }
            else
            {
                _inPreview = false;
                RectTransform.anchoredPosition = pointerLocal;
                RectTransform.sizeDelta        = _traySize;
                RectTransform.localRotation    = Quaternion.identity;
            }
        }

        public void OnEndDrag(PointerEventData e)
        {
            if (_locked) return;

            float dist = Vector2.Distance(
                RectTransform.anchoredPosition,
                _slot.RectTransform.anchoredPosition);

            if (dist <= _cfg.SnapRadius)
                _undo.Push(new SnapPieceCommand(this));   // Push calls Execute → SnapInternal
            else
                ReturnToTray();
        }

        private void ApplyPreviewLerp(Vector2 pointerLocal, float dist)
        {
            float t = Mathf.Clamp01(1f - dist / _cfg.PreviewRadius);
            if (_cfg.PreviewCurve != null) t = _cfg.PreviewCurve.Evaluate(t);

            var slot = _slot.RectTransform;
            RectTransform.anchoredPosition = Vector2.Lerp(pointerLocal, slot.anchoredPosition, t);
            RectTransform.sizeDelta        = Vector2.Lerp(_traySize,    slot.sizeDelta,        t);
            RectTransform.localRotation    = Quaternion.Slerp(Quaternion.identity, slot.localRotation, t);
        }

        // ---- Command-driven snap / un-snap ----
        internal void SnapInternal()
        {
            Lock();
            var slot = _slot.RectTransform;

            Tween.UIAnchoredPosition(RectTransform, slot.anchoredPosition, _cfg.SnapDuration, Ease.OutBack);
            Tween.UISizeDelta       (RectTransform, slot.sizeDelta,        _cfg.SnapDuration, Ease.OutBack);
            Tween.LocalRotation     (RectTransform, slot.localRotation,    _cfg.SnapDuration, Ease.OutBack);

            _sfx.Play(SfxId.ShapeSnap);
            _bus.Publish(new PieceSnappedSignal(_shape.Id));
        }

        internal void UnsnapInternal(Transform parent, Vector2 pos, Vector2 size, Quaternion rot)
        {
            _locked = false;
            image.raycastTarget = true;

            RectTransform.SetParent(parent, worldPositionStays: false);

            Tween.UIAnchoredPosition(RectTransform, pos,  _cfg.ReturnDuration, Ease.OutQuad);
            Tween.UISizeDelta       (RectTransform, size, _cfg.ReturnDuration, Ease.OutQuad);
            Tween.LocalRotation     (RectTransform, rot,  _cfg.ReturnDuration, Ease.OutQuad);

            _sfx.Play(SfxId.ShapeReturn);
            _bus.Publish(new PieceUnsnappedSignal(_shape.Id));
        }

        private void SnapInstantly()
        {
            Lock();
            var slot = _slot.RectTransform;
            RectTransform.anchoredPosition = slot.anchoredPosition;
            RectTransform.sizeDelta        = slot.sizeDelta;
            RectTransform.localRotation    = slot.localRotation;
        }

        private void ReturnToTray()
        {
            _sfx.Play(SfxId.ShapeReturn);
            Sequence.Create()
                .Group(Tween.UIAnchoredPosition(RectTransform, _trayPos,            _cfg.ReturnDuration, Ease.OutQuad))
                .Group(Tween.UISizeDelta       (RectTransform, _traySize,           _cfg.ReturnDuration, Ease.OutQuad))
                .Group(Tween.LocalRotation     (RectTransform, Quaternion.identity, _cfg.ReturnDuration, Ease.OutQuad));
        }

        private void Lock()
        {
            _locked = true;
            image.raycastTarget = false;
            RectTransform.SetParent(_slot.RectTransform.parent, worldPositionStays: false);
        }

        private void ApplyTrayPose()
        {
            RectTransform.anchoredPosition = _trayPos;
            RectTransform.sizeDelta        = _traySize;
            RectTransform.localRotation    = Quaternion.identity;
        }

        private Vector2 ScreenToLocal(Vector2 screenPos)
        {
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                _parentRect, screenPos, _eventCam, out var local);
            return local;
        }
    }
}
