// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package init

import (
	"errors"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"

	"github.com/charmbracelet/huh"
	"github.com/spf13/cobra"

	"git.secluded.site/go-lunatask"
	"git.secluded.site/lune/internal/config"
	"git.secluded.site/lune/internal/ui"
)

const (
	choiceBack   = "back"
	choiceNext   = "next"
	choiceAdd    = "add"
	choiceDone   = "done"
	actionEdit   = "edit"
	actionDelete = "delete"
	actionGoals  = "goals"
	colorAuto    = "auto"
)

var (
	errNameRequired  = errors.New("name is required")
	errKeyRequired   = errors.New("key is required")
	errTokenRequired = errors.New("access token is required")
	errKeyFormat     = errors.New("key must be lowercase letters, numbers, and hyphens (e.g. 'work' or 'q1-goals')")
	errKeyDuplicate  = errors.New("this key is already in use")
	errRefRequired   = errors.New("reference is required")
	errRefFormat     = errors.New("expected UUID or lunatask:// deep link")
	errIndexOutRange = errors.New("index out of range")
	errBack          = errors.New("user went back")
)

func configureUIPrefs(cfg *config.Config) error {
	color := cfg.UI.Color
	if color == "" {
		color = colorAuto
	}

	err := huh.NewSelect[string]().
		Title("Color output").
		Description("When should lune use colored output?").
		Options(
			huh.NewOption("Auto (detect terminal capability)", colorAuto),
			huh.NewOption("Always", "always"),
			huh.NewOption("Never", "never"),
		).
		Value(&color).
		Run()
	if err != nil {
		if errors.Is(err, huh.ErrUserAborted) {
			return errQuit
		}

		return err
	}

	cfg.UI.Color = color

	return nil
}

func resetConfig(cmd *cobra.Command, cfg *config.Config) error {
	path, err := config.Path()
	if err != nil {
		return err
	}

	var confirm bool

	err = huh.NewConfirm().
		Title("Reset all configuration?").
		Description(fmt.Sprintf("This will delete %s and restart the setup wizard.", path)).
		Affirmative("Reset").
		Negative("Cancel").
		Value(&confirm).
		Run()
	if err != nil {
		if errors.Is(err, huh.ErrUserAborted) {
			return errQuit
		}

		return err
	}

	if confirm {
		if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
			return fmt.Errorf("removing config: %w", err)
		}

		fmt.Fprintln(cmd.OutOrStdout(), ui.Success.Render("Configuration reset."))
		fmt.Fprintln(cmd.OutOrStdout())

		*cfg = config.Config{}

		return errReset
	}

	return nil
}

func runListSelect(title, description string, options []huh.Option[string]) (string, error) {
	var choice string

	selector := huh.NewSelect[string]().
		Title(title).
		Options(options...).
		Value(&choice)

	if description != "" {
		selector = selector.Description(description)
	}

	if err := selector.Run(); err != nil {
		if errors.Is(err, huh.ErrUserAborted) {
			return "", errQuit
		}

		return "", err
	}

	return choice, nil
}

func parseEditIndex(choice string) (int, bool) {
	if !strings.HasPrefix(choice, "edit:") {
		return 0, false
	}

	idx, err := strconv.Atoi(strings.TrimPrefix(choice, "edit:"))
	if err != nil {
		return 0, false
	}

	return idx, true
}

var keyPattern = regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`)

func validateKeyFormat(input string) error {
	if input == "" {
		return errKeyRequired
	}

	if !keyPattern.MatchString(input) {
		return errKeyFormat
	}

	return nil
}

func validateReference(input string, _ bool) (string, error) {
	if input == "" {
		return "", errRefRequired
	}

	_, id, err := lunatask.ParseDeepLink(input)
	if err != nil {
		return "", errRefFormat
	}

	return id, nil
}

type itemFormConfig struct {
	itemType         string
	namePlaceholder  string
	keyPlaceholder   string
	keyValidator     func(string) error
	supportsDeepLink bool
}

func titleCase(s string) string {
	if s == "" {
		return s
	}

	return strings.ToUpper(s[:1]) + s[1:]
}

// itemAction represents the result of an action selection dialog.
type itemAction int

const (
	itemActionNone itemAction = iota
	itemActionEdit
	itemActionDelete
	itemActionGoals
)

// runActionSelect shows an action selection dialog for an item and returns the chosen action.
func runActionSelect(title string, includeGoals bool) (itemAction, error) {
	var action string

	options := []huh.Option[string]{
		huh.NewOption("Edit", actionEdit),
	}

	if includeGoals {
		options = append(options, huh.NewOption("Manage goals", actionGoals))
	}

	options = append(options,
		huh.NewOption("Delete", actionDelete),
		huh.NewOption("Back", choiceBack),
	)

	err := huh.NewSelect[string]().
		Title(title).
		Options(options...).
		Value(&action).
		Run()
	if err != nil {
		if errors.Is(err, huh.ErrUserAborted) {
			return itemActionNone, errQuit
		}

		return itemActionNone, err
	}

	switch action {
	case actionEdit:
		return itemActionEdit, nil
	case actionDelete:
		return itemActionDelete, nil
	case actionGoals:
		return itemActionGoals, nil
	default:
		return itemActionNone, nil
	}
}

func runItemForm(name, key, itemID *string, cfg itemFormConfig) error {
	var save bool

	requireNonEmpty := false

	for {
		form := buildItemFormGroup(name, key, itemID, cfg, &requireNonEmpty, &save)

		if err := form.Run(); err != nil {
			if errors.Is(err, huh.ErrUserAborted) {
				return errBack
			}

			return err
		}

		if !save {
			return errBack
		}

		// If any required field is empty, re-run with strict validation
		if *name == "" || *key == "" || *itemID == "" {
			requireNonEmpty = true

			continue
		}

		// Normalise reference to UUID
		parsedID, _ := validateReference(*itemID, cfg.supportsDeepLink)
		*itemID = parsedID

		return nil
	}
}

func buildItemFormGroup(
	name, key, itemID *string,
	cfg itemFormConfig,
	requireNonEmpty *bool,
	save *bool,
) *huh.Form {
	refDescription := "Settings → 'Copy " + titleCase(cfg.itemType) + " ID' (bottom left)."
	if cfg.supportsDeepLink {
		refDescription = "Paste UUID or lunatask:// deep link."
	}

	return huh.NewForm(
		huh.NewGroup(
			huh.NewInput().
				Title("Name").
				Description("Display name for this "+cfg.itemType+".").
				Placeholder(cfg.namePlaceholder).
				Value(name).
				Validate(makeNameValidator(requireNonEmpty)),
			huh.NewInput().
				Title("Key").
				Description("Short alias for CLI use (lowercase, no spaces).").
				Placeholder(cfg.keyPlaceholder).
				Value(key).
				Validate(makeKeyValidator(requireNonEmpty, cfg.keyValidator)),
			huh.NewInput().
				Title("Reference").
				Description(refDescription).
				Placeholder("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").
				Value(itemID).
				Validate(makeRefValidator(requireNonEmpty, cfg.supportsDeepLink)),
			huh.NewConfirm().
				Title("Save this "+cfg.itemType+"?").
				Affirmative("Save").
				Negative("Cancel").
				Value(save),
		),
	)
}

func makeNameValidator(requireNonEmpty *bool) func(string) error {
	return func(input string) error {
		if *requireNonEmpty && input == "" {
			return errNameRequired
		}

		return nil
	}
}

func makeKeyValidator(requireNonEmpty *bool, formatValidator func(string) error) func(string) error {
	return func(input string) error {
		if input == "" {
			if *requireNonEmpty {
				return errKeyRequired
			}

			return nil
		}

		return formatValidator(input)
	}
}

func makeRefValidator(requireNonEmpty *bool, supportsDeepLink bool) func(string) error {
	return func(input string) error {
		if input == "" {
			if *requireNonEmpty {
				return errRefRequired
			}

			return nil
		}

		_, err := validateReference(input, supportsDeepLink)

		return err
	}
}
