using UnityEngine;
using System.Collections.Generic;
using Firebase.Firestore;
using Firebase.Auth;
using System.Threading.Tasks;
using System;
using System.Collections;
namespace BulletHellTemplate
{
///
/// Manages reward data saving and local tracking (PlayerPrefs).
/// This object uses DontDestroyOnLoad to persist across scenes.
///
public class RewardManagerPopup : MonoBehaviour
{
public static RewardManagerPopup Singleton;
// PlayerPrefs keys
private const string CompletedMapKey = "CompletedMap_";
private const string ClaimedRewardKey = "ClaimedReward_";
private FirebaseFirestore _db;
private FirebaseAuth _auth;
private void Awake()
{
if (Singleton == null)
{
Singleton = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
StartWaitAndLoadPendingMapRewards();
}
public void StartWaitAndLoadPendingMapRewards()
{
StartCoroutine(WaitAndLoadPendingMapRewards());
}
public IEnumerator WaitAndLoadPendingMapRewards()
{
while (!BackendManager.Singleton.CheckInitialized())
{
yield return null;
}
StartSyncRewardsFromServer();
}
public async void StartSyncRewardsFromServer()
{
await SyncRewardsFromServer();
}
///
/// Called when a map is completed successfully. Saves that completion
/// both locally (PlayerPrefs) and to Firestore, unless it was already claimed.
/// After saving, it waits for the UIRewardManagerPopup to exist,
/// then shows any newly pending rewards.
///
/// Identifier of the map completed.
public async void TrySaveCompletedMap(int mapId)
{
_db = FirebaseManager.Singleton.firestore;
_auth = FirebaseManager.Singleton.auth;
if (_auth == null)
{
Debug.LogError("Authentication instance (_auth) is null. Cannot save map completion.");
return;
}
var user = _auth.CurrentUser;
if (user == null)
{
Debug.LogError("User not authenticated. Cannot save map completion.");
return;
}
// Local map data check
var mapData = GetMapDataById(mapId);
if (mapData == null)
{
Debug.LogError($"Map data not found for mapId: {mapId}");
return;
}
// If there's no valid reward to save, we skip.
if (!ShouldSaveMapCompletion(mapData))
{
Debug.Log($"Map completion should not be saved for mapId: {mapId}");
return;
}
// If the reward is already claimed locally, skip.
if (IsRewardClaimed(mapId))
{
Debug.Log($"Reward for map {mapId} already claimed.");
return;
}
try
{
// Check Firestore if there's already a doc for that map
var docRef = _db.Collection("Players")
.Document(user.UserId)
.Collection("rewardsToRedeem")
.Document(mapId.ToString());
var snapshot = await docRef.GetSnapshotAsync();
if (!snapshot.Exists)
{
// If not in Firestore, create doc
await SaveMapCompletionFirestore(mapId, user.UserId);
}
else
{
Debug.Log($"Map {mapId} already has pending reward in Firestore.");
}
// Mark locally as completed, so we know there's a pending reward.
MarkMapCompleted(mapId);
// After finishing the save, wait for UI to show
StartCoroutine(WaitAndShowRewards());
}
catch (Exception e)
{
Debug.LogError($"Error in TrySaveCompletedMap: {e.Message}");
}
}
///
/// Coroutine that waits until UIRewardManagerPopup.Singleton is not null,
/// then calls ShowPendingRewards() to display the local pending data.
///
private IEnumerator WaitAndShowRewards()
{
while (UIRewardManagerPopup.Singleton == null)
{
yield return null;
}
yield return null;
UIRewardManagerPopup.Singleton.ShowPendingRewards();
}
///
/// NEW FUNCTION:
/// Loads all 'rewardsToRedeem' from Firestore and updates local PlayerPrefs accordingly.
/// - If 'claimed' == false, marks that map as completed locally (pending).
/// - If 'claimed' == true, marks that map as claimed locally (no longer pending).
/// This is typically called once after BackendManager.CheckInitialized() is true,
/// so the user has the latest data offline.
///
public async Task SyncRewardsFromServer()
{
_db = FirebaseManager.Singleton.firestore;
_auth = FirebaseManager.Singleton.auth;
if (_auth == null)
{
Debug.LogError("Auth is null in SyncRewardsFromServer.");
return;
}
var user = _auth.CurrentUser;
if (user == null)
{
Debug.LogError("User is not authenticated in SyncRewardsFromServer.");
return;
}
try
{
var docs = await _db.Collection("Players")
.Document(user.UserId)
.Collection("rewardsToRedeem")
.GetSnapshotAsync();
Debug.Log($"SyncRewardsFromServer found {docs.Count} docs in rewardsToRedeem.");
foreach (var doc in docs.Documents)
{
bool claimed = doc.ContainsField("claimed") && doc.GetValue("claimed");
int mapId = int.Parse(doc.Id);
if (!claimed)
{
MarkMapCompleted(mapId);
}
else
{
MarkRewardClaimed(mapId);
}
}
StartCoroutine(WaitAndShowRewards());
}
catch (Exception e)
{
Debug.LogError($"SyncRewardsFromServer error: {e.Message}");
}
}
///
/// Writes the map completion data in Firestore, setting 'claimed' to false by default.
///
private async Task SaveMapCompletionFirestore(int mapId, string userId)
{
var data = new Dictionary
{
{ "mapId", mapId },
{ "claimed", false },
{ "timestamp", FieldValue.ServerTimestamp }
};
var docRef = _db.Collection("Players")
.Document(userId)
.Collection("rewardsToRedeem")
.Document(mapId.ToString());
await docRef.SetAsync(data);
Debug.Log($"Map {mapId} saved successfully in Firestore.");
}
///
/// Finds the MapInfoData object by ID from GameInstance.
///
private MapInfoData GetMapDataById(int mapId)
{
if (GameInstance.Singleton == null ||
GameInstance.Singleton.mapInfoData == null ||
GameInstance.Singleton.mapInfoData.Length == 0)
{
Debug.LogError("GameInstance or mapInfoData not initialized.");
return null;
}
return Array.Find(GameInstance.Singleton.mapInfoData, m => m != null && m.mapId == mapId);
}
///
/// Determines if a map's first-time reward should be saved (local + Firestore).
///
private bool ShouldSaveMapCompletion(MapInfoData mapData)
{
if (mapData == null) return false;
// Must have first-time reward enabled and either currency or special item set
return mapData.isRewardOnCompleteFirstTime &&
(mapData.WinMapRewards.Count > 0 || mapData.rewardType != MapRewardType.None);
}
///
/// Marks a map as completed in PlayerPrefs (meaning there's a pending reward if not claimed).
///
public void MarkMapCompleted(int mapId)
{
if (!IsMapCompleted(mapId))
{
PlayerPrefs.SetInt(CompletedMapKey + mapId, 1);
PlayerPrefs.Save();
}
}
///
/// Checks if a given map is marked completed (pending reward) in PlayerPrefs.
///
public bool IsMapCompleted(int mapId)
{
return PlayerPrefs.GetInt(CompletedMapKey + mapId, 0) == 1;
}
///
/// Marks a map reward as claimed in PlayerPrefs (no longer pending).
///
public void MarkRewardClaimed(int mapId)
{
PlayerPrefs.SetInt(ClaimedRewardKey + mapId, 1);
PlayerPrefs.Save();
}
///
/// Checks if a reward was already claimed locally.
///
public bool IsRewardClaimed(int mapId)
{
return PlayerPrefs.GetInt(ClaimedRewardKey + mapId, 0) == 1;
}
///
/// Returns all unclaimed maps by checking local PlayerPrefs (completed but not claimed).
/// This method does NOT call Firestore; it uses offline data only.
///
public List GetLocalUnclaimedMaps()
{
List result = new List();
if (GameInstance.Singleton == null || GameInstance.Singleton.mapInfoData == null)
{
Debug.LogWarning("No map data in GameInstance.Singleton.");
return result;
}
foreach (var map in GameInstance.Singleton.mapInfoData)
{
if (map == null) continue;
int mapId = map.mapId;
bool completed = IsMapCompleted(mapId);
bool claimed = IsRewardClaimed(mapId);
if (completed && !claimed)
{
result.Add(mapId);
}
}
return result;
}
}
}