package main

import "fmt"

// providerCollector owns selector, matches, and seen for provider traversal.
type providerCollector struct {
	selector string
	matches  []match
	seen     map[string]bool
}

// newProviderCollector returns a collector ready to find selector matches.
func newProviderCollector(selector string) *providerCollector {
	return &providerCollector{
		selector: selector,
		seen:     make(map[string]bool),
	}
}

// addEntryMatch records a match when the entry value or key equals the selector.
// isKeyMatch reports whether the map key itself equals the selector.
// item is the entry value, which may be a scalar, map with id/provider, or other.
func (c *providerCollector) addEntryMatch(isKeyMatch bool, item interface{}, entryPath string) {
	if isKeyMatch {
		c.matches = append(c.matches, match{Path: entryPath, Value: item})
		c.seen[entryPath] = true
		return
	}
	switch v := item.(type) {
	case string:
		if v == c.selector && !c.seen[entryPath] {
			c.matches = append(c.matches, match{Path: entryPath, Value: v})
			c.seen[entryPath] = true
		}
	case map[string]interface{}:
		if id, ok := v["id"].(string); ok && id == c.selector {
			if !c.seen[entryPath] {
				c.matches = append(c.matches, match{Path: entryPath, Value: v})
				c.seen[entryPath] = true
			}
		} else if provider, ok := v["provider"].(string); ok && provider == c.selector {
			if !c.seen[entryPath] {
				c.matches = append(c.matches, match{Path: entryPath, Value: v})
				c.seen[entryPath] = true
			}
		}
	}
}

// collect walks val and records provider matches, recursing into nested shapes.
func (c *providerCollector) collect(val interface{}, path string) {
	switch v := val.(type) {
	case map[string]interface{}:
		c.checkProviderField(v, path)
		c.checkProvidersField(v, path)
		c.checkDirectProvidersField(v, path)
		c.recurseNonProviderFields(v, path)

	case []interface{}:
		for i, item := range v {
			c.collect(item, fmt.Sprintf("%s[%d]", path, i))
		}
	}
}

// checkProviderField inspects the singular "provider" key for selector matches.
func (c *providerCollector) checkProviderField(obj map[string]interface{}, basePath string) {
	provVal, ok := obj["provider"]
	if !ok {
		return
	}
	childPath := joinPath(basePath, "provider")
	switch p := provVal.(type) {
	case string:
		if p == c.selector && !c.seen[childPath] {
			c.matches = append(c.matches, match{Path: childPath, Value: p})
			c.seen[childPath] = true
		}
	case map[string]interface{}:
		if id, ok := p["id"].(string); ok && id == c.selector {
			if !c.seen[childPath] {
				c.matches = append(c.matches, match{Path: childPath, Value: p})
				c.seen[childPath] = true
			}
		} else if id, ok := p["provider"].(string); ok && id == c.selector {
			if !c.seen[childPath] {
				c.matches = append(c.matches, match{Path: childPath, Value: p})
				c.seen[childPath] = true
			}
		}
	}
}

// checkProvidersField inspects the "providers" key (map or list) for selector matches.
func (c *providerCollector) checkProvidersField(obj map[string]interface{}, basePath string) {
	provsVal, ok := obj["providers"]
	if !ok {
		return
	}
	childPath := joinPath(basePath, "providers")
	switch p := provsVal.(type) {
	case map[string]interface{}:
		for key, item := range p {
			entryPath := joinPath(childPath, key)
			c.addEntryMatch(key == c.selector, item, entryPath)
			c.collect(item, entryPath)
		}
	case []interface{}:
		for i, item := range p {
			pPath := fmt.Sprintf("%s[%d]", childPath, i)
			if s, ok := item.(string); ok && s == c.selector {
				if !c.seen[pPath] {
					c.matches = append(c.matches, match{Path: pPath, Value: s})
					c.seen[pPath] = true
				}
			} else if m, ok := item.(map[string]interface{}); ok {
				if id, ok := m["id"].(string); ok && id == c.selector {
					if !c.seen[pPath] {
						c.matches = append(c.matches, match{Path: pPath, Value: m})
						c.seen[pPath] = true
					}
				} else if id, ok := m["provider"].(string); ok && id == c.selector {
					if !c.seen[pPath] {
						c.matches = append(c.matches, match{Path: pPath, Value: m})
						c.seen[pPath] = true
					}
				}
			}
			c.collect(item, pPath)
		}
	}
}

// checkDirectProvidersField inspects the "direct_providers" list for selector matches.
func (c *providerCollector) checkDirectProvidersField(obj map[string]interface{}, basePath string) {
	dpsVal, ok := obj["direct_providers"]
	if !ok {
		return
	}
	p, ok := dpsVal.([]interface{})
	if !ok {
		return
	}
	childPath := joinPath(basePath, "direct_providers")
	for i, item := range p {
		pPath := fmt.Sprintf("%s[%d]", childPath, i)
		if s, ok := item.(string); ok && s == c.selector {
			if !c.seen[pPath] {
				c.matches = append(c.matches, match{Path: pPath, Value: s})
				c.seen[pPath] = true
			}
		} else if m, ok := item.(map[string]interface{}); ok {
			if id, ok := m["id"].(string); ok && id == c.selector {
				if !c.seen[pPath] {
					c.matches = append(c.matches, match{Path: pPath, Value: m})
					c.seen[pPath] = true
				}
			}
		}
		c.collect(item, pPath)
	}
}

// recurseNonProviderFields continues traversal into fields that are not provider-shaped.
func (c *providerCollector) recurseNonProviderFields(obj map[string]interface{}, basePath string) {
	for k, valItem := range obj {
		if k == "provider" || k == "providers" || k == "direct_providers" {
			continue
		}
		c.collect(valItem, joinPath(basePath, k))
	}
}
