package dyn

import (
	"fmt"
	"slices"
)

// Pattern represents a matcher for paths in a [Value] configuration tree.
// It is used by [MapByPattern] to apply a function to the values whose paths match the pattern.
// Every [Path] is a valid [Pattern] that matches a single unique path.
// The reverse is not true; not every [Pattern] is a valid [Path], as patterns may contain wildcards.
type Pattern []patternComponent

// A pattern component can visit a [Value] and recursively call into [visit] for matching elements.
// Fixed components can match a single key or index, while wildcards can match any key or index.
type patternComponent interface {
	visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error)
}

// NewPattern returns a new pattern from the given components.
// The individual components may be created with [Key], [Index], or [Any].
func NewPattern(cs ...patternComponent) Pattern {
	return cs
}

// NewPatternFromPath returns a new pattern from the given path.
func NewPatternFromPath(p Path) Pattern {
	cs := make(Pattern, len(p))
	for i, c := range p {
		cs[i] = c
	}
	return cs
}

// Append appends the given components to the pattern.
func (p Pattern) Append(cs ...patternComponent) Pattern {
	out := make(Pattern, len(p)+len(cs))
	copy(out, p)
	copy(out[len(p):], cs)
	return out
}

type anyKeyComponent struct{}

// AnyKey returns a pattern component that matches any key.
func AnyKey() patternComponent {
	return anyKeyComponent{}
}

// This function implements the patternComponent interface.
func (c anyKeyComponent) visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) {
	m, ok := v.AsMap()
	if !ok {
		return InvalidValue, fmt.Errorf("expected a map at %q, found %s", prefix, v.Kind())
	}

	m = m.Clone()
	for _, pair := range m.Pairs() {
		pk := pair.Key
		pv := pair.Value

		var err error
		nv, err := visit(pv, append(prefix, Key(pk.MustString())), suffix, opts)
		if err != nil {
			// Leave the value intact if the suffix pattern didn't match any value.
			if IsNoSuchKeyError(err) || IsIndexOutOfBoundsError(err) {
				continue
			}
			return InvalidValue, err
		}

		m.Set(pk, nv)
	}

	return NewValue(m, v.Locations()), nil
}

type anyIndexComponent struct{}

// AnyIndex returns a pattern component that matches any index.
func AnyIndex() patternComponent {
	return anyIndexComponent{}
}

// This function implements the patternComponent interface.
func (c anyIndexComponent) visit(v Value, prefix Path, suffix Pattern, opts visitOptions) (Value, error) {
	s, ok := v.AsSequence()
	if !ok {
		return InvalidValue, fmt.Errorf("expected a sequence at %q, found %s", prefix, v.Kind())
	}

	s = slices.Clone(s)
	for i, value := range s {
		var err error
		nv, err := visit(value, append(prefix, Index(i)), suffix, opts)
		if err != nil {
			// Leave the value intact if the suffix pattern didn't match any value.
			if IsNoSuchKeyError(err) || IsIndexOutOfBoundsError(err) {
				continue
			}
			return InvalidValue, err
		}
		s[i] = nv
	}

	return NewValue(s, v.Locations()), nil
}
