// Recipe "vault-structure": object types, the classes of documents with their properties, and
// IDs resolved by alias.
//
// Usage: go run ./recipes/vault_structure [class alias, default "MF.CL.Document"]
// [property alias, default "MF.PD.Keywords"]
package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/M-Files/mfiles-grpc-api-samples/recipes/internal/recipe"
	"github.com/M-Files/mfiles-grpc-api/go/mfiles"
)

func main() {
	recipe.Main(run)
}

func run(ctx context.Context) error {
	classAlias := recipe.StringArg(0, "MF.CL.Document")
	propertyAlias := recipe.StringArg(1, "MF.PD.Keywords")
	client, err := recipe.Connect(ctx)
	if err != nil {
		return err
	}
	defer client.Close()

	// [START vault-structure]
	// Object types. GetObjectTypes returns the real object types, not the value lists.
	types, err := mfiles.NewIRPCObjectTypesClient(client.Conn).GetObjectTypes(ctx,
		&mfiles.GetObjectTypesRequest{})
	if err != nil {
		return err
	}
	fmt.Printf("Object types: %d\n", len(types.GetObjectTypes()))
	for _, objectType := range types.GetObjectTypes() {
		fmt.Printf("  %d %s\n", objectType.GetId(), objectType.GetNamePlural())
	}

	// Property definitions and classes.
	propertyDefs := mfiles.NewIRPCPropertyDefsClient(client.Conn)
	defs, err := propertyDefs.GetPropertyDefs(ctx, &mfiles.GetPropertyDefsRequest{})
	if err != nil {
		return err
	}
	properties := map[int32]*mfiles.PropertyDef{}
	for _, def := range defs.GetPropertyDefs() {
		properties[def.GetId()] = def
	}
	fmt.Printf("Property definitions: %d\n", len(properties))

	classes, err := propertyDefs.GetObjectClassesAndGroups(ctx,
		&mfiles.GetObjectClassesAndGroupsRequest{})
	if err != nil {
		return err
	}
	var documentClasses []*mfiles.ObjectClass
	for _, class := range classes.GetClasses() {
		if class.GetObjectType() == 0 {
			documentClasses = append(documentClasses, class)
		}
	}
	fmt.Printf("Document classes: %d\n", len(documentClasses))
	for _, class := range documentClasses {
		info := class.GetBaseInfo().GetItemInfo()
		// The properties a new object of the class must have, other than built-in ones.
		var required []string
		for _, associated := range class.GetAssociatedPropertyDefs() {
			def, ok := properties[associated.GetPropertyDef()]
			if associated.GetIsRequired() && ok && !def.GetIsPredefined() {
				required = append(required, def.GetName())
			}
		}
		if len(required) == 0 {
			required = []string{"-"}
		}
		fmt.Printf("  %d %s; required: %s\n", info.GetObjId().GetItemId().GetInternalId(),
			info.GetName(), strings.Join(required, ", "))
	}
	// [END vault-structure]

	// [START resolve-alias]
	// Code that must work in many vaults refers to classes and properties by alias, not ID.
	// The answer is -1 when no item, or more than one, has the alias.
	vault := mfiles.NewIRPCVaultClient(client.Conn)
	class, err := vault.GetMetadataStructureItemIdByAlias(ctx,
		&mfiles.GetMetadataStructureItemIdByAliasRequest{
			ItemType: mfiles.MetadataStructureItem_METADATA_STRUCTURE_ITEM_OBJECT_CLASS,
			Alias:    classAlias,
		})
	if err != nil {
		return err
	}
	property, err := vault.GetMetadataStructureItemIdByAlias(ctx,
		&mfiles.GetMetadataStructureItemIdByAliasRequest{
			ItemType: mfiles.MetadataStructureItem_METADATA_STRUCTURE_ITEM_PROPERTY_DEF,
			Alias:    propertyAlias,
		})
	if err != nil {
		return err
	}
	fmt.Printf("Class alias %s: ID %d\n", classAlias, class.GetId())
	fmt.Printf("Property alias %s: ID %d\n", propertyAlias, property.GetId())
	// [END resolve-alias]
	return nil
}
