package util

import (
	"fmt"
	"sort"
	"strings"

	"github.com/danielmiessler/fabric/internal/i18n"
	"github.com/samber/lo"
)

func NewGroupsItemsSelector[I any](selectionLabel string,
	getItemLabel func(I) string) *GroupsItemsSelector[I] {

	return &GroupsItemsSelector[I]{SelectionLabel: selectionLabel,
		GetItemKey:  getItemLabel,
		GroupsItems: make([]*GroupItems[I], 0),
	}
}

type GroupItems[I any] struct {
	Group string
	Items []I
}

func (o *GroupItems[I]) Count() int {
	return len(o.Items)
}

func (o *GroupItems[I]) ContainsItemBy(predicate func(item I) bool) (ret bool) {
	ret = lo.ContainsBy(o.Items, predicate)
	return
}

type GroupsItemsSelector[I any] struct {
	SelectionLabel string
	GetItemKey     func(I) string

	GroupsItems []*GroupItems[I]
}

func (o *GroupsItemsSelector[I]) AddGroupItems(group string, items ...I) {
	o.GroupsItems = append(o.GroupsItems, &GroupItems[I]{group, items})
}

// getSortedGroupsItems returns a copy with groups and items sorted case-insensitively.
// It does not modify o.GroupsItems.
func (o *GroupsItemsSelector[I]) getSortedGroupsItems() []*GroupItems[I] {
	sortedGroupsItems := make([]*GroupItems[I], len(o.GroupsItems))
	copy(sortedGroupsItems, o.GroupsItems)
	sort.SliceStable(sortedGroupsItems, func(i, j int) bool {
		return strings.ToLower(sortedGroupsItems[i].Group) < strings.ToLower(sortedGroupsItems[j].Group)
	})

	for i, groupItems := range sortedGroupsItems {
		sortedItems := make([]I, len(groupItems.Items))
		copy(sortedItems, groupItems.Items)
		sort.SliceStable(sortedItems, func(i, j int) bool {
			return strings.ToLower(o.GetItemKey(sortedItems[i])) < strings.ToLower(o.GetItemKey(sortedItems[j]))
		})

		sortedGroupsItems[i] = &GroupItems[I]{
			Group: groupItems.Group,
			Items: sortedItems,
		}
	}

	return sortedGroupsItems
}

func (o *GroupsItemsSelector[I]) GetGroupAndItemByItemNumber(number int) (group string, item I, err error) {
	var currentItemNumber int
	found := false

	sortedGroupsItems := o.getSortedGroupsItems()

	for _, groupItems := range sortedGroupsItems {
		if currentItemNumber+len(groupItems.Items) < number {
			currentItemNumber += len(groupItems.Items)
			continue
		}

		for _, groupItem := range groupItems.Items {
			currentItemNumber++
			if currentItemNumber == number {
				group = groupItems.Group
				item = groupItem
				found = true
				break
			}
		}

		if found {
			break
		}
	}

	if !found {
		err = fmt.Errorf("%s", fmt.Sprintf(i18n.T("groups_items_number_out_of_range"), number))
	}
	return
}

func (o *GroupsItemsSelector[I]) Print(shellCompleteList bool) {
	if !shellCompleteList {
		fmt.Printf("\n%v:\n", o.SelectionLabel)
	}

	var currentItemIndex int
	sortedGroupsItems := o.getSortedGroupsItems()

	for _, groupItems := range sortedGroupsItems {
		if !shellCompleteList {
			fmt.Println()
			fmt.Printf("%s\n\n", groupItems.Group)
		}

		for _, item := range groupItems.Items {
			currentItemIndex++
			if shellCompleteList {
				fmt.Printf("%s\n", o.GetItemKey(item))
			} else {
				fmt.Printf("\t[%d]\t%s\n", currentItemIndex, o.GetItemKey(item))
			}
		}
	}
}

func (o *GroupsItemsSelector[I]) HasGroup(group string) (ret bool) {
	for _, groupItems := range o.GroupsItems {
		if ret = strings.EqualFold(groupItems.Group, group); ret {
			break
		}
	}
	return
}

func (o *GroupsItemsSelector[I]) FindGroupsByItemFirst(item I) (ret string) {
	itemKey := o.GetItemKey(item)

	for _, groupItems := range o.GroupsItems {
		if groupItems.ContainsItemBy(func(groupItem I) bool {
			groupItemKey := o.GetItemKey(groupItem)
			return strings.EqualFold(groupItemKey, itemKey)
		}) {
			ret = groupItems.Group
			break
		}
	}
	return
}

func (o *GroupsItemsSelector[I]) FindGroupsByItem(item I) (groups []string) {
	itemKey := o.GetItemKey(item)

	for _, groupItems := range o.GroupsItems {
		if groupItems.ContainsItemBy(func(groupItem I) bool {
			groupItemKey := o.GetItemKey(groupItem)
			return strings.EqualFold(groupItemKey, itemKey)
		}) {
			groups = append(groups, groupItems.Group)
		}
	}
	return
}

func ReturnItem(item string) string {
	return item
}

func NewGroupsItemsSelectorString(selectionLabel string) *GroupsItemsSelectorString {
	return &GroupsItemsSelectorString{GroupsItemsSelector: NewGroupsItemsSelector(selectionLabel, ReturnItem)}
}

type GroupsItemsSelectorString struct {
	*GroupsItemsSelector[string]
}