package got

import (
	"fmt"
	. "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common" // . "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/gen"
	G "github.com/dave/jennifer/jen"
	"regexp"
	"strings"
	"time" // "github.com/davecgh/go-spew/spew"
)

var (
	filled      = map[string]*Entity{}
	numberRegex = regexp.MustCompile("(int|float).*")

	// bool
	// string
	// int  int8  int16  int32  int64
	// uint uint8 uint16 uint32 uint64 uintptr
	// byte // alias for uint8
	// rune // alias for int32
	//      // represents a Unicode code point
	// float32 float64
	// complex64 complex128

	primitiveRegex = regexp.MustCompile(`^(bool|string|u?int\d{0,2}|byte|rune|float\d{0,2}|interface|complex\d{2,3})$`)
)

func hasReplaceMethod(typ string) bool{ 
	typ = strings.Replace(typ, "*","", 1)
	return !primitiveRegex.MatchString(typ)
}

func replaceAccessType(typ string) (accessType string) {
	typ = strings.Replace(typ, "*","", 1)
	if strings.Contains(typ, "map"){
		accessType = "map"
	} else {
		accessType = "id"
	}
	return
}

func GenSchemas(p *Project) error {
	var (
		// requiredOnCreate bool
		hasAddOrRemove bool
		filename       string
		inputName      string
		typ            string
		extName        string
		f              *G.File
		propertie      *G.Statement
		extend         *G.Statement
		base           = "models/"
		posfix         = ""
		properties     G.Statement
		// values                  G.Dict
		cproperties         map[string]*G.Statement
		addUpdate           map[string]string
		replaceUpdate       map[string]map[string]string
		removeUpdate        map[string]map[string]string
		entityName          string
		entityPatchName     string
		propName            string
		entityInfo          *EntityInfo
		inputProperties     G.Statement
		patchProperties     G.Statement
		referenceProperties G.Statement
		entities, err       = Schemas(p)
		// entities           = p.Schemas
		// err error
	)

	if err != nil {
		return err
	}

	go GenModelIndex(p)

	if err = GenFilterEntityDocument(p); err != nil {
		return err
	}

	if err = GenEntityHelpers(p); err != nil {
		return err
	}

	for _, entity := range entities {

		entityInfo = p.ResponseEntity(entity.ID)

		if entityInfo.IsGeneric {
			continue
		}

		inputName = entity.ID
		entityPatchName = ""
		hasAddOrRemove = false
		// requiredOnCreate = false
		entityName = GenericPart.ReplaceAllString(entity.ID, "")
		filename = base + strings.ToLower(entityName)
		// f = G.NewFile(p.Package)
		f = G.NewFile("models")

		generateIndexsEntity(f, entity)
		// fmt.Println("Gerando entity: ", entityName)

		cproperties = map[string]*G.Statement{}
		// f.Comment(entity.Description)
		properties = G.Statement{}
		patchProperties = G.Statement{
			G.Qual(API_URL, "PatchHistoryRegister").Tag(map[string]string{
				"json": "-",
				"bson": ",inline",
			}),
		}

		entityModel := G.Qual(API_URL, "EntityModel").Tag(map[string]string{
			"json": "-",
			"bson": "-",
		})

		patchProperties = append(patchProperties, entityModel)
		// values = G.Dict{}

		// entityInfo = p.IsGenericEntity(entity.ID)

		inputProperties = G.Statement{
			// G.Qual(API_URL, "DotNotation").Tag(map[string]string{
			// 	"json": "-",
			// 	"bson": ",inline",
			// }),
			G.Id("DotNotation map[string]interface{}").Tag(map[string]string{
				"json": "-",
				"bson": ",inline",
			}),
		}

		// inputProperties = append(inputProperties, entityModel)
		referenceProperties = G.Statement{}

		addUpdate = map[string]string{}
		replaceUpdate = map[string]map[string]string{}
		removeUpdate = map[string]map[string]string{}

		for _, meta := range entity.Properties {

			// fmt.Println("Gerando ---------- ", entityName, "---------", propName, "----", meta.Array)

			if meta.Targets != "" && !strings.Contains(meta.Targets, "go") {
				continue
			}

			propName = strings.Title(meta.ID)
			propertie = G.Id(propName)
			meta.FillTags(p, propName)
			posfix = ""
			// Registra a relaao entre as entidades
			if meta.Relation {
				posfix = "Reference"
				SR.Add(&Relation{
					Source:     meta.GetType(),
					Target:     entity.ID,
					Attr:       strings.Replace(meta.Tags["bson"], ",omitempty", "", 1),
					DB:         entity.DB,
					Collection: entity.Collection,
					IsArray:    meta.Array,
				})
			}
			typ = meta.Type + posfix

			if typ == "any" {
				typ = "interface{}"
			}

			if meta.Array {
				hasAddOrRemove = true
				propertie.Op("*")
				propertie.Index()
				if !meta.Readonly {

					extName = "Add" + propName
					addUpdate[extName] = meta.ID

					extend = G.Id(extName).Index().Id(ConvType(typ)).Tag(map[string]string{
						"json": extName + ",omitempty",
						"bson": extName + ",omitempty",
					})

					patchProperties = append(patchProperties, extend)

					if hasReplaceMethod(typ) {
						extName = "Replace" + propName

						replaceUpdate[extName] = map[string]string{
							"name": meta.ID,
							"type": replaceAccessType(typ),
						}

						extend = G.Id(extName).Index().Id(ConvType(typ)).Tag(map[string]string{
							"json": extName + ",omitempty",
							"bson": extName + ",omitempty",
						})
						patchProperties = append(patchProperties, extend)
					}


					extName = "Remove" + propName
					removeUpdate[extName] = map[string]string{
						"type":   ConvType(typ),
						"propId": meta.ID,
					}
					extend = G.Id(extName).Index().Interface().Tag(map[string]string{
						"json": extName + ",omitempty",
						"bson": extName + ",omitempty",
					})

					patchProperties = append(patchProperties, extend)
				}
			}

			// propertie.Id(entityInfo.TranslateType(meta.Type) + posfix)
			if strings.Contains(typ, ".") {
				typt := strings.Split(typ, ".")
				propertie.Qual(ImportMap(typt[0]), ConvType(typt[1]))
			} else {
				propertie.Id(ConvType(typ))
			}

			entity.HasMode = true
			// Adiciona as tags caso sejam definidas
			if meta.Tags != nil {
				// if name, ok := meta.Tags["valid"]; ok && strings.Contains(name, "requiredOnCreate") {
				// 	entity.HasMode = true
				// }

				propertie.Tag(meta.Tags)
				// if name, ok := meta.Tags["json"]; ok {
				// 	// tsPropertieName = strings.Split(name, ",")[0]
				// }
			} else {
				// tsPropertieName = meta.ID
			}
			// Adiciona a crescricao como comentario

			// tsPropertie = &ts.Group{}

			if meta.Description != "" {
				propertie.Comment(meta.Description)
				// tsPropertie.Comment(meta.Description)
			}

			// if tsPropertieName != "" {

			// 	tsPropertie.Add(ts.Public().Id(tsPropertieName).Op(":").Id(ts.ConvType(meta.Type)))
			// 	if meta.Array {
			// 		tsPropertie.Add(ts.Index())
			// 	}
			// 	tsPropertie.Endl()
			// 	tsProperties = append(tsProperties, tsPropertie)
			// }

			cproperties[meta.ID] = propertie

			// Adiciona o valor padrao de inicializacao
			// spew.Dump(meta)
			// if tvalue, ok := meta.Autogenerate["create"]; ok {
			// 	switch tvalue {
			// 	case "objectId":
			// 		values[G.Id(propName)] = G.Qual(BSON_PRIMITIVE, "NewObjectID").Call()
			// 	case "now":
			// 		values[G.Id(propName)] = G.Qual("time", "Now").Call().Id(".Unix()")
			// 	case "time:now":
			// 	case "user:current":
			// 	default:
			// 		// Verifica se possui valor padrão
			// 		if meta.Default != nil {
			// 			values[G.Id(propName)] = G.Lit(meta.Default)
			// 		}
			// 	}
			// }

			// Adiciona as propriedades readonly na entidade principal
			// e as que tem permissao de escrita na entidade input

			if !meta.Readonly {
				inputProperties = append(inputProperties, propertie)
			} else {
				properties = append(properties, propertie)
			}
			// Adiciona a propriedade que é referencia a lista de propriedades
			// da entidade reference
			if meta.Reference {
				if propName == "Id" {
					propertie = G.Id("Id").Interface().Tag(map[string]string{
						"json": "_id",
						"bson": "_id",
					})
				}
				referenceProperties = append(referenceProperties, propertie)
			}
		}

		extend = G.Qual(API_URL, "EntityModel").Tag(map[string]string{
			"json": "-",
			"bson": ",inline",
		})

		inputProperties = append(G.Statement{extend}, inputProperties...)

		if len(inputProperties) > 0 {
			// if entity.HasMode {
			// values[G.Id("Mode")] = G.Qual(API_URL, "Mode").Values(G.Dict{
			// 	G.Id("M"): G.Lit("create"),
			// })
			// }

			if len(properties) == 0 {
				properties = inputProperties

				patchProperties = append(patchProperties, G.Id("Set").Op("*").Id(entityName).Tag(map[string]string{
					"json": "$set,omitempty",
					"bson": "$set,omitempty",
				}))

			} else {

				inputName = entity.ID + "Input"

				extend = G.Id(inputName).Tag(map[string]string{
					"json": ",inline",
					"bson": ",inline",
				})

				properties = append(G.Statement{extend}, properties...)

				f.Comment("Representação Input aplicada em operações de update.")
				f.Type().Id(inputName).Struct(inputProperties...)

				patchProperties = append(patchProperties, G.Id("Set").Op("*").Id(inputName).Tag(map[string]string{
					"json": "$set,omitempty",
					"bson": "-",
				}))
			}
		}

		if len(referenceProperties) > 0 {
			f.Comment("Representação reference aplicada quando  a entidade é associada a outra.")
			f.Type().Id(entity.ID + "Reference").Struct(referenceProperties...)
		}

		// if entity.Representations != nil {
		// 	for k, rep := range entity.Representations {
		// 		tproperties = G.Statement{}
		// 		for _, attr := range rep {
		// 			tproperties = append(tproperties, cproperties[attr])
		// 		}
		// 		f.Comment("Representação " + k)
		// 		f.Type().Id(entity.ID + k).Struct(tproperties...)
		// 	}
		// }

		if len(patchProperties) > 1 {
			entityPatchName = fmt.Sprintf("%sPatchs", entityName)
			f.Comment(entity.Description).Line().Comment("Representação de atualização")

			// patchProperties = append(G.Statement{*entityModel}, patchProperties)

			f.Type().Id(entityPatchName).Struct(patchProperties...)
		}
		// Cria a entidade normal
		f.Comment(entity.Description).Line().Comment("Representação Completa")
		f.Type().Id(entityName).Struct(properties...)

		// Cria a entidade em typescript
		// tsModels.Line().Export().Class().Id(strings.Title(entity.ID)).Block(tsProperties...).Line()

		f.Comment("Cria uma instancia de " + entity.ID + ".")
		// Cria a função de instanciar um novo elemento com os valores padrão determinados
		f.Func().Id("New"+entityName).Params().Op("*").Id(entityName).Block(
			G.Id("entity").Op(":=").Op("&").Id(entityName).Values(),

			G.Do(func(x *G.Statement) {
				if entity.HasMode {
					x.Add(G.Id(`entity.SetMode("create")`))
				}
			}),

			G.Do(func(part *G.Statement) {
				// user := false
				for _, prop := range entity.Properties {

					typ := ConvType(prop.Type)
					isMap := strings.Contains(typ, "map")

					if prop.Relation {
						typ = typ + "Reference"
					}

					if def, ok := prop.Autogenerate["create"]; ok {
						switch def.Type {
						case "objectId":
							part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual(BSON_PRIMITIVE, "NewObjectID()").Line())
						case "now":
							part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual("time", "Now").Call().Id(".Unix()").Line())
						case "default":
							part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Lit(prop.Default).Line())
						}

					} else if prop.Array {
						part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("= &").Index().Id(typ).Values().Line())
					} else if isMap {
						part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Id(typ).Values().Line())
					}
				}
			}).Line(),

			G.Return(G.Id("entity")).Line(),
		)

		// update = bson.M{"$set": f.Entity}

		// if data, ok = f.Entity.Push(); ok {
		// 	update["$push"] = data
		// }

		// if data, ok = f.Entity.Pull(); ok {
		// 	update["$pull"] = data
		// }
		if entityPatchName != "" {

			f.Func().Params(
				G.Id("t").Op("*").Id(entityPatchName),
			).Id("Patch").Params().Params(
				G.Op("*").Qual(BSON, "A"),
				G.Op("*").Qual("go.mongodb.org/mongo-driver/mongo/options", "UpdateOptions"),
			).Block(
				
				G.Id("updt").Op(":=").Qual(BSON, "A").Values(),
				G.Id("patchOptions := ").Qual("go.mongodb.org/mongo-driver/mongo/options","Update").Call(),

				G.Do(func(y *G.Statement) {
					if !hasAddOrRemove {
						return
					}
					stmts := G.Statement{
						G.Id("hasAdd").Op(":=").Lit(0).Line(),
						G.Id("hasReplace").Op(":=").Lit(0).Line(),
						G.Id("hasRemove").Op(":=").Lit(0).Line(),
						G.Id("pull").Op(":=").Qual(BSON, "M").Values().Line(),
						G.Id("push").Op(":=").Qual(BSON, "M").Values().Line(),
						G.Id("dotNotation := map[string]interface{}{}").Line(),
						G.Id("arrayFilters := []interface{}{}").Line(),
					}
					// add entitys
					// spew.Dump(addUpdate)
					for prop, value := range addUpdate {
						stmts = append(stmts, G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block(
							G.Id("hasAdd").Op("++"),
							G.Id("push").Index(G.Lit(value)).Op("=").Qual(BSON, "M").Values(G.Dict{
								G.Lit("$each"): G.Id("t").Dot(prop),
							}),
						).Line())
					}
					// replace entitys
					// spew.Dump(addUpdate)
					for prop, value := range replaceUpdate {
						prop = fmt.Sprintf("t.%s", prop)

						stmts = append(stmts, G.If(G.Len(G.Id(prop)).Op(">").Lit(0)).Block(
							G.Id("hasReplace").Op("++"),
							G.Do(func(replace *G.Statement) {
								name := value["name"]
								typi := value["type"]
								arrayFiltersTemplates := map[string]string{
									"id": `arrayFilters = append(arrayFilters, bson.M{
											api.Format("%s._id", key): value.Id,
										})`,
									"map": `arrayFilters = append(arrayFilters, bson.M{
											api.Format("%s._id", key): value["_id"],
										})`,
								}
								arrayFilters := arrayFiltersTemplates[typi]
								template := fmt.Sprintf(`for idx, value := range %s {
									key := api.Format("%s%%d", idx)
									dotNotation[api.Format("%s.$[%%s]", key)] = value
									%s
								}`,
								prop,
								name,
								name,
								arrayFilters,
								)
								replace.Add(G.Id(template))
							}),
						).Line())
					}
					// remove entitys
					for prop, value := range removeUpdate {
						stmts = append(stmts, G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block(
							G.Id("hasRemove").Op("++"),
							G.Do(func(hasRemove *G.Statement) {
								var assign G.Dict
								// Se o array for de um atributo de tipo primitivo
								if primitiveRegex.MatchString(value["type"]) {
									assign = G.Dict{
										G.Lit("$in"): G.Id("t").Dot(prop),
									}
								} else {
									assign = G.Dict{
										G.Lit("_id"): G.Qual(BSON, "M").Values(G.Dict{
											G.Lit("$in"): G.Id("t").Dot(prop),
										}),
									}
								}
								hasRemove.Id("pull").Index(G.Lit(value["propId"])).Op("=").Qual(BSON, "M").Values(assign)
							}),
						).Line())
					}
					// fmt.Println("Remove:", inputName, prop, value)

					// fmt.Printf("%#v", G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block(
					// 	G.Id("hasRemove").Op("++"),
					// 	G.Id("pull").Index(G.Lit(value)).Op("=").Qual(BSON, "M").Values(G.Dict{
					// 		G.Lit("$in"): G.Id("t").Dot(prop),
					// 	}),
					// ).Line())

					// x.Add(G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block(
					// 	G.Id("hasRemove").Op("++"),
					// 	G.Id("pull").Index(G.Lit(value)).Op("=").Qual(BSON, "M").Values(G.Dict{
					// 		G.Lit("$in"): G.Id("t").Dot(prop),
					// 	}),
					// ).Line())

					stmts = append(stmts, G.If(G.Id("hasAdd").Op(">").Lit(0)).Block(
						// G.Id("updt").Index(G.Lit("$push")).Op("=").Id("push"),
						G.Id("updt").Op("=").Append(
							G.Id("updt"),
							G.Qual(BSON, "M").Values(G.Lit("$push").Id(": push")),
						),

						// .Index(G.Lit("$push")).Op("=").Id("push"),
					).Line())
					
					stmts = append(stmts, G.If(G.Id("hasRemove").Op(">").Lit(0)).Block(
					// G.Id("updt").Index(G.Lit("$pull")).Op("=").Id("pull"),
					G.Id("updt").Op("=").Append(
						G.Id("updt"),
						G.Qual(BSON, "M").Values(G.Lit("$pull").Id(": pull")),
					),
					).Line())
						
					stmts = append(stmts, G.If(G.Id("hasReplace").Op(">").Lit(0)).Block(
						G.Id(fmt.Sprintf(`if t.Set == nil {
								t.Set = &%s{}
							}
							t.Set.DotNotation = dotNotation
							
							patchOptions.SetArrayFilters(options.ArrayFilters{
								Filters: arrayFilters,
							})
							`, 
							inputName,
						)),
					).Line())

					y.Add(stmts...)
				}),

				G.Id(`if t.Set != nil {
					updt = append(updt, bson.M{"$set": t.Set})
				}`).Line(),

				// []interface{}{bson.D{
				// 	{"elem._id", 1},
				// }}
				G.Return(
					G.Op("&").Id("updt"),
					G.Id("patchOptions"),
				),
			)

		}
		// Salva o arquivo da entidade

		// if err := f.Save(fmt.Sprintf("%s/%s/%s_gen.go", p.OutPath, p.Package, filename)); err != nil {
		if err := Write(fmt.Sprintf("%s/%s/%s_gen.go", p.OutPath, p.Package, filename), f); err != nil {
			return err
		}
	} // Fim do for de schema
	return nil
}

func ConvType(ntype string) string {
	if len(ntype) < 3 {
		fmt.Printf("Invalid type name '%s':\n", ntype)
	}
	if ntype[0:3] == "map" {
		parts := strings.Split(ntype, "|")
		ntype = fmt.Sprintf("map[%s]%s", parts[1], parts[2])
	}
	return ntype
}

func GenModelIndex(p *Project) error {
	var (
		// Index = G.NewFile(p.Package)
		Index = G.NewFile("models")
	)

	Index.Id(`type Entity struct {}`).Line()

	Index.Comment("")

	Index.Var().Defs(
		G.Id("Api").Op("=").Op("&").Qual(API_URL, "Mongo").Values(),
	).Line()

	return Write(fmt.Sprintf("%s/%s/models/index_gen.go", p.OutPath, p.Package), Index)
}

func fill(p *Project, schema *Entity) (*Entity, error) {
	var (
		// found, ok    bool
		found bool
		cls   *Entity
		// newSchema = &(*schema)

		err error
	)
	// Se o esquema ainda não esta completo com as classes extendidas caso possua
	if _, found = filled[schema.ID]; !found {

		// fEntity = schema
		//

		// fmt.Println("Fill ............ ", schema.ID)
		// fmt.Println("Nao estava cheio ", schema.ID)
		for _, entity := range schema.Extends {

			if cls, err = fill(p, p.GetSchema(entity)); err != nil {
				return nil, err
			}

			// fmt.Println("extends ", entity)

			// if cls, ok = filled[entity]; !ok {

			// 	cls = p.GetSchema(entity)

			// 	// fmt.Println("Não tava cheio", entity)

			// 	// fmt.Println("fill:", schema)

			// 	// fmt.Println(cls)

			// 	if err = fill(p, cls); err != nil {
			// 		return err
			// 	}
			// }
			//  else {
			// 	fmt.Println("estava cheio ", entity)

			// fmt.Println("Merge ", schema.ID, cls.ID, ok)

			schema.Properties = append(schema.Properties, cls.Properties...)

		}
		// for _, x := range schema.Properties {
		// fmt.Println("fill----------------", schema.ID, x.ID, len(schema.Properties))
		// }
		filled[schema.ID] = schema
	}

	return filled[schema.ID], nil
}

func Schemas(p *Project) ([]*Entity, error) {

	var (
		err      error
		entities = []*Entity{}
	)

	for _, schema := range p.Schemas {

		if schema, err = fill(p, schema); err != nil {
			return nil, err
		}

		if schema.Type != "abstract" {
			entities = append(entities, schema)
		}

		// for _, x := range schema.Properties {
		// 	fmt.Printf("%s , %s, %p\n", schema.ID, x.ID, x)
		// }

	}

	// for _, s := range entities {
	// 	for _, x := range s.Properties {
	// 		fmt.Printf("final-schemas....%s , %s, %p\n", s.ID, x.ID, x)
	// 	}
	// }

	return entities, nil
}

func GenFilterEntityDocument(p *Project) error {
	var (
		content string
		path    string
		err     error
	)
	for _, s := range p.Schemas {

		if s.Type == "nested.object" {
			continue
		}
		// fmt.Println("gerando filtro ", s.ID)

		filter := &ApiFilter{
			Id:   strings.ToLower(s.ID),
			Date: time.Now().Unix(),
		}

		for _, attr := range s.Properties {
			if attr.Filter == nil {
				continue
			}
			// Atualiza o tipo do filtro na estrutura  do campo
			for _, filterItem := range attr.Filter {
				FilterTypeMap(attr, filterItem)
				// Atualiza o path da query
				if filterItem.Type == "nested" {
					FilterPath(filter, p, attr, attr.ID)
				} else {

					if filterItem.Path == "" {
						filterItem.Path = attr.ID
					}

					if filterItem.UserEnumAsOptions {
						filterItem.Options = []FilterOption{}

						// if len(attr.Values) == 0 {
						// 	attr.Values = make([]string, len(attr.Enum))
						// }
						// 	values = make()
						// }

						for key, value := range attr.Enum {
							filterItem.Options = append(filterItem.Options, FilterOption{
								Value: attr.Values[key],
								Label: value,
							})
						}

					}

					filter.Fields = append(filter.Fields, filterItem)
				}
			}
		}

		if filter.Fields == nil {
			continue
		}

		if content, err = JSONStringfy(filter); err != nil {
			return err
		}

		path = fmt.Sprintf("%s/filters/%s.json", p.OutPath, strings.ToLower(s.ID))
		// fmt.Printf("storing filter '%s'\n",path)
		if err = FilePutContents(path, content, 0777); err != nil {
			return err
		}
	}
	return nil
}

func FilterPath(filter *ApiFilter, p *Project, attr *Propertie, path string) {
	var (
		nfil  Filter
		npath string
		typ   = attr.Type
	)
	if attr.Relation {
		typ += "Reference"
	}

	entity := p.GetSchema(typ)

	for _, x := range entity.Properties {

		if x.Filter == nil {
			continue
		}

		npath = fmt.Sprintf("%s.%s", path, x.ID)

		for _, item := range x.Filter {

			if item.Type != "nested" {
				nfil = *item
				if nfil.Path == "" {
					nfil.Path = npath
				}
				FilterTypeMap(x, item)
				filter.Fields = append(filter.Fields, &nfil)
			} else {
				FilterPath(filter, p, x, npath)
			}
		}
	}
}

func FilterTypeMap(attr *Propertie, f *Filter) {
	typ := f.Type
	if typ != "" {
		return
	}
	switch {
	// Se o tipo for numerico
	case numberRegex.Match([]byte(typ)):
		f.Type = "number"
	default:
		f.Type = attr.Type
	}
}

func generateIndexsEntity(file *G.File, entity *Entity) {
	// keys := G.{}
	// options := G.Dict{
	// 	G.Id("Unique"): G.Lit(true),
	// 	// G.Id("Key"):   G.Lit("unique"),
	// 	// G.Id("Value"): G.Lit(true),
	// }
	var create = false
	indexOptions := G.Dict{
		G.Id("Keys"): G.Qual(BSONX, "Doc").ValuesFunc(func(keys *G.Group) {

			for _, propertie := range entity.Properties {
				if propertie.Unique {
					keys.Add(G.Values(G.Lit(propertie.ID), G.Qual(BSONX, "Int32").Call(G.Lit(1))))
					create = true
					// keys[G.Lit(propertie.ID)] = G.Lit(1)
				}
			}

		}),
		// G.Id("Options"): G.Qual(BSON, "M").Values(options),
		G.Id("Options"): G.Id("opts.SetUnique(true)"),
	}

	if create {
		file.Line().Func().Id("init").Call().Block(
			G.Id("models.Api.Ready().Subscribe").Call(
				G.Func().Call(G.Id("value").Id("...interface{}")).Block(
					G.Id("opts := &").Qual("go.mongodb.org/mongo-driver/mongo/options", "IndexOptions").Values(),
					G.Id(`index := `).Qual("go.mongodb.org/mongo-driver/mongo", "IndexModel").Values(indexOptions),
					// G.Qual("fmt", "Println").Call(G.Lit("create index for "), G.Lit(entity.ID)),
					G.Id(`if err := models.Api.CreateIndex(`).Lit(entity.DB).Id(`, `).Lit(entity.Collection).Id(`, index); err != nil`).
						Block(
							G.Id("panic").Call(
								G.Qual("fmt", "Sprintf").Call(
									// G.Lit(fmt.Sprintf("Index %s creation error %s", entity.ID, err.Error())),
									G.Id(`"Index`).Id(entity.ID).Id(`creation error %s", err.Error()`),
								),
							).Line(),
							// (`).
							// Call().
							// Line().
						),
				),
			),
		)

		// .BlockFunc(func(statement *G.Group) {
		// 	statement.Add(
		// 		,
		// 	)
		// 	statement.Add(G.Line().Id("})"))
		// })

	}

}

func GenEntityHelpers(project  *Project) error {

	file := G.NewFile("models")
	entities, err := Schemas(project)
	if err != nil {
		return err
	}

	file.Add(G.Id(`
	import(
		"reflect"
		"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
		context "github.com/kataras/iris/v12/context"
		"go.mongodb.org/mongo-driver/bson"
		"go.mongodb.org/mongo-driver/bson/primitive"
	)
	`))

	getStmtsTmpl, getStmtsErr := ParseTemplate(`
	func Get{{.entity}}ByID(
		ctx context.Context,
		id interface{},
	) (
	   entity *{{.entity}}, 
	   err *errs.Error,
	) {
		var errd error
		entity = &{{.entity}}{}
		value := reflect.ValueOf(id)

		if value.Kind() == reflect.Ptr  {
			id = value.Elem() 
		}

		if idString, ok := id.(string); ok {
			if id, errd = primitive.ObjectIDFromHex(idString); errd != nil {
				return
			}
		}

		options := Filter{{.entity}}Options(ctx)
		options.Query = &bson.M{"_id": id}
		options.Entity = entity
		
		if _, err = Api.FindOne(options); err != nil {
			return
		}
		return
	}
	`)

	if getStmtsErr != nil {
		return getStmtsErr
	}

	for _, entity := range entities {

		entityInfo := project.ResponseEntity(entity.ID)

		if entityInfo.IsGeneric || entity.Type != "object" {
			continue
		}

		context := map[string]interface{}{
			"entity": strings.Title(entity.ID),
		}
		out, _ := TemplateToString(getStmtsTmpl, context)
		file.Add(G.Id(out).Line())
	}
	
	if err := Write(
		fmt.Sprintf("../project/include/go/models/get_helpers.go"),
		file,
	); err != nil {
		return err
	}

	return nil
}

