//go:build ignore

// clone_generate generates zdeepcopy.go which houses all the Clone methods.

package main

import (
	"bytes"
	"flag"
	"fmt"
	"go/ast"
	"log"
	"slices"
	"strings"

	"codeberg.org/miekg/dns"
	"codeberg.org/miekg/dns/internal/generate"
)

var skip = []string{""}

var hdr = `
// Code generated by "go run clone_generate.go"; DO NOT EDIT.

package dns

import (
	"slices"

    "codeberg.org/miekg/dns/rdata"
    "codeberg.org/miekg/dns/svcb"
    "codeberg.org/miekg/dns/deleg"
)

`

const out = "zclone.go"

func main() {
	flag.Parse()
	specs, err := generate.StructTypeSpecs("rdata/rdata.go")
	if err != nil {
		log.Fatalf("Failed to generate %s: %v", out, err)
	}
	emptydata, err := generate.StructTypeSpecs("types.go")
	if err != nil {
		log.Fatalf("Failed to generate %s: %v", out, err)
	}
	specs = append(specs, generate.FilterTypeSpecs(emptydata, generate.EmptyData)...)

	lspecs := len(specs)
	especs, err := generate.StructTypeSpecs("edns_types.go")
	if err != nil {
		log.Fatalf("Failed to generate %s: %v", out, err)
	}

	dspecs, err := generate.StructTypeSpecs("dso_types.go")
	if err != nil {
		log.Fatalf("Failed to generate %s: %v", out, err)
	}
	specs = append(specs, especs...)
	specs = append(specs, dspecs...)

	b := &bytes.Buffer{}
	b.WriteString(hdr)

	for i, spec := range specs {
		rrname := spec.Name.Name
		if slices.Contains(skip, rrname) {
			continue
		}

		fmt.Fprintf(b, "func (rr *%s) Clone() RR {\n", rrname)
		strct := spec.Type.(*ast.StructType)

		// an embedded type, need to be copied by using that type.
		// we detect these when the field name matches an rrtype
		// this only true for real rrs, not edns0 ones: ESU has URI field, that would be considered an RR.
		if generate.IsEmbedded(strct) && i < lspecs {
			field := strct.Fields.List[0].Type.(*ast.Ident).String()
			if _, ok := dns.StringToType[field]; ok {
				fmt.Fprintf(b, "return &%s{*rr.%s.Clone().(*%s)}\n}\n", rrname, field, field)
				continue
			}
		}

		fmt.Fprintf(b, "return &%s{\n", rrname)
		if i < lspecs { // edns0 types dont' even have Hdr.
			fmt.Fprintf(b, "\tHdr: rr.Hdr,\n")
		}

		if i < lspecs {
			if !slices.Contains(generate.EmptyData, rrname) {
				fmt.Fprintf(b, "%s: rdata.%s{\n", rrname, rrname)
			}
		}

		for _, field := range strct.Fields.List {
			if len(field.Names) == 0 {
				continue
			}

			fieldname := field.Names[0].String()
			if fieldname == "Hdr" {
				continue
			}

			// fieldtype is either slice or the actual singular type name.
			fieldtype := ""
			if id, ok := field.Type.(*ast.Ident); ok {
				fieldtype = id.Name
			}
			if _, ok := field.Type.(*ast.ArrayType); ok {
				fieldtype = "slice"
			}

			tag := ""
			if field.Tag != nil {
				tag = strings.Trim(field.Tag.Value, "`")
			}

			o := func(s string) { fmt.Fprintf(b, s, fieldname, fieldname) }

			if fieldtype == "slice" {
				switch tag {
				case `dns:"pairs"`:
					// create a function that returns the copied values
					// these pairs live in a subdir, which lowecased rrname, SVCB -> svbc
					// some fiddling here, to inject this name here, will keeping using o(...)
					// this should also work for DELEG in the future
					pkg := strings.ToLower(rrname)
					fn := `%s: func() []` + pkg + `.Pair {
						pairs := make([]` + pkg + `.Pair, len(rr.%[1]s))
						for i, p := range rr.%[1]s {
							pairs[i] = p.Clone()
						}
						return pairs
						}()` + ",\n"
					o(fn)
					continue
				case `dns:"infos"`:
					pkg := strings.ToLower(rrname)
					fn := `%s: func() []` + pkg + `.Info {
						infos := make([]` + pkg + `.Info, len(rr.%[1]s))
						for i, p := range rr.%[1]s {
							infos[i] = p.Clone()
						}
						return infos
						}()` + ",\n"
					o(fn)
					continue
				}

				o("%s: slices.Clone(rr.%s),\n")
				continue
			}

			if fieldname == "Data" { // only the case for RFC3597 RR
				o("%s: rr.RFC3597.%s,\n")
				continue
			}

			o("%s: rr.%s,\n")
		}
		if i < lspecs {
			if !slices.Contains(generate.EmptyData, rrname) {
				fmt.Fprint(b, "},\n")
			}
		}
		fmt.Fprint(b, "}\n}\n\n")
	}

	generate.Write(b, out)
}
