using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Vanguard.Api;
using Vanguard.Api.Contracts;
using Vanguard.Auth;
using Vanguard.Meta;
namespace Vanguard.UI
{
///
/// Простой декбилдер: коллекция слева, текущая колода справа,
/// фильтры в оверлее, статистика по триггерам и грейдам.
///
public sealed class DeckBuilderView
{
private readonly MetaService _meta;
private readonly AuthService _auth;
private readonly Action _onBack;
private readonly Action _onPlay;
private readonly RectTransform _root;
private readonly RectTransform _listView;
private readonly RectTransform _editorView;
private RectTransform _listContent;
private RectTransform _collectionContent;
private RectTransform _deckContent;
private InputField _nameInput;
private Text _listStatus;
private Text _editorStatus;
private Text _stats;
private Text _deckCount;
private Text _filterSummary;
private RectTransform _filterOverlay;
private RectTransform _clanContent;
private RectTransform _confirmOverlay;
private Text _confirmText;
private DeckDto _pendingDelete;
private readonly List _decks = new List();
private readonly List _collection = new List();
private readonly Dictionary _catalog =
new Dictionary(StringComparer.Ordinal);
private readonly List _cards = new List();
private readonly Dictionary _collectionCountTexts =
new Dictionary(StringComparer.Ordinal);
private DeckDto _editing;
private string _filterClan = "Все";
private string _filterTrigger = "Все";
private int _filterGrade = -1;
private bool _loading;
private bool _busy;
public DeckBuilderView(
Transform parent,
MetaService meta,
AuthService auth,
Action onBack,
Action onPlay)
{
_meta = meta;
_auth = auth;
_onBack = onBack;
_onPlay = onPlay;
_root = UiKit.CreatePanel(parent, "DeckBuilderScreen", new Color(0.018f, 0.03f, 0.06f, 0.97f));
_listView = BuildListView();
_editorView = BuildEditorView();
_filterOverlay = BuildFilterOverlay();
_confirmOverlay = BuildDeleteConfirm();
_listView.gameObject.SetActive(false);
_editorView.gameObject.SetActive(false);
_filterOverlay.gameObject.SetActive(false);
_confirmOverlay.gameObject.SetActive(false);
_root.gameObject.SetActive(false);
}
public void Show()
{
UiKit.SetActive(_root, true);
ShowList();
_ = LoadAsync();
}
public void Hide()
{
_filterOverlay.gameObject.SetActive(false);
_confirmOverlay.gameObject.SetActive(false);
_root.gameObject.SetActive(false);
}
private RectTransform BuildListView()
{
var view = UiKit.CreatePanel(_root, "DeckList", new Color(0.018f, 0.03f, 0.06f, 0.97f));
var top = BuildTopBar(view, "Колоды", out _);
var create = UiKit.CreateButton(top, "Create", "Создать колоду", NewDeck, 19);
UiKit.SetPreferredHeight(create, 46f, 0f);
var body = UiKit.CreatePanel(view, "Body", new Color(0f, 0f, 0f, 0f));
body.anchorMin = Vector2.zero;
body.anchorMax = Vector2.one;
body.offsetMin = new Vector2(44f, 30f);
body.offsetMax = new Vector2(-44f, -110f);
UiKit.AddVerticalLayout(body, 8, 10);
var hint = UiKit.CreateText(
body,
"Hint",
"Откройте колоду для редактирования или создайте новую. «В бой» ставит колоду в очередь.",
17,
new Color(0.60f, 0.68f, 0.84f),
TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(hint, 26f);
var scroll = UiKit.CreateScrollView(body, "DeckListScroll", out _listContent, 8f);
var element = scroll.gameObject.AddComponent();
element.flexibleHeight = 1f;
element.minHeight = 200f;
_listStatus = UiKit.CreateText(
body, "Status", string.Empty, 18,
new Color(0.62f, 0.68f, 0.82f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(_listStatus, 34f);
return view;
}
private RectTransform BuildEditorView()
{
var view = UiKit.CreatePanel(_root, "DeckEditor", new Color(0.018f, 0.03f, 0.06f, 0.97f));
var top = UiKit.CreatePanel(view, "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, 18, 12);
var back = UiKit.CreateButton(top, "Back", "← Колоды", BackToCards, 18);
UiKit.SetPreferredHeight(back, 44f, 0f);
var title = UiKit.CreateText(top, "Title", "Конструктор", 28, Color.white, TextAnchor.MiddleLeft);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 44f, 0f);
_nameInput = UiKit.CreateInput(top, "Name", "Название колоды");
UiKit.SetPreferredHeight(_nameInput, 46f);
var save = UiKit.CreateButton(top, "Save", "Сохранить", SaveDeck, 18);
UiKit.SetPreferredHeight(save, 44f, 0f);
var body = UiKit.CreatePanel(view, "Body", new Color(0f, 0f, 0f, 0f));
body.anchorMin = Vector2.zero;
body.anchorMax = Vector2.one;
body.offsetMin = new Vector2(34f, 30f);
body.offsetMax = new Vector2(-34f, -106f);
UiKit.AddHorizontalLayout(body, 0, 14);
var left = UiKit.CreatePanel(body, "CollectionArea", new Color(0.04f, 0.06f, 0.10f, 0.7f));
var leftElement = left.gameObject.AddComponent();
leftElement.flexibleWidth = 1f;
leftElement.flexibleHeight = 1f;
leftElement.minHeight = 300f;
UiKit.AddVerticalLayout(left, 10, 6);
var filterRow = UiKit.CreatePanel(left, "FilterRow", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(filterRow, 48f);
UiKit.AddHorizontalLayout(filterRow, 0, 10);
var filters = UiKit.CreateButton(filterRow, "Filters", "Фильтры", OpenFilters, 18);
UiKit.SetPreferredHeight(filters, 44f, 0f);
var reset = UiKit.CreateButton(filterRow, "ResetFilters", "Сбросить фильтры", ResetFilters, 17);
UiKit.SetPreferredHeight(reset, 44f, 0f);
_filterSummary = UiKit.CreateText(
filterRow, "FilterSummary", "Фильтры: все", 16,
new Color(0.60f, 0.68f, 0.84f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(_filterSummary, 44f);
var statsPanel = UiKit.CreatePanel(left, "Stats", new Color(0.06f, 0.08f, 0.13f, 1f));
UiKit.SetPreferredHeight(statsPanel, 76f);
UiKit.AddVerticalLayout(statsPanel, 8, 2);
_stats = UiKit.CreateText(
statsPanel, "Text", string.Empty, 16,
new Color(0.72f, 0.78f, 0.92f), TextAnchor.MiddleLeft);
_stats.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_stats, 56f);
var gridScroll = UiKit.CreateGridScrollView(
left,
"CollectionGrid",
new Vector2(166f, 186f),
new Vector2(8f, 8f),
out _collectionContent);
var gridElement = gridScroll.gameObject.AddComponent();
gridElement.flexibleHeight = 1f;
gridElement.minHeight = 220f;
var right = UiKit.CreatePanel(body, "DeckArea", new Color(0.04f, 0.06f, 0.10f, 0.85f));
var rightElement = right.gameObject.AddComponent();
rightElement.preferredWidth = 430f;
rightElement.minWidth = 360f;
rightElement.flexibleWidth = 0f;
rightElement.flexibleHeight = 1f;
rightElement.minHeight = 300f;
UiKit.AddVerticalLayout(right, 10, 6);
_deckCount = UiKit.CreateText(
right, "DeckCount", "Колода: 0/50", 20, Color.white, TextAnchor.MiddleLeft);
_deckCount.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(_deckCount, 30f);
var deckScroll = UiKit.CreateHorizontalScrollView(
right, "DeckScroll", out _deckContent, 6f);
var deckElement = deckScroll.gameObject.AddComponent();
deckElement.flexibleHeight = 1f;
deckElement.minHeight = 220f;
var editorStatusPanel = UiKit.CreatePanel(right, "StatusPanel", new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(editorStatusPanel, 54f);
_editorStatus = UiKit.CreateText(
editorStatusPanel, "Status", string.Empty, 16,
new Color(0.62f, 0.68f, 0.82f), TextAnchor.UpperLeft);
_editorStatus.horizontalOverflow = HorizontalWrapMode.Wrap;
UiKit.SetPreferredHeight(_editorStatus, 50f);
return view;
}
private RectTransform BuildFilterOverlay()
{
var overlay = UiKit.CreatePanel(
_editorView, "FilterOverlay", new Color(0f, 0f, 0f, 0.78f));
var card = UiKit.CreateCenteredCard(
overlay, "FilterCard", new Vector2(860f, 820f),
new Color(0.06f, 0.08f, 0.13f, 1f));
UiKit.AddVerticalLayout(card, 26, 10);
var title = UiKit.CreateText(card, "Title", "Фильтры коллекции", 28, Color.white);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 40f);
AddFilterSection(card, "Ранг", new[] { "Все", "0", "1", "2", "3" }, OnGradeFilter);
AddFilterSection(card, "Триггер", new[] { "Все", "None", "Critical", "Draw", "Stand", "Heal" }, OnTriggerFilter);
var clanTitle = UiKit.CreateText(
card, "ClanTitle", "Клан", 17, new Color(0.66f, 0.74f, 0.90f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(clanTitle, 24f);
var clanScroll = UiKit.CreateScrollView(card, "ClanScroll", out _clanContent, 4f);
var clanElement = clanScroll.gameObject.AddComponent();
clanElement.flexibleHeight = 1f;
clanElement.minHeight = 150f;
var close = UiKit.CreateButton(card, "Close", "Готово", CloseFilters, 20);
UiKit.SetPreferredHeight(close, 48f);
return overlay;
}
private void RebuildClanFilters()
{
ClearChildren(_clanContent);
AddClanButton(_clanContent, "Все");
foreach (var clan in _catalog.Values.Select(item => item.Clan).Distinct().OrderBy(item => item))
AddClanButton(_clanContent, clan);
}
private RectTransform BuildDeleteConfirm()
{
var overlay = UiKit.CreatePanel(
_root, "DeleteDeckConfirm", new Color(0f, 0f, 0f, 0.78f));
var card = UiKit.CreateCenteredCard(
overlay, "ConfirmCard", new Vector2(640f, 300f),
new Color(0.06f, 0.08f, 0.13f, 1f));
UiKit.AddVerticalLayout(card, 30, 12);
var title = UiKit.CreateText(card, "Title", "Удалить колоду?", 28, Color.white);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 42f);
_confirmText = UiKit.CreateText(
card, "Text", string.Empty, 19,
new Color(0.72f, 0.78f, 0.92f));
UiKit.SetPreferredHeight(_confirmText, 70f);
var yes = UiKit.CreateButton(card, "Yes", "Удалить", ConfirmDelete, 20);
UiKit.SetPreferredHeight(yes, 48f);
var no = UiKit.CreateButton(card, "No", "Отмена", CancelDelete, 20);
UiKit.SetPreferredHeight(no, 44f);
return overlay;
}
private void RequestDelete(DeckDto deck)
{
_pendingDelete = deck;
_confirmText.text = deck.Name + "\nКолода будет удалена без восстановления.";
_confirmOverlay.SetAsLastSibling();
_confirmOverlay.gameObject.SetActive(true);
}
private void CancelDelete()
{
_pendingDelete = null;
_confirmOverlay.gameObject.SetActive(false);
}
private void ConfirmDelete()
{
var deck = _pendingDelete;
_confirmOverlay.gameObject.SetActive(false);
_pendingDelete = null;
if (deck != null)
DeleteDeck(deck);
}
private void AddFilterSection(Transform parent, string title, string[] labels, Action onSelected)
{
var caption = UiKit.CreateText(
parent, "Caption_" + title, title, 17,
new Color(0.66f, 0.74f, 0.90f), TextAnchor.MiddleLeft);
UiKit.SetPreferredHeight(caption, 24f);
var row = UiKit.CreatePanel(parent, "Row_" + title, new Color(0f, 0f, 0f, 0f));
UiKit.SetPreferredHeight(row, 44f);
UiKit.AddHorizontalLayout(row, 0, 6);
foreach (var label in labels)
{
var captured = label;
var button = UiKit.CreateButton(
row, "Filter_" + label, label, () => onSelected(captured), 16);
UiKit.SetPreferredHeight(button, 40f);
}
}
private void AddClanButton(Transform parent, string clan)
{
var captured = clan;
var button = UiKit.CreateButton(
parent, "Clan_" + clan, clan, () => OnClanFilter(captured), 16);
UiKit.SetPreferredHeight(button, 40f);
}
private RectTransform BuildTopBar(RectTransform view, string titleText, out Text title)
{
var top = UiKit.CreatePanel(view, "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, 18, 12);
var back = UiKit.CreateButton(top, "Back", "← В лобби", () => _onBack(), 18);
UiKit.SetPreferredHeight(back, 44f, 0f);
title = UiKit.CreateText(top, "Title", titleText, 30, Color.white, TextAnchor.MiddleLeft);
title.fontStyle = FontStyle.Bold;
UiKit.SetPreferredHeight(title, 46f);
return top;
}
private async Task LoadAsync()
{
if (_loading)
return;
_loading = true;
_listStatus.text = "Загружаем коллекцию и колоды…";
_listStatus.color = new Color(0.62f, 0.68f, 0.82f);
ClearChildren(_listContent);
try
{
await _auth.EnsureAccessAsync();
var collection = await _meta.GetCollectionAsync();
var catalog = await _meta.GetCatalogAsync();
var decks = await _meta.GetDecksAsync();
if (!_root.gameObject.activeSelf)
return;
_collection.Clear();
_collection.AddRange(collection.Items ?? new List());
_catalog.Clear();
foreach (var item in catalog)
_catalog[item.PrintingId] = item;
RebuildClanFilters();
_decks.Clear();
_decks.AddRange(decks);
BuildDeckList();
_listStatus.text = _decks.Count == 0
? "Колод пока нет. Создайте первую."
: "Колод: " + _decks.Count + ".";
}
catch (ApiException exception)
{
_listStatus.text = exception.Message;
_listStatus.color = new Color(1f, 0.55f, 0.45f);
}
catch (Exception exception)
{
Debug.LogException(exception);
_listStatus.text = "Не удалось загрузить декбилдер.";
_listStatus.color = new Color(1f, 0.55f, 0.45f);
}
finally
{
_loading = false;
}
}
private void BuildDeckList()
{
ClearChildren(_listContent);
if (_decks.Count == 0)
return;
foreach (var deck in _decks)
{
var row = UiKit.CreatePanel(
_listContent, "Deck_" + deck.Id, new Color(0.07f, 0.09f, 0.14f, 1f));
var rowElement = row.gameObject.AddComponent();
rowElement.preferredHeight = 82f;
UiKit.AddHorizontalLayout(row, 14, 10);
var name = UiKit.CreateText(
row, "Name", deck.Name, 21, Color.white, TextAnchor.MiddleLeft);
name.fontStyle = FontStyle.Bold;
var nameElement = name.gameObject.AddComponent();
nameElement.flexibleWidth = 1f;
nameElement.preferredHeight = 40f;
var info = UiKit.CreateText(
row, "Info", deck.Cards.Count + " карт · " + deck.Format, 16,
new Color(0.60f, 0.68f, 0.84f), TextAnchor.MiddleLeft);
var infoElement = info.gameObject.AddComponent();
infoElement.preferredWidth = 190f;
infoElement.preferredHeight = 36f;
infoElement.flexibleWidth = 0f;
var edit = UiKit.CreateButton(row, "Edit", "Открыть", () => OpenDeck(deck), 17);
var editElement = edit.gameObject.AddComponent();
editElement.preferredWidth = 120f;
editElement.preferredHeight = 44f;
editElement.flexibleWidth = 0f;
var play = UiKit.CreateButton(row, "Play", "В бой", () => _onPlay(deck), 17);
var playElement = play.gameObject.AddComponent();
playElement.preferredWidth = 100f;
playElement.preferredHeight = 44f;
playElement.flexibleWidth = 0f;
var delete = UiKit.CreateButton(row, "Delete", "Удалить", () => RequestDelete(deck), 16);
var deleteElement = delete.gameObject.AddComponent();
deleteElement.preferredWidth = 105f;
deleteElement.preferredHeight = 44f;
deleteElement.flexibleWidth = 0f;
}
}
private void NewDeck()
{
_editing = null;
_cards.Clear();
_nameInput.text = "Новая колода";
ShowEditor();
}
private void OpenDeck(DeckDto deck)
{
_editing = deck;
_cards.Clear();
_cards.AddRange(deck.Cards);
_nameInput.text = deck.Name;
ShowEditor();
}
private void ShowEditor()
{
_listView.gameObject.SetActive(false);
_editorView.gameObject.SetActive(true);
_nameInput.text = _editing?.Name ?? "Новая колода";
_deckCount.text = "Колода: " + _cards.Count + "/50";
_editorStatus.text = string.Empty;
RebuildEditor();
}
private void BackToCards()
{
_filterOverlay.gameObject.SetActive(false);
_confirmOverlay.gameObject.SetActive(false);
_editorView.gameObject.SetActive(false);
_listView.gameObject.SetActive(true);
BuildDeckList();
}
private void RebuildEditor()
{
UpdateStats();
RebuildDeck();
RebuildCollection();
_deckCount.text = "Колода: " + _cards.Count + "/50";
}
private void RebuildCollection()
{
ClearChildren(_collectionContent);
_collectionCountTexts.Clear();
if (_catalog.Count == 0)
return;
foreach (var item in _collection.OrderBy(item => GradeOf(item)).ThenBy(item => item.NameEn, StringComparer.Ordinal))
{
if (!_catalog.TryGetValue(item.PrintingId, out var meta))
continue;
if (_filterGrade >= 0 && meta.Grade != _filterGrade)
continue;
if (_filterTrigger != "Все" && meta.Trigger != _filterTrigger)
continue;
if (_filterClan != "Все" && meta.Clan != _filterClan)
continue;
AddCollectionCell(item, meta);
}
}
private void AddCollectionCell(CollectionItemDto item, CardCatalogItemDto meta)
{
var cell = UiKit.CreatePanel(
_collectionContent, "Owned_" + item.PrintingId, new Color(0.08f, 0.11f, 0.17f, 1f));
var button = cell.gameObject.AddComponent