//go:build ignore

// gen-clone generates the deepCloneTask function for internal/store.
//
// It is invoked via go:generate from internal/store/models.go and writes
// internal/store/tasks_clone_gen.go.  Run from the package directory:
//
//	go generate ./internal/store/
package main

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"log"
	"os"
	"unicode"
)

// inputFile and outputFile are relative to CWD, which is internal/store/
// when invoked via go:generate.
const (
	inputFile  = "models.go"
	outputFile = "tasks_clone_gen.go"
)

// main parses the Task struct from models.go, classifies each field by its
// clone strategy (shallow copy, slices.Clone, maps.Clone, or pointer deref),
// and writes the deepCloneTask function to tasks_clone_gen.go.
func main() {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, inputFile, nil, 0)
	if err != nil {
		log.Fatalf("parse %s: %v", inputFile, err)
	}

	// Collect all struct types defined in the file so we can inspect element
	// types of slice fields to decide whether they need a custom clone helper.
	structs := map[string]*ast.StructType{}
	for _, decl := range f.Decls {
		genDecl, ok := decl.(*ast.GenDecl)
		if !ok {
			continue
		}
		for _, spec := range genDecl.Specs {
			typeSpec, ok := spec.(*ast.TypeSpec)
			if !ok {
				continue
			}
			structType, ok := typeSpec.Type.(*ast.StructType)
			if !ok {
				continue
			}
			structs[typeSpec.Name.Name] = structType
		}
	}

	taskStruct, ok := structs["Task"]
	if !ok {
		log.Fatal("Task struct not found in " + inputFile)
	}

	// hasComplexFields reports whether a named struct type (looked up by name)
	// contains any field that requires deep cloning: a slice, map, or pointer.
	// This is used to decide whether a []ElemType field on Task should use
	// slices.Clone (safe when ElemType is flat) or a custom clone<ElemType>Slice
	// helper (necessary when ElemType itself contains pointer-bearing sub-fields
	// such as a nested slice).  Only one level of recursion is needed for the
	// current model hierarchy.
	hasComplexFields := func(typeName string) bool {
		st, ok := structs[typeName]
		if !ok {
			return false // builtin or external type — assume flat
		}
		for _, field := range st.Fields.List {
			switch field.Type.(type) {
			case *ast.ArrayType, *ast.MapType, *ast.StarExpr:
				return true
			}
		}
		return false
	}

	// fieldOp records what the generator should emit for one Task field.
	type fieldOp struct {
		kind string // "slice" | "slice_custom" | "map" | "ptr"
		name string // Go field name
		aux  string // slice_custom: helper fn name; ptr: local variable name
	}

	// Walk every field of the Task struct and classify it by the kind of clone
	// operation needed. Scalar types are handled by the shallow copy (`cp := *t`)
	// so only slices, maps, and pointers generate explicit code.
	var ops []fieldOp
	needSlices := false
	needMaps := false

	for _, field := range taskStruct.Fields.List {
		if len(field.Names) == 0 {
			continue // embedded field — nothing to do
		}
		for _, ident := range field.Names {
			name := ident.Name
			switch t := field.Type.(type) {
			case *ast.ArrayType:
				needSlices = true
				// If the element type is a named struct in this package that
				// itself has complex (pointer-bearing) sub-fields, we must not
				// use slices.Clone — it only copies the slice header and would
				// leave the sub-fields shared between the original and the clone.
				// Instead, emit a call to clone<ElemType>Slice and leave that
				// manually-written helper intact in models.go.
				if elemIdent, ok := t.Elt.(*ast.Ident); ok && hasComplexFields(elemIdent.Name) {
					customFn := "clone" + elemIdent.Name + "Slice"
					ops = append(ops, fieldOp{"slice_custom", name, customFn})
				} else {
					ops = append(ops, fieldOp{"slice", name, ""})
				}
			case *ast.MapType:
				_ = t // the type assertion variable is unused; we only need the type match
				needMaps = true
				ops = append(ops, fieldOp{"map", name, ""})
			case *ast.StarExpr:
				_ = t // the type assertion variable is unused; we only need the type match
				// Each pointer field gets an explicit nil-guard + dereference copy
				// so the caller gets a fully independent Task value.
				ops = append(ops, fieldOp{"ptr", name, lowerFirst(name)})
				// All other types (primitives, named scalars, time.Time, uuid.UUID,
				// plain structs without pointer-bearing sub-fields) are handled by
				// the shallow `cp := *t` copy at the top of the function.
			}
		}
	}

	// Build the output source file into buf, then gofmt it before writing.
	var buf bytes.Buffer

	buf.WriteString("// Code generated by scripts/gen-clone.go; DO NOT EDIT.\n\n")
	buf.WriteString("package store\n\n")

	// Only emit import block when the generated function actually needs stdlib
	// generic helpers (slices.Clone / maps.Clone).
	if needSlices || needMaps {
		buf.WriteString("import (\n")
		if needMaps {
			buf.WriteString("\t\"maps\"\n")
		}
		if needSlices {
			buf.WriteString("\t\"slices\"\n")
		}
		buf.WriteString(")\n\n")
	}

	buf.WriteString("// deepCloneTask returns a full copy of t, duplicating all slices, maps, and\n")
	buf.WriteString("// pointer-bearing fields so callers can safely mutate the returned value.\n")
	buf.WriteString("func deepCloneTask(t *Task) Task {\n")
	buf.WriteString("\tif t == nil {\n")
	buf.WriteString("\t\treturn Task{}\n")
	buf.WriteString("\t}\n\n")
	buf.WriteString("\tcp := *t\n")

	// Emit clone assignments in three passes (slices, maps, pointers) to
	// group related operations and produce readable generated code.

	// Pass 1: Emit slice assignments.
	for _, op := range ops {
		switch op.kind {
		case "slice":
			fmt.Fprintf(&buf, "\tcp.%s = slices.Clone(t.%s)\n", op.name, op.name)
		case "slice_custom":
			fmt.Fprintf(&buf, "\tcp.%s = %s(t.%s)\n", op.name, op.aux, op.name)
		}
	}

	// Pass 2: Emit map assignments.
	for _, op := range ops {
		if op.kind == "map" {
			fmt.Fprintf(&buf, "\tcp.%s = maps.Clone(t.%s)\n", op.name, op.name)
		}
	}

	// Pass 3: Emit pointer nil-guard + dereference-copy blocks.
	buf.WriteString("\n")
	for _, op := range ops {
		if op.kind != "ptr" {
			continue
		}
		fmt.Fprintf(&buf, "\tif t.%s != nil {\n", op.name)
		fmt.Fprintf(&buf, "\t\t%s := *t.%s\n", op.aux, op.name)
		fmt.Fprintf(&buf, "\t\tcp.%s = &%s\n", op.name, op.aux)
		fmt.Fprintf(&buf, "\t}\n")
	}

	buf.WriteString("\n\treturn cp\n")
	buf.WriteString("}\n")

	// Run gofmt on the generated source to ensure canonical formatting.
	src, err := format.Source(buf.Bytes())
	if err != nil {
		log.Fatalf("format error: %v\nsource:\n%s", err, buf.String())
	}

	if err := os.WriteFile(outputFile, src, 0644); err != nil {
		log.Fatalf("write %s: %v", outputFile, err)
	}
	fmt.Println("generated", outputFile)
}

// lowerFirst returns s with the first Unicode code point lowercased.
// Used to derive a local variable name from a field name, e.g. "SessionID" → "sessionID".
func lowerFirst(s string) string {
	if s == "" {
		return s
	}
	runes := []rune(s)
	runes[0] = unicode.ToLower(runes[0])
	return string(runes)
}
