using System;
using System.Collections.Generic;
using UnityEngine;

namespace Davanci.Nexus
{
    // Per-install memory behind the tutorial and progress events (PlayerPrefs, prefix nexus.evt.):
    // which tutorial steps were already reported, and per progress kind the highest completed
    // index plus the attempts at the index being played. Progress indices are ordered, so one
    // high-water mark per kind stands for every completed level, however many there are.
    static class NexusEventGuards
    {
        const string Prefix = "nexus.evt.";
        const string KeyShown = Prefix + "t.shown";
        const string KeyDone = Prefix + "t.done";
        const string KeyLastStep = Prefix + "t.last";
        const string KeyKinds = Prefix + "p.kinds";
        static readonly string[] OnceNames = { "t.begin", "t.complete" };
        static readonly char[] Comma = { ',' };

        static HashSet<int> _shown, _done;

        // ---- Tutorial -----------------------------------------------------------------

        // True the first time `name` (t.begin, t.complete) is marked on this install.
        public static bool MarkOnce(string name)
        {
            string key = Prefix + name;
            if (PlayerPrefs.GetInt(key, 0) == 1) return false;
            PlayerPrefs.SetInt(key, 1);
            PlayerPrefs.Save();
            return true;
        }

        // True the first time this step is shown (or done) on this install.
        public static bool MarkStep(bool done, int step)
        {
            string key = done ? KeyDone : KeyShown;
            var steps = done ? (_done ??= LoadSteps(KeyDone)) : (_shown ??= LoadSteps(KeyShown));
            if (!steps.Add(step)) return false;

            PlayerPrefs.SetString(key, string.Join(",", steps));
            if (!done && step > LastTutorialStep) PlayerPrefs.SetInt(KeyLastStep, step);
            PlayerPrefs.Save();
            return true;
        }

        // Highest tutorial step shown so far (0 before any step).
        public static int LastTutorialStep => PlayerPrefs.GetInt(KeyLastStep, 0);

        static HashSet<int> LoadSteps(string key)
        {
            var steps = new HashSet<int>();
            foreach (var part in PlayerPrefs.GetString(key, "").Split(Comma, StringSplitOptions.RemoveEmptyEntries))
                if (int.TryParse(part, out int step)) steps.Add(step);
            return steps;
        }

        // ---- Progress -----------------------------------------------------------------

        public static int HighestCompleted(string kind) => PlayerPrefs.GetInt(MaxKey(kind), 0);

        // A Start at `index`: attempts count up until that index is completed.
        public static int StartAttempt(string kind, int index)
        {
            Remember(kind);
            int attempt = PlayerPrefs.GetInt(CurrentKey(kind), 0) == index
                ? PlayerPrefs.GetInt(AttemptKey(kind), 0) + 1
                : 1;
            PlayerPrefs.SetInt(CurrentKey(kind), index);
            PlayerPrefs.SetInt(AttemptKey(kind), attempt);
            PlayerPrefs.Save();
            return attempt;
        }

        // Attempt number for a Complete or Fail at `index`; 1 when it was never started.
        public static int CurrentAttempt(string kind, int index)
            => PlayerPrefs.GetInt(CurrentKey(kind), 0) == index
                ? Math.Max(1, PlayerPrefs.GetInt(AttemptKey(kind), 1))
                : 1;

        public static void Completed(string kind, int index)
        {
            Remember(kind);
            PlayerPrefs.SetInt(MaxKey(kind), index);
            PlayerPrefs.DeleteKey(CurrentKey(kind));
            PlayerPrefs.DeleteKey(AttemptKey(kind));
            PlayerPrefs.Save();
        }

        static string MaxKey(string kind) => $"{Prefix}p.{kind}.max";
        static string CurrentKey(string kind) => $"{Prefix}p.{kind}.cur";
        static string AttemptKey(string kind) => $"{Prefix}p.{kind}.att";

        // Kinds are listed so Reset can find their keys (PlayerPrefs cannot be enumerated).
        static void Remember(string kind)
        {
            var kinds = Kinds();
            if (kinds.Add(kind)) PlayerPrefs.SetString(KeyKinds, string.Join(",", kinds));
        }

        static HashSet<string> Kinds()
            => new HashSet<string>(PlayerPrefs.GetString(KeyKinds, "").Split(Comma, StringSplitOptions.RemoveEmptyEntries));

        // ---- Reset --------------------------------------------------------------------

        public static void Reset()
        {
            foreach (var kind in Kinds())
            {
                PlayerPrefs.DeleteKey(MaxKey(kind));
                PlayerPrefs.DeleteKey(CurrentKey(kind));
                PlayerPrefs.DeleteKey(AttemptKey(kind));
            }
            foreach (var name in OnceNames) PlayerPrefs.DeleteKey(Prefix + name);
            PlayerPrefs.DeleteKey(KeyKinds);
            PlayerPrefs.DeleteKey(KeyShown);
            PlayerPrefs.DeleteKey(KeyDone);
            PlayerPrefs.DeleteKey(KeyLastStep);
            PlayerPrefs.Save();
            _shown = _done = null;
        }
    }
}
