using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using TMPro;
using Newtonsoft.Json;
using CommanderAILab.Models;
using CommanderAILab.Services;
namespace CommanderAILab.UI
{
///
/// Precon browser scene — browse, search, and install preconstructed Commander decks.
/// Grid display with card images, deck details panel, install/install-all buttons,
/// and refresh from GitHub functionality.
///
public class PreconController : MonoBehaviour
{
// -- Header ——————————————————————————————————
[Header("Navigation")]
[SerializeField] private Button backButton;
[Header("Search & Filter")]
[SerializeField] private TMP_InputField searchInput;
[SerializeField] private TMP_Dropdown colorFilterDropdown;
[SerializeField] private Button searchButton;
[SerializeField] private Button clearButton;
[Header("Deck Grid")]
[SerializeField] private Transform deckGridParent;
[SerializeField] private GameObject deckCardPrefab;
[SerializeField] private ScrollRect gridScrollRect;
[Header("Deck Detail Panel")]
[SerializeField] private GameObject detailPanel;
[SerializeField] private Image detailCommanderImage;
[SerializeField] private TMP_Text detailDeckName;
[SerializeField] private TMP_Text detailColorIdentity;
[SerializeField] private TMP_Text detailCardCount;
[SerializeField] private TMP_Text detailThemeText;
[SerializeField] private Button detailInstallButton;
[SerializeField] private Button detailCloseButton;
[Header("Batch Actions")]
[SerializeField] private Button installAllButton;
[SerializeField] private Button refreshButton;
[SerializeField] private TMP_Text statusText;
[Header("Loading & Error")]
[SerializeField] private GameObject loadingSpinner;
[SerializeField] private GameObject errorPanel;
[SerializeField] private TMP_Text errorText;
[SerializeField] private Button retryButton;
// -- State ——————————————————————————————————
private List _allPrecons = new();
private List _filteredPrecons = new();
private PreconDeck _selectedPrecon;
private bool _isLoading = false;
private HashSet _installedDecks = new();
// -- Serializable response wrappers ———————————————————
[Serializable]
private class PreconListResponse
{
public List precons;
}
[Serializable]
public class PreconDeck
{
public string fileName;
public string name;
public string commander;
public string colorIdentity;
public int cardCount;
public string theme;
public string imageUri;
}
[Serializable]
private class InstallRequest
{
public string fileName;
}
[Serializable]
private class InstallResponse
{
public bool installed;
public string deckName;
public string destination;
public string message;
}
[Serializable]
private class BatchInstallRequest
{
public List fileNames;
}
[Serializable]
private class BatchInstallResponse
{
public List results;
}
[Serializable]
private class BatchInstallResult
{
public string fileName;
public bool installed;
public string deckName;
public string error;
}
[Serializable]
private class RefreshResponse
{
public string message;
public int total;
}
// ========================================================
// Lifecycle
// ========================================================
private void Start()
{
backButton.onClick.AddListener(OnBack);
searchButton.onClick.AddListener(OnSearch);
clearButton.onClick.AddListener(OnClear);
retryButton.onClick.AddListener(LoadPrecons);
installAllButton.onClick.AddListener(OnInstallAll);
refreshButton.onClick.AddListener(OnRefresh);
detailInstallButton.onClick.AddListener(OnInstallSelected);
detailCloseButton.onClick.AddListener(() => detailPanel.SetActive(false));
if (searchInput != null)
searchInput.onSubmit.AddListener(_ => OnSearch());
detailPanel.SetActive(false);
errorPanel.SetActive(false);
LoadPrecons();
}
// ========================================================
// Data Loading
// ========================================================
private void LoadPrecons()
{
if (_isLoading) return;
_isLoading = true;
loadingSpinner.SetActive(true);
errorPanel.SetActive(false);
statusText.text = "Loading precons...";
ApiClient.Instance.Get(
"/api/lab/precons",
OnPreconsLoaded,
OnError
);
}
private void OnPreconsLoaded(PreconListResponse response)
{
_isLoading = false;
loadingSpinner.SetActive(false);
_allPrecons = response.precons ?? new List();
statusText.text = $"{_allPrecons.Count} precon deck(s) available";
ApplyFilter();
}
// ========================================================
// Search & Filter
// ========================================================
private void OnSearch()
{
ApplyFilter();
}
private void OnClear()
{
searchInput.text = "";
if (colorFilterDropdown != null)
colorFilterDropdown.value = 0;
ApplyFilter();
}
private void ApplyFilter()
{
string query = searchInput.text.Trim().ToLowerInvariant();
string colorFilter = "";
if (colorFilterDropdown != null && colorFilterDropdown.value > 0)
colorFilter = colorFilterDropdown.options[colorFilterDropdown.value].text;
_filteredPrecons = _allPrecons.FindAll(p =>
{
bool matchesSearch = string.IsNullOrEmpty(query)
|| (p.name != null && p.name.ToLowerInvariant().Contains(query))
|| (p.commander != null && p.commander.ToLowerInvariant().Contains(query))
|| (p.theme != null && p.theme.ToLowerInvariant().Contains(query));
bool matchesColor = string.IsNullOrEmpty(colorFilter)
|| (p.colorIdentity != null && p.colorIdentity.Contains(colorFilter));
return matchesSearch && matchesColor;
});
RenderGrid();
}
// ========================================================
// Grid Rendering
// ========================================================
private void RenderGrid()
{
// Clear existing children
for (int i = deckGridParent.childCount - 1; i >= 0; i--)
Destroy(deckGridParent.GetChild(i).gameObject);
foreach (var precon in _filteredPrecons)
{
var card = Instantiate(deckCardPrefab, deckGridParent);
var texts = card.GetComponentsInChildren(true);
if (texts.Length > 0) texts[0].text = precon.name ?? precon.fileName;
if (texts.Length > 1) texts[1].text = precon.commander ?? "";
if (texts.Length > 2) texts[2].text = precon.colorIdentity ?? "";
// Installed badge
bool installed = _installedDecks.Contains(precon.fileName);
var badge = card.transform.Find("InstalledBadge");
if (badge != null) badge.gameObject.SetActive(installed);
// Click to show detail
var btn = card.GetComponent