package main

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

func printArgs(args []fnArg) string {
	var argStr []string
	for _, arg := range args {
		argStr = append(argStr, arg.String())
	}
	return strings.Join(argStr, ", ")
}

func buildImports(specs []importSpec) string {
	var imports strings.Builder
	imports.WriteString(`
import(
	"errors"
	"time"

	"github.com/moby/moby/v2/pkg/plugins"
`)
	for _, i := range specs {
		imports.WriteString("\t" + i.String() + "\n")
	}
	imports.WriteString(`)
`)
	return imports.String()
}

func marshalType(t string) string {
	switch t {
	case "error":
		// convert error types to plain strings to ensure the values are encoded/decoded properly
		return "string"
	default:
		return t
	}
}

func isErr(t string) bool {
	switch t {
	case "error":
		return true
	default:
		return false
	}
}

// Need to use this helper due to issues with go-vet
func buildTag(s string) string {
	return "+build " + s
}

var templFuncs = template.FuncMap{
	"printArgs":   printArgs,
	"marshalType": marshalType,
	"isErr":       isErr,
	"lower":       strings.ToLower,
	"title":       title,
	"tag":         buildTag,
	"imports":     buildImports,
	"goduration":  goduration,
}

// goduration formats a time.Duration as Go code (e.g., 2 * time.Minute)
func goduration(d time.Duration) string {
	seconds := int64(d.Seconds())
	switch {
	case d == 0:
		return "0 * time.Nanosecond"
	case seconds%(60*60) == 0 && seconds != 0:
		return fmt.Sprintf("%d * time.Hour", seconds/3600)
	case seconds%60 == 0 && seconds != 0:
		return fmt.Sprintf("%d * time.Minute", seconds/60)
	case seconds != 0:
		return fmt.Sprintf("%d * time.Second", seconds)
	default:
		// fallback to nanoseconds for sub-second durations
		return fmt.Sprintf("%d * time.Nanosecond", d.Nanoseconds())
	}
}

func title(s string) string {
	if strings.ToLower(s) == "id" {
		return "ID"
	}
	return strings.Title(s) //nolint:staticcheck // SA1019: strings.Title is deprecated: The rule Title uses for word boundaries does not handle Unicode punctuation properly. Use golang.org/x/text/cases instead.
}

var generatedTempl = template.Must(template.New("rpc_cient").Funcs(templFuncs).Parse(`
// Code generated by pluginrpc-gen. DO NOT EDIT.
{{ range $k, $v := .BuildTags }}
	// {{ tag $k }} {{ end }}

package {{ .Name }}

{{ imports .Imports }}

const (
	longTimeout  = {{ .LongTimeout | goduration }}
	shortTimeout = {{ .ShortTimeout | goduration }}
)

type client interface{
	CallWithOptions(serviceMethod string, args interface{}, ret interface{}, opts ...func(*plugins.RequestOpts)) error
}

type {{ .InterfaceType }}Proxy struct {
	client
}

{{ range .Functions }}
	type {{ $.InterfaceType }}Proxy{{ .Name }}Request struct{
		{{- range .Args }}
			{{ title .Name }} {{ .ArgType }} {{- end -}}
	}

	type {{ $.InterfaceType }}Proxy{{ .Name }}Response struct{
		{{- range .Returns }}
			{{ title .Name }} {{ marshalType .ArgType }} {{- end -}}
	}

	{{- if .Doc }}
	{{ .Doc }}
	{{- end }}
	func (pp *{{ $.InterfaceType }}Proxy) {{ .Name }}({{ printArgs .Args }}) ({{ printArgs .Returns }}) {
		var(
			req {{ $.InterfaceType }}Proxy{{ .Name }}Request
			ret {{ $.InterfaceType }}Proxy{{ .Name }}Response
		)
		{{ range .Args }}
			req.{{ title .Name }} = {{ lower .Name }} {{ end }}

		if err = pp.CallWithOptions("{{ $.RPCName }}.{{ .Name }}", req, &ret, plugins.WithRequestTimeout({{ .TimeoutType }}Timeout)); err != nil {
			return
		}
		{{ range $r := .Returns }}
			{{ if isErr .ArgType }}
				if ret.{{ title .Name }} != "" {
					{{ lower .Name }} = errors.New(ret.{{ title .Name }})
				} {{ end }}
			{{ if isErr .ArgType | not }} {{ lower .Name }} = ret.{{ title .Name }} {{ end }} {{ end }}

		return
	}
{{ end }}
`))
