package erro

// |@@| C

import (
	"encoding/json"
	"fmt"
	"runtime"
	"strconv"
	"strings"
)

type Error struct {
	Kind  string
	Text  string
	Prev  error
	Kvts  []KVT
	Stack []*StackFrame
	Frame *StackFrame
}

type KVT struct {
	Key   string
	Value string
	Tags  []string
}

type StackFrame struct {
	File     string
	Line     int
	Function string
}

func N(text string) *Error {
	e := &Error{}
	e.Text = text
	improveError(e)
	return e
}

func improveError(e *Error) {
	if e.Prev != nil {
		e2, ok := e.Prev.(*Error)
		if ok {
			e.Stack = e2.Stack
			e.Frame = getFrame(3)
			return
		}
	}

	for i := 3; i < 28; i++ {
		frame := getFrame(i)
		if e.Frame == nil {
			e.Frame = frame
		}

		e.Stack = append(e.Stack, frame)
	}
}

func getFrame(i int) *StackFrame {
	pc, file, line, ok := runtime.Caller(i)
	if !ok {
		return nil
	}
	fn := runtime.FuncForPC(pc)
	funcName := "unknown"
	if fn != nil {
		funcName = fn.Name()
	}
	frame := StackFrame{
		File:     file,
		Line:     line,
		Function: funcName,
	}
	return &frame
}

func NK(text string, kind string) *Error {
	e := &Error{}
	e.Text = text
	e.Kind = kind
	improveError(e)
	return e
}

func W(text string, prev error) *Error {
	e := &Error{}
	e.Text = text
	e.Prev = prev
	improveError(e)
	return e
}

// Deprecated
func P(err any) {
	panic(err)
}

func (e *Error) KV(key string, value interface{}) *Error {
	v := e.castToString(value, key)
	kvt := KVT{
		Key:   key,
		Value: v,
	}
	e.Kvts = append(e.Kvts, kvt)
	return e
}

func (e *Error) KVT(key string, value interface{}, tags ...string) *Error {
	v := e.castToString(value, key)
	kvt := KVT{
		Key:   key,
		Value: v,
		Tags:  tags,
	}
	e.Kvts = append(e.Kvts, kvt)
	return e
}

func (e *Error) castToString(value interface{}, key string) string {
	c, ok := value.(string)
	if ok {
		return c
	}
	c2, ok := value.(int)
	if ok {
		return strconv.Itoa(c2)
	}
	c3, ok := value.(bool)
	if ok {
		if c3 {
			return "true"
		}
		if !c3 {
			return "false"
		}
	}
	panic("Cannot cast to string the value of the key: " + key)
}

func (e *Error) KjVT(key string, value interface{}, tags ...string) *Error {
	b, err := json.Marshal(value)
	if err != nil {
		panic("Cannot marshal")
	}
	kvt := KVT{
		Key:   key,
		Value: string(b),
		Tags:  tags,
	}
	e.Kvts = append(e.Kvts, kvt)
	return e
}

func displayKVTs(ss []string, e *Error) []string {
	for _, kvt := range e.Kvts {
		v := strings.ReplaceAll(kvt.Value, "\n", "")
		ss = append(ss, "    - "+kvt.Key+":"+v)
		if len(kvt.Tags) > 0 {
			ss = append(ss, "     ["+strings.Join(kvt.Tags, "|")+"]")
		}
	}
	return ss
}

func (e *Error) Error() string {
	var ss []string
	ss = append(ss, "--- TOP ERROR --- ")
	ss = append(ss, "  "+e.Text)
	ss = append(ss, e.renderFrame())
	ss = displayKVTs(ss, e)
	prev := e.Prev
	prevMessage := false
	for prev != nil {
		cast, ok := prev.(*Error)
		if ok {
			if !prevMessage {
				ss = append(ss, "--- PREV ERRORS --- ")
				prevMessage = true
			}
			prev = cast.Prev
			ss = append(ss, "  "+cast.Text)
			ss = append(ss, cast.renderFrame())
			ss = displayKVTs(ss, cast)
		} else {
			ss = append(ss, "--- BASE ERROR --- ")
			ss = append(ss, "  "+prev.Error())
			prev = nil
		}
	}

	return fmt.Sprintf("\n%s\n\n", strings.Join(ss, "\n"))
}

func (e *Error) renderFrame() string {
	return "    in " + e.Frame.File + ":" + strconv.Itoa(e.Frame.Line)
}
