package provider

import (
	"errors"
	"io"
	"strings"

	"github.com/metacubex/mihomo/component/trie"
	C "github.com/metacubex/mihomo/constant"
	P "github.com/metacubex/mihomo/constant/provider"
	"github.com/metacubex/mihomo/log"

	"golang.org/x/exp/slices"
)

type domainStrategy struct {
	count      int
	domainTrie *trie.DomainTrie[struct{}]
	domainSet  *trie.DomainSet
}

func (d *domainStrategy) Behavior() P.RuleBehavior {
	return P.Domain
}

func (d *domainStrategy) Match(metadata *C.Metadata, helper C.RuleMatchHelper) bool {
	return d.domainSet != nil && d.domainSet.Has(metadata.RuleHost())
}

func (d *domainStrategy) Count() int {
	return d.count
}

func (d *domainStrategy) Reset() {
	d.domainTrie = trie.New[struct{}]()
	d.domainSet = nil
	d.count = 0
}

func (d *domainStrategy) Insert(rule string) {
	if strings.ContainsRune(rule, '/') {
		log.Warnln("invalid domain:[%s]", rule)
		return
	}
	err := d.domainTrie.Insert(rule, struct{}{})
	if err != nil {
		log.Warnln("invalid domain:[%s]", rule)
	} else {
		d.count++
	}
}

func (d *domainStrategy) FinishInsert() {
	d.domainSet = d.domainTrie.NewDomainSet()
	d.domainTrie = nil
}

func (d *domainStrategy) FromMrs(r io.Reader, count int) error {
	domainSet, err := trie.ReadDomainSetBin(r)
	if err != nil {
		return err
	}
	d.count = count
	d.domainSet = domainSet
	return nil
}

func (d *domainStrategy) WriteMrs(w io.Writer) error {
	if d.domainSet == nil {
		return errors.New("nil domainSet")
	}
	return d.domainSet.WriteBin(w)
}

func (d *domainStrategy) DumpMrs(f func(key string) bool) {
	if d.domainSet != nil {
		var keys []string
		d.domainSet.Foreach(func(key string) bool {
			keys = append(keys, key)
			return true
		})
		slices.Sort(keys)

		for _, key := range keys {
			if _, ok := slices.BinarySearch(keys, "+."+key); ok {
				continue // ignore the rules added by trie internal processing
			}
			if !f(key) {
				return
			}
		}
	}
}

var _ mrsRuleStrategy = (*domainStrategy)(nil)

func NewDomainStrategy() *domainStrategy {
	return &domainStrategy{}
}
