using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using BaseGames.Core.Events;
namespace BaseGames.UI
{
///
/// 可复用标签组(从 的 Tab 逻辑提取)。
///
/// 职责:管理一组 Tab(内容根 + 头部按钮 + 高亮),处理 Next/Prev/Select 切换、
/// 头部高亮、焦点委托(内容若实现 则接管,否则聚焦头部按钮),
/// 可选跨重建记忆当前 Tab,并通过 C# 事件 + 可选 SO 频道广播索引。
///
/// 解耦:不引用任何具体 Tab 类型,只持有 Tab 根 。
/// 由宿主面板在 OnEnable 中调用 ,并把肩键频道接到 /。
///
public class UITabGroup : MonoBehaviour
{
[Serializable]
public struct Tab
{
[Tooltip("Tab 内容根节点(激活时 SetActive(true))。")]
public GameObject content;
[Tooltip("Tab 头部按钮(点击跳转;高亮显示当前选中)。可空。")]
public Button headerButton;
[Tooltip("Tab 头部高亮节点(选中时 SetActive(true))。可空。")]
public GameObject headerHighlight;
}
[Header("Tabs(按显示顺序)")]
[SerializeField] private Tab[] _tabs;
[Tooltip("默认打开的 Tab 索引。")]
[SerializeField] private int _defaultIndex = 0;
[Tooltip("是否记住上次停留的 Tab(跨面板重开)。")]
[SerializeField] private bool _rememberLast = true;
[Tooltip("跨重建记忆用的键;为空则只在本实例存活期间记忆。")]
[SerializeField] private string _persistKey = "";
[Header("Event Channels(可选)")]
[Tooltip("当前 Tab 变化时广播索引。可空。")]
[SerializeField] private IntEventChannelSO _onTabChanged;
/// 当前 Tab 变化时触发(参数为新索引)。
public event Action TabChanged;
public int CurrentIndex { get; private set; } = -1;
public int TabCount => _tabs?.Length ?? 0;
// 跨重建记忆(静态:面板/组件被销毁重建后仍保留)
private static readonly Dictionary s_persist = new();
private bool _wired;
private void Awake() => EnsureWired();
private void EnsureWired()
{
if (_wired || _tabs == null) return;
for (int i = 0; i < _tabs.Length; i++)
{
int captured = i;
if (_tabs[i].headerButton != null)
{
_tabs[i].headerButton.onClick.RemoveAllListeners();
_tabs[i].headerButton.onClick.AddListener(() => Select(captured));
}
if (_tabs[i].content != null) _tabs[i].content.SetActive(false);
if (_tabs[i].headerHighlight != null) _tabs[i].headerHighlight.SetActive(false);
}
_wired = true;
}
/// 激活标签组并打开起始 Tab(默认或记忆)。宿主面板在 OnEnable 中调用。
public void Activate()
{
EnsureWired();
if (TabCount == 0) return;
int start = _rememberLast && TryGetPersisted(out var p) ? p : _defaultIndex;
CurrentIndex = -1; // 强制 Select 执行切换
Select(Mathf.Clamp(start, 0, TabCount - 1));
}
public void Next() => Step(+1);
public void Prev() => Step(-1);
private void Step(int dir)
{
if (TabCount == 0) return;
int next = CurrentIndex;
for (int i = 0; i < _tabs.Length; i++) // 跳过未配置 content 的空槽
{
next = (next + dir + _tabs.Length) % _tabs.Length;
if (_tabs[next].content != null) break;
}
Select(next);
}
/// 切换到指定 Tab;无效或与当前相同则忽略。
public void Select(int index)
{
EnsureWired();
if (TabCount == 0) return;
index = Mathf.Clamp(index, 0, _tabs.Length - 1);
if (index == CurrentIndex) return;
// 关闭旧 Tab
if (CurrentIndex >= 0 && CurrentIndex < _tabs.Length)
{
if (_tabs[CurrentIndex].content != null) _tabs[CurrentIndex].content.SetActive(false);
if (_tabs[CurrentIndex].headerHighlight != null) _tabs[CurrentIndex].headerHighlight.SetActive(false);
}
CurrentIndex = index;
SetPersisted(index);
// 打开新 Tab
var tab = _tabs[index];
if (tab.content != null) tab.content.SetActive(true);
if (tab.headerHighlight != null) tab.headerHighlight.SetActive(true);
FocusCurrent();
_onTabChanged?.Raise(index);
TabChanged?.Invoke(index);
}
/// 把焦点交给当前 Tab(内容 IFocusable 优先,否则其头部按钮)。供宿主 OnFocusRestored 调用。
public void FocusCurrent()
{
if (CurrentIndex < 0 || CurrentIndex >= TabCount) return;
var tab = _tabs[CurrentIndex];
var focusable = tab.content != null ? tab.content.GetComponent() : null;
if (focusable != null) focusable.OnFocusRestored();
else if (tab.headerButton != null && EventSystem.current != null)
EventSystem.current.SetSelectedGameObject(tab.headerButton.gameObject);
}
// ── 记忆 ──────────────────────────────────────────────────────────────
private bool TryGetPersisted(out int index)
{
if (!string.IsNullOrEmpty(_persistKey) && s_persist.TryGetValue(_persistKey, out index)) return true;
if (CurrentIndex >= 0) { index = CurrentIndex; return true; }
index = -1;
return false;
}
private void SetPersisted(int index)
{
if (!string.IsNullOrEmpty(_persistKey)) s_persist[_persistKey] = index;
}
}
}