using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Vanguard.Api;
using Vanguard.Api.Contracts;
using Vanguard.Meta;
using Vanguard.Shared.Protocol;
namespace Vanguard.UI
{
/// Просмотр реплея: события и кадры состояния, собранные сервером.
public sealed class ReplayView
{
private readonly MetaService _meta;
private readonly Action _onClose;
private readonly RectTransform _root;
private readonly Text _title;
private readonly Text _result;
private readonly Text _frameLabel;
private readonly Text _summary;
private readonly Text _status;
private readonly RectTransform _events;
private ReplayResponseDto _replay;
private int _frameIndex;
private bool _loading;
public ReplayView(Transform parent, MetaService meta, Action onClose)
{
_meta = meta;
_onClose = onClose;
_root = UiKit.CreatePanel(parent, "ReplayScreen", new Color(0.015f, 0.025f, 0.05f, 0.97f));
var top = UiKit.CreatePanel(_root, "TopBar", new Color(0.06f, 0.08f, 0.13f, 1f));
top.anchorMin = new Vector2(0f, 1f);
top.anchorMax = new Vector2(1f, 1f);
top.pivot = new Vector2(0.5f, 1f);
top.anchoredPosition = Vector2.zero;
top.sizeDelta = new Vector2(0f, 88f);
UiKit.AddHorizontalLayout(top, 22, 16);
var back = UiKit.CreateButton(top, "Back", "← К истории", () => _onClose(), 18);
UiKit.SetPreferredHeight(back, 44f, 0f);
_title = UiKit.CreateText(top, "Title", "Реплей", 30, Color.white, TextAnchor.MiddleLeft);
_title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(_title, 46f);
_result = UiKit.CreateText(top, "Result", string.Empty, 20,
new Color(0.85f, 0.90f, 1f), TextAnchor.MiddleRight);
UiKit.SetPreferredHeight(_result, 36f);
var body = UiKit.CreatePanel(_root, "Body", new Color(0f, 0f, 0f, 0f));
body.anchorMin = Vector2.zero;
body.anchorMax = Vector2.one;
body.offsetMin = new Vector2(40f, 28f);
body.offsetMax = new Vector2(-40f, -110f);
UiKit.AddVerticalLayout(body, 8, 10);
var summaryPanel = UiKit.CreatePanel(body, "SummaryPanel", new Color(0.06f, 0.08f, 0.13f, 1f));
UiKit.SetPreferredHeight(summaryPanel, 250f);
UiKit.AddVerticalLayout(summaryPanel, 18, 8);
_frameLabel = UiKit.CreateText(
summaryPanel, "Frame", "Кадр", 19, Color.white, TextAnchor.MiddleLeft);
_frameLabel.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(_frameLabel, 28f);
_summary = UiKit.CreateText(
summaryPanel, "Summary", string.Empty, 16,
new Color(0.72f, 0.78f, 0.92f), TextAnchor.UpperLeft);
_summary.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_summary, 170f);
var controls = UiKit.CreatePanel(body, "Controls", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(controls, 52f);
UiKit.AddHorizontalLayout(controls, 0, 12);
var prev = UiKit.CreateButton(controls, "Prev", "◀ Кадр", OnPrev, 18);
UiKit.SetPreferredHeight(prev, 46f);
var next = UiKit.CreateButton(controls, "Next", "Кадр ▶", OnNext, 18);
UiKit.SetPreferredHeight(next, 46f);
var eventsTitle = UiKit.CreateText(
body, "EventsTitle", "События", 22, Color.white, TextAnchor.MiddleLeft);
eventsTitle.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(eventsTitle, 30f);
var scroll = UiKit.CreateScrollView(body, "EventsScroll", out _events, 4f);
var scrollElement = scroll.gameObject.AddComponent();
scrollElement.flexibleHeight = 1f;
scrollElement.minHeight = 160f;
_status = UiKit.CreateText(
body, "Status", string.Empty, 17,
new Color(0.62f, 0.68f, 0.82f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(_status, 30f);
_root.gameObject.SetActive(false);
}
public void Show(string matchId)
{
UiKit.SetActive(_root, true);
_replay = null;
_frameIndex = 0;
SetStatus("Загружаем реплей…", new Color(0.62f, 0.68f, 0.82f));
_ = LoadAsync(matchId);
}
public void Hide() => UiKit.SetActive(_root, false);
private async Task LoadAsync(string matchId)
{
if (_loading)
return;
_loading = true;
try
{
_replay = await _meta.GetReplayAsync(matchId);
if (_root == null || !_root.gameObject.activeSelf)
return;
_title.text = "Реплей · " + _replay.MatchId;
_result.text = _replay.Winner == _replay.ViewerSeat
? "Победа"
: _replay.Winner < 0 ? "Ничья" : "Поражение";
_result.color = _replay.Winner == _replay.ViewerSeat
? new Color(0.45f, 0.85f, 0.55f)
: new Color(1f, 0.58f, 0.45f);
_frameIndex = Math.Max(0, (_replay.Frames?.Count ?? 0) - 1);
RebuildEvents();
UpdateFrame();
SetStatus(
"Кадров: " + (_replay.Frames?.Count ?? 0) + " · событий: "
+ (_replay.Events?.Count ?? 0),
new Color(0.62f, 0.68f, 0.82f));
}
catch (ApiException exception)
{
SetStatus(exception.Message, new Color(1f, 0.55f, 0.45f));
}
catch (Exception exception)
{
Debug.LogException(exception);
SetStatus("Не удалось загрузить реплей.", new Color(1f, 0.55f, 0.45f));
}
finally
{
_loading = false;
}
}
private void RebuildEvents()
{
ClearChildren(_events);
if (_replay?.Events == null || _replay.Events.Count == 0)
{
var empty = UiKit.CreateText(
_events, "Empty", "Событий нет.", 16,
new Color(0.62f, 0.68f, 0.82f));
UiKit.SetPreferredHeight(empty, 60f);
return;
}
foreach (var item in _replay.Events)
{
var line = UiKit.CreateText(
_events,
"Event",
"#" + item.Seq + " " + item.Kind,
15,
new Color(0.72f, 0.78f, 0.92f),
TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(line, 22f);
}
}
private void OnPrev()
{
if (_replay?.Frames == null || _replay.Frames.Count == 0)
return;
_frameIndex = Math.Max(0, _frameIndex - 1);
UpdateFrame();
}
private void OnNext()
{
if (_replay?.Frames == null || _replay.Frames.Count == 0)
return;
_frameIndex = Math.Min(_replay.Frames.Count - 1, _frameIndex + 1);
UpdateFrame();
}
private void UpdateFrame()
{
if (_replay?.Frames == null || _replay.Frames.Count == 0)
{
_frameLabel.text = "Кадров нет";
_summary.text = string.Empty;
return;
}
var frame = _replay.Frames[Mathf.Clamp(_frameIndex, 0, _replay.Frames.Count - 1)];
_frameLabel.text = "Кадр " + (_frameIndex + 1) + " / " + _replay.Frames.Count
+ " · eventSeq " + frame.EventSeq;
var view = frame.View;
if (view == null)
{
_summary.text = "Состояние кадра недоступно.";
return;
}
var text = "Фаза: " + view.Phase + " · шаг: " + view.Step
+ " · ход " + view.TurnNumber
+ " · решает: " + (view.ActivePlayer == view.You ? "вы" : "противник")
+ "\n";
var opponent = view.You == 0 ? 1 : 0;
foreach (var index in new[] { view.You, opponent })
{
if (view.Players == null || index >= view.Players.Count)
continue;
var player = view.Players[index];
text += (index == view.You ? "Вы: " : "Противник: ") + player.Name
+ " · рука " + player.HandCount
+ " · колода " + player.DeckCount
+ " · урон " + (player.FaceDownDamage + player.FaceUpDamage.Count)
+ " · Vanguard " + (player.Vanguard?.PrintingId ?? "—")
+ "\n RG: " + RearGuards(player) + "\n";
}
_summary.text = text;
}
private static string RearGuards(PlayerView player)
{
if (player.RearGuards == null)
return "—";
var parts = new System.Collections.Generic.List();
for (var i = 0; i < player.RearGuards.Length; i++)
{
var card = player.RearGuards[i];
parts.Add("R" + (i + 1) + ":" + (card?.PrintingId ?? "—"));
}
return string.Join(" · ", parts);
}
private void SetStatus(string text, Color color)
{
if (_status == null)
return;
_status.text = text ?? string.Empty;
_status.color = color;
}
private static void ClearChildren(Transform parent)
{
for (var i = parent.childCount - 1; i >= 0; i--)
UnityEngine.Object.Destroy(parent.GetChild(i).gameObject);
}
}
}