//go:generate bumper codegen --file versions.yaml --out version_schema_gen.go

package statecfg

import (
	"bytes"
	"fmt"
	"go/format"
	"os"
	"path/filepath"
	"text/template"

	"golang.org/x/text/cases"
	"golang.org/x/text/language"
	"gopkg.in/yaml.v3"

	"github.com/erigontech/erigon/common/log/v3"
	"github.com/erigontech/erigon/db/version"
)

type Versioned interface {
	GetVersions() VersionTypes
}

type Version = version.Version

/* ---------- YAML ---------- */

type pair struct {
	Current Version `yaml:"current"`
	Min     Version `yaml:"min"`
}

type domainSection map[string]pair        // kv, bt, …
type domainEntry map[string]domainSection // domain, hist, ii
type yamlRoot map[string]domainEntry      // accounts, storage, …

/* ---------- Generator ---------- */

func GenerateSchemaVersions(yamlPath, outPath string) error {
	raw, err := os.ReadFile(yamlPath)
	if err != nil {
		return err
	}
	var cfg yamlRoot
	if err := yaml.Unmarshal(raw, &cfg); err != nil {
		return err
	}

	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, cfg); err != nil {
		return err
	}

	if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
		return err
	}

	return writeGoFile(outPath, buf.Bytes())
}

func writeGoFile(path string, src []byte) error {
	formatted, err := format.Source(src)
	if err != nil {
		// at least keep original if format failing
		log.Warn("failed to format generated code", "err", err)
		return os.WriteFile(path, src, 0644)
	}
	return os.WriteFile(path, formatted, 0644)
}

/* ---------- Helpers ---------- */

func versLit(v Version) string {
	return fmt.Sprintf("version.Version{%d, %d}", v.Major, v.Minor)
}

func goStruct(dom string) string {
	switch dom {
	case "accounts":
		return "AccountsDomain"
	case "storage":
		return "StorageDomain"
	case "code":
		return "CodeDomain"
	case "commitment":
		return "CommitmentDomain"
	case "receipt":
		return "ReceiptDomain"
	case "rcache":
		return "RCacheDomain"
	case "logaddrs":
		return "LogAddrIdx"
	case "logtopics":
		return "LogTopicIdx"
	case "tracesfrom":
		return "TracesFromIdx"
	case "tracesto":
		return "TracesToIdx"
	case "transactiontoblock":
		return "TxnHash2BlockNumBlock"
	case Headers:
		return "HeadersBlock"
	case Bodies:
		return "BodiesBlock"
	case Transactions:
		return "TransactionsBlock"
	default:
		return cases.Title(language.Und).String(dom) + "Domain"
	}
}

func pathPrefix(sec, dom string) string {
	if sec == "domain" || sec == "block" {
		return ".FileVersion"
	}
	if sec == "hist" {
		return ".Hist.FileVersion"
	}
	// ii
	switch dom {
	case "logaddrs", "logtopics", "tracesfrom", "tracesto":
		return ".FileVersion"
	default:
		return ".Hist.IiCfg.FileVersion"
	}
}

func fieldName(sec, key string) string {
	switch sec {
	case "domain":
		switch key {
		case "kv":
			return "DataKV"
		case "bt":
			return "AccessorBT"
		case "kvei":
			return "AccessorKVEI"
		case "kvi":
			return "AccessorKVI"
		}
	case "hist":
		switch key {
		case "v":
			return "DataV"
		case "vi":
			return "AccessorVI"
		}
	case "ii":
		switch key {
		case "ef":
			return "DataEF"
		case "efi":
			return "AccessorEFI"
		}
	case "block":
		switch key {
		case "idx":
			return "AccessorIdx"
		case "seg":
			return "DataSeg"
		}
	}

	return "UNKNOWN"
}

/* ---------- Template ---------- */

var tmpl = template.Must(template.New("schema").
	Funcs(template.FuncMap{
		"goStruct":   goStruct,
		"pathPrefix": pathPrefix,
		"field":      fieldName,
		"vlit":       versLit,
	}).Parse(`// Code generated by bumper; DO NOT EDIT.

package statecfg

import "github.com/erigontech/erigon/db/version"

func InitSchemasGen() {
{{- range $dom, $body := . }}
{{- $base := printf "Schema.%s" (goStruct $dom) }}
{{- range $sec, $pairs := $body }}
{{- range $k, $v := $pairs }}
    {{ $base }}{{ pathPrefix $sec $dom }}.{{ field $sec $k }} = version.Versions{ {{ vlit $v.Current }}, {{ vlit $v.Min }} }
{{- end }}{{ end }}

{{- end }}
}
`))
