using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.History;
using Darkmatter.Core.Contracts.Features.ShapeBuilder;
using Darkmatter.Core.Contracts.Services.Assets;
using Darkmatter.Core.Contracts.Services.Audio;
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Data.Signals.Features.ShapeBuilder;
using Darkmatter.Core.Data.Static.Features.ShapeBuilder;
using Darkmatter.Features.ShapeBuilder.UI;
using Darkmatter.Libs.Observer;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Darkmatter.Features.ShapeBuilder.Systems
{
    public class ShapeBuilderController : IShapeBuilderController, IDisposable
    {
        private const string PiecePrefabKey = "ShapeBuilder/Piece";

        private string _currentTemplateId;
        private int _expected;
        private int _snapped;
        private GameObject _drawingInstance;
        private GameObject _piecePrefab;
        private IDisposable _snappedSub;

        private readonly List<string> _snappedPieceIds = new();
        private readonly ShapeBuilderConfig _cfg;
        private readonly ISfxPlayer _sfx;
        private readonly IEventBus _bus;
        private readonly IUndoStack _undo;
        private readonly IAssetProviderService _assetProviderService;
        private readonly IEventBus _eventBus;
        private readonly IGameplaySceneRefs _refs;


        public ShapeBuilderController(
            ShapeBuilderConfig cfg,
            ISfxPlayer sfx,
            IEventBus bus,
            IUndoStack undo,
            IAssetProviderService assetProviderService,
            IEventBus eventBus,
            IGameplaySceneRefs refs)
        {
            _cfg = cfg;
            _sfx = sfx;
            _bus = bus;
            _undo = undo;
            _assetProviderService = assetProviderService;
            _eventBus = eventBus;
            _refs = refs;
        }

        public async UniTask InitializeAsync(CancellationToken cancellationToken)
        {
            await TryLoadPiecePrefabAsync(cancellationToken);

            _snappedSub = _eventBus.Subscribe<PieceSnappedSignal>(OnPieceSnapped);
        }

        private void OnPieceSnapped(PieceSnappedSignal obj)
        {
            _snappedPieceIds.Add(obj.PieceId);
            CheckIfShapeAssembled();
        }

        public async UniTask BuildAsync(
            IDrawingTemplate template,
            IReadOnlyCollection<string> preSnappedIds,
            CancellationToken ct = default)
        {
            Clear();

            if (template.Prefab == null)
                throw new System.Exception($"No drawing layout prefab for '{template.Id}'");

            _drawingInstance = Object.Instantiate(template.Prefab, _refs.PaperRoot);
            ct.ThrowIfCancellationRequested();

            var slots = _drawingInstance.GetComponentsInChildren<SlotMarker>(includeInactive: true);

            await TryLoadPiecePrefabAsync(ct);

            int count = template.Pieces.Count;
            float trayW = _refs.TrayPanel.rect.width;
            float pitch = trayW / (count + 1);

            _currentTemplateId = template.Id;
            _expected = count;
            _snapped = 0;

            CreateShapePieceInstances(template, preSnappedIds, count, slots, pitch, trayW);

            CheckIfShapeAssembled();
        }

        private void CreateShapePieceInstances(IDrawingTemplate template, IReadOnlyCollection<string> preSnappedIds,
            int count,
            SlotMarker[] slots, float pitch, float trayW)
        {
            for (int i = 0; i < count; i++)
            {
                var shape = template.Pieces[i];
                var slot = FindSlotForShape(slots, shape);
                if (slot == null)
                {
                    Debug.LogError($"[ShapeBuilder] No SlotMarker for '{shape.Id}' in '{template.Id}'");
                    continue;
                }

                var trayPos = new Vector2(pitch * (i + 1) - trayW * 0.5f, 0f);
                bool preSnapped = preSnappedIds != null && preSnappedIds.Contains(shape.Id);

                var go = Object.Instantiate(_piecePrefab, _refs.TrayPanel);
                go.name = $"Piece_{shape.Id}";

                var piece = go.GetComponent<ShapePiece>();
                piece.Setup(shape, slot, _cfg, _sfx, _bus, _undo, trayPos, preSnapped);

                if (preSnapped) _snapped++;
            }
        }

        private void CheckIfShapeAssembled()
        {
            if (_expected > 0 && _snapped == _expected)
                _bus.Publish(new ShapeAssembledSignal(_currentTemplateId));
        }

        private async UniTask TryLoadPiecePrefabAsync(CancellationToken ct)
        {
            if (_piecePrefab == null)
            {
                _piecePrefab = await _assetProviderService.LoadAssetAsync<GameObject>(
                    PiecePrefabKey, progress: null, cancellationToken: ct);
                if (_piecePrefab == null)
                    throw new System.Exception($"No piece prefab at '{PiecePrefabKey}'");
            }
        }

        private static SlotMarker FindSlotForShape(SlotMarker[] slots, ShapeSO shape)
        {
            foreach (var s in slots)
                if (s.Shape == shape)
                    return s;
            return null;
        }

        public IReadOnlyCollection<string> GetSnappedPieceIds()
        {
            return _snappedPieceIds;
        }

        public void Clear()
        {
        }

        public void Dispose()
        {
            _snappedSub?.Dispose();
        }
    }
}