// 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.

// Package ent is the interface between end-user schemas and entc (ent codegen).
package ent

import (
	"context"

	"entgo.io/ent/schema"
	"entgo.io/ent/schema/edge"
	"entgo.io/ent/schema/field"
	"entgo.io/ent/schema/index"
)

type (
	// The Interface type describes the requirements for an exported type defined in the schema package.
	// It functions as the interface between the user's schema types and codegen loader.
	// Users should use the Schema type for embedding as follows:
	//
	//	type T struct {
	//		ent.Schema
	//	}
	//
	Interface interface {
		// Type is a dummy method, that is used in edge declaration.
		//
		// The Type method should be used as follows:
		//
		//	type S struct { ent.Schema }
		//
		//	type T struct { ent.Schema }
		//
		//	func (T) Edges() []ent.Edge {
		//		return []ent.Edge{
		//			edge.To("S", S.Type),
		//		}
		//	}
		//
		Type()
		// Fields returns the fields of the schema.
		Fields() []Field
		// Edges returns the edges of the schema.
		Edges() []Edge
		// Indexes returns the indexes of the schema.
		Indexes() []Index
		// Config returns an optional config for the schema.
		//
		// Deprecated: the Config method predates the Annotations method, and it
		// is planned be removed in v0.5.0. New code should use Annotations instead.
		//
		//	func (T) Annotations() []schema.Annotation {
		//		return []schema.Annotation{
		//			entsql.Annotation{Table: "Name"},
		//		}
		//	}
		//
		Config() Config
		// Mixin returns an optional list of Mixin to extends
		// the schema.
		Mixin() []Mixin
		// Hooks returns an optional list of Hook to apply on
		// the executed mutations.
		Hooks() []Hook
		// Interceptors returns an optional list of Interceptor
		// to apply on the executed queries.
		Interceptors() []Interceptor
		// Policy returns the privacy policy of the schema.
		Policy() Policy
		// Annotations returns a list of schema annotations to be used by
		// codegen extensions.
		Annotations() []schema.Annotation
	}

	// A Field interface returns a field descriptor for vertex fields/properties.
	// The usage for the interface is as follows:
	//
	//	func (T) Fields() []ent.Field {
	//		return []ent.Field{
	//			field.Int("int"),
	//		}
	//	}
	//
	Field interface {
		Descriptor() *field.Descriptor
	}

	// An Edge interface returns an edge descriptor for vertex edges.
	// The usage for the interface is as follows:
	//
	//	func (T) Edges() []ent.Edge {
	//		return []ent.Edge{
	//			edge.To("S", S.Type),
	//		}
	//	}
	//
	Edge interface {
		Descriptor() *edge.Descriptor
	}

	// An Index interface returns an index descriptor for vertex indexes.
	// The usage for the interface is as follows:
	//
	//	func (T) Indexes() []ent.Index {
	//		return []ent.Index{
	//			index.Fields("f1", "f2").
	//				Unique(),
	//		}
	//	}
	//
	Index interface {
		Descriptor() *index.Descriptor
	}

	// A Config structure is used to configure an entity schema.
	// The usage of this structure is as follows:
	//
	//	func (T) Config() ent.Config {
	//		return ent.Config{
	//			Table: "Name",
	//		}
	//	}
	//
	// Deprecated: the Config object predates the schema.Annotation method and it
	// is planned to be removed in future versions. New code should use Annotations
	// instead.
	//
	//	func (T) Annotations() []schema.Annotation {
	//		return []schema.Annotation{
	//			entsql.Annotation{Table: "Name"},
	//		}
	//	}
	//
	Config struct {
		// A Table is an optional table name defined for the schema.
		Table string
	}

	// The Mixin type describes a set of methods that can extend
	// other methods in the schema without calling them directly.
	//
	//	type TimeMixin struct {}
	//
	//	func (TimeMixin) Fields() []ent.Field {
	//		return []ent.Field{
	//			field.Time("created_at").
	//				Immutable().
	//				Default(time.Now),
	//			field.Time("updated_at").
	//				Default(time.Now).
	//				UpdateDefault(time.Now),
	//		}
	//	}
	//
	//	type T struct {
	//		ent.Schema
	//	}
	//
	// 	func(T) Mixin() []ent.Mixin {
	// 		return []ent.Mixin{
	//			TimeMixin{},
	// 		}
	// 	}
	//
	Mixin interface {
		// Fields returns a slice of fields to add to the schema.
		Fields() []Field
		// Edges returns a slice of edges to add to the schema.
		Edges() []Edge
		// Indexes returns a slice of indexes to add to the schema.
		Indexes() []Index
		// Hooks returns a slice of hooks to add to the schema.
		// Note that mixin hooks are executed before schema hooks.
		Hooks() []Hook
		// Interceptors returns a slice of interceptors to add to the schema.
		// Note that mixin interceptors are executed before schema interceptors.
		Interceptors() []Interceptor
		// Policy returns a privacy policy to add to the schema.
		// Note that mixin policy are executed before schema policy.
		Policy() Policy
		// Annotations returns a list of schema annotations to add
		// to the schema annotations.
		Annotations() []schema.Annotation
	}

	// The Policy type defines the privacy policy of an entity.
	// The usage for the interface is as follows:
	//
	//	type T struct {
	//		ent.Schema
	//	}
	//
	//	func(T) Policy() ent.Policy {
	//		return privacy.AlwaysAllowRule()
	//	}
	//
	Policy interface {
		EvalMutation(context.Context, Mutation) error
		EvalQuery(context.Context, Query) error
	}

	// Schema is the default implementation for the schema Interface.
	// It can be embedded in end-user schemas as follows:
	//
	//	type T struct {
	//		ent.Schema
	//	}
	//
	Schema struct {
		Interface
	}

	// A View only schema describes an entity that all its operations
	// are limited to read-only. For example, a database view.
	//
	// Users that wants to define a view schema should embed the View
	// struct in their schema as follows:
	//
	//	type V struct {
	//		ent.View
	//	}
	//
	View struct {
		Schema
	}
)

// Fields of the schema.
func (Schema) Fields() []Field { return nil }

// Edges of the schema.
func (Schema) Edges() []Edge { return nil }

// Indexes of the schema.
func (Schema) Indexes() []Index { return nil }

// Config of the schema.
func (Schema) Config() Config { return Config{} }

// Mixin of the schema.
func (Schema) Mixin() []Mixin { return nil }

// Hooks of the schema.
func (Schema) Hooks() []Hook { return nil }

// Interceptors of the schema.
func (Schema) Interceptors() []Interceptor { return nil }

// Policy of the schema.
func (Schema) Policy() Policy { return nil }

// Annotations of the schema.
func (Schema) Annotations() []schema.Annotation { return nil }

// Viewer is an interface that wraps the view method.
// Implemented by the View struct.
type Viewer interface{ view() }

// view is a dummy method to distinguish between Schema and View.
func (View) view() {}

type (
	// Value represents a dynamic value returned by mutations or queries.
	Value any

	// Mutation represents an operation that mutate the graph.
	// For example, adding a new node, updating many, or dropping
	// data. The implementation is generated by entc (ent codegen).
	Mutation interface {
		// Op returns the operation name generated by entc.
		Op() Op
		// Type returns the schema type for this mutation.
		Type() string

		// Fields returns all fields that were changed during
		// this mutation. Note that, in order to get all numeric
		// fields that were in/decremented, call AddedFields().
		Fields() []string
		// Field returns the value of a field with the given name.
		// The second boolean value indicates that this field was
		// not set, or was not defined in the schema.
		Field(name string) (Value, bool)
		// SetField sets the value for the given name. It returns an
		// error if the field is not defined in the schema, or if the
		// type mismatch the field type.
		SetField(name string, value Value) error

		// AddedFields returns all numeric fields that were incremented
		// or decremented during this mutation.
		AddedFields() []string
		// AddedField returns the numeric value that was in/decremented
		// from a field with the given name. The second value indicates
		// that this field was not set, or was not define in the schema.
		AddedField(name string) (Value, bool)
		// AddField adds the value for the given name. It returns an
		// error if the field is not defined in the schema, or if the
		// type mismatch the field type.
		AddField(name string, value Value) error

		// ClearedFields returns all nullable fields that were cleared
		// during this mutation.
		ClearedFields() []string
		// FieldCleared returns a bool indicates if this field was
		// cleared in this mutation.
		FieldCleared(name string) bool
		// ClearField clears the value for the given name. It returns an
		// error if the field is not defined in the schema.
		ClearField(name string) error

		// ResetField resets all changes in the mutation regarding the
		// given field name. It returns an error if the field is not
		// defined in the schema.
		ResetField(name string) error

		// AddedEdges returns all edge names that were set/added in this
		// mutation.
		AddedEdges() []string
		// AddedIDs returns all ids (to other nodes) that were added for
		// the given edge name.
		AddedIDs(name string) []Value

		// RemovedEdges returns all edge names that were removed in this
		// mutation.
		RemovedEdges() []string
		// RemovedIDs returns all ids (to other nodes) that were removed for
		// the given edge name.
		RemovedIDs(name string) []Value

		// ClearedEdges returns all edge names that were cleared in this
		// mutation.
		ClearedEdges() []string
		// EdgeCleared returns a bool indicates if this edge was
		// cleared in this mutation.
		EdgeCleared(name string) bool
		// ClearEdge clears the value for the given name. It returns an
		// error if the edge name is not defined in the schema.
		ClearEdge(name string) error

		// ResetEdge resets all changes in the mutation regarding the
		// given edge name. It returns an error if the edge is not
		// defined in the schema.
		ResetEdge(name string) error

		// OldField returns the old value of the field from the database.
		// An error is returned if the mutation operation is not UpdateOne,
		// or the query to the database was failed.
		OldField(ctx context.Context, name string) (Value, error)
	}

	// Mutator is the interface that wraps the Mutate method.
	Mutator interface {
		// Mutate apply the given mutation on the graph. The returned
		// ent.Value is changing according to the mutation operation:
		//
		// OpCreate, the returned value is the created node (T).
		// OpUpdateOne, the returned value is the updated node (T).
		// OpUpdate, the returned value is the amount of updated nodes (int).
		// OpDeleteOne, OpDelete, the returned value is the amount of deleted nodes (int).
		//
		Mutate(context.Context, Mutation) (Value, error)
	}

	// The MutateFunc type is an adapter to allow the use of ordinary
	// function as Mutator. If f is a function with the appropriate signature,
	// MutateFunc(f) is a Mutator that calls f.
	MutateFunc func(context.Context, Mutation) (Value, error)

	// Hook defines the "mutation middleware". A function that gets a Mutator
	// and returns a Mutator. For example:
	//
	//	hook := func(next ent.Mutator) ent.Mutator {
	//		return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
	//			fmt.Printf("Type: %s, Operation: %s, ConcreteType: %T\n", m.Type(), m.Op(), m)
	//			return next.Mutate(ctx, m)
	//		})
	//	}
	//
	Hook func(Mutator) Mutator
)

// Mutate calls f(ctx, m).
func (f MutateFunc) Mutate(ctx context.Context, m Mutation) (Value, error) {
	return f(ctx, m)
}

type (
	// Query represents a query builder of an entity. It is
	// usually one of the following types: <T>Query.
	Query any

	// Querier is the interface that wraps the Query method.
	// Calling Querier.Query(ent.Query) triggers the execution
	// of the query.
	Querier interface {
		// Query runs the given query on the graph and returns its result.
		Query(context.Context, Query) (Value, error)
	}

	// The QuerierFunc type is an adapter to allow the use of ordinary
	// function as Querier. If f is a function with the appropriate signature,
	// QuerierFunc(f) is a Querier that calls f.
	QuerierFunc func(context.Context, Query) (Value, error)

	// Interceptor defines an execution middleware for various types of Ent queries.
	// Contrary to Hooks, Interceptors are implemented as interfaces, allows them to
	// intercept and modify the query at different stages, providing more fine-grained
	// control over its behavior. For example, see the Traverser interface.
	Interceptor interface {
		// Intercept is a function that gets a Querier and returns a Querier. For example:
		//
		//	ent.InterceptFunc(func(next ent.Querier) ent.Querier {
		//		return ent.QuerierFunc(func(ctx context.Context, query ent.Query) (ent.Value, error) {
		//			// Do something before the query execution.
		//			value, err := next.Query(ctx, query)
		//			// Do something after the query execution.
		//			return value, err
		//		})
		//	})
		//
		// Note that unlike Traverse functions, which are called at each traversal stage, Intercept functions
		// are invoked before the query executions. This means that using Traverse functions is a better fit
		// for adding default filters, while using Intercept functions is a better fit for implementing logging
		// or caching.
		//
		//
		//	client.User.Query().
		//		QueryGroups().	// User traverse functions applied.
		//		QueryPosts().	// Group traverse functions applied.
		//		All(ctx)	// Post traverse and intercept functions applied.
		//
		Intercept(Querier) Querier
	}

	// The InterceptFunc type is an adapter to allow the use of ordinary function as Interceptor.
	// If f is a function with the appropriate signature, InterceptFunc(f) is an Interceptor that calls f.
	InterceptFunc func(Querier) Querier

	// Traverser defines a graph-traversal middleware for various types of Ent queries.
	// Contrary to Interceptors, the Traverse are executed on graph traversals before the
	// query is executed. For example:
	//
	//	ent.TraverseFunc(func(ctx context.Context, q ent.Query) error {
	//		// Filter out deleted pets.
	//		if pq, ok := q.(*gen.PetQuery); ok {
	//			pq.Where(pet.DeletedAtIsNil())
	//		}
	//		return nil
	//	})
	//
	//	client.Pet.Query().
	//		QueryOwner().	// Pet traverse functions are applied and filter deleted pets.
	//		All(ctx)	// User traverse and interceptor functions are applied.
	//
	Traverser interface {
		Traverse(context.Context, Query) error
	}

	// The TraverseFunc type is an adapter to allow the use of ordinary function as Traverser.
	// If f is a function with the appropriate signature, TraverseFunc(f) is a Traverser that calls f.
	TraverseFunc func(context.Context, Query) error
)

// Query calls f(ctx, q).
func (f QuerierFunc) Query(ctx context.Context, q Query) (Value, error) {
	return f(ctx, q)
}

// Intercept calls f(ctx, q).
func (f InterceptFunc) Intercept(next Querier) Querier {
	return f(next)
}

// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseFunc) Intercept(next Querier) Querier {
	return next
}

// Traverse calls f(ctx, q).
func (f TraverseFunc) Traverse(ctx context.Context, q Query) error {
	return f(ctx, q)
}

//go:generate go run golang.org/x/tools/cmd/stringer -type Op

// An Op represents a mutation operation.
type Op uint

// Mutation operations.
const (
	OpCreate    Op = 1 << iota // node creation.
	OpUpdate                   // update nodes by predicate (if any).
	OpUpdateOne                // update one node.
	OpDelete                   // delete nodes by predicate (if any).
	OpDeleteOne                // delete one node.
)

// Is reports whether o is match the given operation.
func (i Op) Is(o Op) bool { return i&o != 0 }

// List of query operations used by the codegen.
const (
	OpQueryFirst   = "First"
	OpQueryFirstID = "FirstID"
	OpQueryOnly    = "Only"
	OpQueryOnlyID  = "OnlyID"
	OpQueryAll     = "All"
	OpQueryIDs     = "IDs"
	OpQueryCount   = "Count"
	OpQueryExist   = "Exist"
	OpQueryGroupBy = "GroupBy"
	OpQuerySelect  = "Select"
)

type (
	// QueryContext contains additional information about
	// the context in which the query is executed.
	QueryContext struct {
		// Op defines the operation name. e.g., First, All, Count, etc.
		Op string
		// Type defines the query type as defined in the generated code.
		Type string
		// Unique indicates if the Unique modifier was set on the query and
		// its value. Calling Unique(false) sets the value of Unique to false.
		Unique *bool
		// Limit indicates if the Limit modifier was set on the query and
		// its value. Calling Limit(10) sets the value of Limit to 10.
		Limit *int
		// Offset indicates if the Offset modifier was set on the query and
		// its value. Calling Offset(10) sets the value of Offset to 10.
		Offset *int
		// Fields specifies the fields that were selected in the query.
		Fields []string
	}
	queryCtxKey struct{}
)

// NewQueryContext returns a new context with the given QueryContext attached.
func NewQueryContext(parent context.Context, c *QueryContext) context.Context {
	return context.WithValue(parent, queryCtxKey{}, c)
}

// QueryFromContext returns the QueryContext value stored in ctx, if any.
func QueryFromContext(ctx context.Context) *QueryContext {
	c, _ := ctx.Value(queryCtxKey{}).(*QueryContext)
	return c
}

// Clone returns a deep copy of the query context.
func (q *QueryContext) Clone() *QueryContext {
	c := &QueryContext{
		Op:     q.Op,
		Type:   q.Type,
		Fields: append([]string(nil), q.Fields...),
	}
	if q.Unique != nil {
		v := *q.Unique
		c.Unique = &v
	}
	if q.Limit != nil {
		v := *q.Limit
		c.Limit = &v
	}
	if q.Offset != nil {
		v := *q.Offset
		c.Offset = &v
	}
	return c
}

// AppendFieldOnce adds the given field to the spec if it is not already present.
func (q *QueryContext) AppendFieldOnce(f string) *QueryContext {
	for _, f1 := range q.Fields {
		if f == f1 {
			return q
		}
	}
	q.Fields = append(q.Fields, f)
	return q
}
