package jsonpath

import "context"

type path interface {
	evaluate(c context.Context, parameter interface{}) (interface{}, error)
	visitMatchs(c context.Context, r interface{}, visit pathMatcher)
	withPlainSelector(plainSelector) path
	withAmbiguousSelector(ambiguousSelector) path
}

type plainPath []plainSelector

type ambiguousMatcher func(key, v interface{})

func (p plainPath) evaluate(ctx context.Context, root interface{}) (interface{}, error) {
	return p.evaluatePath(ctx, root, root)
}

func (p plainPath) evaluatePath(ctx context.Context, root, value interface{}) (interface{}, error) {
	var err error
	for _, sel := range p {
		value, err = sel(ctx, root, value)
		if err != nil {
			return nil, err
		}
	}
	return value, nil
}

func (p plainPath) matcher(ctx context.Context, r interface{}, match ambiguousMatcher) ambiguousMatcher {
	if len(p) == 0 {
		return match
	}
	return func(k, v interface{}) {
		res, err := p.evaluatePath(ctx, r, v)
		if err == nil {
			match(k, res)
		}
	}
}

func (p plainPath) visitMatchs(ctx context.Context, r interface{}, visit pathMatcher) {
	res, err := p.evaluatePath(ctx, r, r)
	if err == nil {
		visit(nil, res)
	}
}

func (p plainPath) withPlainSelector(selector plainSelector) path {
	return append(p, selector)
}
func (p plainPath) withAmbiguousSelector(selector ambiguousSelector) path {
	return &ambiguousPath{
		parent: p,
		branch: selector,
	}
}

type ambiguousPath struct {
	parent path
	branch ambiguousSelector
	ending plainPath
}

func (p *ambiguousPath) evaluate(ctx context.Context, parameter interface{}) (interface{}, error) {
	matchs := []interface{}{}
	p.visitMatchs(ctx, parameter, func(keys []interface{}, match interface{}) {
		matchs = append(matchs, match)
	})
	return matchs, nil
}

func (p *ambiguousPath) visitMatchs(ctx context.Context, r interface{}, visit pathMatcher) {
	p.parent.visitMatchs(ctx, r, func(keys []interface{}, v interface{}) {
		p.branch(ctx, r, v, p.ending.matcher(ctx, r, visit.matcher(keys)))
	})
}

func (p *ambiguousPath) branchMatcher(ctx context.Context, r interface{}, m ambiguousMatcher) ambiguousMatcher {
	return func(k, v interface{}) {
		p.branch(ctx, r, v, m)
	}
}

func (p *ambiguousPath) withPlainSelector(selector plainSelector) path {
	p.ending = append(p.ending, selector)
	return p
}
func (p *ambiguousPath) withAmbiguousSelector(selector ambiguousSelector) path {
	return &ambiguousPath{
		parent: p,
		branch: selector,
	}
}

type pathMatcher func(keys []interface{}, match interface{})

func (m pathMatcher) matcher(keys []interface{}) ambiguousMatcher {
	return func(key, match interface{}) {
		m(append(keys, key), match)
	}
}
