// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.

package graph

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"sync"

	"github.com/99designs/gqlgen/_examples/federation/reviews/graph/model"
	"github.com/99designs/gqlgen/plugin/federation/fedruntime"
)

var (
	ErrUnknownType  = errors.New("unknown type")
	ErrTypeNotFound = errors.New("type not found")
)

func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) {
	if ec.DisableIntrospection {
		return fedruntime.Service{}, errors.New("federated introspection disabled")
	}

	var sdl []string

	for _, src := range sources {
		if src.BuiltIn {
			continue
		}
		sdl = append(sdl, src.Input)
	}

	return fedruntime.Service{
		SDL: strings.Join(sdl, "\n"),
	}, nil
}

func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]any) []fedruntime.Entity {
	list := make([]fedruntime.Entity, len(representations))

	repsMap := ec.buildRepresentationGroups(ctx, representations)

	switch len(repsMap) {
	case 0:
		return list
	case 1:
		for typeName, reps := range repsMap {
			ec.resolveEntityGroup(ctx, typeName, reps, list)
		}
		return list
	default:
		var g sync.WaitGroup
		g.Add(len(repsMap))
		for typeName, reps := range repsMap {
			go func(typeName string, reps []EntityWithIndex) {
				ec.resolveEntityGroup(ctx, typeName, reps, list)
				g.Done()
			}(typeName, reps)
		}
		g.Wait()
		return list
	}
}

type EntityWithIndex struct {
	// The index in the original representation array
	index  int
	entity EntityRepresentation
}

// EntityRepresentation is the JSON representation of an entity sent by the Router
// used as the inputs for us to resolve.
//
// We make it a map because we know the top level JSON is always an object.
type EntityRepresentation map[string]any

// We group entities by typename so that we can parallelize their resolution.
// This is particularly helpful when there are entity groups in multi mode.
func (ec *executionContext) buildRepresentationGroups(
	ctx context.Context,
	representations []map[string]any,
) map[string][]EntityWithIndex {
	repsMap := make(map[string][]EntityWithIndex)
	for i, rep := range representations {
		typeName, ok := rep["__typename"].(string)
		if !ok {
			// If there is no __typename, we just skip the representation;
			// we just won't be resolving these unknown types.
			ec.Error(ctx, errors.New("__typename must be an existing string"))
			continue
		}

		repsMap[typeName] = append(repsMap[typeName], EntityWithIndex{
			index:  i,
			entity: rep,
		})
	}

	return repsMap
}

func (ec *executionContext) resolveEntityGroup(
	ctx context.Context,
	typeName string,
	reps []EntityWithIndex,
	list []fedruntime.Entity,
) {
	if isMulti(typeName) {
		err := ec.resolveManyEntities(ctx, typeName, reps, list)
		if err != nil {
			ec.Error(ctx, err)
		}
	} else {
		// if there are multiple entities to resolve, parallelize (similar to
		// graphql.FieldSet.Dispatch)
		var e sync.WaitGroup
		e.Add(len(reps))
		for i, rep := range reps {
			i, rep := i, rep
			go func(i int, rep EntityWithIndex) {
				entity, err := ec.resolveEntity(ctx, typeName, rep.entity)
				if err != nil {
					ec.Error(ctx, err)
				} else {
					list[rep.index] = entity
				}
				e.Done()
			}(i, rep)
		}
		e.Wait()
	}
}

func isMulti(typeName string) bool {
	switch typeName {
	case "Product":
		return true
	default:
		return false
	}
}

func (ec *executionContext) resolveEntity(
	ctx context.Context,
	typeName string,
	rep EntityRepresentation,
) (e fedruntime.Entity, err error) {
	// we need to do our own panic handling, because we may be called in a
	// goroutine, where the usual panic handling can't catch us
	defer func() {
		if r := recover(); r != nil {
			err = ec.Recover(ctx, r)
		}
	}()

	switch typeName {
	case "User":
		resolverName, err := entityResolverNameForUser(ctx, rep)
		if err != nil {
			return nil, fmt.Errorf(`finding resolver for Entity "User": %w`, err)
		}
		switch resolverName {

		case "findUserByID":
			id0, err := ec.unmarshalNID2string(ctx, rep["id"])
			if err != nil {
				return nil, fmt.Errorf(`unmarshalling param 0 for findUserByID(): %w`, err)
			}
			entity, err := ec.resolvers.Entity().FindUserByID(ctx, id0)
			if err != nil {
				return nil, fmt.Errorf(`resolving Entity "User": %w`, err)
			}

			return entity, nil
		}

	}
	return nil, fmt.Errorf("%w: %s", ErrUnknownType, typeName)
}

func (ec *executionContext) resolveManyEntities(
	ctx context.Context,
	typeName string,
	reps []EntityWithIndex,
	list []fedruntime.Entity,
) (err error) {
	// we need to do our own panic handling, because we may be called in a
	// goroutine, where the usual panic handling can't catch us
	defer func() {
		if r := recover(); r != nil {
			err = ec.Recover(ctx, r)
		}
	}()

	switch typeName {

	case "Product":
		resolverName, err := entityResolverNameForProduct(ctx, reps[0].entity)
		if err != nil {
			return fmt.Errorf(`finding resolver for Entity "Product": %w`, err)
		}
		switch resolverName {

		case "findManyProductByManufacturerIDAndIDs":
			typedReps := make([]*model.ProductByManufacturerIDAndIDsInput, len(reps))

			for i, rep := range reps {
				id0, err := ec.unmarshalNString2string(ctx, rep.entity["manufacturer"].(map[string]any)["id"])
				if err != nil {
					return errors.New(fmt.Sprintf("Field %s undefined in schema.", "manufacturerID"))
				}
				id1, err := ec.unmarshalNString2string(ctx, rep.entity["id"])
				if err != nil {
					return errors.New(fmt.Sprintf("Field %s undefined in schema.", "id"))
				}

				typedReps[i] = &model.ProductByManufacturerIDAndIDsInput{
					ManufacturerID: id0,
					ID:             id1,
				}
			}

			entities, err := ec.resolvers.Entity().FindManyProductByManufacturerIDAndIDs(ctx, typedReps)
			if err != nil {
				return err
			}

			for i, entity := range entities {

				list[reps[i].index] = entity
			}
			return nil

		default:
			return fmt.Errorf("unknown resolver: %s", resolverName)
		}

	default:
		return errors.New("unknown type: " + typeName)
	}
}

func entityResolverNameForProduct(ctx context.Context, rep EntityRepresentation) (string, error) {
	// we collect errors because a later entity resolver may work fine
	// when an entity has multiple keys
	entityResolverErrs := []error{}
	for {
		var (
			m   EntityRepresentation
			val any
			ok  bool
		)
		_ = val
		// if all of the KeyFields values for this resolver are null,
		// we shouldn't use use it
		allNull := true
		m = rep
		val, ok = m["manufacturer"]
		if !ok {
			entityResolverErrs = append(entityResolverErrs,
				fmt.Errorf("%w due to missing Key Field \"manufacturer\" for Product", ErrTypeNotFound))
			break
		}
		if m, ok = val.(map[string]any); !ok {
			// nested field value is not a map[string]interface so don't use it
			entityResolverErrs = append(entityResolverErrs,
				fmt.Errorf("%w due to nested Key Field \"manufacturer\" value not matching map[string]any for Product", ErrTypeNotFound))
			break
		}
		val, ok = m["id"]
		if !ok {
			entityResolverErrs = append(entityResolverErrs,
				fmt.Errorf("%w due to missing Key Field \"id\" for Product", ErrTypeNotFound))
			break
		}
		if allNull {
			allNull = val == nil
		}
		m = rep
		val, ok = m["id"]
		if !ok {
			entityResolverErrs = append(entityResolverErrs,
				fmt.Errorf("%w due to missing Key Field \"id\" for Product", ErrTypeNotFound))
			break
		}
		if allNull {
			allNull = val == nil
		}
		if allNull {
			entityResolverErrs = append(entityResolverErrs,
				fmt.Errorf("%w due to all null value KeyFields for Product", ErrTypeNotFound))
			break
		}
		return "findManyProductByManufacturerIDAndIDs", nil
	}
	return "", fmt.Errorf("%w for Product due to %v", ErrTypeNotFound,
		errors.Join(entityResolverErrs...).Error())
}

func entityResolverNameForUser(ctx context.Context, rep EntityRepresentation) (string, error) {
	// we collect errors because a later entity resolver may work fine
	// when an entity has multiple keys
	entityResolverErrs := []error{}
	for {
		var (
			m   EntityRepresentation
			val any
			ok  bool
		)
		_ = val
		// if all of the KeyFields values for this resolver are null,
		// we shouldn't use use it
		allNull := true
		m = rep
		val, ok = m["id"]
		if !ok {
			entityResolverErrs = append(entityResolverErrs,
				fmt.Errorf("%w due to missing Key Field \"id\" for User", ErrTypeNotFound))
			break
		}
		if allNull {
			allNull = val == nil
		}
		if allNull {
			entityResolverErrs = append(entityResolverErrs,
				fmt.Errorf("%w due to all null value KeyFields for User", ErrTypeNotFound))
			break
		}
		return "findUserByID", nil
	}
	return "", fmt.Errorf("%w for User due to %v", ErrTypeNotFound,
		errors.Join(entityResolverErrs...).Error())
}
