package util

import (
	"context"
	"errors"
	"time"
)

type Promise struct {
	context.Context
	context.CancelCauseFunc
	timer *time.Timer
}

func NewPromise(ctx context.Context) *Promise {
	p := &Promise{}
	p.Context, p.CancelCauseFunc = context.WithCancelCause(ctx)
	return p
}

func NewPromiseWithTimeout(ctx context.Context, timeout time.Duration) *Promise {
	p := &Promise{}
	p.Context, p.CancelCauseFunc = context.WithCancelCause(ctx)
	p.timer = time.AfterFunc(timeout, func() {
		p.CancelCauseFunc(errTimeout)
	})
	return p
}

var ErrResolve = errors.New("promise resolved")
var errTimeout = errors.New("promise timeout")

func (p *Promise) Resolve() {
	p.Fulfill(nil)
}

func (p *Promise) Reject(err error) {
	p.Fulfill(err)
}

func (p *Promise) Await() (err error) {
	<-p.Done()
	err = context.Cause(p.Context)
	if errors.Is(err, ErrResolve) {
		err = nil
	}
	return
}

func (p *Promise) IsRejected() bool {
	return context.Cause(p.Context) != ErrResolve
}

func (p *Promise) Fulfill(err error) {
	if p.timer != nil {
		p.timer.Stop()
	}
	p.CancelCauseFunc(Conditional(err == nil, ErrResolve, err))
}

func (p *Promise) IsPending() bool {
	return context.Cause(p.Context) == nil
}
