// Copyright (c) 2026 EzAdmin. All rights reserved.
// Licensed under the EzAdmin Client License. See LICENSE-CLIENT.txt.

using UnityEngine;
using UnityEngine.Events;

namespace EzAdmin.Client
{

    [DisallowMultipleComponent]
    public sealed class EzAdminLinkPanel : MonoBehaviour
    {
        [System.Serializable]
        public class StringEvent : UnityEvent<string> { }

        [System.Serializable]
        public class BoolEvent : UnityEvent<bool> { }

        [EzAdminGroup("Display — wire these to your text components")]
        [Tooltip("The code as typed so far, padded with placeholders. e.g. 'AB3 _ _ _'")]
        [SerializeField] private StringEvent _onCodeChanged = new StringEvent();

        [Tooltip("Status line shown under the code box.")]
        [SerializeField] private StringEvent _onStatusChanged = new StringEvent();

        [EzAdminGroup("State")]
        [Tooltip("True when the code is complete and Submit will actually do something. " +
                 "Wire to your submit button's IsInteractable.")]
        [SerializeField] private BoolEvent _onCanSubmitChanged = new BoolEvent();

        [Tooltip("True while a request is in flight. Wire to a spinner, or to the " +
                 "keypad's interactable state.")]
        [SerializeField] private BoolEvent _onBusyChanged = new BoolEvent();

        [EzAdminGroup("Outcome")]
        [SerializeField] private UnityEvent _onLinkSucceeded = new UnityEvent();
        [SerializeField] private UnityEvent _onLinkFailed = new UnityEvent();

        [EzAdminGroup("Look")]
        [Tooltip("Character shown for each digit not yet typed.")]
        [SerializeField] private string _placeholder = "_";

        [Tooltip("Put a space between characters so the code is readable at VR text " +
                 "sizes. The dash between the two groups is always shown.")]
        [SerializeField] private bool _spaceOutCode = true;

        [EzAdminGroup("Behaviour")]
        [Tooltip("Submit automatically as soon as the last character is entered, " +
                 "so the player never hunts for a Submit button.")]
        [SerializeField] private bool _autoSubmitWhenComplete = true;

        [Tooltip("Check whether this account is already linked when the panel opens.")]
        [SerializeField] private bool _checkStatusOnEnable = true;

        public enum LinkMethod
        {

            Automatic,

            CodeOnly,

            DiscordOnly,

            CodeAndDiscord
        }

        [EzAdminGroup("Linking method")]
        [Tooltip("Automatic: players in a VR headset type the code, everyone else can also sign in " +
                 "with Discord. Signing in with Discord needs EzAdminDiscord and the Discord Social SDK.")]
        [SerializeField] private LinkMethod _linkMethod = LinkMethod.Automatic;

        [Tooltip("True when a Sign in with Discord button should be shown. Wire to that button's GameObject.SetActive.")]
        [SerializeField] private BoolEvent _onDiscordOptionChanged = new BoolEvent();

        [Tooltip("True when the code box and keypad should be shown. Wire to their GameObject.SetActive.")]
        [SerializeField] private BoolEvent _onCodeOptionChanged = new BoolEvent();

        private readonly System.Text.StringBuilder _buffer = new System.Text.StringBuilder(8);
        private bool _isBusy;
        private bool _lastCanSubmit;

        public string CurrentCode
        {
            get { return _buffer.ToString(); }
        }

        public bool CanSubmit
        {
            get { return !_isBusy && EzAdminCode.IsComplete(_buffer.ToString()); }
        }

        public bool DiscordAvailable
        {
            get
            {
                if (_linkMethod == LinkMethod.CodeOnly || !DiscordReady())
                {
                    return false;
                }

                return _linkMethod != LinkMethod.Automatic || !UnityEngine.XR.XRSettings.isDeviceActive;
            }
        }

        public bool CodeAvailable
        {
            get { return _linkMethod != LinkMethod.DiscordOnly || !DiscordAvailable; }
        }

        private void OnEnable()
        {
            Clear();
            PushOptions();

            if (_checkStatusOnEnable)
            {
                CheckExistingLink();
            }
        }

        private void Start()
        {

            PushOptions();
        }

        public void LinkWithDiscord()
        {
            if (_isBusy)
            {
                return;
            }

            if (!DiscordAvailable)
            {
                SetStatus("Linking with Discord is not available here. Use the code from /link.");
                return;
            }

#if EZADMIN_DISCORD_SDK
            SetBusy(true);
            SetStatus("Opening Discord...");
            EzAdminDiscord.Instance.LinkWithDiscord((succeeded, message) =>
            {
                if (this == null || !isActiveAndEnabled)
                {
                    return;
                }

                SetBusy(false);
                SetStatus(message);

                if (succeeded)
                {
                    _onLinkSucceeded.Invoke();
                }
                else
                {
                    _onLinkFailed.Invoke();
                }
            });
#endif
        }

        private static bool DiscordReady()
        {
#if EZADMIN_DISCORD_SDK
            var client = EzAdminClient.Instance;
            return EzAdminDiscord.Instance != null && EzAdminDiscord.Instance.IsReady
                   && client != null && client.CanLinkWithDiscord;
#else
            return false;
#endif
        }

        private void PushOptions()
        {
            _onDiscordOptionChanged.Invoke(DiscordAvailable);
            _onCodeOptionChanged.Invoke(CodeAvailable);
        }

        public void AppendCharacter(string character)
        {
            if (_isBusy || string.IsNullOrEmpty(character))
            {
                return;
            }

            char c = char.ToUpperInvariant(character[0]);

            if (!EzAdminCode.IsAllowed(c))
            {
                return;
            }

            if (_buffer.Length >= EzAdminCode.Length)
            {
                return;
            }

            _buffer.Append(c);
            PushCode();

            if (_autoSubmitWhenComplete && _buffer.Length == EzAdminCode.Length)
            {
                Submit();
            }
        }

        public void Backspace()
        {
            if (_isBusy || _buffer.Length == 0)
            {
                return;
            }

            _buffer.Length = _buffer.Length - 1;
            PushCode();
        }

        public void Clear()
        {
            _buffer.Length = 0;
            SetStatus(CodeAvailable
                ? "Run /link in Discord to get your code."
                : "Sign in with Discord to link your account.");
            PushCode();
        }

        public void SetCode(string code)
        {
            if (_isBusy)
            {
                return;
            }

            string cleaned = EzAdminCode.Normalise(code);

            _buffer.Length = 0;
            if (!string.IsNullOrEmpty(cleaned))
            {
                _buffer.Append(cleaned);
            }

            PushCode();
        }

        public void Submit()
        {
            if (_isBusy)
            {
                return;
            }

            string code = _buffer.ToString();

            if (!EzAdminCode.IsComplete(code))
            {
                SetStatus("Enter all " + EzAdminCode.Length + " characters.");
                return;
            }

            var client = EzAdminClient.Instance;
            if (client == null)
            {
                SetStatus("EzAdmin is not set up in this scene.");
                Debug.LogError("EzAdminLinkPanel: no EzAdminClient in the scene. Add the " +
                               "EzAdminClient component to a bootstrap object.", this);
                return;
            }

            SetBusy(true);
            SetStatus("Checking your code...");

            EzAdminLink.Redeem(code, (succeeded, message) =>
            {

                if (this == null || !isActiveAndEnabled)
                {
                    return;
                }

                SetBusy(false);
                SetStatus(message);

                if (succeeded)
                {
                    _onLinkSucceeded.Invoke();
                }
                else
                {
                    _onLinkFailed.Invoke();

                }
            });
        }

        public void CheckExistingLink()
        {
            var client = EzAdminClient.Instance;
            if (client == null)
            {
                return;
            }

            EzAdminLink.Check((isLinked, discordName) =>
            {
                if (this == null || !isActiveAndEnabled)
                {
                    return;
                }

                if (!isLinked)
                {
                    return;
                }

                SetStatus(string.IsNullOrEmpty(discordName)
                    ? "This account is already linked to Discord."
                    : "Already linked to " + discordName + ".");
            });
        }

        private void PushCode()
        {
            _onCodeChanged.Invoke(BuildDisplayCode());

            bool canSubmit = CanSubmit;
            if (canSubmit != _lastCanSubmit)
            {
                _lastCanSubmit = canSubmit;
                _onCanSubmitChanged.Invoke(canSubmit);
            }
        }

        private string BuildDisplayCode()
        {
            string typed = _buffer.ToString();
            var display = new System.Text.StringBuilder(EzAdminCode.Length * 2);

            for (int i = 0; i < EzAdminCode.Length; i++)
            {
                if (i > 0 && i % EzAdminCode.GroupSize == 0)
                {

                    display.Append(_spaceOutCode ? " - " : "-");
                }
                else if (i > 0 && _spaceOutCode)
                {
                    display.Append(' ');
                }

                display.Append(i < typed.Length ? typed[i].ToString() : _placeholder);
            }

            return display.ToString();
        }

        private void SetBusy(bool busy)
        {
            _isBusy = busy;
            _onBusyChanged.Invoke(busy);
            PushCode();
        }

        private void SetStatus(string message)
        {
            _onStatusChanged.Invoke(message ?? string.Empty);
        }
    }
}
