package repl

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"reflect"
	"strings"
	"ucl.lmika.dev/ucl"
)

type NoResults struct{}

func (r *REPL) EvalAndDisplay(ctx context.Context, expr string) error {
	res, err := r.inst.Eval(ctx, expr)
	if err != nil {
		if errors.Is(err, ucl.ErrNotConvertable) {
			return nil
		}
		return err
	}

	return r.displayResult(ctx, os.Stdout, res, false)
}

func (r *REPL) echoPrinter(ctx context.Context, w io.Writer, args []any) (err error) {
	if len(args) == 0 {
		_, err := fmt.Fprintln(w)
		return err
	}

	var line strings.Builder
	for _, arg := range args {
		if err := r.displayResult(ctx, &line, arg, len(args) > 1); err != nil {
			return err
		}
	}

	res := line.String()
	if strings.HasSuffix(res, "\n") {
		res = res[:len(res)-1]
	}

	_, err = fmt.Fprintln(w, res)
	return nil
}

func (r *REPL) displayResult(ctx context.Context, w io.Writer, res any, concise bool) (err error) {
	// Check type printers
	tp, ok := r.typePrinters[reflect.TypeOf(res)]
	if ok {
		return tp(w, res, concise)
	}

	switch v := res.(type) {
	case nil:
		if _, err = fmt.Fprintln(os.Stdout, "(nil)"); err != nil {
			return err
		}
	case ucl.Listable:
		if concise {
			fmt.Fprintf(w, "[")
			for i := 0; i < v.Len(); i++ {
				if i > 0 {
					fmt.Fprintf(w, " ")
				}
				if err = r.displayResult(ctx, w, v.Index(i), true); err != nil {
					return err
				}
			}
			fmt.Fprintf(w, "]")
		} else {
			for i := 0; i < v.Len(); i++ {
				if err = r.displayResult(ctx, w, v.Index(i), true); err != nil {
					return err
				}
				fmt.Fprintf(w, "\n")
			}
		}
	case []interface{}:
		if concise {
			fmt.Fprintf(w, "[")
			for i := 0; i < len(v); i++ {
				if i > 0 {
					fmt.Fprintf(w, " ")
				}
				if err = r.displayResult(ctx, w, v[i], true); err != nil {
					return err
				}
			}
			fmt.Fprintf(w, "]")
		} else {
			// In the off-chance that this is actually a slice of printables
			vt := reflect.SliceOf(reflect.TypeOf(v[0]))
			if tp, ok := r.typePrinters[vt]; ok {
				canDisplay := true

				typeSlice := reflect.MakeSlice(vt, len(v), len(v))
				for i := 0; i < len(v); i++ {
					vv := reflect.ValueOf(v[i])
					if vv.CanConvert(vt.Elem()) {
						typeSlice.Index(i).Set(vv)
					} else {
						canDisplay = false
						break
					}
				}

				if canDisplay {
					return tp(w, typeSlice.Interface(), concise)
				}
			}

			for i := 0; i < len(v); i++ {
				if err = r.displayResult(ctx, w, v[i], true); err != nil {
					return err
				}
				fmt.Fprintf(w, "\n")
			}
		}
	case ucl.Object:
		if _, err = fmt.Fprint(w, v.String()); err != nil {
			return err
		}
		if !concise {
			fmt.Fprintln(w)
		}
	default:
		if _, err = fmt.Fprint(w, v); err != nil {
			return err
		}
		if !concise {
			fmt.Fprintln(w)
		}
	}
	return nil
}