package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/spf13/cobra"
	"gopkg.in/yaml.v3"
)

/* TODO

I've finished writing the "contrib" half of `town contrib`. but what about the other half? this is two pieces:

1. as an admin, reviewing what's been submitted and:
  - discarding
	- clarifying
	- accepting
2. as a user, running `town` in order to discover and run contrib'ed commands.

for 2, I think it's fine for now to just let the usage for town get long. as far as how to manage this, i think moving to a folder of yaml files that have an execPath and then dynamically building the command list up from the yaml files works. that way, the contrib yaml can just be copied over (renaming to command name and stripping comments) to the accepted path (/town/commands/contrib or whatever).

for 1, it can just be manually copying the files but that could easily lead to brainrot and errors, so i want automation there.

how about:

town contrib --review?

it would:

- list all the commands that have been contribed so dupes can be looked at
- iterate through each with:
  - edit
	- approve
	- reject
	- pass
	- contact (email)
*/

const binroot = "/town/commands"

var rootCmd = &cobra.Command{
	Use:   "town",
	Short: "Run commands unique to tilde.town",
	// TODO Long example showing how to contribute a command
}

func parseCommands(targetCmd *cobra.Command, path string) error {
	binPath := filepath.Join(binroot, path)
	files, err := ioutil.ReadDir(binPath)
	if err != nil {
		return fmt.Errorf("failed to list directory %s: %s", binPath, err)
	}

	for _, file := range files {
		if strings.HasSuffix(file.Name(), "yml") {
			parseCommand(targetCmd, filepath.Join(binPath, file.Name()))
		}
	}

	return nil
}

type cmdDoc struct {
	CmdName    string
	ExecPath   string
	Maintainer string
	Category   string
	ShortDesc  string
	LongDesc   string
}

func parseCommand(targetCmd *cobra.Command, yamlPath string) {
	yamlBytes, err := ioutil.ReadFile(yamlPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not read %s; skipping...\n", yamlPath)
		return
	}

	var doc cmdDoc
	err = yaml.Unmarshal(yamlBytes, &doc)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not parse %s; skipping...\n", yamlPath)
		return
	}

	parsedCmd := &cobra.Command{
		Use:                doc.CmdName,
		RunE:               execWrapper(doc.ExecPath),
		DisableFlagParsing: true,
	}
	if doc.ShortDesc != "" {
		parsedCmd.Short = doc.ShortDesc
	}
	if doc.LongDesc != "" {
		parsedCmd.Long = doc.LongDesc
	}

	parsedCmd.Long += fmt.Sprintf("\nMaintained by %s; reach out to them via mail or chat with questions", doc.Maintainer)

	targetCmd.AddCommand(parsedCmd)
}

func execWrapper(executablePath string) func(*cobra.Command, []string) error {
	return func(cmd *cobra.Command, args []string) error {
		execCmd := exec.Command(executablePath, args...)
		execCmd.Stderr = os.Stderr
		execCmd.Stdout = os.Stdout
		execCmd.Stdin = os.Stdin
		return execCmd.Run()
	}
}

func cli() int {
	files, err := ioutil.ReadDir(binroot)
	if err != nil {
		fmt.Fprintf(os.Stderr, "failed to list directory %s: %s", binroot, err)
	}

	for _, file := range files {
		if strings.HasSuffix(file.Name(), "yml") {
			parseCommand(rootCmd, filepath.Join(binroot, file.Name()))
		}
	}

	// I feel like the example/documentation yaml can be frontmatter for non-binary files. to start
	// i'll just do the accompanying yaml file.
	rootCmd.Execute()

	return 0
}

func main() {
	os.Exit(cli())
}
