package details

import (
	"context"

	"github.com/alcionai/corso/src/cli/print"
)

// DetailsModel describes what was stored in a Backup
type DetailsModel struct {
	Entries []Entry `json:"entries"`
}

// Print writes the DetailModel Entries to StdOut, in the format
// requested by the caller.
func (dm DetailsModel) PrintEntries(ctx context.Context) {
	printEntries(ctx, dm.Entries)
}

type infoer interface {
	Entry | *Entry
	// Need this here so we can access the infoType function without a type
	// assertion. See https://stackoverflow.com/a/71378366 for more details.
	infoType() ItemType
}

func printEntries[T infoer](ctx context.Context, entries []T) {
	if print.DisplayJSONFormat() {
		printJSON(ctx, entries)
	} else {
		printTable(ctx, entries)
	}
}

func printTable[T infoer](ctx context.Context, entries []T) {
	perType := map[ItemType][]print.Printable{}

	for _, ent := range entries {
		it := ent.infoType()
		ps, ok := perType[it]

		if !ok {
			ps = []print.Printable{}
		}

		perType[it] = append(ps, print.Printable(ent))
	}

	for _, ps := range perType {
		print.All(ctx, ps...)
	}
}

func printJSON[T infoer](ctx context.Context, entries []T) {
	ents := []print.Printable{}

	for _, ent := range entries {
		ents = append(ents, print.Printable(ent))
	}

	print.All(ctx, ents...)
}

// Paths returns the list of Paths for non-folder and non-meta items extracted
// from the Entries slice.
func (dm DetailsModel) Paths() []string {
	r := make([]string, 0, len(dm.Entries))

	for _, ent := range dm.Entries {
		if ent.Folder != nil ||
			ent.isMetaFile() {
			continue
		}

		r = append(r, ent.RepoRef)
	}

	return r
}

// Items returns a slice of *ItemInfo that does not contain any FolderInfo
// entries. Required because not all folders in the details are valid resource
// paths, and we want to slice out metadata.
func (dm DetailsModel) Items() entrySet {
	res := make([]*Entry, 0, len(dm.Entries))

	for i := 0; i < len(dm.Entries); i++ {
		ent := dm.Entries[i]
		if ent.Folder != nil ||
			ent.isMetaFile() {
			continue
		}

		res = append(res, &ent)
	}

	return res
}

// FilterMetaFiles returns a copy of the Details with all of the
// .meta files removed from the entries.
func (dm DetailsModel) FilterMetaFiles() DetailsModel {
	d2 := DetailsModel{
		Entries: []Entry{},
	}

	for _, ent := range dm.Entries {
		if !ent.isMetaFile() {
			d2.Entries = append(d2.Entries, ent)
		}
	}

	return d2
}

// SumNonMetaFileSizes returns the total size of items excluding all the
// .meta files from the items.
func (dm DetailsModel) SumNonMetaFileSizes() int64 {
	var size int64

	// Items will provide only files and filter out folders
	for _, ent := range dm.FilterMetaFiles().Items() {
		size += ent.size()
	}

	return size
}
