package mo

import (
	"sync"
)

// NewFuture instanciate a new future.
func NewFuture[T any](cb func(resolve func(T), reject func(error))) *Future[T] {
	future := Future[T]{
		cb:       cb,
		cancelCb: func() {},
		done:     make(chan struct{}),
	}

	future.active()

	return &future
}

// Future represents a value which may or may not currently be available, but will be
// available at some point, or an exception if that value could not be made available.
type Future[T any] struct {
	mu sync.Mutex

	cb       func(func(T), func(error))
	cancelCb func()
	next     *Future[T]
	done     chan struct{}
	doneOnce sync.Once
	result   Result[T]
}

func (f *Future[T]) active() {
	go f.cb(f.resolve, f.reject)
}

func (f *Future[T]) activeSync() {
	f.cb(f.resolve, f.reject)
}

func (f *Future[T]) resolve(value T) {
	f.doneOnce.Do(func() {
		f.mu.Lock()
		defer f.mu.Unlock()

		f.result = Ok(value)
		if f.next != nil {
			f.next.activeSync()
		}
		close(f.done)
	})
}

func (f *Future[T]) reject(err error) {
	f.doneOnce.Do(func() {
		f.mu.Lock()
		defer f.mu.Unlock()

		f.result = Err[T](err)
		if f.next != nil {
			f.next.activeSync()
		}
		close(f.done)
	})
}

// Then is called when Future is resolved. It returns a new Future.
func (f *Future[T]) Then(cb func(T) (T, error)) *Future[T] {
	f.mu.Lock()

	next := &Future[T]{
		cb: func(resolve func(T), reject func(error)) {
			if f.result.IsError() {
				reject(f.result.Error())
				return
			}
			newValue, err := cb(f.result.MustGet())
			if err != nil {
				reject(err)
				return
			}
			resolve(newValue)
		},
		cancelCb: func() {
			f.Cancel()
		},
		done: make(chan struct{}),
	}
	f.next = next

	select {
	case <-f.done:
		f.mu.Unlock()
		next.active()
	default:
		f.mu.Unlock()
	}

	return next
}

// Catch is called when Future is rejected. It returns a new Future.
func (f *Future[T]) Catch(cb func(error) (T, error)) *Future[T] {
	f.mu.Lock()

	next := &Future[T]{
		cb: func(resolve func(T), reject func(error)) {
			if f.result.IsOk() {
				resolve(f.result.MustGet())
				return
			}
			newValue, err := cb(f.result.Error())
			if err != nil {
				reject(err)
				return
			}
			resolve(newValue)
		},
		cancelCb: func() {
			f.Cancel()
		},
		done: make(chan struct{}),
	}
	f.next = next

	select {
	case <-f.done:
		f.mu.Unlock()
		next.active()
	default:
		f.mu.Unlock()
	}

	return next
}

// Finally is called when Future is processed either resolved or rejected. It returns a new Future.
func (f *Future[T]) Finally(cb func(T, error) (T, error)) *Future[T] {
	f.mu.Lock()

	next := &Future[T]{
		cb: func(resolve func(T), reject func(error)) {
			newValue, err := cb(f.result.Get())
			if err != nil {
				reject(err)
				return
			}
			resolve(newValue)
		},
		cancelCb: func() {
			f.Cancel()
		},
		done: make(chan struct{}),
	}
	f.next = next

	select {
	case <-f.done:
		f.mu.Unlock()
		next.active()
	default:
		f.mu.Unlock()
	}

	return next
}

// Cancel cancels the Future chain.
func (f *Future[T]) Cancel() {
	f.mu.Lock()
	defer f.mu.Unlock()

	f.next = nil
	if f.cancelCb != nil {
		f.cancelCb()
	}
}

// Collect awaits and return result of the Future.
func (f *Future[T]) Collect() (T, error) {
	<-f.done
	return f.result.Get()
}

// Result wraps Collect and returns a Result.
func (f *Future[T]) Result() Result[T] {
	return TupleToResult(f.Collect())
}

// Either wraps Collect and returns a Either.
func (f *Future[T]) Either() Either[error, T] {
	v, err := f.Collect()
	if err != nil {
		return Left[error, T](err)
	}
	return Right[error](v)
}
