package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	_ "embed"

	logger "git.site.quack-lab.dev/dave/cylogger"
	utils "git.site.quack-lab.dev/dave/cyutils"
	"github.com/PuerkitoBio/goquery"
)

func main() {
	outdir := flag.String("o", ".", "Output directory")
	flag.Parse()
	logger.InitFlag()
	logger.Info("Starting...")

	files := flag.Args()
	if len(files) == 0 {
		logger.Error("No files specified")
		flag.Usage()
		return
	}

	// Group files by their base class name (without [Client]/[Server])
	classGroups := make(map[string][]string)
	for _, file := range files {
		// Extract base class name from filename
		baseName := extractBaseClassName(file)
		classGroups[baseName] = append(classGroups[baseName], file)
	}

	// Convert groups to work items for parallel processing
	var workItems []WorkItem
	for baseName, groupFiles := range classGroups {
		workItems = append(workItems, WorkItem{
			BaseName:   baseName,
			GroupFiles: groupFiles,
		})
	}

	// Process each group in parallel
	utils.WithWorkers(100, workItems, func(worker int, item WorkItem) {
		// First check for enums in each file
		for _, file := range item.GroupFiles {
			hasEnums, err := processEnums(file, *outdir)
			if err != nil {
				logger.Error("Error processing enums in file %s: %v", file, err)
			}
			// If the file only contained enums, skip class parsing
			if hasEnums && !strings.Contains(file, "[Client]") && !strings.Contains(file, "[Server]") {
				return
			}
		}

		// Then process classes
		if len(item.GroupFiles) == 1 {
			// Single file, process normally
			class, err := ParseClass(item.GroupFiles[0])
			if err != nil {
				logger.Error("Error parsing file: %v", err)
				return
			}
			class.Write(*outdir, classTemplate)
		} else {
			// Multiple files for same class, merge them
			mergedClass, err := MergeClasses(item.GroupFiles)
			if err != nil {
				logger.Error("Error merging classes for %s: %v", item.BaseName, err)
				return
			}
			mergedClass.Write(*outdir, classTemplate)
		}
	})
}

func processEnums(file string, outdir string) (bool, error) {
	log := logger.Default.WithPrefix("processEnums")
	log.Info("Processing enums from file: %s", file)

	filehandle, err := os.Open(file)
	if err != nil {
		return false, fmt.Errorf("error opening file: %w", err)
	}
	defer filehandle.Close()

	doc, err := goquery.NewDocumentFromReader(filehandle)
	if err != nil {
		return false, fmt.Errorf("error parsing file: %w", err)
	}

	// Find all code containers
	codeblocks := doc.Find("div.codecontainer")
	log.Trace("Found %d code blocks", codeblocks.Length())

	foundEnums := false
	codeblocks.Each(func(i int, s *goquery.Selection) {
		// Check if this is an enum block
		enumType := s.Find("span.type").First()
		if !strings.HasPrefix(strings.TrimSpace(enumType.Text()), "enum") {
			return
		}

		foundEnums = true

		// Get enum name from the ID attribute
		enumName := s.AttrOr("id", "")
		if enumName == "" {
			return
		}

		enum := &Enum{
			Name:   enumName,
			Values: []string{},
		}

		// Get enum comment if any
		comment := s.Find("p").Text()
		if comment != "" {
			enum.Comment = strings.TrimSpace(comment)
		}

		// Get enum values
		s.Contents().Each(func(i int, s *goquery.Selection) {
			if s.Is("br") {
				return
			}
			text := strings.TrimSpace(s.Text())
			if text == "" || strings.HasPrefix(text, "enum") {
				return
			}
			enum.Values = append(enum.Values, text)
		})

		if len(enum.Values) > 0 {
			log.Info("Writing enum %s with %d values", enum.Name, len(enum.Values))
			if err := enum.Write(outdir, enumTemplate); err != nil {
				log.Error("Error writing enum %s: %v", enum.Name, err)
			}
		}
	})

	return foundEnums, nil
}

func MapType(t string) string {
	// Handle complex types like table<int, string>
	if strings.Contains(t, "<") && strings.Contains(t, ">") {
		// Extract the base type and inner types
		openBracket := strings.Index(t, "<")
		closeBracket := strings.LastIndex(t, ">")
		if openBracket != -1 && closeBracket != -1 {
			baseType := t[:openBracket]
			innerTypes := t[openBracket+1 : closeBracket]

			// Split inner types by comma, but be careful about nested brackets
			var mappedInnerTypes []string
			var current strings.Builder
			bracketDepth := 0

			for i := 0; i < len(innerTypes); i++ {
				char := innerTypes[i]
				if char == '<' {
					bracketDepth++
					current.WriteByte(char)
				} else if char == '>' {
					bracketDepth--
					current.WriteByte(char)
				} else if char == ',' && bracketDepth == 0 {
					// Only split on commas that are not inside nested brackets
					mappedInnerTypes = append(mappedInnerTypes, MapType(strings.TrimSpace(current.String())))
					current.Reset()
				} else {
					current.WriteByte(char)
				}
			}
			// Add the last inner type
			if current.Len() > 0 {
				mappedInnerTypes = append(mappedInnerTypes, MapType(strings.TrimSpace(current.String())))
			}

			// Reconstruct the complex type
			return baseType + "<" + strings.Join(mappedInnerTypes, ", ") + ">"
		}
	}

	// Handle simple types
	switch t {
	case "var":
		return "any"
	case "var...":
		return "..."
	case "int":
		return "number"
	case "int...":
		return "number..."
	case "unsigned int":
		return "number"
	case "float":
		return "number"
	case "double":
		return "number"
	case "bool":
		return "boolean"
	case "char":
		return "string"
	case "table_t":
		return "table"
	default:
		return t
	}
}

func IsReservedKeyword(t string) bool {
	switch t {
	case "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "any", "boolean", "number", "string", "table", "thread", "userdata", "var":
		return true
	}
	return false
}

// extractBaseClassName extracts the base class name from a filename
// e.g., "Player [Client].html" -> "Player"
func extractBaseClassName(filename string) string {
	// Extract filename without path
	base := filepath.Base(filename)
	// Remove .html extension
	base = strings.TrimSuffix(base, ".html")
	// Remove [Client] or [Server] suffix
	base = strings.ReplaceAll(base, " [Client]", "")
	base = strings.ReplaceAll(base, " [Server]", "")
	return base
}

// MergeClasses merges multiple class files into a single class
func MergeClasses(files []string) (*Class, error) {
	if len(files) == 0 {
		return nil, fmt.Errorf("no files to merge")
	}

	// Parse all classes
	var classes []*Class
	var baseName string
	var inheritance string

	for _, file := range files {
		class, err := ParseClass(file)
		if err != nil {
			return nil, fmt.Errorf("error parsing %s: %w", file, err)
		}
		classes = append(classes, class)

		// Use the first class name as base
		if baseName == "" {
			baseName = class.ClassName
		}

		// Check for inheritance information in the original file
		originalClassName := getOriginalClassName(file)
		if strings.Contains(originalClassName, " : ") {
			parts := strings.Split(originalClassName, " : ")
			if len(parts) == 2 {
				// Extract the inherited class name and clean it
				inheritedClass := strings.TrimSpace(parts[1])
				inheritedClass = strings.ReplaceAll(inheritedClass, "[Client]", "")
				inheritedClass = strings.ReplaceAll(inheritedClass, "[Server]", "")
				inheritedClass = strings.ReplaceAll(inheritedClass, "[", "")
				inheritedClass = strings.ReplaceAll(inheritedClass, "]", "")
				inheritedClass = strings.ReplaceAll(inheritedClass, "-", "_")
				inheritedClass = strings.ReplaceAll(inheritedClass, ",", "")
				inheritedClass = strings.ReplaceAll(inheritedClass, " ", "_")
				// Clean up multiple underscores
				for strings.Contains(inheritedClass, "__") {
					inheritedClass = strings.ReplaceAll(inheritedClass, "__", "_")
				}
				inheritedClass = strings.Trim(inheritedClass, "_")

				if inheritance == "" {
					inheritance = inheritedClass
				}
			}
		}
	}

	// Merge all classes into the first one
	merged := classes[0]

	// Set inheritance if found
	if inheritance != "" {
		merged.Inheritance = inheritance
	}

	// Create maps to track methods and fields by name
	methodMap := make(map[string]*Method)
	fieldMap := make(map[string]*Field)

	// Add methods from all classes, handling duplicates
	for _, class := range classes {
		for _, method := range class.Methods {
			if existing, exists := methodMap[method.Name]; exists {
				// Method exists in multiple contexts, update availability
				if !strings.Contains(existing.Comment, "[Client/Server]") {
					if strings.Contains(existing.Comment, "[Client]") && strings.Contains(method.Comment, "[Server]") {
						existing.Comment = "[Client/Server] " + strings.TrimPrefix(existing.Comment, "[Client] ")
					} else if strings.Contains(existing.Comment, "[Server]") && strings.Contains(method.Comment, "[Client]") {
						existing.Comment = "[Client/Server] " + strings.TrimPrefix(existing.Comment, "[Server] ")
					}
				}
			} else {
				// New method, add it
				methodMap[method.Name] = &method
			}
		}

		// Add fields from all classes, avoiding duplicates
		for _, field := range class.Fields {
			if _, exists := fieldMap[field.Name]; !exists {
				fieldMap[field.Name] = &field
			}
		}
	}

	// Convert maps back to slices
	merged.Methods = make([]Method, 0, len(methodMap))
	for _, method := range methodMap {
		merged.Methods = append(merged.Methods, *method)
	}

	merged.Fields = make([]Field, 0, len(fieldMap))
	for _, field := range fieldMap {
		merged.Fields = append(merged.Fields, *field)
	}

	return merged, nil
}

// getOriginalClassName extracts the original class name from the HTML file
func getOriginalClassName(file string) string {
	filehandle, err := os.Open(file)
	if err != nil {
		return ""
	}
	defer filehandle.Close()

	doc, err := goquery.NewDocumentFromReader(filehandle)
	if err != nil {
		return ""
	}

	class := doc.Find("div.floatright > h1")
	if class.Length() == 0 {
		return ""
	}

	return strings.TrimSpace(class.Text())
}

// WorkItem represents a group of files to be processed
type WorkItem struct {
	BaseName   string
	GroupFiles []string
}
