/*
Generator for the API type definition on the go server side
*/
package main

import (
	"flag"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"
	"unicode"

	"git.mstar.dev/mstar/goutils/sliceutils"
)

var findRoleStructRegex = regexp.MustCompile(`type Role struct \{([\s\S]+)\}\n\n/\*`)

var (
	flagInputFile  = flag.String("input", "", "Specify the input file. If empty, read from stdin")
	flagOutputFile = flag.String(
		"output",
		"",
		"Specify the output file. If empty, writes to stdout",
	)
)

func main() {
	flag.Parse()
	var input io.Reader
	var output io.Writer

	if *flagInputFile == "" {
		input = os.Stdin
	} else {
		file, err := os.Open(*flagInputFile)
		if err != nil {
			panic(err)
		}
		defer file.Close()
		input = file
	}
	if *flagOutputFile == "" {
		output = os.Stdout
	} else {
		file, err := os.Create(*flagOutputFile)
		if err != nil {
			panic(err)
		}
		defer file.Close()
		output = file
	}

	data, err := io.ReadAll(input)
	if err != nil {
		panic(err)
	}
	if !findRoleStructRegex.Match(data) {
		panic("Input doesn't contain role struct")
	}
	content := findRoleStructRegex.FindStringSubmatch(string(data))[1]
	lines := strings.Split(content, "\n")
	lines = sliceutils.Map(lines, func(t string) string { return strings.TrimSpace(t) })
	importantLines := sliceutils.Filter(lines, func(t string) bool {
		if strings.HasPrefix(t, "//") {
			return false
		}
		if strings.Contains(t, "gorm.Model") {
			return false
		}
		data := sliceutils.Filter(strings.Split(t, " "), func(t string) bool { return t != "" })
		if len(data) < 2 {
			return false
		}
		if !strings.HasPrefix(data[1], "*") && !strings.HasPrefix(data[1], "[]") {
			return false
		}
		return true
	})
	nameTypeMap := map[string]string{}
	for _, line := range importantLines {
		parts := sliceutils.Filter(strings.Split(line, " "), func(t string) bool { return t != "" })
		nameTypeMap[parts[0]] = parts[1]
	}

	outBuilder := strings.Builder{}
	outBuilder.WriteString(`// Code generated by cmd/RolesApiTypeGenerator DO NOT EDIT.
// If you need to refresh the content, run go generate again
`)
	outBuilder.WriteString("package server\n\n")
	outBuilder.WriteString("import \"time\"\n")

	outBuilder.WriteString("type linstromRole struct {\n")
	outBuilder.WriteString("  Id uint `jsonapi:\"primary,roles\"`\n")
	outBuilder.WriteString(fNT("CreatedAt", "time.Time"))
	outBuilder.WriteString(fNT("UpdatedAt", "time.Time"))
	outBuilder.WriteString(fNT("Name", "string"))
	outBuilder.WriteString(fNT("Priority", "uint32"))
	outBuilder.WriteString(fNT("IsUserRole", "bool"))
	outBuilder.WriteString(fNT("IsBuiltIn", "bool"))

	for n, t := range nameTypeMap {
		outBuilder.WriteString(fNT(n, t))
	}

	outBuilder.WriteString("}")
	fmt.Fprint(output, outBuilder.String())
}

func convertGoNameToJsonApiName(name string) string {
	buffer := []rune{}
	isFirst := true
	for _, char := range name {
		if unicode.IsUpper(char) {
			if isFirst {
				buffer = append(buffer, unicode.ToLower(char))
				isFirst = false
			} else {
				buffer = append(buffer, '-', unicode.ToLower(char))
			}
		} else {
			buffer = append(buffer, char)
		}
	}
	return string(buffer)
}

func fNT(name, valType string) string {
	return fmt.Sprintf(
		"  %s %s `jsonapi:\"attr,%s\"`\n",
		name,
		valType,
		convertGoNameToJsonApiName(name),
	)
}
