//go:build ignore

package main

import (
	"encoding/xml"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/template"
)

// XMLDefinition represents the root element containing structs
type XMLDefinition struct {
	XMLName xml.Name    `xml:"structs"`
	Structs []XMLStruct `xml:"struct"`
}

// XMLStruct represents a packet structure definition
type XMLStruct struct {
	Name          string    `xml:"name,attr"`
	ClientVersion string    `xml:"clientVersion,attr"`
	OpcodeName    string    `xml:"opcodeName,attr"`
	Fields        []XMLData `xml:"data"`
}

// XMLData represents a field in the packet structure
type XMLData struct {
	Name              string    `xml:"name,attr"`
	Type              string    `xml:"type,attr"`
	Size              int       `xml:"size,attr"`
	ArraySizeVariable string    `xml:"arraySizeVariable,attr"`
	IfVarSet          string    `xml:"ifVarSet,attr"`
	IfVarNotSet       string    `xml:"ifVarNotSet,attr"`
	OversizedValue    int       `xml:"oversizedValue,attr"`
	Children          []XMLData `xml:"data"` // For nested array elements
}

// TypeMapping maps XML types to Go types
var TypeMapping = map[string]string{
	"int8":             "int8",
	"int16":            "int16",
	"int32":            "int32",
	"int64":            "int64",
	"uint8":            "uint8",
	"uint16":           "uint16",
	"uint32":           "uint32",
	"uint64":           "uint64",
	"i8":               "int8",
	"i16":              "int16",
	"i32":              "int32",
	"i64":              "int64",
	"u8":               "uint8",
	"u16":              "uint16",
	"u32":              "uint32",
	"u64":              "uint64",
	"float":            "float32",
	"double":           "float64",
	"str8":             "string",
	"str16":            "string",
	"str32":            "string",
	"EQ2_32Bit_String": "string",
	"EQ2_8Bit_String":  "string",
	"EQ2_16Bit_String": "string",
	"char":             "byte",
	"color":            "types.Color",        // RGB color as 32-bit value
	"equipmentItem":    "types.EquipmentItem", // Custom type
	"Array":            "array",               // Capital A variant
}

// GoStruct represents the generated Go struct
type GoStruct struct {
	Name          string
	ClientVersion string
	OpcodeName    string
	Fields        []GoField
	PackageName   string
}

// GoField represents a field in the Go struct
type GoField struct {
	Name              string
	GoName            string
	Type              string
	IsArray           bool
	IsDynamicArray    bool
	ArraySizeVariable string
	Size              int
	Tag               string
	Comment           string
	IfVarSet          string
	IfVarNotSet       string
	ArrayElements     []GoField // For complex array elements (anonymous struct)
}

// GenerateOutput holds the generated code
type GenerateOutput struct {
	SourceFile  string
	PackageName string
	Structs     []GoStruct
	NeedsMath   bool
}

// parseXMLFile parses an XML definition file
func parseXMLFile(filename string) ([]XMLStruct, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	data, err := io.ReadAll(file)
	if err != nil {
		return nil, err
	}

	// Wrap content in root element if needed
	content := string(data)
	if !strings.HasPrefix(strings.TrimSpace(content), "<?xml") && !strings.HasPrefix(strings.TrimSpace(content), "<structs>") {
		content = "<structs>" + content + "</structs>"
	}

	var def XMLDefinition
	if err := xml.Unmarshal([]byte(content), &def); err != nil {
		return nil, err
	}

	return def.Structs, nil
}

// toGoName converts snake_case to CamelCase
func toGoName(name string) string {
	parts := strings.Split(name, "_")
	for i, part := range parts {
		if len(part) > 0 {
			parts[i] = strings.ToUpper(part[:1]) + part[1:]
		}
	}
	return strings.Join(parts, "")
}

// mapSimpleType maps XML type to Go type
func mapSimpleType(xmlType string) (string, bool) {
	goType, ok := TypeMapping[xmlType]
	if !ok {
		return "byte", false
	}
	return goType, true
}

// convertArrayChildren converts nested array data to Go fields
func convertArrayChildren(children []XMLData) []GoField {
	fields := make([]GoField, 0, len(children))

	for _, child := range children {
		if child.Type == "array" && len(child.Children) > 0 {
			// Nested array - recursive
			nestedFields := convertArrayChildren(child.Children)
			goField := GoField{
				Name:              child.Name,
				GoName:            toGoName(child.Name),
				IsDynamicArray:    true,
				ArraySizeVariable: child.ArraySizeVariable,
				ArrayElements:     nestedFields,
			}
			fields = append(fields, goField)
		} else {
			// Simple field
			goType, _ := mapSimpleType(child.Type)
			isArray := child.Size > 0

			goField := GoField{
				Name:    child.Name,
				GoName:  toGoName(child.Name),
				Type:    goType,
				IsArray: isArray,
				Size:    child.Size,
			}

			if isArray {
				goField.Type = fmt.Sprintf("[%d]%s", child.Size, goType)
			}

			fields = append(fields, goField)
		}
	}

	return fields
}

// buildAnonymousStructType builds the anonymous struct type string
func buildAnonymousStructType(fields []GoField) string {
	if len(fields) == 0 {
		return "struct{}"
	}

	var sb strings.Builder
	sb.WriteString("struct {\n")
	for _, field := range fields {
		sb.WriteString("\t\t")
		sb.WriteString(field.GoName)
		sb.WriteString(" ")

		if field.IsDynamicArray && len(field.ArrayElements) > 0 {
			// Nested array
			sb.WriteString("[]")
			sb.WriteString(buildAnonymousStructType(field.ArrayElements))
		} else if field.IsArray {
			sb.WriteString(field.Type)
		} else {
			sb.WriteString(field.Type)
		}

		// Add struct tag
		sb.WriteString(" `eq2:\"")
		sb.WriteString(field.Name)
		if field.Type == "string" {
			// Determine string type from original XML
			sb.WriteString(",type:str16") // Default to str16 for now
		}
		if field.IsArray && field.Size > 0 {
			sb.WriteString(fmt.Sprintf(",size:%d", field.Size))
		}
		sb.WriteString("\"`")
		sb.WriteString("\n")
	}
	sb.WriteString("\t}")
	return sb.String()
}

// convertToGoStruct converts XML struct definition to Go struct
func convertToGoStruct(xmlStruct XMLStruct, packageName string) GoStruct {
	goStruct := GoStruct{
		Name:          toGoName(xmlStruct.Name),
		ClientVersion: xmlStruct.ClientVersion,
		OpcodeName:    xmlStruct.OpcodeName,
		PackageName:   packageName,
		Fields:        make([]GoField, 0, len(xmlStruct.Fields)),
	}

	// Add version suffix if needed
	if xmlStruct.ClientVersion != "" && xmlStruct.ClientVersion != "1" {
		goStruct.Name = fmt.Sprintf("%sV%s", goStruct.Name, xmlStruct.ClientVersion)
	}

	// Track field names to avoid duplicates
	fieldNames := make(map[string]int)

	for _, field := range xmlStruct.Fields {
		if field.Type == "array" || field.Type == "Array" {
			// Handle array with nested structure
			if len(field.Children) > 0 {
				arrayElements := convertArrayChildren(field.Children)
				structType := buildAnonymousStructType(arrayElements)

				// Check for duplicate field names and make unique if needed
				baseName := toGoName(field.Name)
				finalName := baseName
				if count, exists := fieldNames[baseName]; exists {
					finalName = fmt.Sprintf("%s_%d", baseName, count+1)
					fieldNames[baseName] = count + 1
				} else {
					fieldNames[baseName] = 1
				}

				goField := GoField{
					Name:              field.Name,
					GoName:            finalName,
					Type:              "[]" + structType,
					IsDynamicArray:    true,
					ArraySizeVariable: field.ArraySizeVariable,
					ArrayElements:     arrayElements,
					IfVarSet:          field.IfVarSet,
					IfVarNotSet:       field.IfVarNotSet,
				}

				// Generate struct tag
				tag := fmt.Sprintf("`eq2:\"%s", field.Name)
				if field.ArraySizeVariable != "" {
					tag += fmt.Sprintf(",sizeVar:%s", field.ArraySizeVariable)
				}
				if field.IfVarSet != "" {
					tag += fmt.Sprintf(",ifSet:%s", field.IfVarSet)
				}
				tag += "\"`"
				goField.Tag = tag

				goStruct.Fields = append(goStruct.Fields, goField)
			} else {
				// Simple array (shouldn't happen but handle it)
				log.Printf("Warning: array field %s has no children", field.Name)
			}
		} else if field.Type == "equipmentItem" {
			// Handle equipment item arrays
			// Check for duplicate field names and make unique if needed
			baseName := toGoName(field.Name)
			finalName := baseName
			if count, exists := fieldNames[baseName]; exists {
				finalName = fmt.Sprintf("%s_%d", baseName, count+1)
				fieldNames[baseName] = count + 1
			} else {
				fieldNames[baseName] = 1
			}

			goField := GoField{
				Name:    field.Name,
				GoName:  finalName,
				Type:    fmt.Sprintf("[%d]types.EquipmentItem", field.Size),
				IsArray: true,
				Size:    field.Size,
			}

			tag := fmt.Sprintf("`eq2:\"%s,size:%d\"`", field.Name, field.Size)
			goField.Tag = tag

			goStruct.Fields = append(goStruct.Fields, goField)
		} else {
			// Regular field
			goType, _ := mapSimpleType(field.Type)
			isArray := field.Size > 0

			// Check for duplicate field names and make unique if needed
			baseName := toGoName(field.Name)
			finalName := baseName
			if count, exists := fieldNames[baseName]; exists {
				finalName = fmt.Sprintf("%s_%d", baseName, count+1)
				fieldNames[baseName] = count + 1
			} else {
				fieldNames[baseName] = 1
			}

			goField := GoField{
				Name:        field.Name,
				GoName:      finalName,
				Type:        goType,
				IsArray:     isArray,
				Size:        field.Size,
				IfVarSet:    field.IfVarSet,
				IfVarNotSet: field.IfVarNotSet,
			}

			if isArray {
				goField.Type = fmt.Sprintf("[%d]%s", field.Size, goType)
			}

			// Generate struct tag
			tag := fmt.Sprintf("`eq2:\"%s", field.Name)
			if field.Type == "str8" || field.Type == "str16" || field.Type == "str32" ||
				field.Type == "EQ2_32Bit_String" || field.Type == "EQ2_16Bit_String" || field.Type == "EQ2_8Bit_String" {
				tag += fmt.Sprintf(",type:%s", field.Type)
			}
			if isArray && field.Size > 0 {
				tag += fmt.Sprintf(",size:%d", field.Size)
			}
			if field.IfVarSet != "" {
				tag += fmt.Sprintf(",ifSet:%s", field.IfVarSet)
			}
			tag += "\"`"
			goField.Tag = tag

			// Add comment if needed
			if strings.HasPrefix(field.Name, "unknown") || strings.HasPrefix(field.Name, "Unknown") {
				goField.Comment = " // TODO: Identify purpose"
			}

			goStruct.Fields = append(goStruct.Fields, goField)
		}
	}

	return goStruct
}

const structTemplate = `// Code generated by codegen. DO NOT EDIT.
// Source: {{.SourceFile}}

package {{.PackageName}}

import (
	"encoding/binary"{{if .NeedsMath}}
	"math"{{end}}
	"git.sharkk.net/EQ2/Protocol/types"
)

{{range .Structs}}
// {{.Name}} represents packet structure for {{if .OpcodeName}}{{.OpcodeName}}{{else}}client version {{.ClientVersion}}{{end}}
type {{.Name}} struct {
{{- range .Fields}}
	{{.GoName}} {{.Type}} {{.Tag}}{{if .Comment}} {{.Comment}}{{end}}
{{- end}}
}

// Serialize writes the packet data to the provided buffer
func (p *{{.Name}}) Serialize(dest []byte) uint32 {
	offset := uint32(0)
	{{- range .Fields}}
	{{- if .IfVarSet}}
	if p.{{toGoName .IfVarSet}} != 0 {
	{{- else if .IfVarNotSet}}
	if p.{{toGoName .IfVarNotSet}} == 0 {
	{{- end}}
	{{- if .IsDynamicArray}}
	for _, elem := range p.{{.GoName}} {
		{{- template "serializeFields" .ArrayElements}}
	}
	{{- else if eq .Type "string"}}
	{{- if or (contains .Tag "str16") (contains .Tag "EQ2_16Bit_String")}}
	binary.LittleEndian.PutUint16(dest[offset:], uint16(len(p.{{.GoName}})))
	offset += 2
	{{- else if or (contains .Tag "str32") (contains .Tag "EQ2_32Bit_String")}}
	binary.LittleEndian.PutUint32(dest[offset:], uint32(len(p.{{.GoName}})))
	offset += 4
	{{- else}}
	dest[offset] = byte(len(p.{{.GoName}}))
	offset++
	{{- end}}
	copy(dest[offset:], []byte(p.{{.GoName}}))
	offset += uint32(len(p.{{.GoName}}))
	{{- else if .IsArray}}
	for i := 0; i < {{.Size}}; i++ {
		{{- if eq (baseType .Type) "float32"}}
		binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(p.{{.GoName}}[i]))
		offset += 4
		{{- else if eq (baseType .Type) "float64"}}
		binary.LittleEndian.PutUint64(dest[offset:], math.Float64bits(p.{{.GoName}}[i]))
		offset += 8
		{{- else if (eq (baseType .Type) "int8")}}
		dest[offset] = byte(p.{{.GoName}}[i])
		offset++
		{{- else if or (eq (baseType .Type) "uint8") (eq (baseType .Type) "byte")}}
		dest[offset] = p.{{.GoName}}[i]
		offset++
		{{- else if or (eq (baseType .Type) "int16") (eq (baseType .Type) "uint16")}}
		binary.LittleEndian.PutUint16(dest[offset:], uint16(p.{{.GoName}}[i]))
		offset += 2
		{{- else if or (eq (baseType .Type) "int32") (eq (baseType .Type) "uint32")}}
		binary.LittleEndian.PutUint32(dest[offset:], uint32(p.{{.GoName}}[i]))
		offset += 4
		{{- else if or (eq (baseType .Type) "int64") (eq (baseType .Type) "uint64")}}
		binary.LittleEndian.PutUint64(dest[offset:], uint64(p.{{.GoName}}[i]))
		offset += 8
		{{- else if eq (baseType .Type) "types.EquipmentItem"}}
		binary.LittleEndian.PutUint16(dest[offset:], p.{{.GoName}}[i].Type)
		offset += 2
		dest[offset] = p.{{.GoName}}[i].Color.R
		dest[offset+1] = p.{{.GoName}}[i].Color.G
		dest[offset+2] = p.{{.GoName}}[i].Color.B
		offset += 3
		dest[offset] = p.{{.GoName}}[i].Highlight.R
		dest[offset+1] = p.{{.GoName}}[i].Highlight.G
		dest[offset+2] = p.{{.GoName}}[i].Highlight.B
		offset += 3
		{{- end}}
	}
	{{- else}}
	{{- if eq .Type "float32"}}
	binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(p.{{.GoName}}))
	offset += 4
	{{- else if eq .Type "float64"}}
	binary.LittleEndian.PutUint64(dest[offset:], math.Float64bits(p.{{.GoName}}))
	offset += 8
	{{- else if or (eq .Type "int8") (eq .Type "uint8") (eq .Type "byte")}}
	dest[offset] = byte(p.{{.GoName}})
	offset++
	{{- else if or (eq .Type "int16") (eq .Type "uint16")}}
	binary.LittleEndian.PutUint16(dest[offset:], uint16(p.{{.GoName}}))
	offset += 2
	{{- else if eq .Type "types.Color"}}
	dest[offset] = p.{{.GoName}}.R
	dest[offset+1] = p.{{.GoName}}.G
	dest[offset+2] = p.{{.GoName}}.B
	offset += 3
	{{- else if or (eq .Type "int32") (eq .Type "uint32")}}
	binary.LittleEndian.PutUint32(dest[offset:], uint32(p.{{.GoName}}))
	offset += 4
	{{- else if or (eq .Type "int64") (eq .Type "uint64")}}
	binary.LittleEndian.PutUint64(dest[offset:], uint64(p.{{.GoName}}))
	offset += 8
	{{- end}}
	{{- end}}
	{{- if or .IfVarSet .IfVarNotSet}}
	}
	{{- end}}
	{{- end}}
	return offset
}

// Size returns the serialized size of the packet
func (p *{{.Name}}) Size() uint32 {
	size := uint32(0)
	{{- range .Fields}}
	{{- if .IfVarSet}}
	if p.{{toGoName .IfVarSet}} != 0 {
	{{- else if .IfVarNotSet}}
	if p.{{toGoName .IfVarNotSet}} == 0 {
	{{- end}}
	{{- if .IsDynamicArray}}
	for _, elem := range p.{{.GoName}} {
		_ = elem
		{{- template "sizeFields" .ArrayElements}}
	}
	{{- else if eq .Type "string"}}
	{{- if or (contains .Tag "str16") (contains .Tag "EQ2_16Bit_String")}}
	size += 2 + uint32(len(p.{{.GoName}}))
	{{- else if or (contains .Tag "str32") (contains .Tag "EQ2_32Bit_String")}}
	size += 4 + uint32(len(p.{{.GoName}}))
	{{- else}}
	size += 1 + uint32(len(p.{{.GoName}}))
	{{- end}}
	{{- else if .IsArray}}
	{{- if eq (baseType .Type) "types.EquipmentItem"}}
	size += uint32({{.Size}}) * 8
	{{- else if eq (baseType .Type) "types.Color"}}
	size += uint32({{.Size}}) * 3
	{{- else if or (eq (baseType .Type) "float32") (eq (baseType .Type) "int32") (eq (baseType .Type) "uint32")}}
	size += uint32({{.Size}}) * 4
	{{- else if or (eq (baseType .Type) "float64") (eq (baseType .Type) "int64") (eq (baseType .Type) "uint64")}}
	size += uint32({{.Size}}) * 8
	{{- else if or (eq (baseType .Type) "int16") (eq (baseType .Type) "uint16")}}
	size += uint32({{.Size}}) * 2
	{{- else}}
	size += uint32({{.Size}})
	{{- end}}
	{{- else}}
	{{- if eq .Type "types.Color"}}
	size += 3
	{{- else if or (eq .Type "float32") (eq .Type "int32") (eq .Type "uint32")}}
	size += 4
	{{- else if or (eq .Type "float64") (eq .Type "int64") (eq .Type "uint64")}}
	size += 8
	{{- else if or (eq .Type "int16") (eq .Type "uint16")}}
	size += 2
	{{- else}}
	size += 1
	{{- end}}
	{{- end}}
	{{- if or .IfVarSet .IfVarNotSet}}
	}
	{{- end}}
	{{- end}}
	return size
}
{{end}}

{{define "serializeFields"}}
{{- range .}}
{{- if .IsDynamicArray}}
		for _, nestedElem := range elem.{{.GoName}} {
			{{- template "serializeNestedFields" .ArrayElements}}
		}
{{- else if eq .Type "string"}}
		dest[offset] = byte(len(elem.{{.GoName}}))
		offset++
		copy(dest[offset:], []byte(elem.{{.GoName}}))
		offset += uint32(len(elem.{{.GoName}}))
{{- else if .IsArray}}
		for i := 0; i < {{.Size}}; i++ {
			{{- if eq (baseType .Type) "float32"}}
			binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(elem.{{.GoName}}[i]))
			offset += 4
			{{- else if eq (baseType .Type) "uint32"}}
			binary.LittleEndian.PutUint32(dest[offset:], elem.{{.GoName}}[i])
			offset += 4
			{{- else if eq (baseType .Type) "uint16"}}
			binary.LittleEndian.PutUint16(dest[offset:], elem.{{.GoName}}[i])
			offset += 2
			{{- else}}
			dest[offset] = byte(elem.{{.GoName}}[i])
			offset++
			{{- end}}
		}
{{- else if eq .Type "float32"}}
		binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(elem.{{.GoName}}))
		offset += 4
{{- else if eq .Type "types.Color"}}
		dest[offset] = elem.{{.GoName}}.R
		dest[offset+1] = elem.{{.GoName}}.G
		dest[offset+2] = elem.{{.GoName}}.B
		offset += 3
{{- else if eq .Type "uint32"}}
		binary.LittleEndian.PutUint32(dest[offset:], elem.{{.GoName}})
		offset += 4
{{- else if eq .Type "int32"}}
		binary.LittleEndian.PutUint32(dest[offset:], uint32(elem.{{.GoName}}))
		offset += 4
{{- else if eq .Type "uint16"}}
		binary.LittleEndian.PutUint16(dest[offset:], elem.{{.GoName}})
		offset += 2
{{- else if eq .Type "int16"}}
		binary.LittleEndian.PutUint16(dest[offset:], uint16(elem.{{.GoName}}))
		offset += 2
{{- else if eq .Type "int8"}}
		dest[offset] = byte(elem.{{.GoName}})
		offset++
{{- else if eq .Type "uint8"}}
		dest[offset] = elem.{{.GoName}}
		offset++
{{- else}}
		dest[offset] = byte(elem.{{.GoName}})
		offset++
{{- end}}
{{- end}}
{{- end}}

{{define "serializeNestedFields"}}
{{- range .}}
{{- if .IsDynamicArray}}
			for _, deepNested := range nestedElem.{{.GoName}} {
				// TODO: Handle deeper nesting if needed
				_ = deepNested
			}
{{- else if eq .Type "string"}}
			dest[offset] = byte(len(nestedElem.{{.GoName}}))
			offset++
			copy(dest[offset:], []byte(nestedElem.{{.GoName}}))
			offset += uint32(len(nestedElem.{{.GoName}}))
{{- else if .IsArray}}
			for i := 0; i < {{.Size}}; i++ {
				{{- if eq (baseType .Type) "float32"}}
				binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(nestedElem.{{.GoName}}[i]))
				offset += 4
				{{- else if eq (baseType .Type) "uint32"}}
				binary.LittleEndian.PutUint32(dest[offset:], nestedElem.{{.GoName}}[i])
				offset += 4
				{{- else if eq (baseType .Type) "uint16"}}
				binary.LittleEndian.PutUint16(dest[offset:], nestedElem.{{.GoName}}[i])
				offset += 2
				{{- else}}
				dest[offset] = nestedElem.{{.GoName}}[i]
				offset++
				{{- end}}
			}
{{- else if eq .Type "float32"}}
			binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(nestedElem.{{.GoName}}))
			offset += 4
{{- else if eq .Type "types.Color"}}
			dest[offset] = nestedElem.{{.GoName}}.R
			dest[offset+1] = nestedElem.{{.GoName}}.G
			dest[offset+2] = nestedElem.{{.GoName}}.B
			offset += 3
{{- else if eq .Type "uint32"}}
			binary.LittleEndian.PutUint32(dest[offset:], nestedElem.{{.GoName}})
			offset += 4
{{- else if eq .Type "int32"}}
			binary.LittleEndian.PutUint32(dest[offset:], uint32(nestedElem.{{.GoName}}))
			offset += 4
{{- else if eq .Type "uint16"}}
			binary.LittleEndian.PutUint16(dest[offset:], nestedElem.{{.GoName}})
			offset += 2
{{- else if eq .Type "int16"}}
			binary.LittleEndian.PutUint16(dest[offset:], uint16(nestedElem.{{.GoName}}))
			offset += 2
{{- else if eq .Type "int8"}}
			dest[offset] = byte(nestedElem.{{.GoName}})
			offset++
{{- else if eq .Type "uint8"}}
			dest[offset] = nestedElem.{{.GoName}}
			offset++
{{- else}}
			dest[offset] = byte(nestedElem.{{.GoName}})
			offset++
{{- end}}
{{- end}}
{{- end}}

{{define "sizeFields"}}
{{- range .}}
{{- if .IsDynamicArray}}
		for _, nestedElem := range elem.{{.GoName}} {
			_ = nestedElem
			{{- template "sizeNestedFields" .ArrayElements}}
		}
{{- else if eq .Type "string"}}
		size += 1 + uint32(len(elem.{{.GoName}}))
{{- else if .IsArray}}
		{{- if eq (baseType .Type) "float32"}}
		size += uint32({{.Size}}) * 4
		{{- else if eq (baseType .Type) "uint32"}}
		size += uint32({{.Size}}) * 4
		{{- else if eq (baseType .Type) "uint16"}}
		size += uint32({{.Size}}) * 2
		{{- else}}
		size += uint32({{.Size}})
		{{- end}}
{{- else if eq .Type "float32"}}
		size += 4
{{- else if eq .Type "types.Color"}}
		size += 3
{{- else if eq .Type "uint32"}}
		size += 4
{{- else if eq .Type "int32"}}
		size += 4
{{- else if eq .Type "uint16"}}
		size += 2
{{- else if eq .Type "int16"}}
		size += 2
{{- else}}
		size += 1
{{- end}}
{{- end}}
{{end}}

{{define "sizeNestedFields"}}
{{- range .}}
{{- if .IsDynamicArray}}
			for _, deepNested := range nestedElem.{{.GoName}} {
				// TODO: Handle deeper nesting size calculation
				_ = deepNested
			}
{{- else if eq .Type "string"}}
			size += 1 + uint32(len(nestedElem.{{.GoName}}))
{{- else if .IsArray}}
			{{- if eq (baseType .Type) "float32"}}
			size += uint32({{.Size}}) * 4
			{{- else if eq (baseType .Type) "uint32"}}
			size += uint32({{.Size}}) * 4
			{{- else if eq (baseType .Type) "uint16"}}
			size += uint32({{.Size}}) * 2
			{{- else}}
			size += uint32({{.Size}})
			{{- end}}
{{- else if eq .Type "float32"}}
			size += 4
{{- else if eq .Type "types.Color"}}
			size += 3
{{- else if eq .Type "uint32"}}
			size += 4
{{- else if eq .Type "int32"}}
			size += 4
{{- else if eq .Type "uint16"}}
			size += 2
{{- else if eq .Type "int16"}}
			size += 2
{{- else}}
			size += 1
{{- end}}
{{- end}}
{{end}}
`

func contains(s, substr string) bool {
	return strings.Contains(s, substr)
}

func toGoNameInTemplate(name string) string {
	return toGoName(name)
}

func baseType(arrayType string) string {
	// Extract base type from array declaration like "[10]uint32"
	if strings.HasPrefix(arrayType, "[") {
		idx := strings.Index(arrayType, "]")
		if idx > 0 {
			return arrayType[idx+1:]
		}
	}
	return arrayType
}

func sizeOf(typeName string) int {
	// Return the size in bytes for a given type
	switch typeName {
	case "int8", "uint8", "byte":
		return 1
	case "int16", "uint16":
		return 2
	case "types.Color":
		return 3 // RGB: 3 bytes
	case "int32", "uint32", "float32":
		return 4
	case "int64", "uint64", "float64":
		return 8
	case "types.EquipmentItem":
		return 8 // 2 bytes type + 3 bytes color + 3 bytes highlight
	default:
		return 0
	}
}

func main() {
	var (
		input   = flag.String("input", "", "Input XML file or directory")
		output  = flag.String("output", "", "Output Go file or directory")
		pkgName = flag.String("package", "generated", "Package name for generated code")
	)
	flag.Parse()

	if *input == "" || *output == "" {
		fmt.Fprintf(os.Stderr, "Usage: %s -input <xml-file> -output <go-file> [-package <name>]\n", os.Args[0])
		os.Exit(1)
	}

	// Get file info
	info, err := os.Stat(*input)
	if err != nil {
		log.Fatalf("Error accessing input: %v", err)
	}

	if info.IsDir() {
		// Process directory
		files, err := filepath.Glob(filepath.Join(*input, "*.xml"))
		if err != nil {
			log.Fatalf("Error listing XML files: %v", err)
		}

		for _, xmlFile := range files {
			processFile(xmlFile, *output, *pkgName)
		}
	} else {
		// Process single file
		processFile(*input, *output, *pkgName)
	}
}

func processFile(inputFile, outputPath, packageName string) {
	log.Printf("Processing %s...", inputFile)

	// Parse XML file
	structs, err := parseXMLFile(inputFile)
	if err != nil {
		log.Printf("Error parsing %s: %v", inputFile, err)
		return
	}

	// Convert to Go structs
	goStructs := make([]GoStruct, 0, len(structs))

	for _, xmlStruct := range structs {
		goStructs = append(goStructs, convertToGoStruct(xmlStruct, packageName))
	}

	// Determine output file
	outputFile := outputPath
	if info, err := os.Stat(outputPath); err == nil && info.IsDir() {
		base := strings.TrimSuffix(filepath.Base(inputFile), ".xml")
		outputFile = filepath.Join(outputPath, base+".go")
	}

	// Generate code
	tmpl := template.New("struct")
	tmpl.Funcs(template.FuncMap{
		"contains": contains,
		"baseType": baseType,
		"toGoName": toGoNameInTemplate,
		"sizeOf":   sizeOf,
	})

	// Parse the main template
	tmpl, err = tmpl.Parse(structTemplate)
	if err != nil {
		log.Fatalf("Error parsing template: %v", err)
	}

	// Create output file
	out, err := os.Create(outputFile)
	if err != nil {
		log.Fatalf("Error creating output file: %v", err)
	}
	defer out.Close()

	// Check if math package is needed (for float types)
	needsMath := false
	for _, goStruct := range goStructs {
		for _, field := range goStruct.Fields {
			if strings.Contains(field.Type, "float") {
				needsMath = true
				break
			}
		}
		if needsMath {
			break
		}
	}

	// Execute template
	data := GenerateOutput{
		SourceFile:  filepath.Base(inputFile),
		PackageName: packageName,
		Structs:     goStructs,
		NeedsMath:   needsMath,
	}

	if err := tmpl.Execute(out, data); err != nil {
		log.Fatalf("Error executing template: %v", err)
	}

	log.Printf("Generated %s", outputFile)
}
