package binding

import (
	"bytes"
	_ "embed"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/wailsapp/wails/v2/internal/fs"

	"github.com/leaanthony/slicer"
)

const _comment = `

const backend = {
        main: {
            "xbarApp": {
                "GetCategories": () => {
                    window.backend.main.xbarApp.GetCategories.call(arguments);
                },

                /**
                 * @param {string} arg1
                 */
                "InstallPlugin": (arg1) => {
                    window.backend.main.xbarApp.InstallPlugin.call(arguments);
                },
                "GetPlugins": () => {
                    window.backend.main.xbarApp.GetPlugins.call(arguments);
                }
            }
        }
}

export default backend;`

//go:embed assets/package.json
var packageJSON []byte

func (b *Bindings) GenerateBackendJS() {

	store := b.db.store
	var output bytes.Buffer

	output.WriteString(`// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT

const backend = {`)
	output.WriteString("\n")

	var sortedPackageNames slicer.StringSlicer
	for packageName := range store {
		sortedPackageNames.Add(packageName)
	}
	sortedPackageNames.Sort()
	for _, packageName := range sortedPackageNames.AsSlice() {
		packages := store[packageName]
		output.WriteString(fmt.Sprintf("  \"%s\": {", packageName))
		output.WriteString("\n")
		var sortedStructNames slicer.StringSlicer
		for structName := range packages {
			sortedStructNames.Add(structName)
		}
		sortedStructNames.Sort()
		for _, structName := range sortedStructNames.AsSlice() {
			structs := packages[structName]
			output.WriteString(fmt.Sprintf("    \"%s\": {", structName))
			output.WriteString("\n")

			var sortedMethodNames slicer.StringSlicer
			for methodName := range structs {
				sortedMethodNames.Add(methodName)
			}
			sortedMethodNames.Sort()

			for _, methodName := range sortedMethodNames.AsSlice() {
				methodDetails := structs[methodName]
				output.WriteString("      /**\n")
				output.WriteString("       * " + methodName + "\n")
				var args slicer.StringSlicer
				for count, input := range methodDetails.Inputs {
					arg := fmt.Sprintf("arg%d", count+1)
					args.Add(arg)
					output.WriteString(fmt.Sprintf("       * @param {%s} %s - Go Type: %s\n", goTypeToJSDocType(input.TypeName), arg, input.TypeName))
				}
				returnType := "Promise"
				returnTypeDetails := ""
				if methodDetails.OutputCount() > 0 {
					firstType := goTypeToJSDocType(methodDetails.Outputs[0].TypeName)
					returnType += "<" + firstType
					if methodDetails.OutputCount() == 2 {
						secondType := goTypeToJSDocType(methodDetails.Outputs[1].TypeName)
						returnType += "|" + secondType
					}
					returnType += ">"
					returnTypeDetails = " - Go Type: " + methodDetails.Outputs[0].TypeName
				}
				output.WriteString("       * @returns {" + returnType + "} " + returnTypeDetails + "\n")
				output.WriteString("       */\n")
				argsString := args.Join(", ")
				output.WriteString(fmt.Sprintf("      \"%s\": (%s) => {", methodName, argsString))
				output.WriteString("\n")
				output.WriteString(fmt.Sprintf("        return window.backend.%s.%s.%s(%s);", packageName, structName, methodName, argsString))
				output.WriteString("\n")
				output.WriteString(fmt.Sprintf("      },"))
				output.WriteString("\n")
			}
			output.WriteString(fmt.Sprintf("    }"))
			output.WriteString("\n")
		}
		output.WriteString(fmt.Sprintf("  }\n"))
		output.WriteString("\n")
	}

	output.WriteString(`};
export default backend;`)
	output.WriteString("\n")

	dirname, err := fs.RelativeToCwd("frontend/src/backend")
	if err != nil {
		log.Fatal(err)
	}

	if !fs.DirExists(dirname) {
		err := fs.Mkdir(dirname)
		if err != nil {
			log.Fatal(err)
		}
	}

	packageJsonFile := filepath.Join(dirname, "package.json")
	if !fs.FileExists(packageJsonFile) {
		err := os.WriteFile(packageJsonFile, packageJSON, 0755)
		if err != nil {
			log.Fatal(err)
		}
	}

	filename := filepath.Join(dirname, "index.js")
	err = os.WriteFile(filename, output.Bytes(), 0755)
	if err != nil {
		log.Fatal(err)
	}
}

func goTypeToJSDocType(input string) string {
	switch true {
	case input == "string":
		return "string"
	case input == "error":
		return "Error"
	case
		strings.HasPrefix(input, "int"),
		strings.HasPrefix(input, "uint"),
		strings.HasPrefix(input, "float"):
		return "number"
	case input == "bool":
		return "boolean"
	case strings.HasPrefix(input, "[]"):
		arrayType := goTypeToJSDocType(input[2:])
		return "Array.<" + arrayType + ">"
	default:
		return "any"
	}
}
