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

package tsrpc

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

type TSEndpoint struct {
	Name       string
	Path       string
	Method     string
	Request    string
	Response   string
	RequestTs  string
	ResponseTs string
	Source     string
	File       string
	Line       int
}

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

	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) {
	a := strings.Split(e.Request, ".")
	if e.Request != "" {
		if len(a) == 2 {
			e.RequestTs = a[1]
			if strings.HasPrefix(a[1], "[]") {
				a[1] = strings.TrimPrefix(a[1], "[]")
				e.RequestTs = fmt.Sprintf("%s[]", a[1])
			}
			if strings.HasPrefix(a[1], "*") {
				a[1] = strings.TrimPrefix(a[1], "*")
				e.RequestTs = fmt.Sprintf("Nullable<%s>", a[1])
			}
			e.Request = fmt.Sprintf("%s.%s", a[0], 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))
			}
			info.setTypescript(a[0], a[1], true)
		}
		if len(a) == 1 {
			e.RequestTs = a[0]
			if strings.HasPrefix(a[0], "[]") {
				a[0] = strings.TrimPrefix(a[0], "[]")
				e.RequestTs = fmt.Sprintf("%s[]", a[0])
			}
			if strings.HasPrefix(a[0], "*") {
				a[0] = strings.TrimPrefix(a[0], "*")
				e.RequestTs = fmt.Sprintf("Nullable<%s>", a[0])
			}
			e.Request = a[0]
		}
	}
	a = strings.Split(e.Response, ".")
	if len(a) == 2 {
		if !info.find(a[0], a[1]) {
			exitOnError(fmt.Errorf("endpoint response not found: %s AT %s Line: %d ", e.Request, e.File, e.Line))
		}
		e.ResponseTs = a[1]
		if strings.HasPrefix(a[1], "[]") {
			a[1] = strings.TrimPrefix(a[1], "[]")
			e.ResponseTs = fmt.Sprintf("%s[]", a[1])
		}
		if strings.HasPrefix(a[1], "*") {
			a[1] = strings.TrimPrefix(a[1], "*")
			e.ResponseTs = fmt.Sprintf("Nullable<%s>", a[1])
		}
		e.Response = fmt.Sprintf("%s.%s", a[0], a[1])
	}
	if len(a) == 1 {
		e.ResponseTs = a[0]
		if strings.HasPrefix(a[0], "[]") {
			a[0] = strings.TrimPrefix(a[0], "[]")
			e.ResponseTs = fmt.Sprintf("%s[]", a[0])
		}
		if strings.HasPrefix(a[0], "*") {
			a[0] = strings.TrimPrefix(a[0], "*")
			e.ResponseTs = fmt.Sprintf("Nullable<%s>", a[0])
		}
		e.Response = a[0]
	}

}

func (e *TSEndpoint) ToTs(pkg string) 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.ResponseTs}}; error: Nullable<string> }> => {
	return await api.GET({{ .Path}}) as { data: {{ .E.ResponseTs}}; error: Nullable<string> };
}{{end}}
{{if eq .E.Method "DELETE"}}export const {{ .E.Name}} = async ({{range $v := .Params}}{{$v}}{{end}}):Promise<{ data:{{.E.ResponseTs}}; error: Nullable<string> }> => {
	return await api.DELETE({{ .Path}}) as { data: {{ .E.ResponseTs}}; error: Nullable<string> };
}{{end}}
{{if eq .E.Method "PUT"}}export const {{ .E.Name}} = async (data: {{ .E.RequestTs}}):Promise<{ data:{{.E.ResponseTs}}; error: Nullable<string> }> => {
	return await api.PUT("{{ .Path}}", data) as { data: {{ .E.ResponseTs}}; error: Nullable<string> };
}{{end}}
{{if eq .E.Method "POST"}}export const {{ .E.Name}} = async (data: {{ .E.RequestTs}}):Promise<{ data:{{.E.ResponseTs}}; error: Nullable<string> }> => {
	return await api.POST("{{ .Path}}", data) as { data: {{ .E.ResponseTs}}; 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 = ", "
			}

			if prefix == ":" {
				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)
			} else if prefix == "*" {
				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()
}
