/*
/*
 * Copyright (c) 2024 Augur AI, Inc.
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. 
 * If a copy of the MPL was not distributed with this file, you can obtain one at https://mozilla.org/MPL/2.0/.
 *
 
 * Copyright (c) 2024 Augur AI, Inc.
 *
 * This file is licensed under the Augur AI Proprietary License.
 *
 * Attribution:
 * This work is based on code from https://github.com/hofstadter-io/hof, licensed under the Apache License 2.0.
 */

package structural

import (
	"fmt"

	"cuelang.org/go/cue"
	// "cuelang.org/go/cue/errors"
)

// PickValue uses 'pick' to pick a subvalue from 'from'
// by checking if values unify
func PickValue(pick, from cue.Value, opts *Options) (cue.Value, error) {
	if opts == nil {
		opts = &Options{}
	}
	p, _ := pickValue(pick, from, opts)
	return p, nil
}

// this is the recursive version that also returns
// whether the value was picked
func pickValue(pick, from cue.Value, opts *Options) (cue.Value, bool) {
	switch pick.IncompleteKind() {
	// pick everything
	case cue.TopKind:
		return from, true

	// recurse on matching labels
	case cue.StructKind:
		return pickStruct(pick, from, opts)

	case cue.ListKind:
		return pickList(pick, from, opts)

	default:
		return pickLeaf(pick, from, opts)
	}
}

func pickStruct(pick, from cue.Value, opts *Options) (cue.Value, bool) {
	ctx := pick.Context()

	//if k := from.IncompleteKind(); k != cue.StructKind {
	//if opts.NodeTypeErrors {
	//e := errors.Newf(pick.Pos(), "pick type '%v' does not match target value type '%v'", pick.IncompleteKind(), from.IncompleteKind())
	//ev := ctx.MakeError(e)
	//return ev, true
	//}
	//}

	result := newStruct(ctx)
	iter, _ := pick.Fields(defaultWalkOptions...)

	cnt := 0
	for iter.Next() {
		cnt++
		s := iter.Selector()
		// HACK, this works around a bug in CUE
		// p := cue.MakePath(s)
		p := cue.ParsePath(fmt.Sprint(s))
		f := from.LookupPath(p)
		// fmt.Println(cnt, iter.Value(), f, f.Exists())
		// check that field exists in from. Should we be checking f.Err()?
		if f.Exists() {
			r, ok := pickValue(iter.Value(), f, opts)
			// fmt.Println("r:", r, ok, p)
			if ok {
				result = result.FillPath(p, r)
			}
		}
	}

	// need to check for {...}
	// no fields and open
	if cnt == 0 && pick.Allows(cue.AnyString) {
		return from, true
	}

	// fmt.Println("result:", result)

	return result, true

	return pick, false
}

func pickList(pick, from cue.Value, opts *Options) (cue.Value, bool) {
	ctx := pick.Context()

	if k := from.IncompleteKind(); k != cue.ListKind {
		// should this return or just continue? do we need some way to specify?
		// probably prefer to be more strict, so that you know your schemas
		// return errors.Newf(from.Pos(), "expected list, but got %v", k), true
		return newStruct(ctx), false
	}

	//lpt, err := getListProcType(pick)
	//if err != nil {
	//ce := errors.Newf(pick.Pos(), "%v", err)
	//ev := ctx.MakeError(ce)
	//return ev, true
	//}

	//_ = lpt

	// how to consider different list sizes
	// if len(pick) == 1, apply to all elements
	// if len(pick) > 1
	//   attributes? @pick(and,or,pos)
	// maybe we don't care about length if attribute is used?

	pi, _ := pick.List()
	fi, _ := from.List()

	result := []cue.Value{}
	for pi.Next() && fi.Next() {
		p, ok := pickValue(pi.Value(), fi.Value(), opts)
		if ok {
			result = append(result, p)
		}
	}

	return ctx.NewList(result...), true
}

func pickLeaf(pick, from cue.Value, opts *Options) (cue.Value, bool) {
	// if pick is concrete, so must from
	// make sure 1 does not pick int
	// but we do want int to pick any num
	if pick.IsConcrete() {
		if from.IsConcrete() {
			r := pick.Unify(from)
			return from, r.Exists()
		} else {
			return cue.Value{}, false
		}
	} else {
		r := pick.Unify(from)
		if !r.Exists() || r.Err() != nil {
			// inspect opts to see what to return
			return cue.Value{}, false
		}
		return from, true
	}

	return pick, false
}
