// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package interact

import (
	"fmt"
	"strings"
	"time"

	"code.gitea.io/tea/modules/utils"
	"github.com/AlecAivazis/survey/v2"
	"github.com/araddon/dateparse"
)

// Multiline represents options for a prompt that expects multiline input
type Multiline struct {
	Message             string
	Default             string
	Syntax              string
	UseEditor           bool
	EditorAppendDefault bool
	EditorHideDefault   bool
}

// NewMultiline creates a prompt that switches between the inline multiline text
// and a texteditor based prompt
func NewMultiline(opts Multiline) (prompt survey.Prompt) {
	if opts.UseEditor {
		prompt = &survey.Editor{
			Message:       opts.Message,
			Default:       opts.Default,
			FileName:      "*." + opts.Syntax,
			AppendDefault: opts.EditorAppendDefault,
			HideDefault:   opts.EditorHideDefault,
		}
	} else {
		prompt = &survey.Multiline{Message: opts.Message, Default: opts.Default}
	}
	return
}

// PromptPassword asks for a password and blocks until input was made.
func PromptPassword(name string) (pass string, err error) {
	promptPW := &survey.Password{Message: name + " password:"}
	err = survey.AskOne(promptPW, &pass, survey.WithValidator(survey.Required))
	return
}

// promptRepoSlug interactively prompts for a Gitea repository or returns the current one
func promptRepoSlug(defaultOwner, defaultRepo string) (owner, repo string, err error) {
	prompt := "Target repo:"
	defaultVal := ""
	required := true
	if len(defaultOwner) != 0 && len(defaultRepo) != 0 {
		defaultVal = fmt.Sprintf("%s/%s", defaultOwner, defaultRepo)
		required = false
	}
	var repoSlug string

	owner = defaultOwner
	repo = defaultRepo

	err = survey.AskOne(
		&survey.Input{
			Message: prompt,
			Default: defaultVal,
		},
		&repoSlug,
		survey.WithValidator(func(input interface{}) error {
			if str, ok := input.(string); ok {
				if !required && len(str) == 0 {
					return nil
				}
				split := strings.Split(str, "/")
				if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {
					return fmt.Errorf("must follow the <owner>/<repo> syntax")
				}
			} else {
				return fmt.Errorf("invalid result type")
			}
			return nil
		}),
	)

	if err == nil && len(repoSlug) != 0 {
		repoSlugSplit := strings.Split(repoSlug, "/")
		owner = repoSlugSplit[0]
		repo = repoSlugSplit[1]
	}
	return
}

// promptDatetime prompts for a date or datetime string.
// Supports all formats understood by araddon/dateparse.
func promptDatetime(prompt string) (val *time.Time, err error) {
	var input string
	err = survey.AskOne(
		&survey.Input{Message: prompt},
		&input,
		survey.WithValidator(func(input interface{}) error {
			if str, ok := input.(string); ok {
				if len(str) == 0 {
					return nil
				}
				t, err := dateparse.ParseAny(str)
				if err != nil {
					return err
				}
				val = &t
			} else {
				return fmt.Errorf("invalid result type")
			}
			return nil
		}),
	)
	return
}

// promptSelect creates a generic multiselect prompt, with processing of custom values.
func promptMultiSelect(prompt string, options []string, customVal string) ([]string, error) {
	var selection []string
	promptA := &survey.MultiSelect{
		Message: prompt,
		Options: makeSelectOpts(options, customVal, ""),
		VimMode: true,
	}
	if err := survey.AskOne(promptA, &selection); err != nil {
		return nil, err
	}
	return promptCustomVal(prompt, customVal, selection)
}

// promptSelectV2 creates a generic select prompt
func promptSelectV2(prompt string, options []string) (string, error) {
	if len(options) == 0 {
		return "", nil
	}
	var selection string
	promptA := &survey.Select{
		Message: prompt,
		Options: options,
		VimMode: true,
		Default: options[0],
	}
	if err := survey.AskOne(promptA, &selection); err != nil {
		return "", err
	}
	return selection, nil
}

// promptSelect creates a generic select prompt, with processing of custom values or none-option.
func promptSelect(prompt string, options []string, customVal, noneVal, defaultVal string) (string, error) {
	var selection string
	if defaultVal == "" && noneVal != "" {
		defaultVal = noneVal

	}
	promptA := &survey.Select{
		Message: prompt,
		Options: makeSelectOpts(options, customVal, noneVal),
		VimMode: true,
		Default: defaultVal,
	}
	if err := survey.AskOne(promptA, &selection); err != nil {
		return "", err
	}
	if noneVal != "" && selection == noneVal {
		return "", nil
	}
	if customVal != "" {
		sel, err := promptCustomVal(prompt, customVal, []string{selection})
		if err != nil {
			return "", err
		}
		selection = sel[0]
	}
	return selection, nil
}

// makeSelectOpts adds cusotmVal & noneVal to opts if set.
func makeSelectOpts(opts []string, customVal, noneVal string) []string {
	if customVal != "" {
		opts = append(opts, customVal)
	}
	if noneVal != "" {
		opts = append(opts, noneVal)
	}
	return opts
}

// promptCustomVal checks if customVal is present in selection, and prompts
// for custom input to add to the selection instead.
func promptCustomVal(prompt, customVal string, selection []string) ([]string, error) {
	// check for custom value & prompt again with text input
	// HACK until https://github.com/AlecAivazis/survey/issues/339 is implemented
	if otherIndex := utils.IndexOf(selection, customVal); otherIndex != -1 {
		var customAssignees string
		promptA := &survey.Input{Message: prompt, Help: "comma separated list"}
		if err := survey.AskOne(promptA, &customAssignees); err != nil {
			return nil, err
		}
		selection = append(selection[:otherIndex], selection[otherIndex+1:]...)
		selection = append(selection, strings.Split(customAssignees, ",")...)
	}
	return selection, nil
}