package util

import (
	"errors"
	"regexp"
	"strings"
	"unicode"
)

type fder interface {
	Fd() uintptr
}

// CaseInsensitiveIndexFunc returns a function that matches runes equal to r, ignoring case.
func CaseInsensitiveIndexFunc(r rune) func(rune) bool {
	lr := unicode.ToUpper(r)
	return func(v rune) bool {
		return lr == unicode.ToUpper(v)
	}
}

// CaseInsensitiveIndex returns the byte index of the first rune in s that
// is case-insensitively equal to r. Returns -1 if not found. This avoids
// the closure allocation of CaseInsensitiveIndexFunc + strings.IndexFunc.
func CaseInsensitiveIndex(s string, r rune) int {
	upper := unicode.ToUpper(r)
	for i, c := range s {
		if unicode.ToUpper(c) == upper {
			return i
		}
	}
	return -1
}

// ContainsUpper reports whether the string contains any uppercase letter.
func ContainsUpper(query string) bool {
	for _, c := range query {
		if unicode.IsUpper(c) {
			return true
		}
	}
	return false
}

// Global var used to strips ansi sequences
var reANSIEscapeChars = regexp.MustCompile("\x1B\\[(?:[0-9]{1,2}(?:;[0-9]{1,2})?)*[a-zA-Z]")

// StripANSISequence strips ANSI escape sequences from the given string.
// Fast-path: if the string contains no ESC byte, return it unchanged
// to avoid a regexp allocation.
func StripANSISequence(s string) string {
	if !strings.Contains(s, "\x1b") {
		return s
	}
	return reANSIEscapeChars.ReplaceAllString(s, "")
}

type ignorable interface {
	Ignorable() bool
}

type collectResults interface {
	CollectResults() bool
}

type exitStatuser interface {
	ExitStatus() int
}

// IsIgnorableError checks whether err implements the Ignorable interface and returns true.
func IsIgnorableError(err error) bool {
	for e := err; e != nil; e = errors.Unwrap(e) {
		if v, ok := e.(ignorable); ok {
			return v.Ignorable()
		}
	}
	return false
}

// IsCollectResultsError checks whether err signals that results should be collected.
func IsCollectResultsError(err error) bool {
	for e := err; e != nil; e = errors.Unwrap(e) {
		if v, ok := e.(collectResults); ok {
			return v.CollectResults()
		}
	}
	return false
}

// GetExitStatus extracts the exit status code from an error, returning 1 and false if not found.
func GetExitStatus(err error) (int, bool) {
	for e := err; e != nil; e = errors.Unwrap(e) {
		if ese, ok := e.(exitStatuser); ok {
			return ese.ExitStatus(), true
		}
	}
	return 1, false
}
