package wand

import (
	"fmt"
	"github.com/iancoleman/strcase"
	"io"
	"sort"
	"strings"
)

func formatHelp(out io.Writer, doc doc) error {
	printf, println, finish := printer(out)
	printf(escapeTeletype(doc.fullCommand))
	println()
	if doc.hasImplementation || doc.synopsis.hasSubcommands {
		println()
		printf("Synopsis")
		println()
	}

	if doc.hasImplementation {
		println()
		printf(escapeTeletype(doc.synopsis.command))
		if doc.synopsis.hasOptions {
			printf(" [options...]")
		}

		for i := range doc.synopsis.arguments.names {
			printf(
				" [%s %s]",
				doc.synopsis.arguments.names[i],
				doc.synopsis.arguments.types[i],
			)
		}

		if doc.synopsis.arguments.variadic {
			printf("...")
			min := doc.synopsis.arguments.minPositional
			max := doc.synopsis.arguments.maxPositional
			switch {
			case min > 0 && max > 0:
				printf("\nmin %d and max %d total positional arguments", min, max)
			case min > 0:
				printf("\nmin %d total positional arguments", min)
			case max > 0:
				printf("\nmax %d total positional arguments", max)
			}
		}

		println()
	}

	if doc.synopsis.hasSubcommands {
		println()
		printf("%s <subcommand> [options or args...]", escapeTeletype(doc.synopsis.command))
		println()
		println()
		printf("(For the details about the available subcommands, see the related section below.)")
		println()
	}

	if doc.description != "" {
		println()
		printf(escapeTeletype(paragraphs(doc.description)))
		println()
	}

	if len(doc.options) > 0 {
		println()
		printf("Options")
		if doc.hasBoolOptions || doc.hasListOptions {
			println()
			if doc.hasBoolOptions {
				println()
				printf("[b]: booelan flag, true or false, or no argument means true")
			}

			if doc.hasListOptions {
				println()
				printf("[*]: accepts multiple instances of the same option")
			}
		}

		println()
		println()
		names, od := prepareOptions(doc.options)

		var max int
		for _, n := range names {
			if len(n) > max {
				max = len(n)
			}
		}

		for i := range names {
			pad := strings.Join(make([]string, max-len(names[i])+1), " ")
			names[i] = fmt.Sprintf("%s:%s", names[i], pad)
		}

		for _, n := range names {
			printf(n)
			if od[n] != "" {
				printf(" %s", escapeTeletype(lines(od[n])))
			}

			println()
		}
	}

	if len(doc.subcommands) > 0 {
		println()
		printf("Subcommands")
		println()
		println()

		var names []string
		cd := make(map[string]string)
		for _, sc := range doc.subcommands {
			name := sc.name
			if sc.isDefault {
				name = fmt.Sprintf("%s (default)", name)
			}

			d := sc.description
			if sc.isHelp {
				d = fmt.Sprintf("Show this help. %s", d)
			}

			if sc.hasHelpSubcommand {
				d = fmt.Sprintf("%s - For help, see: %s %s help", d, escapeTeletype(doc.name), sc.name)
			} else if sc.hasHelpOption {
				d = fmt.Sprintf("%s - For help, see: %s %s --help", d, escapeTeletype(doc.name), sc.name)
			}

			cd[name] = d
		}

		sort.Strings(names)

		var max int
		for _, n := range names {
			if len(n) > max {
				max = len(n)
			}
		}

		for i := range names {
			pad := strings.Join(make([]string, max-len(names[i])+1), " ")
			names[i] = fmt.Sprintf("%s%s", names[i], pad)
		}

		for _, n := range names {
			printf(n)
			if cd[n] != "" {
				printf(": %s", escapeTeletype(paragraphs(cd[n])))
			}

			println()
		}
	}

	if len(doc.options) > 0 && commandNameExpression.MatchString(doc.appName) {
		println("Environment Variables")
		println()
		printf(escapeTeletype(paragraphs(envDocs)))
		println()
		println()
		o := doc.options[0]
		printf("Example environment variable:")
		println()
		println()
		printf(strcase.ToSnake(fmt.Sprintf("%s-%s", doc.appName, o.name)))
		printf("=")
		if o.isBool {
			printf("true")
		} else {
			printf("42")
		}

		println()
	}

	if len(doc.options) > 0 && len(doc.configFiles) > 0 {
		println("Configuration Files")
		println()
		printf(escapeTeletype(paragraphs(configDocs)))
		println()
		println()
		printf("Config files:")
		println()
		println()
		for _, cf := range doc.configFiles {
			if cf.fromOption {
				printf("zero or more configuration files defined by the --config option")
				println()
				continue
			}

			if cf.fn != "" {
				printf(cf.fn)
				if cf.optional {
					printf(" (optional)")
				}

				println()
				continue
			}
		}

		println()
		o := doc.options[0]
		printf("Example configuration entry:")
		println()
		println()
		printf("# default for --")
		printf(o.name)
		printf(":")
		println()
		printf(strcase.ToSnake(o.name))
		printf(" = ")
		if o.isBool {
			printf("true")
		} else {
			printf("42")
		}

		println()
		println()
		printf("Example for discarding an inherited entry:")
		println()
		println()
		printf("# discarding an inherited entry:")
		println()
		printf(strcase.ToSnake(o.name))
		println()
	}

	return finish()
}
