using System;
using UnityEngine.Rendering.Universal;

namespace KitsuneCafe.System
{
    public static class Result
    {
        /// <summary>
        /// Creates a successful Result with the specified value.
        /// Type inference for TError often occurs from the assignment context.
        /// </summary>
        public static IResult<TValue, TError> Ok<TValue, TError>(TValue value)
        {
            return new Ok<TValue, TError>(value);
        }

        /// <summary>
        /// Creates an erroneous Result with the specified error.
        /// Type inference for TValue often occurs from the assignment context.
        /// </summary>
        public static IResult<TValue, TError> Err<TValue, TError>(TError error)
        {
            return new Err<TValue, TError>(error);
        }
    }

    public interface IResult<TValue, TError>
    {
        public abstract bool IsOk { get; }

        public static IResult<TValue, TError> Ok(TValue value) => new Ok<TValue, TError>(value);
        public static IResult<TValue, TError> Err(TError error) => new Err<TValue, TError>(error);

        public abstract TValue Unwrap();
        public abstract TError UnwrapErr();

        public abstract IResult<TResult, TError> SelectMany<TResult>(Func<TValue, IResult<TResult, TError>> func);
        public abstract IResult<TResult, TError> Select<TResult>(Func<TValue, TResult> func);
        public abstract IResult<TValue, TResult> SelectErr<TResult>(Func<TError, TResult> func);
        public abstract IResult<TValue, TError> Where(Func<TValue, bool> predicate);
        public abstract TResult Match<TResult>(Func<TValue, TResult> okFunc, Func<TError, TResult> errFunc);
        public abstract TValue UnwrapOrDefault(TValue defaultValue = default);
        public abstract TValue UnwrapOrElse(Func<TValue> func);

        public virtual IResult<TValue, TError> Do(Action<TValue> func)
        {
            if (IsOk) { func(Unwrap()); }
            return this;
        }

        public virtual IResult<TValue, TError> DoIfErr(Action<TError> func)
        {
            if (!IsOk) { func(UnwrapErr()); }
            return this;
        }
    }

    public readonly struct Ok<TValue, TError> : IResult<TValue, TError>
    {
        private readonly TValue value;

        public readonly bool IsOk => true;

        internal Ok(TValue value)
        {
            this.value = value;
        }

        public void Deconstruct(out TValue value)
        {
            value = this.value;
        }

        public IResult<TResult, TError> SelectMany<TResult>(Func<TValue, IResult<TResult, TError>> func)
        {
            return func(value);
        }

        public TValue Unwrap()
        {
            return value;
        }

        public TError UnwrapErr()
        {
            throw new InvalidOperationException("Attempted to UnwrapErr an Ok");
        }

        public IResult<TResult, TError> Select<TResult>(Func<TValue, TResult> func)
        {
            return IResult<TResult, TError>.Ok(func(value));
        }

        public IResult<TValue, TResult> SelectErr<TResult>(Func<TError, TResult> func)
        {
            return IResult<TValue, TResult>.Ok(value);
        }

        public IResult<TValue, TError> Where(Func<TValue, bool> predicate)
        {
            if (predicate(value))
            {
                return this;
            }

            return IResult<TValue, TError>.Err(default);
        }

        public TResult Match<TResult>(Func<TValue, TResult> okFunc, Func<TError, TResult> errFunc)
        {
            return okFunc(value);
        }

        public TValue UnwrapOrDefault(TValue defaultValue = default)
        {
            return value;
        }

        public TValue UnwrapOrElse(Func<TValue> func)
        {
            return value;
        }

        public override bool Equals(object obj)
        {
            return obj is Ok<TValue, TError> other && Equals(other);
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(value);
        }

        public override string ToString()
        {
            return $"Ok({value})";
        }
    }

    public readonly struct Err<TValue, TError> : IResult<TValue, TError>
    {
        private readonly TError error;

        public readonly bool IsOk => false;

        internal Err(TError error)
        {
            this.error = error;
        }

        public void Deconstruct(out TError error)
        {
            error = this.error;
        }

        public IResult<TResult, TError> SelectMany<TResult>(Func<TValue, IResult<TResult, TError>> func)
        {
            return IResult<TResult, TError>.Err(error);
        }

        public TValue Unwrap()
        {
            throw new InvalidOperationException("Attempted to unwrap value of an Err.");
        }

        public TError UnwrapErr()
        {
            return error;
        }

        public IResult<TResult, TError> Select<TResult>(Func<TValue, TResult> func)
        {
            return IResult<TResult, TError>.Err(error);
        }

        public IResult<TValue, TResult> SelectErr<TResult>(Func<TError, TResult> func)
        {
            return IResult<TValue, TResult>.Err(func(error));
        }

        public IResult<TValue, TError> Where(Func<TValue, bool> predicate)
        {
            return IResult<TValue, TError>.Err(error);
        }

        public TResult Match<TResult>(Func<TValue, TResult> okFunc, Func<TError, TResult> errFunc)
        {
            return errFunc(error);
        }

        public TValue UnwrapOrDefault(TValue defaultValue = default)
        {
            return defaultValue;
        }

        public TValue UnwrapOrElse(Func<TValue> func)
        {
            return func();
        }

        public override bool Equals(object obj)
        {
            return obj is Err<TValue, TError> other && Equals(other);
        }

        public override int GetHashCode()
        {
            return HashCode.Combine(error);
        }

        public override string ToString()
        {
            return $"Err({error})";
        }
    }
}
