package repl

import (
	"context"
	"errors"
	"fmt"
	"github.com/lmika/gopkgs/fp/maps"
	"os"
	"slices"
	"sort"
	"strings"
	"text/tabwriter"
	"ucl.lmika.dev/ucl"
	"unicode"
)

type ArgDoc struct {
	Name  string
	Brief string
}

type Doc struct {
	Brief    string
	Usage    string
	Args     []ArgDoc
	Detailed string
}

func (d Doc) config(cmdName string, r *REPL) {
	r.commandDocs[cmdName] = d
}

func (r *REPL) helpBuiltin(ctx context.Context, args ucl.CallArgs) (any, error) {
	switch {
	case args.NArgs() == 0:
		names := maps.Keys(r.commandDocs)
		sort.Strings(names)

		tabWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)

		for _, name := range names {
			cdoc := r.commandDocs[name]
			if cdoc.Brief != "" {
				fmt.Fprintf(tabWriter, "%v\t  %v\n", name, r.commandDocs[name].Brief)
			} else {
				fmt.Fprintf(tabWriter, "%v\n", name)
			}
		}

		tabWriter.Flush()
	default:
		var cmdName string
		if err := args.Bind(&cmdName); err != nil {
			return nil, err
		}

		docs, ok := r.commandDocs[cmdName]
		if !ok {
			return nil, errors.New("no help docs found for command")
		}

		fmt.Printf("%v\n", cmdName)
		fmt.Printf("  %v\n", docs.Brief)

		if docs.Usage != "" {
			fmt.Println("\nUsage:")
			fmt.Printf("  %v %v\n", cmdName, docs.Usage)
		}

		if len(docs.Args) > 0 {
			fmt.Println("\nArguments:")

			docArgs := slices.Clone(docs.Args)
			sort.Slice(docArgs, func(i, j int) bool {
				return strings.TrimPrefix(docs.Args[i].Name, "-") < strings.TrimPrefix(docs.Args[j].Name, "-")
			})

			tw := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
			for _, arg := range docArgs {
				fmt.Fprintf(tw, "  %v\t  %v\n", arg.Name, arg.Brief)
			}
			tw.Flush()
		}

		if docs.Detailed != "" {
			fmt.Println("\nDetails:")

			lines := strings.Split(docs.Detailed, "\n")

			trimLeft := 0
			if len(lines) == 0 {
				return nil, nil
			} else if len(strings.TrimSpace(lines[0])) == 0 && len(lines) > 1 {
				// indicates that the next line should indicate the indentation
				trimLeft = len(lines[1]) - len(strings.TrimLeftFunc(lines[1], unicode.IsSpace))
				lines = lines[1:]
			}

			for _, line := range lines {
				fmt.Printf("  %v\n", trimSpaceLeftUpto(line, trimLeft))
			}
		}
	}
	return NoResults{}, nil
}

func trimSpaceLeftUpto(s string, n int) string {
	if n == 0 {
		return s
	}

	for i, c := range s {
		if i >= n {
			return s[i:]
		} else if !unicode.IsSpace(c) {
			return s[i:]
		}
	}
	return s
}