using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using DanroJump.Audio;
using DanroJump.Gameplay;
using DanroJump.Hardware.Com;
using DanroJump.Prizes;
using DanroJump.Prizes.Qr;
using DanroJump.SceneFlow;
using DanroJump.Settings;
using UnityEngine;
using UnityEngine.UIElements;
using Zenject;
namespace DanroJump.UI.Gameplay
{
///
/// Отображает runtime-показатели игрового забега в UI Toolkit HUD.
///
public sealed class GameplayHudController : MonoBehaviour
{
[SerializeField] private VisualTreeAsset visualTreeAsset;
[SerializeField] private PanelSettings panelSettings;
[SerializeField] private int sortingOrder = 10;
[SerializeField] private string scoreLabelName = "ScoreValue";
[SerializeField] private string heightLabelName = "HeightValue";
[SerializeField] private string bestHeightLabelName = "BestHeightValue";
[SerializeField] private string livesLabelName = "LivesValue";
[SerializeField] private string coinsLabelName = "CoinsValue";
[SerializeField] private string deathsLabelName = "DeathsValue";
[SerializeField] private string stateLabelName = "StateValue";
[SerializeField] private string developmentStatsRootName = "RightStats";
private UIDocument document;
private GameplaySession session;
private IPrizeFlowService prizeFlow;
private IPrizeSessionStateService prizeSessionState;
private IPrizeQrService prizeQrService;
private ISceneFlowService sceneFlow;
private IReadOnlyServiceSettings serviceSettings;
private IGameAudioService audioService;
private IComSystem comSystem;
private PrizeResultOverlayWindow prizeResultOverlay;
private ContinueGameOverlayWindow continueGameOverlay;
private VisualElement prizeOverlayRoot;
private VisualElement continueOverlayRoot;
private VisualElement developmentStatsRoot;
private Label scoreLabel;
private Label heightLabel;
private Label bestHeightLabel;
private Label livesLabel;
private Label coinsLabel;
private Label deathsLabel;
private Label stateLabel;
private CancellationTokenSource refreshCancellation;
private CancellationTokenSource qrTimeoutCancellation;
private CancellationTokenSource physicalDispenseCancellation;
private bool isSubscribedToSession;
private bool isSubscribedToPrizeFlow;
///
/// Получает игровую сессию из DI-контейнера сцены.
///
[Inject]
public void Construct(
GameplaySession injectedSession,
[InjectOptional] IPrizeFlowService injectedPrizeFlow = null,
[InjectOptional] IPrizeSessionStateService injectedPrizeSessionState = null,
[InjectOptional] IPrizeQrService injectedPrizeQrService = null,
[InjectOptional] ISceneFlowService injectedSceneFlow = null,
[InjectOptional] IReadOnlyServiceSettings injectedServiceSettings = null,
[InjectOptional] IGameAudioService injectedAudioService = null,
[InjectOptional] IComSystem injectedComSystem = null)
{
prizeFlow = injectedPrizeFlow;
prizeSessionState = injectedPrizeSessionState;
prizeQrService = injectedPrizeQrService;
sceneFlow = injectedSceneFlow;
serviceSettings = injectedServiceSettings;
audioService = injectedAudioService;
comSystem = injectedComSystem;
SetSession(injectedSession);
SubscribeToPrizeFlow();
}
private void Start()
{
EnsureDocument();
BindLabels();
BindPrizeOverlay();
BindContinueOverlay();
ShowLastPrizeResultIfNeeded();
Refresh();
}
///
/// Подписывается на изменения сессии и запускает кадровую подстраховку обновления HUD.
///
private void OnEnable()
{
SubscribeToSession();
SubscribeToPrizeFlow();
if (refreshCancellation == null)
{
refreshCancellation = new CancellationTokenSource();
RefreshContinuouslyAsync(refreshCancellation.Token).Forget(Debug.LogException);
}
Refresh();
}
///
/// Останавливает обновление HUD и снимает подписку с игровой сессии.
///
private void OnDisable()
{
if (refreshCancellation != null)
{
refreshCancellation.Cancel();
refreshCancellation.Dispose();
refreshCancellation = null;
}
if (qrTimeoutCancellation != null)
{
qrTimeoutCancellation.Cancel();
qrTimeoutCancellation.Dispose();
qrTimeoutCancellation = null;
}
if (physicalDispenseCancellation != null)
{
physicalDispenseCancellation.Cancel();
physicalDispenseCancellation.Dispose();
physicalDispenseCancellation = null;
}
UnsubscribeFromSession();
UnsubscribeFromPrizeFlow();
prizeResultOverlay?.Dispose();
prizeResultOverlay = null;
prizeOverlayRoot = null;
continueGameOverlay?.Dispose();
continueGameOverlay = null;
continueOverlayRoot = null;
}
///
/// Создает или настраивает UIDocument, чтобы HUD мог жить на отдельном GameObject без ручной сборки.
///
private void EnsureDocument()
{
if (document == null && !TryGetComponent(out document))
{
document = gameObject.AddComponent();
}
if (document == null)
{
return;
}
document.visualTreeAsset = visualTreeAsset;
document.panelSettings = panelSettings;
document.sortingOrder = sortingOrder;
}
///
/// Находит текстовые поля в UXML по именам, заданным в инспекторе.
///
private void BindLabels()
{
var root = document != null ? document.rootVisualElement : null;
if (root == null)
{
return;
}
developmentStatsRoot = root.Q(developmentStatsRootName);
scoreLabel = FindLabel(root, scoreLabelName);
heightLabel = FindLabel(root, heightLabelName);
bestHeightLabel = FindLabel(root, bestHeightLabelName);
livesLabel = FindLabel(root, livesLabelName);
coinsLabel = FindLabel(root, coinsLabelName);
deathsLabel = FindLabel(root, deathsLabelName);
stateLabel = FindLabel(root, stateLabelName);
}
private void BindPrizeOverlay()
{
var root = document != null ? document.rootVisualElement : null;
var overlayRoot = root?.Q("PrizeResultOverlay");
if (overlayRoot == null)
{
return;
}
if (prizeResultOverlay != null && prizeOverlayRoot == overlayRoot)
{
return;
}
prizeResultOverlay?.Dispose();
prizeOverlayRoot = overlayRoot;
prizeResultOverlay = new PrizeResultOverlayWindow(overlayRoot, audioService);
prizeResultOverlay.ReturnRequested += HandlePrizeReturnRequested;
}
private void BindContinueOverlay()
{
var root = document != null ? document.rootVisualElement : null;
var overlayRoot = root?.Q("ContinueGameOverlay");
if (overlayRoot == null)
{
return;
}
if (continueGameOverlay != null && continueOverlayRoot == overlayRoot)
{
return;
}
continueGameOverlay?.Dispose();
continueOverlayRoot = overlayRoot;
continueGameOverlay = new ContinueGameOverlayWindow(overlayRoot, audioService);
continueGameOverlay.ContinueRequested += HandleContinueRequested;
continueGameOverlay.MenuRequested += HandleContinueMenuRequested;
}
///
/// Обновляет все значения HUD из текущей игровой сессии.
///
private void Refresh()
{
RefreshDevelopmentStatsVisibility();
if (session == null)
{
SetLabel(stateLabel, "WAITING");
return;
}
SetLabel(scoreLabel, session.Score.ToString());
SetLabel(heightLabel, $"{session.MaxRunHeight:0} м");
SetLabel(bestHeightLabel, $"{session.RecordHeight:0} м");
SetLabel(livesLabel, session.RemainingLives.ToString());
SetLabel(coinsLabel, session.Coins.ToString());
SetLabel(deathsLabel, session.DeathCount.ToString());
SetLabel(stateLabel, FormatState(session.State));
RefreshContinueOverlay();
}
///
/// Безопасно обновляет текст label, если элемент найден в UXML.
///
private static void SetLabel(Label label, string value)
{
if (label != null)
{
label.text = value;
}
}
///
/// Ищет Label по имени, заданному в настройках контроллера.
///
private static Label FindLabel(VisualElement root, string labelName)
{
return root != null && !string.IsNullOrEmpty(labelName)
? root.Q