package main

import (
	"encoding/json"
	"fmt"
	"os"
	"reflect"
	"strings"

	cdccfg "github.com/pingcap/tiflow/pkg/config"
)

func main() {
	cdcComments, err := LoadPackageComments("github.com/pingcap/tiflow/pkg/config")
	if err != nil {
		fmt.Println("Warning: failed to load TiCDC comments:", err)
		cdcComments = make(StructComments)
	}

	cdcJSON, err := json.MarshalIndent(ParseTomlConfig(&cdccfg.ServerConfig{}, cdcComments), "", "    ")
	if err != nil {
		panic(err)
	}
	os.WriteFile("ticdc.json", cdcJSON, 0644)
}

func ParseTomlConfig(input any, comments StructComments) []Param {
	var r func(reflect.Value, string, string) []Param
	r = func(reflectV reflect.Value, path string, commentKey string) (result []Param) {
		comment := comments[commentKey]
		switch reflectV.Type().Name() {
		// common
		case "AtomicBool", "nullableBool":
			return []Param{{Key: path, Type: "bool", Comment: comment}}
		case "Int64":
			return []Param{{Key: path, Type: "number", Comment: comment}}
		case "Duration":
			return []Param{{Key: path, Type: "string", Comment: comment}}
		case "ByteSize":
			return []Param{{Key: path, Type: "string", Comment: comment}}
		// tiflow
		case "TomlDuration":
			return []Param{{Key: path, Type: "string", Comment: comment}}
		default:
			var unmarshalTextType = reflect.TypeOf((*UnmarshalText)(nil)).Elem()
			var unmarshalTOMLType = reflect.TypeOf((*UnmarshalTOML)(nil)).Elem()
			if reflectV.Type().Implements(unmarshalTextType) ||
				(reflect.PointerTo(reflectV.Type()).Implements(unmarshalTextType)) {
				panic(fmt.Sprintf("%s  <-- Implements UnmarshalText, please update code to handler it", reflectV.Type().Name()))
			} else if reflectV.Type().Implements(unmarshalTOMLType) ||
				(reflect.PointerTo(reflectV.Type()).Implements(unmarshalTOMLType)) {
				panic(fmt.Sprintf("%s  <-- Implements unmarshalTOMLType, please update code to handler it", reflectV.Type().Name()))
			}
		}
		switch reflectV.Kind() {
		case reflect.Struct:
			typeName := reflectV.Type().Name()
			for i := 0; i < reflectV.NumField(); i++ {
				field := reflectV.Type().Field(i)
				fullKey := path
				tag := field.Tag.Get("toml")
				paramName := strings.Split(tag, ",")[0]
				switch paramName {
				case "":
					if field.Anonymous {
						// do nothing to passthrough to the child field
					} else {
						fmt.Println("ignore no tag field: " + path + " " + field.Name)
						continue
					}
				case "-":
					continue
				default:
					if path != "" {
						fullKey += "."
					}
					fullKey += paramName
				}
				fieldCommentKey := typeName + "." + field.Name
				result = append(result, r(reflectV.Field(i), fullKey, fieldCommentKey)...)
			}
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
			reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			result = []Param{{Key: path, Type: "int", Comment: comment}}
		case reflect.Float32, reflect.Float64:
			result = []Param{{Key: path, Type: "float", Comment: comment}}
		case reflect.String:
			result = []Param{{Key: path, Type: "string", Comment: comment}}
		case reflect.Bool:
			result = []Param{{Key: path, Type: "bool", Comment: comment}}
		case reflect.Slice, reflect.Map:
			result = []Param{{Key: path, Type: "json", Comment: comment}}
		case reflect.Interface:
			result = append(result, r(reflectV.Elem(), path, commentKey)...)
		case reflect.Ptr:
			result = append(result, r(reflect.Zero(reflectV.Type().Elem()), path, commentKey)...)
		default:
			fmt.Println("Unknown type:", reflectV.Kind(), path)
		}
		return result
	}
	return r(reflect.ValueOf(&input).Elem(), "", "")
}
