package codegen

import (
	"encoding/json"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"unicode"
)

type GoStruct struct {
	Filepath            string           `json:"filepath"`
	Filename            string           `json:"filename"`
	Name                string           `json:"name"`
	FormattedName       string           `json:"formattedName"` // name with package prefix e.g. models.User => Models_User
	Package             string           `json:"package"`
	Fields              []*GoStructField `json:"fields"`
	AliasOf             *GoAlias         `json:"aliasOf,omitempty"`
	Comments            []string         `json:"comments"`
	EmbeddedStructTypes []string         `json:"embeddedStructNames,omitempty"`
}

type GoAlias struct {
	GoType             string   `json:"goType"`
	TypescriptType     string   `json:"typescriptType"`
	UsedTypescriptType string   `json:"usedTypescriptType,omitempty"`
	DeclaredValues     []string `json:"declaredValues"`
	UsedStructType     string   `json:"usedStructName,omitempty"`
}

type GoStructField struct {
	Name     string `json:"name"`
	JsonName string `json:"jsonName"`
	// e.g. map[string]models.User
	GoType string `json:"goType"`
	// e.g. []struct{Test string `json:"test"`, Test2 string `json:"test2"`}
	InlineStructType string `json:"inlineStructType,omitempty"`
	// e.g. User
	TypescriptType string `json:"typescriptType"`
	// e.g. TypescriptType = Array<Models_User> => UsedTypescriptType = Models_User
	UsedTypescriptType string `json:"usedTypescriptType,omitempty"`
	// e.g. GoType = map[string]models.User => TypescriptType = User => UsedStructType = models.User
	UsedStructType string `json:"usedStructName,omitempty"`
	// If no 'omitempty' and not a pointer
	Required bool     `json:"required"`
	Public   bool     `json:"public"`
	Comments []string `json:"comments"`
}

var typePrefixesByPackage = map[string]string{
	"anilist":                "AL_",
	"auto_downloader":        "AutoDownloader_",
	"autodownloader":         "AutoDownloader_",
	"entities":               "",
	"db":                     "DB_",
	"db_bridge":              "DB_",
	"models":                 "Models_",
	"playbackmanager":        "PlaybackManager_",
	"torrent_client":         "TorrentClient_",
	"events":                 "Events_",
	"torrent":                "Torrent_",
	"manga":                  "Manga_",
	"autoscanner":            "AutoScanner_",
	"listsync":               "ListSync_",
	"util":                   "Util_",
	"scanner":                "Scanner_",
	"offline":                "Offline_",
	"discordrpc":             "DiscordRPC_",
	"discordrpc_presence":    "DiscordRPC_",
	"anizip":                 "Anizip_",
	"animap":                 "Animap_",
	"onlinestream":           "Onlinestream_",
	"onlinestream_providers": "Onlinestream_",
	"onlinestream_sources":   "Onlinestream_",
	"manga_providers":        "Manga_",
	"chapter_downloader":     "ChapterDownloader_",
	"manga_downloader":       "MangaDownloader_",
	"docs":                   "INTERNAL_",
	"tvdb":                   "TVDB_",
	"metadata":               "Metadata_",
	"mappings":               "Mappings_",
	"mal":                    "MAL_",
	"handlers":               "",
	"animetosho":             "AnimeTosho_",
	"updater":                "Updater_",
	"anime":                  "Anime_",
	"anime_types":            "Anime_",
	"summary":                "Summary_",
	"filesystem":             "Filesystem_",
	"filecache":              "Filecache_",
	"core":                   "INTERNAL_",
	"comparison":             "Comparison_",
	"mediastream":            "Mediastream_",
	"torrentstream":          "Torrentstream_",
	"extension":              "Extension_",
	"extension_repo":         "ExtensionRepo_",
	//"vendor_hibike_manga":        "HibikeManga_",
	//"vendor_hibike_onlinestream": "HibikeOnlinestream_",
	//"vendor_hibike_torrent":      "HibikeTorrent_",
	//"vendor_hibike_mediaplayer":  "HibikeMediaPlayer_",
	//"vendor_hibike_extension":    "HibikeExtension_",
	"hibikemanga":        "HibikeManga_",
	"hibikeonlinestream": "HibikeOnlinestream_",
	"hibiketorrent":      "HibikeTorrent_",
	"hibikemediaplayer":  "HibikeMediaPlayer_",
	"hibikeextension":    "HibikeExtension_",
	"continuity":         "Continuity_",
	"local":              "Local_",
	"debrid":             "Debrid_",
	"debrid_client":      "DebridClient_",
	"report":             "Report_",
	"habari":             "Habari_",
	"vendor_habari":      "Habari_",
	"discordrpc_client":  "DiscordRPC_",
	"directstream":       "Directstream_",
	"nativeplayer":       "NativePlayer_",
	"mkvparser":          "MKVParser_",
	"nakama":             "Nakama_",
}

func getTypePrefix(packageName string) string {
	if prefix, ok := typePrefixesByPackage[packageName]; ok {
		return prefix
	}
	return ""
}

func ExtractStructs(dir string, outDir string) {

	structs := make([]*GoStruct, 0)

	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() && strings.HasSuffix(info.Name(), ".go") {
			res, err := getGoStructsFromFile(path, info)
			if err != nil {
				return err
			}
			structs = append(structs, res...)
		}
		return nil
	})
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Write structs to file
	_ = os.MkdirAll(outDir, os.ModePerm)
	file, err := os.Create(outDir + "/public_structs.json")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer file.Close()

	encoder := json.NewEncoder(file)
	encoder.SetIndent("", "  ")
	if err := encoder.Encode(structs); err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println("Public structs extracted and saved to public_structs.json")
}

func getGoStructsFromFile(path string, info os.FileInfo) (structs []*GoStruct, err error) {

	// Parse the Go file
	file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ParseComments)
	if err != nil {
		return nil, err
	}
	packageName := file.Name.Name

	// Extract public structs
	for _, decl := range file.Decls {
		genDecl, ok := decl.(*ast.GenDecl)
		if !ok || genDecl.Tok != token.TYPE {
			continue
		}

		//
		// Go through each type declaration
		//
		for _, spec := range genDecl.Specs {
			typeSpec, ok := spec.(*ast.TypeSpec)
			if !ok {
				continue
			}
			if !typeSpec.Name.IsExported() {
				continue
			}

			//
			// The type declaration is an alias
			// e.g. alias.Name: string, typeSpec.Name.Name: MediaListStatus
			//
			alias, ok := typeSpec.Type.(*ast.Ident)
			if ok {

				if alias.Name == typeSpec.Name.Name {
					continue
				}
				goStruct := goStructFromAlias(path, info, genDecl, typeSpec, packageName, alias, file)
				structs = append(structs, goStruct)
				continue
			}

			//
			// The type declaration is a struct
			//
			structType, ok := typeSpec.Type.(*ast.StructType)
			if ok {

				subStructs := make([]*GoStruct, 0)
				for _, field := range structType.Fields.List {
					if field.Names != nil && len(field.Names) > 0 {

						subStructType, ok := field.Type.(*ast.StructType)
						if ok {
							name := fmt.Sprintf("%s_%s", typeSpec.Name.Name, field.Names[0].Name)
							subStruct := goStructFromStruct(path, info, genDecl, name, packageName, subStructType)
							subStructs = append(subStructs, subStruct)
							continue
						}

					}
				}

				goStruct := goStructFromStruct(path, info, genDecl, typeSpec.Name.Name, packageName, structType)

				// Replace struct fields with sub structs
				for _, field := range goStruct.Fields {
					if field.GoType == "__STRUCT__" {
						for _, subStruct := range subStructs {
							if subStruct.Name == fmt.Sprintf("%s_%s", typeSpec.Name.Name, field.Name) {
								field.GoType = subStruct.FormattedName
								field.TypescriptType = subStruct.FormattedName
								field.UsedStructType = fmt.Sprintf("%s.%s", subStruct.Package, subStruct.Name)
								break
							}
						}
					}
				}

				structs = append(structs, goStruct)
				structs = append(structs, subStructs...)
				continue
			}

			mapType, ok := typeSpec.Type.(*ast.MapType)
			if ok {
				goStruct := &GoStruct{
					Filepath:      path,
					Filename:      info.Name(),
					Name:          typeSpec.Name.Name,
					FormattedName: getTypePrefix(packageName) + typeSpec.Name.Name,
					Package:       packageName,
					Fields:        make([]*GoStructField, 0),
				}

				usedStructType, usedStructPkgName := getUsedStructType(mapType, packageName)

				goStruct.AliasOf = &GoAlias{
					GoType:         fieldTypeString(mapType),
					TypescriptType: fieldTypeToTypescriptType(mapType, usedStructPkgName),
					UsedStructType: usedStructType,
				}

				structs = append(structs, goStruct)
				continue
			}

			sliceType, ok := typeSpec.Type.(*ast.ArrayType)
			if ok {
				goStruct := &GoStruct{
					Filepath:      path,
					Filename:      info.Name(),
					Name:          typeSpec.Name.Name,
					FormattedName: getTypePrefix(packageName) + typeSpec.Name.Name,
					Package:       packageName,
					Fields:        make([]*GoStructField, 0),
				}

				usedStructType, usedStructPkgName := getUsedStructType(sliceType, packageName)

				goStruct.AliasOf = &GoAlias{
					GoType:         fieldTypeString(sliceType),
					TypescriptType: fieldTypeToTypescriptType(sliceType, usedStructPkgName),
					UsedStructType: usedStructType,
				}

				structs = append(structs, goStruct)
				continue
			}

		}
	}
	return structs, nil
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Example:
//
//	type User struct {
//		ID   int    `json:"id"`
//		Name string `json:"name"`
//	}
func goStructFromStruct(path string, info os.FileInfo, genDecl *ast.GenDecl, name string, packageName string, structType *ast.StructType) *GoStruct {
	// Get comments
	comments := make([]string, 0)
	if genDecl.Doc != nil && genDecl.Doc.List != nil && len(genDecl.Doc.List) > 0 {
		for _, comment := range genDecl.Doc.List {
			comments = append(comments, strings.TrimPrefix(comment.Text, "//"))
		}
	}

	goStruct := &GoStruct{
		Filepath:            filepath.ToSlash(path),
		Filename:            info.Name(),
		Name:                name,
		FormattedName:       getTypePrefix(packageName) + name,
		Package:             packageName,
		Fields:              make([]*GoStructField, 0),
		EmbeddedStructTypes: make([]string, 0),
		Comments:            comments,
	}

	// Get fields
	for _, field := range structType.Fields.List {
		if field.Names == nil || len(field.Names) == 0 {
			if len(field.Names) == 0 {
				switch field.Type.(type) {
				case *ast.Ident, *ast.StarExpr, *ast.SelectorExpr:
					usedStructType, _ := getUsedStructType(field.Type, packageName)
					goStruct.EmbeddedStructTypes = append(goStruct.EmbeddedStructTypes, usedStructType)
				}
			}
			continue
		}
		// Get fields comments
		comments := make([]string, 0)
		if field.Comment != nil && field.Comment.List != nil && len(field.Comment.List) > 0 {
			for _, comment := range field.Comment.List {
				comments = append(comments, strings.TrimPrefix(comment.Text, "//"))
			}
		}

		required := true
		if field.Tag != nil {
			tag := reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1])
			jsonTag := tag.Get("json")
			if jsonTag != "" {
				jsonParts := strings.Split(jsonTag, ",")
				if len(jsonParts) > 1 && jsonParts[1] == "omitempty" {
					required = false
				}
			}
		}
		switch field.Type.(type) {
		case *ast.StarExpr, *ast.ArrayType, *ast.MapType, *ast.SelectorExpr:
			required = false
		}
		fieldName := field.Names[0].Name

		usedStructType, usedStructPkgName := getUsedStructType(field.Type, packageName)

		tsType := fieldTypeToTypescriptType(field.Type, usedStructPkgName)

		goStructField := &GoStructField{
			Name:               fieldName,
			JsonName:           jsonFieldName(field),
			GoType:             fieldTypeString(field.Type),
			TypescriptType:     tsType,
			UsedTypescriptType: fieldTypeToUsedTypescriptType(tsType),
			Required:           required,
			Public:             field.Names[0].IsExported(),
			UsedStructType:     usedStructType,
			Comments:           comments,
		}

		// If it's an inline struct, capture the full definition as a string
		if goStructField.GoType == "__STRUCT__" {
			if structType, ok := field.Type.(*ast.StructType); ok {
				goStructField.InlineStructType = formatInlineStruct(structType)
			}
		} else {
			// Check if it's a slice of inline structs
			if arrayType, ok := field.Type.(*ast.ArrayType); ok {
				if structType, ok := arrayType.Elt.(*ast.StructType); ok {
					goStructField.InlineStructType = "[]" + formatInlineStruct(structType)
				}
			}
			// Check if it's a map with inline struct values
			if mapType, ok := field.Type.(*ast.MapType); ok {
				if structType, ok := mapType.Value.(*ast.StructType); ok {
					goStructField.InlineStructType = "map[" + fieldTypeString(mapType.Key) + "]" + formatInlineStruct(structType)
				}
			}
		}

		goStruct.Fields = append(goStruct.Fields, goStructField)
	}
	return goStruct
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

func goStructFromAlias(path string, info os.FileInfo, genDecl *ast.GenDecl, typeSpec *ast.TypeSpec, packageName string, alias *ast.Ident, file *ast.File) *GoStruct {
	// Get comments
	comments := make([]string, 0)
	if genDecl.Doc != nil && genDecl.Doc.List != nil && len(genDecl.Doc.List) > 0 {
		for _, comment := range genDecl.Doc.List {
			comments = append(comments, strings.TrimPrefix(comment.Text, "//"))
		}
	}

	usedStructType, usedStructPkgName := getUsedStructType(typeSpec.Type, packageName)
	tsType := fieldTypeToTypescriptType(typeSpec.Type, usedStructPkgName)

	goStruct := &GoStruct{
		Filepath:      filepath.ToSlash(path),
		Filename:      info.Name(),
		Name:          typeSpec.Name.Name,
		Package:       packageName,
		FormattedName: getTypePrefix(packageName) + typeSpec.Name.Name,
		Fields:        make([]*GoStructField, 0),
		Comments:      comments,
		AliasOf: &GoAlias{
			GoType:             alias.Name,
			TypescriptType:     tsType,
			UsedTypescriptType: fieldTypeToUsedTypescriptType(tsType),
			UsedStructType:     usedStructType,
		},
	}

	// Get declared values - useful for building enums or union types
	// e.g. const Something AliasType = "something"
	goStruct.AliasOf.DeclaredValues = make([]string, 0)
	for _, decl := range file.Decls {
		genDecl, ok := decl.(*ast.GenDecl)
		if !ok || genDecl.Tok != token.CONST {
			continue
		}
		for _, spec := range genDecl.Specs {
			valueSpec, ok := spec.(*ast.ValueSpec)
			if !ok {
				continue
			}
			valueSpecType := fieldTypeString(valueSpec.Type)
			if len(valueSpec.Names) == 1 && valueSpec.Names[0].IsExported() && valueSpecType == typeSpec.Name.Name {
				for _, value := range valueSpec.Values {
					name, ok := value.(*ast.BasicLit)
					if !ok {
						continue
					}
					goStruct.AliasOf.DeclaredValues = append(goStruct.AliasOf.DeclaredValues, name.Value)
				}
			}
		}
	}
	return goStruct
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// getUsedStructType returns the used struct type for a given type declaration.
// For example, if the type declaration is `map[string]models.User`, the used struct type is `models.User`.
// If the type declaration is `[]User`, the used struct type is `{packageName}.User`.
func getUsedStructType(expr ast.Expr, packageName string) (string, string) {
	usedStructType := fieldTypeToUsedStructType(expr)

	switch usedStructType {
	case "string", "bool", "byte", "uint", "uint8", "uint16", "uint32", "uint64", "int", "int8", "int16", "int32", "int64", "float", "float32", "float64":
		return "", ""
	case "__STRUCT__":
		return "", ""
	}

	if usedStructType != "__STRUCT__" && usedStructType != "" && !strings.Contains(usedStructType, ".") {
		usedStructType = packageName + "." + usedStructType
	}

	pkgName := strings.Split(usedStructType, ".")[0]

	return usedStructType, pkgName
}

// fieldTypeString returns the field type as a string.
// For example, if the field type is `[]*models.User`, the return value is `[]models.User`.
// If the field type is `[]InternalStruct`, the return value is `[]InternalStruct`.
func fieldTypeString(fieldType ast.Expr) string {
	switch t := fieldType.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.StarExpr:
		//return "*" + fieldTypeString(t.X)
		return fieldTypeString(t.X)
	case *ast.ArrayType:
		if fieldTypeString(t.Elt) == "byte" {
			return "string"
		}
		return "[]" + fieldTypeString(t.Elt)
	case *ast.MapType:
		return "map[" + fieldTypeString(t.Key) + "]" + fieldTypeString(t.Value)
	case *ast.SelectorExpr:
		return fieldTypeString(t.X) + "." + t.Sel.Name
	case *ast.StructType:
		return "__STRUCT__"
	default:
		return ""
	}
}

// fieldTypeToTypescriptType returns the field type as a string in TypeScript format.
// For example, if the field type is `[]*models.User`, the return value is `Array<Models_User>`.
func fieldTypeToTypescriptType(fieldType ast.Expr, usedStructPkgName string) string {
	switch t := fieldType.(type) {
	case *ast.Ident:
		switch t.Name {
		case "string":
			return "string"
		case "uint", "uint8", "uint16", "uint32", "uint64", "int", "int8", "int16", "int32", "int64", "float", "float32", "float64":
			return "number"
		case "bool":
			return "boolean"
		case "byte":
			return "string"
		case "time.Time":
			return "string"
		case "nil":
			return "null"
		default:
			return getTypePrefix(usedStructPkgName) + t.Name
		}
	case *ast.StarExpr:
		return fieldTypeToTypescriptType(t.X, usedStructPkgName)
	case *ast.ArrayType:
		if fieldTypeToTypescriptType(t.Elt, usedStructPkgName) == "byte" {
			return "string"
		}
		return "Array<" + fieldTypeToTypescriptType(t.Elt, usedStructPkgName) + ">"
	case *ast.MapType:
		return "Record<" + fieldTypeToTypescriptType(t.Key, usedStructPkgName) + ", " + fieldTypeToTypescriptType(t.Value, usedStructPkgName) + ">"
	case *ast.SelectorExpr:
		if t.Sel.Name == "Time" {
			return "string"
		}
		return getTypePrefix(usedStructPkgName) + t.Sel.Name
	case *ast.StructType:
		s := "{ "
		for _, field := range t.Fields.List {
			s += jsonFieldName(field) + ": " + fieldTypeToTypescriptType(field.Type, usedStructPkgName) + "; "
		}
		s += "}"
		return s
	default:
		return "any"
	}
}

func stringGoTypeToTypescriptType(goType string) string {
	switch goType {
	case "string":
		return "string"
	case "uint", "uint8", "uint16", "uint32", "uint64", "int", "int8", "int16", "int32", "int64", "float", "float32", "float64":
		return "number"
	case "nil":
		return "null"
	case "bool":
		return "boolean"
	case "time.Time":
		return "string"
	}

	if strings.HasPrefix(goType, "[]") {
		return "Array<" + stringGoTypeToTypescriptType(goType[2:]) + ">"
	}

	if strings.HasPrefix(goType, "*") {
		return stringGoTypeToTypescriptType(goType[1:])
	}

	if strings.HasPrefix(goType, "map[") {
		s := strings.TrimPrefix(goType, "map[")
		key := ""
		value := ""
		for i, c := range s {
			if c == ']' {
				key = s[:i]
				value = s[i+1:]
				break
			}
		}
		return "Record<" + stringGoTypeToTypescriptType(key) + ", " + stringGoTypeToTypescriptType(value) + ">"
	}

	if strings.Contains(goType, ".") {
		parts := strings.Split(goType, ".")
		return getTypePrefix(parts[0]) + parts[1]
	}

	return goType
}

func goTypeToTypescriptType(goType string) string {
	switch goType {
	case "string":
		return "string"
	case "uint", "uint8", "uint16", "uint32", "uint64", "int", "int8", "int16", "int32", "int64", "float", "float32", "float64":
		return "number"
	case "bool":
		return "boolean"
	case "nil":
		return "null"
	case "time.Time":
		return "string"
	default:
		return "unknown"
	}
}

// fieldTypeUnformattedString returns the field type as a string without formatting.
// For example, if the field type is `[]*models.User`, the return value is `models.User`.
// /!\ Caveat: this assumes that the map key is always a string.
func fieldTypeUnformattedString(fieldType ast.Expr) string {
	switch t := fieldType.(type) {
	case *ast.Ident:
		return t.Name
	case *ast.StarExpr:
		//return "*" + fieldTypeString(t.X)
		return fieldTypeUnformattedString(t.X)
	case *ast.ArrayType:
		return fieldTypeUnformattedString(t.Elt)
	case *ast.MapType:
		return fieldTypeUnformattedString(t.Value)
	case *ast.SelectorExpr:
		return fieldTypeString(t.X) + "." + t.Sel.Name
	default:
		return ""
	}
}

// fieldTypeToUsedStructType returns the used struct type for a given field type.
// For example, if the field type is `[]*models.User`, the return value is `models.User`.
func fieldTypeToUsedStructType(fieldType ast.Expr) string {
	switch t := fieldType.(type) {
	case *ast.StarExpr:
		return fieldTypeString(t.X)
	case *ast.ArrayType:
		return fieldTypeString(t.Elt)
	case *ast.MapType:
		return fieldTypeUnformattedString(t.Value)
	case *ast.SelectorExpr:
		return fieldTypeString(t)
	case *ast.Ident:
		return t.Name
	case *ast.StructType:
		return "__STRUCT__"
	default:
		return ""
	}
}

func jsonFieldName(field *ast.Field) string {
	if field.Tag != nil {
		tag := reflect.StructTag(strings.ReplaceAll(field.Tag.Value[1:len(field.Tag.Value)-1], "\\\"", "\""))
		jsonTag := tag.Get("json")
		if jsonTag != "" {
			jsonParts := strings.Split(jsonTag, ",")
			if jsonParts[0] == "-" {
				return ""
			}
			if jsonParts[0] != "" {
				return jsonParts[0]
			}
			return jsonParts[0]
		}
	}
	return field.Names[0].Name
}

func jsonFieldOmitEmpty(field *ast.Field) bool {
	if field.Tag != nil {
		tag := reflect.StructTag(strings.ReplaceAll(field.Tag.Value[1:len(field.Tag.Value)-1], "\\\"", "\""))
		jsonTag := tag.Get("json")
		if jsonTag != "" {
			jsonParts := strings.Split(jsonTag, ",")
			return len(jsonParts) > 1 && jsonParts[1] == "omitempty"
		}
	}
	return false
}

func isCustomStruct(goType string) bool {
	return goTypeToTypescriptType(goType) == "unknown"
}

var nameExceptions = map[string]string{"OAuth2": "oauth2"}

func convertGoToJSName(name string) string {
	if v, ok := nameExceptions[name]; ok {
		return v
	}

	startUppercase := make([]rune, 0, len(name))

	for _, c := range name {
		if c != '_' && !unicode.IsUpper(c) && !unicode.IsDigit(c) {
			break
		}

		startUppercase = append(startUppercase, c)
	}

	totalStartUppercase := len(startUppercase)

	// all uppercase eg. "JSON" -> "json"
	if len(name) == totalStartUppercase {
		return strings.ToLower(name)
	}

	// eg. "JSONField" -> "jsonField"
	if totalStartUppercase > 1 {
		return strings.ToLower(name[0:totalStartUppercase-1]) + name[totalStartUppercase-1:]
	}

	// eg. "GetField" -> "getField"
	if totalStartUppercase == 1 {
		return strings.ToLower(name[0:1]) + name[1:]
	}

	return name
}

// fieldTypeToUsedTypescriptType extracts the core TypeScript type from complex type expressions
// For example, if the type is Array<Models_User>, it returns Models_User
// If the type is Record<string, Models_User>, it returns Models_User
func fieldTypeToUsedTypescriptType(tsType string) string {
	// Handle arrays: Array<Type> -> Type
	if strings.HasPrefix(tsType, "Array<") && strings.HasSuffix(tsType, ">") {
		innerType := strings.TrimPrefix(strings.TrimSuffix(tsType, ">"), "Array<")
		return fieldTypeToUsedTypescriptType(innerType)
	}

	// Handle records: Record<Key, Value> -> Value
	if strings.HasPrefix(tsType, "Record<") && strings.HasSuffix(tsType, ">") {
		innerType := strings.TrimPrefix(strings.TrimSuffix(tsType, ">"), "Record<")
		// Find the comma that separates key and value
		commaIndex := -1
		bracketCount := 0
		for i, char := range innerType {
			if char == '<' {
				bracketCount++
			} else if char == '>' {
				bracketCount--
			} else if char == ',' && bracketCount == 0 {
				commaIndex = i
				break
			}
		}

		if commaIndex != -1 {
			valueType := strings.TrimSpace(innerType[commaIndex+1:])
			return fieldTypeToUsedTypescriptType(valueType)
		}
	}

	// Handle primitive types
	switch tsType {
	case "string", "number", "boolean", "any", "null", "undefined":
		return ""
	}

	return tsType
}

// formatInlineStruct formats an inline struct definition as a string
// e.g. struct{Test string `json:"test"`, Test2 string `json:"test2"`}
func formatInlineStruct(structType *ast.StructType) string {
	result := "struct{\n"

	for i, field := range structType.Fields.List {
		if i > 0 {
			result += "\n"
		}

		if field.Names != nil && len(field.Names) > 0 {
			result += field.Names[0].Name + " " + fieldTypeString(field.Type)

			if field.Tag != nil {
				result += " " + field.Tag.Value
			}
		} else {
			result += fieldTypeString(field.Type)
		}
	}

	result += "}"
	return result
}
