// Copyright (c) 2025 TerraByte Inc.
//
// The concrete implementation of the IPromise interface.
// It manages the state and execution of an asynchronous operation.

using System;
using System.Threading;

namespace Terra.Arbitrator.Promises
{
    public class Promise<TResult> : IPromise<TResult>, ITrackablePromise
    {
        private PromiseState State { get; set; }
        private TResult _resolvedValue;
        private Exception _rejectedException;

        private Action<TResult> _onResolvedCallback;
        private Action<Exception> _onRejectedCallback;
        private Action _onFinallyCallback;
        
        private volatile bool _isResolved;
        private volatile bool _isRejected;

        /// <summary>
        /// Creates a new promise and starts its execution on a background thread.
        /// </summary>
        /// <param name="executor">The function to execute, which takes resolve and reject actions.</param>
        public Promise(Action<Action<TResult>, Action<Exception>> executor)
        {
            _isRejected = false;
            State = PromiseState.Pending;
            // The manager will track this promise until it's settled.
            PromiseManager.Track(this);

            ThreadPool.QueueUserWorkItem(_ =>
            {
                try
                {
                    executor(Resolve, Reject);
                }
                catch (Exception ex)
                {
                    Reject(ex);
                }
            });
        }

        private void Resolve(TResult value)
        {
            if (State != PromiseState.Pending) return;
            _resolvedValue = value;
            State = PromiseState.Resolved;
            _isResolved = true; // Signal that we are done
        }

        private void Reject(Exception ex)
        {
            if (State != PromiseState.Pending) return;
            _rejectedException = ex;
            State = PromiseState.Rejected;
            _isRejected = true; // Signal that we are done
        }

        public IPromise<TResult> Then(Action<TResult> onResolved)
        {
            _onResolvedCallback = onResolved;
            return this;
        }
        
        public IPromise<TNewResult> Then<TNewResult>(Func<TResult, IPromise<TNewResult>> onResolved)
        {
            return new Promise<TNewResult>(LocalExecutor);

            void LocalExecutor(Action<TNewResult> resolve, Action<Exception> reject)
            {
                Then(OnResolved).Catch(reject);
                return;

                void OnResolved(TResult result)
                {
                    try
                    {
                        var nextPromise = onResolved(result);
                        nextPromise.Then(resolve).Catch(reject);
                    }
                    catch (Exception ex)
                    {
                        reject(ex);
                    }
                }
            }
        }

        public IPromise<TResult> Catch(Action<Exception> onRejected)
        {
            _onRejectedCallback = onRejected;
            return this;
        }

        public void Finally(Action onFinally)
        {
            _onFinallyCallback = onFinally;
        }

        /// <summary>
        /// Called by the PromiseManager from the main thread to execute callbacks.
        /// </summary>
        /// <returns>True if the promise is settled, otherwise false.</returns>
        public bool Tick()
        {
            if (_isResolved)
            {
                try
                {
                    _onResolvedCallback?.Invoke(_resolvedValue);
                }
                finally
                {
                    _onFinallyCallback?.Invoke();
                }
                return true;
            }

            if (_isRejected)
            {
                 try
                 {
                     _onRejectedCallback?.Invoke(_rejectedException);
                 }
                 finally
                 {
                     _onFinallyCallback?.Invoke();
                 }
                 return true;
            }
            
            return false;
        }
    }
}
