{{ reserveImport "context"  }}
{{ reserveImport "errors"  }}
{{ reserveImport "fmt"  }}
{{ reserveImport "strings"  }}
{{ reserveImport "sync"  }}

{{ reserveImport "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
}

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

	repsMap := map[string]struct {
		i []int
		r []map[string]interface{}
	}{}

	// We group entities by typename so that we can parallelize their resolution.
	// This is particularly helpful when there are entity groups in multi mode.
	buildRepresentationGroups := func(reps []map[string]interface{}) {
		for i, rep := range reps {
			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
			}

			_r := repsMap[typeName]
			_r.i = append(_r.i, i)
			_r.r = append(_r.r, rep)
			repsMap[typeName] = _r
		}
	}

	isMulti := func(typeName string) bool {
		switch typeName {
		{{- range .Entities -}}
			{{- if .Resolvers -}}
				{{- if .Multi -}}
			case "{{.Def.Name}}":
				return true
				{{ end }}
			{{- end -}}
		{{- end -}}
		default:
			return false
		}
	}

	resolveEntity := func(ctx context.Context, typeName string, rep map[string]interface{}, idx []int, i int) (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 {
			{{ range $_, $entity := .Entities }}
				{{- if and .Resolvers (not .Multi) -}}
				case "{{.Def.Name}}":
					resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, rep)
					if err != nil {
						return fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err)
					}
					switch resolverName {
					{{ range $i, $resolver := .Resolvers }}
					case "{{.ResolverName}}":
						{{- range $j, $keyField := .KeyFields }}
							id{{$j}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
							if err != nil {
								return fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err)
							}
						{{- end}}
						entity, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, {{- range $j, $_ := .KeyFields -}} id{{$j}}, {{end}})
						if err != nil {
							return fmt.Errorf(`resolving Entity "{{$entity.Def.Name}}": %w`, err)
						}
						{{ range $entity.Requires }}
							entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
							if err != nil {
								return err
							}
						{{- end }}
						list[idx[i]] = entity
						return nil
					{{- end }}
					}
				{{ end }}
			{{- end }}
		}
		return fmt.Errorf("%w: %s", ErrUnknownType, typeName)
	}

	resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (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 {
			{{ range $_, $entity := .Entities }}
				{{ if and .Resolvers .Multi -}}
				case "{{.Def.Name}}":
					{{range $i, $_ := .Resolvers -}}
						_reps := make([]*{{.InputType}}, len(reps))

						for i, rep := range reps {
							{{ range $i, $keyField := .KeyFields -}}
								id{{$i}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"])
								if err != nil {
									return errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{.Definition.Name}}"))
								}
							{{end}}

							_reps[i] = &{{.InputType}} {
							{{ range $i, $keyField := .KeyFields -}}
								{{$keyField.Field.ToGo}}: id{{$i}},
							{{end}}
							}
						}

						entities, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, _reps)
						if err != nil {
							return err
						}

						for i, entity := range entities {
							{{- range $entity.Requires }}
									entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, reps[i]["{{.Field.Join `"].(map[string]interface{})["`}}"])
									if err != nil {
										return err
									}
							{{- end}}
							list[idx[i]] = entity
						}
						return nil
					{{ end }}
				{{ end }}
			{{- end }}
		default:
			return errors.New("unknown type: "+typeName)
		}
	}

	resolveEntityGroup := func(typeName string, reps []map[string]interface{}, idx []int) {
		if isMulti(typeName) {
			err := resolveManyEntities(ctx, typeName, reps, idx)
			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 map[string]interface{}) {
					err := resolveEntity(ctx, typeName, rep, idx, i)
					if err != nil {
						ec.Error(ctx, err)
					}
					e.Done()
				}(i, rep)
			}
			e.Wait()
		}
	}
	buildRepresentationGroups(representations)

	switch len(repsMap) {
	case 0:
		return list
	case 1:
		for typeName, reps := range repsMap {
			resolveEntityGroup(typeName, reps.r, reps.i)
		}
		return list
	default:
		var g sync.WaitGroup
		g.Add(len(repsMap))
		for typeName, reps := range repsMap {
			go func(typeName string, reps []map[string]interface{}, idx []int) {
				resolveEntityGroup(typeName, reps, idx)
				g.Done()
			}(typeName, reps.r, reps.i)
		}
		g.Wait()
		return list
	}
}

{{- /* Make sure the required fields are in the given entity representation and return the name of the proper resolver. */ -}}

{{ range $_, $entity := .Entities }}
	{{- if .Resolvers }}

		func entityResolverNameFor{{$entity.Name}}(ctx context.Context, rep map[string]interface{}) (string, error) {
			{{- range .Resolvers }}
				for {
					var (
						m    map[string]interface{}
						val interface{}
						ok bool
					)
					_ = val
					{{- range $_, $keyField := .KeyFields }}
						m = rep
						{{- range $i, $field := .Field }}
							if {{ if (ne $i $keyField.Field.LastIndex ) -}}val{{- else -}}_{{- end -}}, ok = m["{{.}}"]; !ok {
								break
							}
							{{- if (ne $i $keyField.Field.LastIndex ) }}
								if m, ok = val.(map[string]interface{}); !ok {
									break
								}
							{{- end}}
						{{- end}}
					{{- end }}
					return "{{.ResolverName}}", nil
				}
			{{- end }}
			return "", fmt.Errorf("%w for {{$entity.Name}}", ErrTypeNotFound)
		}
	{{- end }}
{{- end }}

{{end}}
