package jsonschema

import (
	"strings"

	"github.com/caddyserver/caddy/v2"
)

func generateSchema() error {
	// fetch all caddy modules available in current build
	for _, mod := range caddy.Modules() {
		split := strings.Split(mod, ".")

		parent := "" // top level modules
		name := split[len(split)-1]
		if len(split) >= 2 {
			// submodules
			parent = strings.Join(split[:len(split)-1], ".")
		}

		info, err := caddy.GetModule(mod)
		if err != nil {
			return err
		}

		if moduleMap[parent] == nil {
			moduleMap[parent] = Modules{}
		}

		newInfo := info.New()
		module := Module{
			Name: name,
			Type: newInfo,
			Interface: Interface{
				Name:   name,
				Module: mod,
			},
		}

		moduleMap[parent][name] = module
		flatModuleMap[mod] = module
	}

	// schema generation
	// use separate loop to ensure module list has populated.
	{
		// all module definitions
		definitions := map[string]*Schema{}

		for modName, module := range flatModuleMap {
			module.Interface.populate(module.Type)
			schema := module.Interface.toSchema()
			if doc, ok := flatCaddyDocMap[modName]; ok {
				addDocToSchema(schema, doc.Result.Structure)
			}
			definitions[modName] = schema
		}

		// full config
		configField := Interface{}
		configField.populate(caddy.Config{})
		rootSchema = configField.toSchema()
		rootSchema.Definitions = definitions

		// in case this schema is incomplete, support additional custom items.
		rootSchema.AdditionalItems = true

		// docs
		rootSchema.Title = "Caddy v2 autogenerated JSON schema  \nhttps://github.com/abiosoft/caddy-json-schema"
		rootSchema.Type = "object"
		if rootDocAPIResp.Result.Structure != nil {
			addDocToSchema(rootSchema, rootDocAPIResp.Result.Structure)
		}

	}

	return nil
}

func addDocToSchema(s *Schema, doc *DocStruct) {
	if s == nil || doc == nil {
		return
	}

	desc := func(description, pkg, doc string) string {
		pkg = godocLink(pkg)
		desc := ""
		for _, d := range []string{description, pkg, doc} {
			if d != "" {
				desc += d + "\n"
			}
		}
		return desc
	}
	mdDesc := func(description, pkg, doc string) string {
		pkg = markdownLink("godoc", godocLink(pkg))
		desc := ""
		for _, d := range []string{description, pkg, doc} {
			if strings.TrimSpace(d) != "" {
				desc += d + "  \n"
			}
		}
		return desc
	}
	setDesc := func(s *Schema, d *DocStruct) {
		s.Description = desc(s.description, d.Package, d.Doc)
		s.MarkdownDescription = mdDesc(s.markdownDescription, d.Package, d.Doc)
	}

	// set only if non-empty, parent may have set the doc if empty
	if doc.Doc != "" {
		setDesc(s, doc)
	}

	switch doc.Type {
	case "struct":
		for i, field := range doc.StructFields {
			if _, ok := s.Properties[field.Key]; ok {
				// if field has no doc, use parent doc.
				if field.Value.Doc == "" {
					setDesc(s.Properties[field.Key], field)
				}
				addDocToSchema(s.Properties[field.Key], doc.StructFields[i].Value)
			}
		}
	case "array":
		if s.ArrayItems != nil && doc.Elems != nil {
			if doc.Elems.Doc != "" {
				setDesc(s, doc.Elems) // use items doc for parent
			}
			addDocToSchema(s.ArrayItems, doc.Elems)
		}
	case "map":
		if s.AdditionalProperties != nil && doc.Elems != nil {
			if doc.Elems.Doc != "" {
				setDesc(s, doc.Elems) // use items doc for parent
			}
			addDocToSchema(s.AdditionalProperties, doc.Elems)
		}
	default:
		// everything else has no nesting
		// do nothing/terminate
	}

}
