package filter

import (
	"strings"

	"github.com/gobwas/glob"
)

type Filter interface {
	Match(string) bool
}

// Compile takes a list of string filters and returns a Filter interface
// for matching a given string against the filter list. The filter list
// supports glob matching with separators too, ie:
//
//	f, err := Compile([]string{"cpu", "mem", "net*"})
//	f.Match("cpu")     // true
//	f.Match("network") // true
//	f.Match("memory")  // false
//
// separators are only to be used for globbing filters, ie:
//
//	f, err := Compile([]string{"cpu.*.count"}, '.')
//	f.Match("cpu.count")     // false
//	f.Match("cpu.measurement.count") // true
//	f.Match("cpu.field.measurement.count")  // false
//
// Compile will return nil if the filter list is empty.
func Compile(filters []string, separators ...rune) (Filter, error) {
	// return if there is nothing to compile
	if len(filters) == 0 {
		return nil, nil
	}

	// check if we can compile a non-glob filter
	wildcards := len(separators) != 0
	for _, filter := range filters {
		if strings.ContainsAny(filter, "*?[") {
			wildcards = true
			break
		}
	}

	switch {
	case !wildcards && len(filters) == 1:
		return &filterSingle{s: filters[0]}, nil
	case !wildcards && len(filters) != 1:
		return newFilterNoGlob(filters), nil
	case wildcards && len(filters) == 1:
		return glob.Compile(filters[0], separators...)
	default:
		return newFilterGlobMultiple(filters, separators...)
	}
}

func MustCompile(filters []string, separators ...rune) Filter {
	f, err := Compile(filters, separators...)
	if err != nil {
		panic(err)
	}
	return f
}

type IncludeExcludeFilter struct {
	include        Filter
	exclude        Filter
	includeDefault bool
	excludeDefault bool
}

func NewIncludeExcludeFilter(include, exclude []string) (Filter, error) {
	return NewIncludeExcludeFilterDefaults(include, exclude, true, false)
}

func NewIncludeExcludeFilterDefaults(include, exclude []string, includeDefault, excludeDefault bool) (Filter, error) {
	in, err := Compile(include)
	if err != nil {
		return nil, err
	}

	ex, err := Compile(exclude)
	if err != nil {
		return nil, err
	}

	return &IncludeExcludeFilter{in, ex, includeDefault, excludeDefault}, nil
}

func (f *IncludeExcludeFilter) Match(s string) bool {
	if f.include != nil {
		if !f.include.Match(s) {
			return false
		}
	} else if !f.includeDefault {
		return false
	}

	if f.exclude != nil {
		if f.exclude.Match(s) {
			return false
		}
	} else if f.excludeDefault {
		return false
	}

	return true
}
