package trie

import (
	"maps"
	"slices"
)

type YieldFunc = func(part string, value any) bool
type YieldKeyFunc = func(key string) bool
type Iterator = func(YieldFunc)
type KeyIterator = func(YieldKeyFunc)

// WalkAll walks all nodes in the trie, yields full key and series
func (node *Node) Walk(yield YieldFunc) {
	node.walkAll(yield)
}

func (node *Node) walkAll(yield YieldFunc) bool {
	if !node.value.IsNil() {
		if !yield(node.key, node.value.Load()) {
			return false
		}
		return true
	}
	for _, v := range node.children.Range {
		if !v.walkAll(yield) {
			return false
		}
	}
	return true
}

func (node *Node) WalkKeys(yield YieldKeyFunc) {
	node.walkKeys(yield)
}

func (node *Node) walkKeys(yield YieldKeyFunc) bool {
	if !node.value.IsNil() {
		return !yield(node.key)
	}
	for _, v := range node.children.Range {
		if !v.walkKeys(yield) {
			return false
		}
	}
	return true
}

func (node *Node) Keys() []string {
	return slices.Collect(node.WalkKeys)
}

func (node *Node) Map() map[string]any {
	return maps.Collect(node.Walk)
}

func (tree Root) Query(key *Key) Iterator {
	if !key.hasWildcard {
		return func(yield YieldFunc) {
			if v, ok := tree.Node.Get(key); ok {
				yield(key.full, v)
			}
			return
		}
	}
	return func(yield YieldFunc) {
		tree.walkQuery(key.segments, tree.Node, yield, false)
	}
}

func (tree Root) walkQuery(patternParts []string, node *Node, yield YieldFunc, recursive bool) bool {
	if len(patternParts) == 0 {
		if !node.value.IsNil() { // end
			if !yield(node.key, node.value.Load()) {
				return true
			}
		} else if recursive {
			return tree.walkAll(yield)
		}
		return true
	}
	pat := patternParts[0]

	switch pat {
	case "**":
		// ** matches zero or more segments
		// Option 1: ** matches zero segment, move to next pattern part
		if !tree.walkQuery(patternParts[1:], node, yield, false) {
			return false
		}
		// Option 2: ** matches one or more segments
		for _, child := range node.children.Range {
			if !tree.walkQuery(patternParts, child, yield, true) {
				return false
			}
		}
	case "*":
		// * matches any single segment
		for _, child := range node.children.Range {
			if !tree.walkQuery(patternParts[1:], child, yield, false) {
				return false
			}
		}
	default:
		// Exact match
		if child, ok := node.children.Load(pat); ok {
			return tree.walkQuery(patternParts[1:], child, yield, false)
		}
	}
	return true
}
