package fish

import (
	"bytes"
	"fmt"
	"html/template"
	"strings"

	"github.com/urfave/cli/v2"
)

// ErrUnknownType is returned when an unknown type is encountered.
var ErrUnknownType = fmt.Errorf("unknown type")

func longName(name string) string {
	// "If s does not contain sep and sep is not empty, Split returns a slice of length 1 whose only element is s."
	// from https://golang.org/pkg/strings/#Split.
	return strings.TrimSpace(strings.Split(name, ",")[0])
}

func shortName(name string) string {
	parts := strings.Split(name, ",")
	if len(parts) < 2 {
		return ""
	}

	return strings.TrimSpace(parts[1])
}

func formatFlag(name, usage, typ string) string {
	switch typ {
	case "short":
		return shortName(name)
	case "long":
		return longName(name)
	case "usage":
		return usage
	default:
		return ""
	}
}

func formatFlagFunc(typ string) func(cli.Flag) (string, error) {
	return func(f cli.Flag) (string, error) {
		switch ft := f.(type) {
		case *cli.BoolFlag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		case *cli.Float64Flag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		case *cli.GenericFlag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		case *cli.Int64Flag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		case *cli.Int64SliceFlag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		case *cli.IntFlag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		case *cli.IntSliceFlag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		case *cli.StringFlag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		case *cli.StringSliceFlag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		case *cli.Uint64Flag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		case *cli.UintFlag:
			return formatFlag(ft.Name, ft.Usage, typ), nil
		default:
			return "", fmt.Errorf("error '%T': %w", f, ErrUnknownType)
		}
	}
}

// GetCompletion returns a fish completion script.
func GetCompletion(a *cli.App) (string, error) {
	tplFuncs := template.FuncMap{
		"formatShortFlag": formatFlagFunc("short"),
		"formatLongFlag":  formatFlagFunc("long"),
		"formatFlagUsage": formatFlagFunc("usage"),
	}

	tpl, err := template.New("fish").Funcs(tplFuncs).Parse(fishTemplate)
	if err != nil {
		return "", fmt.Errorf("failed to parse template: %w", err)
	}

	buf := &bytes.Buffer{}
	if err := tpl.Execute(buf, a); err != nil {
		return "", fmt.Errorf("failed to execute template: %w", err)
	}

	return buf.String(), nil
}
