// Package help contains functions responsible for generating help output for
// asdf and asdf plugins.
package help

import (
	_ "embed"
	"fmt"
	"io"
	"os"
	"strings"

	"github.com/asdf-vm/asdf/internal/config"
	"github.com/asdf-vm/asdf/internal/plugins"
	"github.com/asdf-vm/asdf/internal/toolversions"
)

//go:embed help.txt
var helpText string

const quote = "\"Late but latest\"\n-- Rajinikanth"

// Print help output to STDOUT
func Print(asdfVersion string, plugins []plugins.Plugin) error {
	return Write(asdfVersion, plugins, os.Stdout)
}

// PrintTool write tool help output to STDOUT
func PrintTool(conf config.Config, toolName string) error {
	return WriteToolHelp(conf, toolName, os.Stdout, os.Stderr)
}

// PrintToolVersion write help for specific tool version to STDOUT
func PrintToolVersion(conf config.Config, toolName, toolVersion string) error {
	return WriteToolVersionHelp(conf, toolName, toolVersion, os.Stdout, os.Stderr)
}

// Write help output to an io.Writer
func Write(asdfVersion string, allPlugins []plugins.Plugin, writer io.Writer) error {
	_, err := writer.Write([]byte(fmt.Sprintf("version: %s\n\n", asdfVersion)))
	if err != nil {
		return err
	}

	_, err = writer.Write([]byte(helpText))
	if err != nil {
		return err
	}

	_, err = writer.Write([]byte("\n"))
	if err != nil {
		return err
	}

	extensionCommandHelp, err := pluginExtensionCommands(allPlugins)
	if err != nil {
		fmt.Printf("err %#+v\n", err)
		return err
	}

	_, err = writer.Write([]byte(extensionCommandHelp))
	if err != nil {
		return err
	}

	_, err = writer.Write([]byte("\n"))
	if err != nil {
		return err
	}

	_, err = writer.Write([]byte(quote))
	if err != nil {
		return err
	}

	_, err = writer.Write([]byte("\n"))
	if err != nil {
		return err
	}

	return nil
}

// WriteToolHelp output to an io.Writer
func WriteToolHelp(conf config.Config, toolName string, writer io.Writer, errWriter io.Writer) error {
	return writePluginHelp(conf, toolName, "", writer, errWriter)
}

// WriteToolVersionHelp output to an io.Writer
func WriteToolVersionHelp(conf config.Config, toolName, toolVersion string, writer io.Writer, errWriter io.Writer) error {
	return writePluginHelp(conf, toolName, toolVersion, writer, errWriter)
}

func writePluginHelp(conf config.Config, toolName, toolVersion string, writer io.Writer, errWriter io.Writer) error {
	plugin := plugins.New(conf, toolName)
	env := map[string]string{
		"ASDF_INSTALL_PATH": plugin.Dir,
	}

	if toolVersion != "" {
		version := toolversions.Parse(toolVersion)
		env["ASDF_INSTALL_VERSION"] = version.Value
		env["ASDF_INSTALL_TYPE"] = version.Type
	}

	if err := plugin.Exists(); err != nil {
		errWriter.Write([]byte(fmt.Sprintf("No plugin named %s\n", plugin.Name)))
		return err
	}

	err := plugin.RunCallback("help.overview", []string{}, env, writer, errWriter)
	if _, ok := err.(plugins.NoCallbackError); ok {
		// No such callback, print err msg
		errWriter.Write([]byte(fmt.Sprintf("No documentation for plugin %s\n", plugin.Name)))
		return err
	}

	if err != nil {
		return err
	}

	err = plugin.RunCallback("help.deps", []string{}, env, writer, errWriter)
	if _, ok := err.(plugins.NoCallbackError); !ok {
		return err
	}

	err = plugin.RunCallback("help.config", []string{}, env, writer, errWriter)
	if _, ok := err.(plugins.NoCallbackError); !ok {
		return err
	}

	err = plugin.RunCallback("help.links", []string{}, env, writer, errWriter)
	if _, ok := err.(plugins.NoCallbackError); !ok {
		return err
	}

	return nil
}

func pluginExtensionCommands(plugins []plugins.Plugin) (string, error) {
	var output strings.Builder

	for _, plugin := range plugins {
		commands, err := plugin.GetExtensionCommands()
		if err != nil {
			return output.String(), err
		}
		if len(commands) > 0 {
			output.WriteString(fmt.Sprintf("PLUGIN %s\n", plugin.Name))
			for _, command := range commands {
				if command == "" {
					// must be default command
					output.WriteString(fmt.Sprintf("  asdf %s\n", plugin.Name))
				} else {
					output.WriteString(fmt.Sprintf("  asdf %s %s\n", plugin.Name, command))
				}
			}
		}
	}
	return output.String(), nil
}
