// able typescript generated from golang
// Copyright (C) 2022  Fabio Prada

package tsrpc

import (
	"bytes"
	"fmt"
	"slices"
	"strings"
	"text/template"
)

type TSEndpoint struct {
	Name     string
	Path     string
	Method   string
	Request  string
	Response string
	Source   string
	File     string
	Line     int
	Imports  map[string][]string
}

func ParseEndpoint(source string, file string, line int) TSEndpoint {
	p := strings.Index(source, "TSEndpoint=")
	s := strings.Trim(source[p+len("TSEndpoint="):], " ")
	a := strings.Split(s, ";")
	n := 0
	endpoint := TSEndpoint{}
	endpoint.Source = strings.Trim(source, "\t")
	endpoint.File = file
	endpoint.Line = line
	endpoint.Imports = make(map[string][]string)

	for _, v := range a {
		t := strings.Split(v, "=")
		if len(t) < 2 || strings.Trim(t[1], " ") == "" {
			exitOnError(fmt.Errorf("worong endpoint: %s", s))
		}
		if len(t) == 2 {
			switch strings.Trim(t[0], " ") {
			case "path":
				n++
				endpoint.Path = strings.Trim(t[1], " ")
			case "method":
				n++
				endpoint.Method = strings.Trim(t[1], " ")
			case "name":
				n++
				endpoint.Name = strings.Trim(t[1], " ")
			case "request":
				n++
				endpoint.Request = strings.Trim(t[1], " ")
			case "response":
				n++
				endpoint.Response = strings.Trim(t[1], " ")
			}
		} else {
			exitOnError(fmt.Errorf("wrong endpoint props: %s", s))
		}
	}

	if endpoint.Method != "POST" && endpoint.Method != "GET" && endpoint.Method != "DELETE" && endpoint.Method != "PUT" {
		exitOnError(fmt.Errorf("wrong endpoint method: %s", s))
	}

	if (endpoint.Method == "GET" || endpoint.Method == "DELETE") && n < 4 {
		exitOnError(fmt.Errorf("wrong endpoint number of props: %s", s))
	}

	if (endpoint.Method == "POST" || endpoint.Method == "PUT") && n < 5 {
		exitOnError(fmt.Errorf("wrong endpoint number of props: %s", s))
	}

	return endpoint
}

type tplData struct {
	E      *TSEndpoint
	Path   string
	Params []string
}

func (e *TSEndpoint) VerifyTypes(info TSInfo, p string) {
	kind := ""

	if e.Name == "listUsers" {
		fmt.Println("endpoint request", e.Request)
	}

	a := strings.Split(e.Request, ".")
	if e.Request != "" {
		if len(a) == 2 {
			kind = a[1]
			if strings.HasPrefix(a[1], "[]") {
				kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[1], "[]"))
			}
			// nullable pointer
			if strings.HasPrefix(a[1], "*") {
				kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[1], "*"))
			}

			if !info.find(a[0], a[1]) {
				exitOnError(fmt.Errorf("endpoint request not found: %s AT %s Line: %d ", e.Request, e.File, e.Line))
			}
			if a[0] == p {
				e.Request = fmt.Sprintf("%s", kind)
			} else {
				e.Request = fmt.Sprintf("%s.%s", kind, a[1])

				if _, ok := e.Imports[a[0]]; !ok {
					e.Imports[a[0]] = []string{}
				}
				allreadyImported := slices.Contains(e.Imports[a[0]], strings.Trim(a[1], "[]*"))
				if !allreadyImported {
					e.Imports[a[0]] = append(e.Imports[a[0]], strings.Trim(a[1], "[]*"))
				}
			}
			info.setTypescript(a[0], a[1], true)
		}
		if len(a) == 1 {
			kind = a[0]
			if strings.HasPrefix(a[0], "[]") {
				kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[0], "[]"))
			}
			// nullable pointer
			if strings.HasPrefix(a[0], "*") {
				kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[0], "*"))
			}
			e.Request = fmt.Sprintf("%s", kind)
		}
		// if request is a struct, set it as typescript to generate the ts struct
	}
	kind = ""
	a = strings.Split(e.Response, ".")
	if len(a) == 2 {
		kind = a[1]
		if strings.HasPrefix(a[1], "[]") {
			kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[1], "[]"))
		}
		// nullable pointer
		if strings.HasPrefix(a[1], "*") {
			kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[1], "*"))
		}

		if !info.find(a[0], a[1]) {
			exitOnError(fmt.Errorf("endpoint response not found: %s AT %s Line: %d ", e.Response, e.File, e.Line))
		}

		if a[0] == p {
			e.Response = fmt.Sprintf("%s", kind)
		} else {
			e.Response = fmt.Sprintf("%s.%s", a[0], kind)
			if _, ok := e.Imports[a[0]]; !ok {
				e.Imports[a[0]] = []string{}
			}
			allreadyImported := slices.Contains(e.Imports[a[0]], strings.Trim(a[1], "[]*"))
			if !allreadyImported {
				e.Imports[a[0]] = append(e.Imports[a[0]], strings.Trim(a[1], "[]*"))
				fmt.Printf("endpoint %s response import: %s.%s\n", e.Name, a[0], a[1])
			}
		}
		info.setTypescript(a[0], a[1], true)
	}
	if len(a) == 1 {
		if strings.HasPrefix(a[0], "[]") {
			kind = fmt.Sprintf("%s[]", strings.TrimPrefix(a[0], "[]"))
		}
		// nullable pointer
		if strings.HasPrefix(a[0], "*") {
			kind = fmt.Sprintf("Nullable<%s>", strings.TrimPrefix(a[0], "*"))
		}
		if IsNativeType(a[0]) {
			e.Response = fmt.Sprintf("%s%s", a[0], kind)
			return
		}
		e.Response = fmt.Sprintf("%s", kind)
		// if request is a struct, set it as typescript to generate the ts struct
	}
}

func (e *TSEndpoint) ToTs() string {
	data := tplData{E: e, Path: e.Path, Params: []string{}}
	tpl := `
{{ .E.Source }}
// {{ .E.File }} Line: {{ .E.Line }}
{{if eq .E.Method "GET"}}export const {{ .E.Name}} = async ({{range $v := .Params}}{{$v}}{{end}}):Promise<{ data:{{.E.Response}}; error: Nullable<string> }> => {
	return await api.GET({{ .Path}}) as { data: {{ .E.Response}}; error: Nullable<string> };
}{{end}}{{if eq .E.Method "DELETE"}}export const {{ .E.Name}} = async ({{range $v := .Params}}{{$v}}{{end}}):Promise<{ data:{{.E.Response}}; error: Nullable<string> }> => {
	return await api.DELETE({{ .Path}}) as { data: {{ .E.Response}}; error: Nullable<string> };
}{{end}}{{if eq .E.Method "PUT"}}export const {{ .E.Name}} = async (data: {{ .E.Request}}):Promise<{ data:{{.E.Response}}; error: Nullable<string> }> => {
	return await api.PUT("{{ .Path}}", data) as { data: {{ .E.Response}}; error: Nullable<string> };
}{{end}}{{if eq .E.Method "POST"}}export const {{ .E.Name}} = async (data: {{ .E.Request}}):Promise<{ data:{{.E.Response}}; error: Nullable<string> }> => {
	return await api.POST("{{ .Path}}", data) as { data: {{ .E.Response }}; error: Nullable<string> };
}{{end}}`

	if e.Method == "GET" || e.Method == "DELETE" {
		a := strings.Split(e.Path, "/")
		c := ""
		f := false
		for _, v := range a {
			if len(v) == 0 {
				continue
			}
			prefix := v[0:1]

			if f {
				c = ", "
			}

			switch prefix {
			case ":":
				f = true
				data.Params = append(data.Params, fmt.Sprintf("%s%s: string", c, v[1:]))
				data.Path = strings.Replace(data.Path, v, fmt.Sprintf("${%s}", v[1:]), 1)
			case "*":
				f = true
				data.Params = append(data.Params, fmt.Sprintf("%s%s: Nullable<string>", c, v[1:]))
				data.Path = strings.Replace(data.Path, v, fmt.Sprintf("${%s}", v[1:]), 1)
			}
		}
		if len(data.Params) > 0 {
			data.Path = fmt.Sprintf("`%s`", data.Path)
		} else {
			data.Path = fmt.Sprintf("\"%s\"", data.Path)
		}
	}

	t, err := template.New("test").Parse(tpl)
	if err != nil {
		panic(err)
	}
	var result bytes.Buffer
	err = t.Execute(&result, data)
	if err != nil {
		panic(err)
	}
	return result.String()
}
