using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Equipment;
using BaseGames.Localization;
namespace BaseGames.UI
{
///
/// 护符装备面板。
///
/// 布局:
/// 顶部 — 标题 + 凹槽容量进度条(已用/总数)
/// 左侧 — 已装备护符区域(动态格子列表)
/// 右侧 — 已收集护符目录(可滚动,点击即装备/卸下)
///
/// 数据来源:ServiceLocator 获取 EquipmentManager,读取 Collected / Equipped 列表。
/// 反应式更新:订阅 _onEquipmentChanged VoidEventChannelSO,变更时重建两侧列表。
///
/// Inspector 必填:
/// _notchText — "X / Y 格子" 文本
/// _equippedContainer — 已装备列表父节点
/// _catalogContainer — 收藏目录列表父节点
/// _charmCardTemplate — 护符卡片预制(kept inactive)
/// _btnClose — 关闭按钮
/// _onEquipmentChanged — EquipmentManager 广播的装备变更事件频道
///
public class CharmEquipPanel : MonoBehaviour, IFocusable
{
// ── Inspector 字段 ────────────────────────────────────────────────────
[Header("UI 根节点")]
[SerializeField] private TMP_Text _notchText;
[SerializeField] private Image _notchBarFill;
[SerializeField] private Transform _equippedContainer;
[SerializeField] private Transform _catalogContainer;
[SerializeField] private GameObject _charmCardTemplate; // kept inactive
[SerializeField] private Button _btnClose;
[Header("Event Channels")]
[SerializeField] private VoidEventChannelSO _onEquipmentChanged;
// ── 私有状态 ──────────────────────────────────────────────────────────
private IEquipmentService _manager;
private readonly List _equippedCards = new();
private readonly List _catalogCards = new();
private readonly Queue _cardPool = new(); // O(1) 取用归还
private readonly CompositeDisposable _subs = new();
// ── 生命周期 ──────────────────────────────────────────────────────────
private void OnEnable()
{
_manager = ServiceLocator.GetOrDefault();
if (_btnClose != null)
{
_btnClose.onClick.RemoveAllListeners();
_btnClose.onClick.AddListener(OnCloseBtnClicked);
}
_onEquipmentChanged?.Subscribe(Rebuild).AddTo(_subs);
Rebuild();
// 手柄导航:面板打开时将焦点置于关闭按钮
EventSystem.current?.SetSelectedGameObject(_btnClose?.gameObject);
}
private void OnDisable()
{
_subs.Clear();
RecycleCards(_equippedCards);
RecycleCards(_catalogCards);
}
// ── 重建 UI ───────────────────────────────────────────────────────────
private void Rebuild()
{
if (_manager == null) return;
RefreshNotchBar();
RebuildEquippedList();
RebuildCatalogList();
}
private void RefreshNotchBar()
{
int used = _manager.UsedNotches;
int total = _manager.TotalNotches;
if (_notchText != null)
_notchText.text = $"{used} / {total}";
if (_notchBarFill != null)
_notchBarFill.fillAmount = total > 0 ? (float)used / total : 0f;
}
private void RebuildEquippedList()
{
RecycleCards(_equippedCards);
if (_equippedContainer == null) return;
foreach (var charm in _manager.Equipped)
{
var card = SpawnCard(_equippedContainer, charm, isEquipped: true);
if (card != null) _equippedCards.Add(card);
}
}
private void RebuildCatalogList()
{
RecycleCards(_catalogCards);
if (_catalogContainer == null) return;
foreach (var charm in _manager.Collected)
{
bool isEquipped = _manager.Equipped.Contains(charm);
var card = SpawnCard(_catalogContainer, charm, isEquipped);
if (card != null) _catalogCards.Add(card);
}
}
// ── 卡片生成 ──────────────────────────────────────────────────────────
private GameObject SpawnCard(Transform parent, CharmSO charm, bool isEquipped)
{
if (_charmCardTemplate == null || parent == null) return null;
if (!TryDequeueCard(out GameObject go))
go = Instantiate(_charmCardTemplate);
go.transform.SetParent(parent, worldPositionStays: false);
go.SetActive(true);
// 优先使用预绑定视图组件(推荐);若 Prefab 未挂载则回落到反射查找以保证向后兼容。
var view = go.GetComponent();
if (view != null)
{
view.Bind(charm, isEquipped, OnEquipClicked, OnUnequipClicked);
}
else
{
FallbackBindByReflection(go, charm, isEquipped);
}
return go;
}
private void OnEquipClicked(CharmSO charm)
{
string err = _manager?.TryEquipCharm(charm);
if (err != null) Debug.LogWarning($"[CharmEquipPanel] 装备失败: {err}");
}
private void OnUnequipClicked(CharmSO charm) => _manager?.UnequipCharm(charm);
///
/// 兼容旧 Prefab(未挂载 )的反射绑定。
/// 仅作为过渡方案;首次开发完成后建议在 Editor 中强制要求新组件。
///
private void FallbackBindByReflection(GameObject go, CharmSO charm, bool isEquipped)
{
var images = go.GetComponentsInChildren(includeInactive: true);
if (images.Length > 0 && charm.icon != null)
images[0].sprite = charm.icon;
var texts = go.GetComponentsInChildren(includeInactive: true);
if (texts.Length > 0)
{
string name = LocalizationManager.Get(charm.displayNameKey, LocalizationTable.Items);
texts[0].text = string.IsNullOrEmpty(name) || name == charm.displayNameKey
? charm.charmId : name;
}
if (texts.Length > 1) texts[1].text = charm.notchCost.ToString();
if (texts.Length > 2)
{
string desc = LocalizationManager.Get(charm.descriptionKey, LocalizationTable.Items);
texts[2].text = string.IsNullOrEmpty(desc) || desc == charm.descriptionKey
? string.Empty : desc;
}
var btn = go.GetComponentInChildren