package utils

import (
	"encoding/json"
	"fmt"
	"os"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/jesseduffield/gocui"
	"github.com/jesseduffield/lazygit/pkg/config"
)

// GetProjectRoot returns the path to the root of the project. Only to be used
// in testing contexts, as with binaries it's unlikely this path will exist on
// the machine
func GetProjectRoot() string {
	dir, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	return strings.Split(dir, "lazygit")[0] + "lazygit"
}

// Loader dumps a string to be displayed as a loader
func Loader(now time.Time, config config.SpinnerConfig) string {
	milliseconds := now.UnixMilli()
	index := milliseconds / int64(config.Rate) % int64(len(config.Frames))
	return config.Frames[index]
}

func SortRange(x int, y int) (int, int) {
	if x < y {
		return x, y
	}
	return y, x
}

func Clamp(x int, min int, max int) int {
	if x < min {
		return min
	} else if x > max {
		return max
	}
	return x
}

func AsJson(i interface{}) string {
	bytes, _ := json.MarshalIndent(i, "", "    ")
	return string(bytes)
}

// used to keep a number n between 0 and max, allowing for wraparounds
func ModuloWithWrap(n, max int) int {
	if max == 0 {
		return 0
	}

	if n >= max {
		return n % max
	} else if n < 0 {
		return max + n
	} else {
		return n
	}
}

func FindStringSubmatch(str string, regexpStr string) (bool, []string) {
	re := regexp.MustCompile(regexpStr)
	match := re.FindStringSubmatch(str)
	return len(match) > 0, match
}

func MustConvertToInt(s string) int {
	i, err := strconv.Atoi(s)
	if err != nil {
		panic(err)
	}
	return i
}

// Safe will close tcell if a panic occurs so that we don't end up in a malformed
// terminal state
func Safe(f func()) {
	_ = SafeWithError(func() error { f(); return nil })
}

func SafeWithError(f func() error) error {
	panicking := true
	defer func() {
		if panicking && gocui.Screen != nil {
			gocui.Screen.Fini()
		}
	}()

	err := f()

	panicking = false

	return err
}

func StackTrace() string {
	buf := make([]byte, 10000)
	n := runtime.Stack(buf, false)
	return fmt.Sprintf("%s\n", buf[:n])
}

// returns the path of the file that calls the function.
// 'skip' is the number of stack frames to skip.
func FilePath(skip int) string {
	_, path, _, _ := runtime.Caller(skip)
	return path
}
