using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;
using RR.Common.Managers;
using RR.Common.Prizes;
namespace RR.Common.UI
{
///
/// Отдельная модалка настройки глобальной конфигурации витрины.
/// Позволяет менять выбранную ячейку и порог очков для нее.
///
public class PrizeStandSettingsModal : Modal
{
[Header("UI")]
[SerializeField] private TMP_Text titleText;
[SerializeField] private TMP_Text connectionStatusLabel;
[SerializeField] private TMP_Text boxCountLabel;
[SerializeField] private TMP_Text selectedBoxLabel;
[SerializeField] private TMP_Text thresholdLabel;
[SerializeField] private TMP_Text detailsText;
[Header("Rows")]
[SerializeField] private RectTransform selectedBoxRow;
[SerializeField] private RectTransform thresholdRow;
[Header("Pointer")]
[SerializeField] private UIRowPointer rowPointer;
[Header("Close")]
[SerializeField] private Button closeModalButton;
[Header("Navigation")]
[SerializeField] private InputActionReference navigateAction;
[SerializeField] private float deadZone = 0.5f;
[Header("Limits")]
[SerializeField] private int defaultBoxesCount = 5;
private const int RowSelectedBox = 0;
private const int RowThreshold = 1;
private const int RowClose = 2;
private const int RowCount = 3;
private const int ThresholdStep = 1;
private const int MaxStepMultiplier = 10;
private const float HoldResetTimeout = 0.25f;
private const float InitialHorizontalRepeatDelay = 0.25f;
private const float ThresholdAccelerationTime = 0.8f;
private const float MinimumHorizontalRepeatInterval = 0.03f;
private PrizeStandSettings _settings;
private int _selectedBoxIndex;
private int _activeRow;
private bool _axisHeldY;
private Selectable _pendingFocusTarget;
private int _pendingFocusFrames;
private Selectable _selectedBoxRowSelectable;
private Selectable _thresholdRowSelectable;
private PrizeStandRowMoveHandler _selectedBoxMoveHandler;
private PrizeStandRowMoveHandler _thresholdMoveHandler;
public event Action OnApplied;
protected override void OnEnable()
{
base.OnEnable();
if (navigateAction != null && navigateAction.action != null)
{
navigateAction.action.Enable();
}
}
protected override void OnDisable()
{
_axisHeldY = false;
ResetHorizontalHold();
_pendingFocusTarget = null;
_pendingFocusFrames = 0;
base.OnDisable();
}
protected override void OnReturnedToTop()
{
_axisHeldY = false;
ResetHorizontalHold();
_pendingFocusTarget = null;
_pendingFocusFrames = 0;
RefreshRowHighlights();
}
public override void Open()
{
PrepareForEditing();
base.Open();
RefreshRowHighlights();
}
public void OpenForEditing()
{
Open();
}
private void PrepareForEditing()
{
PrizeSystemManager manager = PrizeSystemManager.Instance;
_settings = manager != null
? CopyPrizeStandSettings(manager.GetPrizeStandSettings())
: CreateDefaultSettings();
EnsureSettingsInitialized();
AutoResolveRows();
AutoResolveValueLabels();
EnsureRowSelectables();
_selectedBoxIndex = 0;
_activeRow = RowSelectedBox;
RefreshUI();
RefreshRowHighlights();
}
public override void Close()
{
Save();
base.Close();
}
private void Update()
{
if (!_isOpen)
{
return;
}
ProcessNavigationInput();
}
private void LateUpdate()
{
if (!_isOpen)
{
return;
}
ApplyPendingFocus();
}
private void ProcessNavigationInput()
{
if (navigateAction == null || navigateAction.action == null)
{
_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;
}
}
private void MoveRow(int delta)
{
_activeRow = (_activeRow + delta + RowCount) % RowCount;
ResetHorizontalHold();
RefreshRowHighlights();
}
private void AdjustSelectedBox(int direction)
{
EnsureSettingsInitialized();
int boxCount = _settings.boxes.Count;
if (boxCount == 0)
{
return;
}
_selectedBoxIndex = (_selectedBoxIndex + direction) % boxCount;
if (_selectedBoxIndex < 0)
{
_selectedBoxIndex += boxCount;
}
RefreshUI();
}
internal void HandleRowMove(int rowIndex, int direction, int multiplier)
{
if (!_isOpen)
{
return;
}
switch (rowIndex)
{
case RowSelectedBox:
AdjustSelectedBox(direction);
break;
case RowThreshold:
AdjustThreshold(direction, multiplier);
break;
}
}
private void AdjustThreshold(int direction, int multiplier)
{
PrizeStandBoxConfig box = GetSelectedBox();
if (box == null)
{
return;
}
box.requiredScore = Mathf.Max(0, box.requiredScore + direction * ThresholdStep * Mathf.Max(1, multiplier));
RefreshUI();
}
private void Save()
{
PrizeSystemManager manager = PrizeSystemManager.Instance;
if (manager == null)
{
Debug.LogError("[PrizeStandSettingsModal] PrizeSystemManager недоступен.");
return;
}
EnsureSettingsInitialized();
NormalizeBoxes();
manager.SetPrizeStandSettings(_settings);
OnApplied?.Invoke();
}
private void RefreshUI()
{
EnsureSettingsInitialized();
PrizeStandConnectionInfo connectionInfo = PrizeSystemManager.Instance != null
? PrizeSystemManager.Instance.GetPrizeStandConnectionInfo()
: new PrizeStandConnectionInfo(false, 0);
PrizeStandBoxConfig selectedBox = GetSelectedBox();
if (titleText != null)
{
titleText.text = "Настройки витрины";
}
if (connectionStatusLabel != null)
{
connectionStatusLabel.text = connectionInfo.IsConnected
? "Витрина подключена"
: "Витрина не подключена";
}
if (boxCountLabel != null)
{
boxCountLabel.text = _settings.boxes.Count.ToString();
}
if (selectedBoxLabel != null)
{
selectedBoxLabel.text = selectedBox != null ? selectedBox.boxNumber.ToString() : "-";
}
if (thresholdLabel != null)
{
thresholdLabel.text = selectedBox != null ? selectedBox.requiredScore.ToString() : "-";
}
if (detailsText != null)
detailsText.text = string.Empty;
}
private void RefreshRowHighlights()
{
Selectable selectableToFocus = null;
if (_activeRow == RowClose)
{
if (rowPointer != null) rowPointer.Hide();
selectableToFocus = closeModalButton;
}
else
{
RectTransform target = _activeRow switch
{
RowSelectedBox => selectedBoxRow,
RowThreshold => thresholdRow,
_ => selectedBoxRow
};
selectableToFocus = _activeRow switch
{
RowSelectedBox => _selectedBoxRowSelectable,
RowThreshold => _thresholdRowSelectable,
_ => _selectedBoxRowSelectable
};
if (rowPointer != null && target != null)
{
rowPointer.SetTarget(target);
}
}
FocusSelectable(selectableToFocus);
}
private void AutoResolveRows()
{
if (selectedBoxRow == null && selectedBoxLabel != null)
{
selectedBoxRow = selectedBoxLabel.transform.parent as RectTransform;
}
if (thresholdRow == null && thresholdLabel != null)
{
thresholdRow = thresholdLabel.transform.parent as RectTransform;
}
}
private void AutoResolveValueLabels()
{
selectedBoxLabel = ResolveValueLabel(selectedBoxRow, selectedBoxLabel);
thresholdLabel = ResolveValueLabel(thresholdRow, thresholdLabel);
}
private static TMP_Text ResolveValueLabel(RectTransform row, TMP_Text current)
{
if (row == null)
{
return current;
}
TMP_Text[] texts = row.GetComponentsInChildren(true);
TMP_Text best = null;
float bestScore = float.MaxValue;
for (int i = 0; i < texts.Length; i++)
{
TMP_Text text = texts[i];
if (text == null)
{
continue;
}
RectTransform textRect = text.rectTransform;
if (textRect == null || !textRect.IsChildOf(row))
{
continue;
}
Vector3 localCenter = row.InverseTransformPoint(textRect.TransformPoint(textRect.rect.center));
float score = Mathf.Abs(localCenter.x) + Mathf.Abs(localCenter.y) * 0.25f;
if (best == null || score < bestScore)
{
best = text;
bestScore = score;
}
}
if (best != null)
{
return best;
}
return current;
}
private void EnsureRowSelectables()
{
_selectedBoxRowSelectable = EnsureRowSelectable(selectedBoxRow);
_thresholdRowSelectable = EnsureRowSelectable(thresholdRow);
_selectedBoxMoveHandler = EnsureRowMoveHandler(selectedBoxRow, RowSelectedBox);
_thresholdMoveHandler = EnsureRowMoveHandler(thresholdRow, RowThreshold);
}
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 PrizeStandRowMoveHandler EnsureRowMoveHandler(RectTransform row, int rowIndex)
{
if (row == null)
{
return null;
}
PrizeStandRowMoveHandler handler = row.GetComponent();
if (handler == null)
{
handler = row.gameObject.AddComponent();
}
handler.Initialize(this, rowIndex);
return handler;
}
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 EnsureSettingsInitialized()
{
_settings ??= CreateDefaultSettings();
_settings.boxes ??= new List();
if (_settings.boxes.Count == 0)
{
for (int i = 1; i <= defaultBoxesCount; i++)
{
_settings.boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 });
}
}
NormalizeBoxes();
}
private void ResetHorizontalHold()
{
_selectedBoxMoveHandler?.ResetHold();
_thresholdMoveHandler?.ResetHold();
}
private void NormalizeBoxes()
{
_settings.boxes.Sort((a, b) => a.boxNumber.CompareTo(b.boxNumber));
for (int i = 0; i < _settings.boxes.Count; i++)
{
_settings.boxes[i] ??= new PrizeStandBoxConfig();
_settings.boxes[i].boxNumber = i + 1;
_settings.boxes[i].requiredScore = Mathf.Max(0, _settings.boxes[i].requiredScore);
}
}
private PrizeStandBoxConfig GetSelectedBox()
{
EnsureSettingsInitialized();
if (_settings.boxes.Count == 0)
{
return null;
}
_selectedBoxIndex = Mathf.Clamp(_selectedBoxIndex, 0, _settings.boxes.Count - 1);
return _settings.boxes[_selectedBoxIndex];
}
private PrizeStandSettings CreateDefaultSettings()
{
PrizeStandSettings settings = new PrizeStandSettings();
for (int i = 1; i <= defaultBoxesCount; i++)
{
settings.boxes.Add(new PrizeStandBoxConfig { boxNumber = i, requiredScore = 0 });
}
return settings;
}
private static PrizeStandSettings CopyPrizeStandSettings(PrizeStandSettings src)
{
PrizeStandSettings copy = new PrizeStandSettings();
if (src?.boxes == null)
{
return copy;
}
for (int i = 0; i < src.boxes.Count; i++)
{
PrizeStandBoxConfig box = src.boxes[i];
if (box == null)
{
continue;
}
copy.boxes.Add(new PrizeStandBoxConfig
{
boxNumber = box.boxNumber,
requiredScore = box.requiredScore
});
}
return copy;
}
}
[DisallowMultipleComponent]
public sealed class PrizeStandRowMoveHandler : MonoBehaviour, IMoveHandler, IDeselectHandler
{
private const int MaxStepMultiplier = 10;
private const int MultiplierStartCount = 6;
private const int MultiplierMaxCount = 30;
private const float HoldResetTimeout = 0.25f;
private PrizeStandSettingsModal _owner;
private int _rowIndex;
private int _holdDirection;
private int _holdCount;
private float _lastMoveTime;
public void Initialize(PrizeStandSettingsModal owner, int rowIndex)
{
_owner = owner;
_rowIndex = rowIndex;
ResetHold();
}
public void OnMove(AxisEventData eventData)
{
if (_owner == null)
{
return;
}
switch (eventData.moveDir)
{
case MoveDirection.Left:
ApplyMove(-1);
eventData.Use();
break;
case MoveDirection.Right:
ApplyMove(1);
eventData.Use();
break;
case MoveDirection.Up:
case MoveDirection.Down:
ResetHold();
break;
}
}
public void OnDeselect(BaseEventData eventData)
{
ResetHold();
}
private void OnDisable()
{
ResetHold();
}
public void ResetHold()
{
_holdDirection = 0;
_holdCount = 0;
_lastMoveTime = 0f;
}
private void ApplyMove(int direction)
{
float now = Time.unscaledTime;
if (_holdDirection != direction || (now - _lastMoveTime) > HoldResetTimeout)
{
_holdDirection = direction;
_holdCount = 0;
}
_lastMoveTime = now;
_holdCount++;
int multiplier = 1;
if (_holdCount > MultiplierStartCount)
{
float progress = Mathf.Clamp01(
(float)(_holdCount - MultiplierStartCount) /
Mathf.Max(1, MultiplierMaxCount - MultiplierStartCount));
multiplier = Mathf.Max(1, Mathf.RoundToInt(Mathf.Lerp(1f, MaxStepMultiplier, progress)));
}
_owner.HandleRowMove(_rowIndex, direction, multiplier);
}
}
}