package introspection

import (
	_ "embed"
)

// Query is the query generated by graphiql to determine type information
//
//go:embed introspection.graphql
var Query string

// Response is the introspection query response
type Response struct {
	Schema *Schema `json:"__schema"`
}

type Schema struct {
	QueryType struct {
		Name string `json:"name"`
	} `json:"queryType"`
	MutationType struct {
		Name string `json:"name"`
	} `json:"mutationType"`
	SubscriptionType struct {
		Name string `json:"name"`
	} `json:"subscriptionType"`

	Types Types `json:"types"`
}

func (s *Schema) Query() *Type {
	return s.Types.Get(s.QueryType.Name)
}

func (s *Schema) Mutation() *Type {
	return s.Types.Get(s.MutationType.Name)
}

func (s *Schema) Subscription() *Type {
	return s.Types.Get(s.SubscriptionType.Name)
}

func (s *Schema) Visit(handlers VisitHandlers) error {
	v := Visitor{
		schema:   s,
		handlers: handlers,
	}
	return v.Run()
}

type TypeKind string

const (
	TypeKindScalar      = TypeKind("SCALAR")
	TypeKindObject      = TypeKind("OBJECT")
	TypeKindInterface   = TypeKind("INTERFACE")
	TypeKindUnion       = TypeKind("UNION")
	TypeKindEnum        = TypeKind("ENUM")
	TypeKindInputObject = TypeKind("INPUT_OBJECT")
	TypeKindList        = TypeKind("LIST")
	TypeKindNonNull     = TypeKind("NON_NULL")
)

type Scalar string

const (
	ScalarInt     = Scalar("Int")
	ScalarFloat   = Scalar("Float")
	ScalarString  = Scalar("String")
	ScalarBoolean = Scalar("Boolean")
)

type Type struct {
	Kind        TypeKind     `json:"kind"`
	Name        string       `json:"name"`
	Description string       `json:"description,omitempty"`
	Fields      []*Field     `json:"fields,omitempty"`
	InputFields []InputValue `json:"inputFields,omitempty"`
}

type Types []*Type

func (t Types) Get(name string) *Type {
	for _, i := range t {
		if i.Name == name {
			return i
		}
	}
	return nil
}

type Field struct {
	Name        string      `json:"name"`
	Description string      `json:"description"`
	TypeRef     *TypeRef    `json:"type"`
	Args        InputValues `json:"args"`

	ParentObject *Type `json:"-"`
}

type TypeRef struct {
	Kind   TypeKind `json:"kind"`
	Name   string   `json:"name,omitempty"`
	OfType *TypeRef `json:"ofType,omitempty"`
}

func (r TypeRef) IsOptional() bool {
	return r.Kind != TypeKindNonNull
}

func (r TypeRef) IsScalar() bool {
	ref := r
	if r.Kind == TypeKindNonNull {
		ref = *ref.OfType
	}
	if ref.Kind == TypeKindScalar {
		return true
	}
	return false
}

func (r TypeRef) IsObject() bool {
	ref := r
	if r.Kind == TypeKindNonNull {
		ref = *ref.OfType
	}
	if ref.Kind == TypeKindObject {
		return true
	}
	return false
}

func (r TypeRef) IsList() bool {
	ref := r
	if r.Kind == TypeKindNonNull {
		ref = *ref.OfType
	}
	if ref.Kind == TypeKindList {
		return true
	}
	return false
}

type InputValues []InputValue

func (i InputValues) HasOptionals() bool {
	for _, v := range i {
		if v.TypeRef.IsOptional() {
			return true
		}
	}
	return false
}

type InputValue struct {
	Name         string   `json:"name"`
	Description  string   `json:"description"`
	DefaultValue *string  `json:"defaultValue"`
	TypeRef      *TypeRef `json:"type"`
}
