using System;
using System.Collections.Generic;
using System.Reflection;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;
using RR.Common.Managers;
using RR.Common.Prizes;
using RR.Common.Utility;
namespace RR.Common.UI
{
///
/// Отдельная модалка глобальной настройки QR-кодов по градациям.
/// Не привязана к уровням: уровень выбирает только qrTier в LevelPrizeSettingsModal.
///
public class QRCodeSettingsModal : Modal
{
[Header("UI")]
[SerializeField] private TMP_Text titleText;
[SerializeField] private TMP_Text tierLabel;
[SerializeField] private TMP_Text prizeTextLabel;
[SerializeField] private Button editPrizeTextButton;
[SerializeField] private TextInputModal textInputModal;
[Header("Rows")]
[SerializeField] private RectTransform tierRow;
[SerializeField] private RectTransform prizeTextRow;
[Header("Pointer")]
[SerializeField] private UIRowPointer rowPointer;
[Header("Close")]
[SerializeField] private Button closeModalButton;
[Header("Navigation")]
[SerializeField] private InputActionReference navigateAction;
[SerializeField] private InputActionReference submitAction;
[SerializeField] private float deadZone = 0.5f;
[Header("Behaviour")]
[SerializeField] private bool createEntryIfMissing = true;
[SerializeField] private string prizeTextModalTitle = "Введите текст приза QR";
[SerializeField] private int prizeTextMaxLength = 12;
[Header("Tier Colors")]
[SerializeField] private Color bronzeTierColor = new Color32(205, 127, 50, 255);
[SerializeField] private Color silverTierColor = new Color32(192, 192, 192, 255);
[SerializeField] private Color goldTierColor = new Color32(255, 215, 0, 255);
private static readonly QRTier[] EditableTiers = { QRTier.Bronze, QRTier.Silver, QRTier.Gold };
private const int MaxPrizeTextLength = 12;
private const int RowTier = 0;
private const int RowPrizeText = 1;
private const int RowClose = 2;
private const int RowCount = 3;
private PrizeSystemManager _manager;
private List _workingEntries;
private int _currentTierIndex;
private int _activeRow;
private string _currentPrizeText = string.Empty;
private bool _axisHeldX;
private bool _axisHeldY;
private bool _isPrizeTextInputOpen;
private Selectable _pendingFocusTarget;
private int _pendingFocusFrames;
private Selectable _tierRowSelectable;
private Selectable _prizeTextRowSelectable;
private RectTransform _prizeTextPointerTarget;
protected override void Awake()
{
base.Awake();
if (editPrizeTextButton != null)
{
Graphic buttonGraphic = editPrizeTextButton.GetComponent();
if (buttonGraphic != null)
{
editPrizeTextButton.targetGraphic = buttonGraphic;
}
editPrizeTextButton.onClick.AddListener(OpenPrizeTextInput);
}
if (textInputModal == null)
{
textInputModal = FindFirstObjectByType(FindObjectsInactive.Include);
}
}
protected override void OnEnable()
{
base.OnEnable();
if (navigateAction != null && navigateAction.action != null)
{
navigateAction.action.Enable();
}
}
protected override void OnDisable()
{
_isPrizeTextInputOpen = false;
_pendingFocusTarget = null;
_pendingFocusFrames = 0;
_axisHeldX = false;
_axisHeldY = false;
base.OnDisable();
}
protected override void OnReturnedToTop()
{
_isPrizeTextInputOpen = false;
_pendingFocusTarget = null;
_pendingFocusFrames = 0;
_axisHeldX = false;
_axisHeldY = false;
RefreshRowHighlights();
}
private void OnDestroy()
{
if (editPrizeTextButton != null)
{
editPrizeTextButton.onClick.RemoveListener(OpenPrizeTextInput);
}
if (textInputModal != null)
{
textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed;
textInputModal.OnTextCancelled -= OnPrizeTextCancelled;
}
}
public override void Open()
{
PrepareForEditing();
base.Open();
RefreshRowHighlights();
}
public void OpenForEditing()
{
Open();
}
public override void Close()
{
Save();
base.Close();
}
private void Save()
{
ApplyCurrentFieldValues();
if (_manager == null)
{
Debug.LogWarning("[QRCodeSettingsModal] PrizeSystemManager не найден.");
return;
}
_manager.SetQRCodeConfigs(_workingEntries);
}
private void PrepareForEditing()
{
_manager = PrizeSystemManager.Instance;
_workingEntries = _manager != null ? _manager.GetQRCodeConfigs() : new List();
_currentTierIndex = Mathf.Clamp(_currentTierIndex, 0, EditableTiers.Length - 1);
_activeRow = RowTier;
AutoResolveRows();
EnsureRowSelectables();
LoadCurrentTierIntoFields();
RefreshRowHighlights();
}
private void ProcessNavigationInput()
{
if (navigateAction == null || navigateAction.action == null)
{
_axisHeldX = false;
_axisHeldY = false;
return;
}
Vector2 value = navigateAction.action.ReadValue();
if (Mathf.Abs(value.y) >= deadZone)
{
if (!_axisHeldY)
{
_axisHeldY = true;
MoveRow(value.y > 0f ? -1 : 1);
}
}
else
{
_axisHeldY = false;
}
if (Mathf.Abs(value.x) >= deadZone)
{
if (!_axisHeldX)
{
_axisHeldX = true;
HandleLeftRight(value.x > 0f ? 1 : -1);
}
}
else
{
_axisHeldX = false;
}
}
private void Update()
{
if (!_isOpen || _isPrizeTextInputOpen)
{
return;
}
ProcessNavigationInput();
HandleSubmit();
}
private void LateUpdate()
{
if (!_isOpen || _isPrizeTextInputOpen)
{
return;
}
ApplyPendingFocus();
}
private void MoveRow(int delta)
{
_activeRow = (_activeRow + delta + RowCount) % RowCount;
RefreshRowHighlights();
}
private void HandleLeftRight(int direction)
{
if (_activeRow != RowTier)
return;
ApplyCurrentFieldValues();
_currentTierIndex = (_currentTierIndex + direction + EditableTiers.Length) % EditableTiers.Length;
LoadCurrentTierIntoFields();
RefreshRowHighlights();
}
private void LoadCurrentTierIntoFields()
{
if (titleText != null)
{
titleText.text = $"Настройки QR: {GetTierName(CurrentTier)}";
}
if (tierLabel != null)
{
tierLabel.text = GetTierName(CurrentTier);
tierLabel.color = GetTierColor(CurrentTier);
}
List entries = GetEntriesForTier(CurrentTier, includeFallbackTemplates: false);
_currentPrizeText = ResolveDisplayedPrizeText(entries);
UpdatePrizeTextLabel(_currentPrizeText);
}
private void ApplyCurrentFieldValues()
{
if (_workingEntries == null)
{
return;
}
_currentPrizeText = NormalizePrizeText(_currentPrizeText);
List tierIndices = GetEntryIndicesForTier(CurrentTier);
if (tierIndices.Count > 0)
{
for (int i = 0; i < tierIndices.Count; i++)
{
int index = tierIndices[i];
QRCodeInfo info = _workingEntries[index];
info.tier = CurrentTier;
info.prizeText = _currentPrizeText;
_workingEntries[index] = info;
}
return;
}
if (!createEntryIfMissing)
{
return;
}
List templateEntries = GetEntriesForTier(QRTier.None, includeFallbackTemplates: false);
if (templateEntries.Count == 0)
{
templateEntries.Add(CreateDefaultEntry(CurrentTier));
}
for (int i = 0; i < templateEntries.Count; i++)
{
QRCodeInfo clone = templateEntries[i];
clone.tier = CurrentTier;
clone.prizeText = _currentPrizeText;
_workingEntries.Add(clone);
}
}
private static QRCodeInfo CreateDefaultEntry(QRTier tier)
{
return new QRCodeInfo
{
tier = tier,
generateQRCode = true,
linkPrefix = string.Empty,
messageFormat = "{0}",
prizeText = string.Empty,
darkColor = QRCodeInfo.GetTierDarkColorVector(tier),
lightColor = new Vector3(1f, 1f, 1f)
};
}
private void OpenPrizeTextInput()
{
if (textInputModal == null)
{
Debug.LogWarning("[QRCodeSettingsModal] TextInputModal не назначен.");
return;
}
_isPrizeTextInputOpen = true;
FocusSelectable(_prizeTextRowSelectable != null ? _prizeTextRowSelectable : editPrizeTextButton);
textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed;
textInputModal.OnTextCancelled -= OnPrizeTextCancelled;
textInputModal.OnTextConfirmed += OnPrizeTextConfirmed;
textInputModal.OnTextCancelled += OnPrizeTextCancelled;
_currentPrizeText = NormalizePrizeText(_currentPrizeText);
textInputModal.SetAlphabet(TextInputAlphabets.RussianPrizeText);
textInputModal.SetCellCount(GetPrizeTextCellCount());
textInputModal.OpenForInput(prizeTextModalTitle, _currentPrizeText);
}
public void OpenPrizeTextEditor()
{
OpenPrizeTextInput();
}
private void OnPrizeTextConfirmed(string text)
{
_isPrizeTextInputOpen = false;
if (textInputModal != null)
{
textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed;
textInputModal.OnTextCancelled -= OnPrizeTextCancelled;
}
_currentPrizeText = NormalizePrizeText(text);
UpdatePrizeTextLabel(_currentPrizeText);
FocusSelectable(_prizeTextRowSelectable != null ? _prizeTextRowSelectable : editPrizeTextButton);
}
private void OnPrizeTextCancelled()
{
_isPrizeTextInputOpen = false;
if (textInputModal != null)
{
textInputModal.OnTextConfirmed -= OnPrizeTextConfirmed;
textInputModal.OnTextCancelled -= OnPrizeTextCancelled;
}
FocusSelectable(_prizeTextRowSelectable != null ? _prizeTextRowSelectable : editPrizeTextButton);
}
private void UpdatePrizeTextLabel(string text)
{
if (prizeTextLabel != null)
{
string normalized = NormalizePrizeText(text);
prizeTextLabel.text = string.IsNullOrWhiteSpace(normalized) ? "<пусто>" : normalized;
}
}
private void RefreshRowHighlights()
{
RectTransform target = GetActiveRowTransform();
Selectable selectableToFocus = null;
switch (_activeRow)
{
case RowTier:
selectableToFocus = _tierRowSelectable;
break;
case RowPrizeText:
selectableToFocus = _prizeTextRowSelectable;
break;
case RowClose:
selectableToFocus = closeModalButton;
break;
}
if (_activeRow == RowClose)
{
if (rowPointer != null)
{
rowPointer.Hide();
}
}
else if (rowPointer != null && target != null)
{
rowPointer.SetTarget(target);
}
FocusSelectable(selectableToFocus);
}
private void AutoResolveRows()
{
if (tierRow == null && tierLabel != null)
{
tierRow = tierLabel.transform.parent as RectTransform;
}
if (prizeTextRow == null)
{
if (prizeTextLabel != null)
prizeTextRow = prizeTextLabel.transform.parent as RectTransform;
else if (editPrizeTextButton != null)
prizeTextRow = editPrizeTextButton.transform.parent as RectTransform;
}
_prizeTextPointerTarget = ResolvePrizeTextPointerTarget();
}
private QRTier CurrentTier => EditableTiers[Mathf.Clamp(_currentTierIndex, 0, EditableTiers.Length - 1)];
private static string GetTierName(QRTier tier)
{
return tier switch
{
QRTier.Bronze => "Бронзовый",
QRTier.Silver => "Серебряный",
QRTier.Gold => "Золотой",
_ => "Не задан"
};
}
private Color GetTierColor(QRTier tier)
{
return tier switch
{
QRTier.Bronze => bronzeTierColor,
QRTier.Silver => silverTierColor,
QRTier.Gold => goldTierColor,
_ => tierLabel != null ? tierLabel.color : Color.white
};
}
private List GetEntriesForTier(QRTier tier, bool includeFallbackTemplates)
{
List result = new();
if (_workingEntries == null)
return result;
for (int i = 0; i < _workingEntries.Count; i++)
{
if (_workingEntries[i].tier == tier)
result.Add(_workingEntries[i]);
}
if (result.Count == 0 && includeFallbackTemplates && tier != QRTier.None)
{
for (int i = 0; i < _workingEntries.Count; i++)
{
if (_workingEntries[i].tier == QRTier.None)
result.Add(_workingEntries[i]);
}
}
return result;
}
private List GetEntryIndicesForTier(QRTier tier)
{
List result = new();
if (_workingEntries == null)
return result;
for (int i = 0; i < _workingEntries.Count; i++)
{
if (_workingEntries[i].tier == tier)
result.Add(i);
}
return result;
}
private string ResolveDisplayedPrizeText(List tierEntries)
{
if (tierEntries == null || tierEntries.Count == 0)
return string.Empty;
for (int i = 0; i < tierEntries.Count; i++)
{
string candidate = NormalizePrizeText(tierEntries[i].prizeText);
if (!string.IsNullOrWhiteSpace(candidate))
return candidate;
}
return string.Empty;
}
private int GetPrizeTextCellCount()
{
return Mathf.Clamp(prizeTextMaxLength, 1, MaxPrizeTextLength);
}
private RectTransform GetActiveRowTransform()
{
return _activeRow switch
{
RowTier => tierRow,
RowPrizeText => _prizeTextPointerTarget,
_ => null
};
}
private void EnsureRowSelectables()
{
_tierRowSelectable = EnsureRowSelectable(tierRow);
_prizeTextRowSelectable = editPrizeTextButton != null
? editPrizeTextButton
: EnsureRowSelectable(_prizeTextPointerTarget);
}
private RectTransform ResolvePrizeTextPointerTarget()
{
if (editPrizeTextButton != null)
{
RectTransform buttonRect = editPrizeTextButton.transform as RectTransform;
if (buttonRect != null)
{
return buttonRect;
}
}
if (prizeTextRow != null)
{
return prizeTextRow;
}
return prizeTextLabel != null ? prizeTextLabel.transform.parent as RectTransform : null;
}
private Selectable EnsureRowSelectable(RectTransform row)
{
if (row == null)
{
return null;
}
Selectable selectable = row.GetComponent();
if (selectable == null)
{
selectable = row.gameObject.AddComponent();
selectable.transition = Selectable.Transition.None;
selectable.navigation = new Navigation { mode = Navigation.Mode.None };
}
selectable.interactable = true;
return selectable;
}
private void FocusSelectable(Selectable selectable)
{
if (selectable == null || !selectable.gameObject.activeInHierarchy)
{
return;
}
SetFirstSelected(selectable);
_pendingFocusTarget = selectable;
_pendingFocusFrames = 2;
ApplyPendingFocus();
}
private void ApplyPendingFocus()
{
if (_pendingFocusTarget == null || _pendingFocusFrames <= 0)
{
return;
}
if (EventSystem.current == null || !_pendingFocusTarget.gameObject.activeInHierarchy)
{
_pendingFocusTarget = null;
_pendingFocusFrames = 0;
return;
}
if (EventSystem.current.currentSelectedGameObject != _pendingFocusTarget.gameObject)
{
EventSystem.current.SetSelectedGameObject(null);
EventSystem.current.SetSelectedGameObject(_pendingFocusTarget.gameObject);
_pendingFocusTarget.Select();
}
_pendingFocusFrames--;
if (_pendingFocusFrames <= 0)
{
_pendingFocusTarget = null;
}
}
private void HandleSubmit()
{
InputAction currentSubmitAction = GetSubmitAction();
if (currentSubmitAction == null || !currentSubmitAction.WasPerformedThisFrame())
{
return;
}
GameObject selectedObject = EventSystem.current != null ? EventSystem.current.currentSelectedGameObject : null;
switch (_activeRow)
{
case RowPrizeText:
if (selectedObject == editPrizeTextButton?.gameObject)
{
return;
}
OpenPrizeTextInput();
break;
case RowClose:
if (selectedObject == closeModalButton?.gameObject)
{
return;
}
Close();
break;
}
}
private InputAction GetSubmitAction()
{
if (submitAction != null && submitAction.action != null)
{
return submitAction.action;
}
if (EventSystem.current?.currentInputModule == null)
{
return null;
}
object inputModule = EventSystem.current.currentInputModule;
PropertyInfo submitProperty = inputModule.GetType().GetProperty("submit", BindingFlags.Instance | BindingFlags.Public);
object submitObject = submitProperty != null ? submitProperty.GetValue(inputModule) : null;
InputActionReference submitReference = submitObject as InputActionReference;
return submitReference != null ? submitReference.action : null;
}
private string NormalizePrizeText(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return string.Empty;
}
string normalized = text.Trim();
int maxLength = GetPrizeTextCellCount();
if (normalized.Length > maxLength)
{
normalized = normalized.Substring(0, maxLength);
}
return normalized;
}
}
}