//go:build ignore

package main

import (
	"encoding/xml"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"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"`
}

// Registration represents a packet registration entry
type Registration struct {
	StructName    string
	TypeName      string
	Version       uint16
	OpcodeName    string
}

// RegistrationData holds all registration data for template
type RegistrationData struct {
	PackageName   string
	Registrations []Registration
}

const registrationTemplate = `// Code generated by go generate; DO NOT EDIT.
// This file was generated from XML packet definitions

package {{.PackageName}}

import (
	"reflect"
	"git.sharkk.net/EQ2/Protocol/defs/generated"
)

// initializePacketMappings loads all packet structure definitions
func (pm *PacketManager) initializePacketMappings() {
{{range .Registrations}}	pm.registerStruct("{{.StructName}}", {{.Version}}, "{{.OpcodeName}}", reflect.TypeOf(generated.{{.TypeName}}{}))
{{end}}}
`

func main() {
	// Process all XML files
	xmlDir := filepath.Join(".", "xml")
	outputFile := "registrations_generated.go"

	registrations := []Registration{}

	// Process common.xml
	commonFile := filepath.Join(xmlDir, "common.xml")
	if regs, err := processXMLFile(commonFile); err == nil {
		registrations = append(registrations, regs...)
	} else {
		log.Printf("Warning: Could not process common.xml: %v", err)
	}

	// Process login.xml
	loginFile := filepath.Join(xmlDir, "login.xml")
	if regs, err := processXMLFile(loginFile); err == nil {
		registrations = append(registrations, regs...)
	} else {
		log.Printf("Warning: Could not process login.xml: %v", err)
	}

	// Sort registrations by struct name, then version
	sort.Slice(registrations, func(i, j int) bool {
		if registrations[i].StructName != registrations[j].StructName {
			return registrations[i].StructName < registrations[j].StructName
		}
		return registrations[i].Version < registrations[j].Version
	})

	// Generate the registration code
	if err := generateRegistrationCode(registrations, outputFile); err != nil {
		log.Fatalf("Failed to generate registration code: %v", err)
	}

	log.Printf("Generated %s with %d registrations", outputFile, len(registrations))
}

func processXMLFile(filename string) ([]Registration, 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 in root element if needed
	xmlContent := string(data)
	if !strings.Contains(xmlContent, "<structs>") {
		xmlContent = "<structs>\n" + xmlContent + "\n</structs>"
	}

	var def XMLDefinition
	if err := xml.Unmarshal([]byte(xmlContent), &def); err != nil {
		return nil, fmt.Errorf("failed to parse XML: %v", err)
	}

	registrations := []Registration{}

	for _, s := range def.Structs {
		if s.OpcodeName == "" {
			continue // Skip structs without opcodes
		}

		version, err := strconv.ParseUint(s.ClientVersion, 10, 16)
		if err != nil {
			log.Printf("Warning: Invalid version %s for %s", s.ClientVersion, s.Name)
			continue
		}

		// Determine the Go type name (remove underscores from struct names)
		typeName := strings.ReplaceAll(s.Name, "_", "")
		if version > 1 {
			typeName = fmt.Sprintf("%sV%d", typeName, version)
		}

		registrations = append(registrations, Registration{
			StructName: s.Name,
			TypeName:   typeName,
			Version:    uint16(version),
			OpcodeName: s.OpcodeName,
		})
	}

	return registrations, nil
}

func generateRegistrationCode(registrations []Registration, outputFile string) error {
	tmpl, err := template.New("registration").Parse(registrationTemplate)
	if err != nil {
		return fmt.Errorf("failed to parse template: %v", err)
	}

	out, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("failed to create output file: %v", err)
	}
	defer out.Close()

	data := RegistrationData{
		PackageName:   "defs",
		Registrations: registrations,
	}

	if err := tmpl.Execute(out, data); err != nil {
		return fmt.Errorf("failed to execute template: %v", err)
	}

	return nil
}