package internal

import (
	"context"
	"sync"
)

type StatusType string

const (
	StatusIdle StatusType = "Idle"

	StatusConnecting     StatusType = "Connecting"
	StatusConnected      StatusType = "Connected"
	StatusNeedsLogin     StatusType = "NeedsLogin"
	StatusLoginFailed    StatusType = "LoginFailed"
	StatusSessionExpired StatusType = "SessionExpired"
)

// CtxInitState setup context state into the context tree.
//
// This function should be used to initialize context before
// CtxGetState will be executed.
func CtxInitState(ctx context.Context) context.Context {
	return context.WithValue(ctx, stateCtx, &contextState{
		status: StatusIdle,
	})
}

// CtxGetState object to get/update state/errors of process.
func CtxGetState(ctx context.Context) *contextState {
	return ctx.Value(stateCtx).(*contextState)
}

type contextState struct {
	err      error
	status   StatusType
	mutex    sync.Mutex
	onChange func()
}

// SetOnChange installs a callback fired after every successful Set. Used by
// the daemon to wire the status recorder's notifyStateChange so any
// state.Set in the connect/login paths pushes a fresh snapshot to
// SubscribeStatus subscribers without each callsite having to opt in.
// The callback runs outside the contextState mutex to avoid a lock-order
// dependency with the recorder's stateChangeMux.
func (c *contextState) SetOnChange(fn func()) {
	c.mutex.Lock()
	c.onChange = fn
	c.mutex.Unlock()
}

func (c *contextState) Set(update StatusType) {
	c.mutex.Lock()
	c.status = update
	c.err = nil
	cb := c.onChange
	c.mutex.Unlock()

	if cb != nil {
		cb()
	}
}

func (c *contextState) Status() (StatusType, error) {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	if c.err != nil {
		return "", c.err
	}

	return c.status, nil
}

// CurrentStatus returns the last status set via Set, ignoring any wrapped
// error. Use when the status is needed for reporting purposes (e.g. the
// status snapshot stream) and a transient wrapped error from a retry loop
// shouldn't blank out the underlying status.
func (c *contextState) CurrentStatus() StatusType {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	return c.status
}

func (c *contextState) Wrap(err error) error {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	c.err = err
	return err
}

type stateKey int

var stateCtx stateKey
