package ktypes

import (
	"fmt"
	"reflect"
	"runtime"

	. "gitp78su.ipnodns.ru/svi/kern/v3/kc/helpers"
)

// Result — обёртка вокруг результата с возможной ошибкой
//
// Может быть либо только полезное значение, либо только ошибка
type Result[T any] struct {
	val     T     // Полезное значение
	isErr   bool  // Если содержит ошибку
	isCheck bool  // Если проверено
	err     error // Ошибка
}

// NewOk -- возвращает успешный Result с значением
func NewOk[T any](result T) Result[T] {
	// Для некоторых типов нужна дополнительная проверка через reflect
	v := reflect.ValueOf(result)
	switch v.Kind() {
	case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func, reflect.Interface:
		Hassert(!v.IsNil(), "NewOk(): result==nil")
	}
	sf := Result[T]{
		val: result,
	}
	runtime.SetFinalizer(&sf, sf.destroy)
	return sf
}

// NewErr -- возвращает Result с ошибкой
func NewErr[T any](err error) Result[T] {
	Hassert(err != nil, "NewError(): err==nil")
	sf := Result[T]{
		err:   err,
		isErr: true,
	}
	runtime.SetFinalizer(&sf, sf.destroy)
	return sf
}

// WrapErr -- оборачивает существующий Result с ошибкой с новой ошибкой
func WrapErr[T any](res Result[T], err error) Result[T] {
	Hassert(res.IsErr(), "WrapErr(): result not have error")
	Hassert(err != nil, "WrapErr(): err==nil")
	err0 := res.Err()
	err = fmt.Errorf("%v, err=\n\t%w", err0, err)
	sf := NewErr[T](err)
	runtime.SetFinalizer(&sf, sf.destroy)
	return sf
}

func (sf *Result[T]) destroy(res *Result[T]) {
	if !res.isCheck {
		Hassert(false, "Result[T].destroy(): err not checked")
	}
}

// IsOk -- возвращает true, если Result содержит значение
func (sf *Result[T]) IsOk() bool {
	sf.isCheck = true
	return !sf.isErr
}

// IsErr -- возвращает true, если Result содержит ошибку
func (sf *Result[T]) IsErr() bool {
	sf.isCheck = true
	return sf.isErr
}

// Val -- возвращает значение, если оно есть, иначе паникует
func (sf *Result[T]) Val() T {
	Hassert(!sf.isErr, "Result[T].Val(): err(%v)!=nil", sf.err)
	return sf.val
}

// ValOr -- возвращает значение, если оно есть, или значение по умолчанию
func (sf *Result[T]) ValOr(defaultValue T) T {
	sf.isCheck = true
	if sf.isErr {
		return defaultValue
	}
	return sf.val
}

// ValOrFn -- возвращает значение, если оно есть, или результат выполнения функции
func (sf *Result[T]) ValOrFn(fn func() T) T {
	Hassert(fn != nil, "Result[T].ValOrFn(): fn==nil")
	sf.isCheck = true
	if sf.isErr {
		return fn()
	}
	return sf.val
}

// Err -- возвращает ошибку, если она есть
func (sf *Result[T]) Err() error {
	Hassert(sf.isErr, "Result[T].Err(): err==nil")
	sf.isCheck = true
	return sf.err
}

// Error -- возвращает строковое представление ошибки, если она есть
func (sf *Result[T]) Error() string {
	Hassert(sf.isErr, "Result[T].Error(): err==nil")
	sf.isCheck = true
	return sf.err.Error()
}

// Hassert -- проверяет, что нет ошибки (с паникой)
func (sf *Result[T]) Hassert(msgFormat string, args ...any) {
	strErr := ""
	if sf.isErr {
		strErr = fmt.Sprintf(", err=\n\t%v", sf.err)
	}
	msg := fmt.Sprintf(msgFormat+strErr, args...)
	Hassert(!sf.isErr, msg)
	sf.isCheck = true
}

// Assert -- проверяет, что нет ошибки (с паникой только на локальном стенде)
func (sf *Result[T]) Assert(msgFormat string, args ...any) {
	strErr := ""
	if sf.isErr {
		strErr = fmt.Sprintf(", err=\n\t%v", sf.err)
	}
	msg := fmt.Sprintf(msgFormat+strErr, args...)
	Assert(!sf.isErr, msg)
	sf.isCheck = true
}
