package components

import (
	"strings"

	"github.com/samber/lo"
)

// for making assertions on string values
type Matcher[T any] struct {
	rules []matcherRule[T]

	// this is printed when there's an error so that it's clear what the context of the assertion is
	prefix string
}

type matcherRule[T any] struct {
	// e.g. "contains 'foo'"
	name string
	// returns a bool that says whether the test passed and if it returns false, it
	// also returns a string of the error message
	testFn func(T) (bool, string)
}

func (self *Matcher[T]) name() string {
	if len(self.rules) == 0 {
		return "anything"
	}

	return strings.Join(
		lo.Map(self.rules, func(rule matcherRule[T], _ int) string { return rule.name }),
		", ",
	)
}

func (self *Matcher[T]) test(value T) (bool, string) {
	for _, rule := range self.rules {
		ok, message := rule.testFn(value)
		if ok {
			continue
		}

		if self.prefix != "" {
			return false, self.prefix + " " + message
		}

		return false, message
	}

	return true, ""
}

func (self *Matcher[T]) appendRule(rule matcherRule[T]) *Matcher[T] {
	self.rules = append(self.rules, rule)

	return self
}

// adds context so that if the matcher test(s) fails, we understand what we were trying to test.
// E.g. prefix: "Unexpected content in view 'files'."
func (self *Matcher[T]) context(prefix string) *Matcher[T] {
	self.prefix = prefix

	return self
}