package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"path"
	"regexp"
	"runtime"
	"strconv"
	"strings"

	"github.com/artalkjs/artalk/v2/internal/config"
	"github.com/artalkjs/artalk/v2/internal/config/meta"
	"github.com/artalkjs/artalk/v2/internal/pkged"
	"github.com/samber/lo"
)

var (
	workDir    string
	locale     string
	format     string
	outputFile string
	pkgName    string
)

func init() {
	flag.StringVar(&workDir, "w", "", "specify work directory")
	flag.StringVar(&outputFile, "o", "", "specify save file")
	flag.StringVar(&format, "format", "markdown", "specify output format")
	flag.StringVar(&locale, "locale", "zh-cn", "specify locale")
	flag.StringVar(&pkgName, "pkg", "config", "specify output package name")
}

func main() {
	flag.Parse()

	// change WorkDir run on project root
	if workDir != "" {
		if err := os.Chdir(workDir); err != nil {
			panic(err)
		}
	}

	// load assets fs
	{
		_, filename, _, _ := runtime.Caller(0)
		rootDir := path.Join(path.Dir(filename), "../../../../")
		dirFS := os.DirFS(rootDir)
		pkged.SetFS(dirFS)
	}

	// get metadata
	confTemplate := config.Template(locale)
	metas, err := meta.GetOptionsMetaData(confTemplate)
	if err != nil {
		panic(err)
	}

	// get mapping data
	mapping := meta.GetEnvPathMap(config.Config{}, confTemplate)

	// output
	result := ""
	switch format {
	case "go":
		result = getGo(metas, mapping)
	case "json":
		result = getJSON(metas)
	case "markdown":
		result = getMarkdown(metas)
	default:
		panic("invalid output format")
	}

	// write
	if outputFile == "" {
		fmt.Println(result)
	} else {
		switch format {
		case "markdown":
			writeMarkdown(outputFile, result)
		case "go", "json":
			file, err := os.Create(outputFile)
			if err != nil {
				fmt.Println("Error creating file:", err)
				return
			}
			defer file.Close()

			_, err = file.WriteString(result)
			if err != nil {
				fmt.Println("Error writing to file:", err)
			}

			fmt.Println("File written successfully")
		default:
			panic("the format does not support writing to a file")
		}
	}
}

func getGo(metas []meta.OptionsMeta, mapping map[string]string) string {
	str := "// Code generated by go run internal/config/meta/gen/main.go. DO NOT EDIT.\n\n"
	str += "package " + pkgName + "\n\nimport \"github.com/artalkjs/artalk/v2/internal/config/meta\"\n\n"
	str += "// Cache result of `meta.GetEnvPathMap(config.Config{}, config.Template(\"" + locale + "\"))`\n"
	str += fmt.Sprintf("var EnvPathMapCache = %#v\n\n", mapping)
	str += "// Cache result of `meta.GetOptionsMetaData(config.Template(\"" + locale + "\"))`\n"
	str += fmt.Sprintf("var OptionsMetaCache = %#v\n", metas)
	return str
}

func getJSON(metas []meta.OptionsMeta) string {
	str, err := json.MarshalIndent(metas, "", "  ")
	if err != nil {
		panic(err)
	}
	return string(str)
}

func getMarkdown(metas []meta.OptionsMeta) string {
	md := ""

	// Get root nodes
	rootNodes := []meta.OptionsMeta{
		{
			Title:  "通用配置",
			Desc:   "通用配置项",
			Path:   "general",
			IsRoot: true,
		},
	}
	rootNodes = append(rootNodes, lo.Filter(metas, func(m meta.OptionsMeta, _ int) bool {
		return m.IsRoot && m.HasChild
	})...)

	printGroup := func(root meta.OptionsMeta, items []meta.OptionsMeta) {
		md += fmt.Sprintf("## %s\n\n", root.Title)
		md += fmt.Sprintln("| 环境变量 | 默认值 | 描述 | 路径 |")
		md += fmt.Sprintln("| --- | --- | --- | --- |")
		for _, m := range items {
			if m.Title == "Enabled" {
				m.Title = "启用"
			}
			options := lo.If(len(m.Options) > 0, "(可选：`["+strings.Join(lo.Map(m.Options, func(o string, _ int) string { return strconv.Quote(o) }), ", ")+"]`) ").Else("")
			desc := lo.If(m.Desc != "", "("+m.Desc+") ").Else("")

			defaultValue := m.Default
			if t, ok := m.Default.(string); ok {
				defaultValue = strconv.Quote(t)
			}

			md += fmt.Sprintf("| **%s** | `%v` | %s %s%s| %s (%s) |\n", m.Env, defaultValue, m.Title, desc, options, m.Path, m.PathText)
		}
		md += "\n\n"
	}

	for _, root := range rootNodes {
		printGroup(root, lo.Filter(metas, func(m meta.OptionsMeta, _ int) bool {
			if root.Path == "general" {
				return m.AllowsSet && m.IsRoot
			} else {
				return m.AllowsSet && !m.IsRoot && strings.HasPrefix(m.Path, root.Path)
			}
		}))
	}

	return strings.TrimSpace(md)
}

func writeMarkdown(file string, text string) {
	// read file content
	content, err := os.ReadFile(file)
	if err != nil {
		panic(err)
	}

	// find `<!-- env-variables --><!-- /env-variables -->`
	start := "<!-- env-variables -->"
	end := "<!-- /env-variables -->"
	re := regexp.MustCompile(start + `(?s:(.*?))` + end)
	matches := re.FindAllStringSubmatch(string(content), -1)
	if len(matches) == 0 {
		panic("<!-- env-variables --> not found")
	}

	// replace
	newContent := strings.ReplaceAll(string(content), matches[0][0], start+"\n\n"+text+"\n\n"+end)

	// write file
	if err := os.WriteFile(file, []byte(newContent), 0644); err != nil {
		panic(err)
	}

	fmt.Println("File updated successfully")
}
