{{/*
Copyright 2019-present Facebook Inc. All rights reserved.
This source code is licensed under the Apache 2.0 license found
in the LICENSE file in the root directory of this source tree.
*/}}

{{ define "base" }}

{{ $pkg := base $.Config.Package }}
{{ template "header" $ }}

{{ template "import" $ }}

// Order applies an ordering on either graph traversal or sql selector.
type Order func({{ if $.MultiStorage }}interface{}{{ else }}{{ (index $.Storage 0).Builder }}{{ end }})

{{ if $.MultiStorage }}
// OrderPerDialect construct the "order by" clause for graph traversals based on dialect type.
func OrderPerDialect({{ range $i, $storage := $.Storage }}{{ if $i }},{{ end }}f{{ $i }} func({{ $storage.Builder }}){{ end }}) Order {
	return Order(func (v interface{}) {
		switch v := v.(type) {
		{{- range $i, $storage := $.Storage }}
		case {{ $storage.Builder }}:
			f{{ $i }}(v)
		{{- end }}
		default:
			panic(fmt.Sprintf("unknown type for order: %T", v))
		}
	})
}
{{ end }}

{{ range $f, $order := order }}
	{{ $f = pascal $f }}
	// {{ $f }} applies the given fields in {{ upper $f }} order.
	func {{ $f }}(fields ...string) Order {
		return Order{{ if $.MultiStorage }}PerDialect{{ end }}(
			{{ range $_, $storage := $.Storage -}}
				{{- with extend (index $.Nodes 0) "Func" $f "Order" $order -}}
					{{ $tmpl := printf "dialect/%s/order" $storage }}
					{{- xtemplate $tmpl . }},
				{{ end -}}
			{{ end -}}
		)
	}
{{ end }}

// Aggregate applies an aggregation step on the group-by traversal/selector.
type Aggregate struct {
	{{ range $_, $storage := $.Storage -}}
		{{ $tmpl := printf "dialect/%s/group/signature" $storage }}
		{{- xtemplate $tmpl . }}
	{{ end -}}
}


// As is a pseudo aggregation function for renaming another other functions with custom names. For example:
//
//	GroupBy(field1, field2).
//	Aggregate({{ $pkg }}.As({{ $pkg }}.Sum(field1), "sum_field1"), ({{ $pkg }}.As({{ $pkg }}.Sum(field2), "sum_field2")).
//	Scan(ctx, &v)
//
func As(fn Aggregate, end string) Aggregate {
	return Aggregate{
		{{ range $_, $storage := $.Storage -}}
			{{ $tmpl := printf "dialect/%s/group/as" $storage }}
			{{- $storage.IdentName }}: {{ xtemplate $tmpl . }},
		{{ end -}}
	}
}

{{ range $name, $withField := aggregate }}
	{{ $fn := pascal $name }}
	{{ range $_, $storage := $.Storage -}}
		{{ $tmpl := printf "dialect/%s/group/const" $storage }}
		{{ if hasTemplate $tmpl }}
			{{ with extend (index $.Nodes 0) "Name" $name "Func" $fn -}}
				{{ xtemplate $tmpl . }}
			{{ end }}
		{{ end }}
	{{ end }}

	// {{ $fn }} applies the {{ quote $name }} aggregation function on {{ if $withField }}the given field of {{ end }}each group.
	func {{ $fn }}({{ if $withField }}field string{{ end }}) Aggregate {
		return Aggregate {
			{{ range $_, $storage := $.Storage -}}
				{{- with extend (index $.Nodes 0) "Func" $fn "WithField" $withField -}}
					{{ $tmpl := printf "dialect/%s/group/func" $storage }}
					{{- $storage.IdentName }}: {{ xtemplate $tmpl . }},
				{{ end -}}
			{{ end -}}
		}
	}
{{ end }}

// ErrNotFound returns when trying to fetch a specific entity and it was not found in the database.
type ErrNotFound struct {
	label string
}

// Error implements the error interface.
func (e *ErrNotFound) Error() string {
	return fmt.Sprintf("{{ $pkg }}: %s not found", e.label)
}

// IsNotFound returns a boolean indicating whether the error is a not found error.
func IsNotFound(err error) bool {
	_, ok := err.(*ErrNotFound)
	return ok
}

// MaskNotFound masks nor found error.
func MaskNotFound(err error) error {
	if IsNotFound(err) {
		return nil
	}
	return err
}

// ErrNotSingular returns when trying to fetch a singular entity and more then one was found in the database.
type ErrNotSingular struct {
	label string
}

// Error implements the error interface.
func (e *ErrNotSingular) Error() string {
	return fmt.Sprintf("{{ $pkg }}: %s not singular", e.label)
}

// IsNotSingular returns a boolean indicating whether the error is a not singular error.
func IsNotSingular(err error) bool {
	_, ok := err.(*ErrNotSingular)
	return ok
}


// ErrConstraintFailed returns when trying to create/update one or more entities and
// one or more of their constraints failed. For example, violation of edge or field uniqueness.
type ErrConstraintFailed struct {
	msg string
	wrap error
}

// Error implements the error interface.
func (e ErrConstraintFailed) Error() string {
	return fmt.Sprintf("{{ $pkg }}: unique constraint failed: %s", e.msg)
}

// Unwrap implements the errors.Wrapper interface.
func (e *ErrConstraintFailed) Unwrap() error {
	return e.wrap
}

// IsConstraintFailure returns a boolean indicating whether the error is a constraint failure.
func IsConstraintFailure(err error) bool {
	_, ok := err.(*ErrConstraintFailed)
	return ok
}

{{/* expand error types and global helpers. */}}
{{ range $_, $storage := $.Storage -}}
	{{ $tmpl := printf "dialect/%s/errors" $storage }}
	{{ if hasTemplate $tmpl }}
		{{ xtemplate $tmpl $ }}
	{{ end }}

	{{ $tmpl = printf "dialect/%s/globals" $storage }}
	{{ if hasTemplate $tmpl }}
		{{ xtemplate $tmpl $ }}
	{{ end }}
{{ end -}}

{{ $id := (index $.Nodes 0).ID.Type }}
// keys returns the keys/ids from the edge map.
func keys(m map[{{ $id }}]struct{}) []{{ $id }} {
	s := make([]{{ $id }}, 0, len(m))
	for id := range m {
		s = append(s, id)
	}
	return s
}
{{ end }}
