package docs

import (
	"fmt"
	"reflect"
	"strings"

	"git.sr.ht/~ashkeel/strimertul/docs/interfaces"
)

type DataObject struct {
	Name        string       `json:"name"`
	Description string       `json:"description,omitempty"`
	Kind        Kind         `json:"kind"`
	Keys        []DataObject `json:"keys,omitempty"`
	Key         *DataObject  `json:"key,omitempty"`
	Element     *DataObject  `json:"element,omitempty"`
	EnumValues  []string     `json:"enumValues,omitempty"`
	IsPointer   bool         `json:"isPointer,omitempty"`
}

type KeyObject struct {
	Description string              `json:"description"`
	Schema      DataObject          `json:"schema"`
	Tags        []interfaces.KeyTag `json:"tags,omitempty"`
}

type Kind string

const (
	KindString  Kind = "string"
	KindInt     Kind = "int"
	KindFloat   Kind = "float"
	KindStruct  Kind = "object"
	KindBoolean Kind = "boolean"
	KindEnum    Kind = "enum"
	KindUnknown Kind = "unknown"
	KindArray   Kind = "array"
	KindDict    Kind = "dictionary"
	KindDate    Kind = "datetime"
)

func getKind(typ reflect.Kind) Kind {
	switch typ {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		return KindInt
	case reflect.Float32, reflect.Float64:
		return KindFloat
	case reflect.String:
		return KindString
	case reflect.Bool:
		return KindBoolean
	case reflect.Struct:
		return KindStruct
	case reflect.Map:
		return KindDict
	case reflect.Array, reflect.Slice:
		return KindArray
	}
	return KindUnknown
}

func parseType(typ reflect.Type) (out DataObject) {
	out.Name = typ.Name()

	// Check for common complex types
	switch typ.String() {
	case "time.Time":
		out.Kind = KindDate
		return
	}

	// Check for known enums
	if enum, ok := Enums[out.Name]; ok {
		out.Kind = KindEnum
		for _, it := range enum.Values {
			out.EnumValues = append(out.EnumValues, fmt.Sprint(it))
		}
		return
	}

	// Dereference pointers
	for typ.Kind() == reflect.Pointer {
		out.IsPointer = true
		typ = typ.Elem()
	}

	out.Kind = getKind(typ.Kind())

	switch out.Kind {
	case KindStruct:
		out.Keys = parseStruct(typ)
	case KindArray, KindDict:
		elem := parseType(typ.Elem())
		out.Element = &elem
		if out.Kind == KindDict {
			key := parseType(typ.Key())
			out.Key = &key
		}
	}

	return
}

func parseStruct(typ reflect.Type) (out []DataObject) {
	for index := 0; index < typ.NumField(); index++ {
		field := typ.Field(index)
		obj := parseType(field.Type)
		if jsonName, ok := field.Tag.Lookup("json"); ok {
			parts := strings.SplitN(jsonName, ",", 2)
			obj.Name = parts[0]
		} else {
			obj.Name = field.Name
		}
		obj.Description = field.Tag.Get("desc")
		out = append(out, obj)
	}
	return out
}
