package details

import (
	"context"

	"github.com/alcionai/clues"

	"github.com/alcionai/corso/src/cli/print"
	"github.com/alcionai/corso/src/internal/version"
	"github.com/alcionai/corso/src/pkg/path"
)

// Add a new type so we can transparently use PrintAll in different situations.
type entrySet []*Entry

func (ents entrySet) PrintEntries(ctx context.Context) {
	printEntries(ctx, ents)
}

// MaybePrintEntries is same as PrintEntries, but only prints if we
// have less than 15 items or is not json output.
func (ents entrySet) MaybePrintEntries(ctx context.Context) {
	if len(ents) <= maxPrintLimit ||
		print.DisplayJSONFormat() ||
		print.DisplayVerbose() {
		printEntries(ctx, ents)
	}
}

// Entry describes a single item stored in a Backup
type Entry struct {
	// RepoRef is the full storage path of the item in Kopia
	RepoRef   string `json:"repoRef"`
	ShortRef  string `json:"shortRef"`
	ParentRef string `json:"parentRef,omitempty"`

	// LocationRef contains the logical path structure by its human-readable
	// display names.  IE:  If an item is located at "/Inbox/Important", we
	// hold that string in the LocationRef, while the actual IDs of each
	// container are used for the RepoRef.
	// LocationRef only holds the container values, and does not include
	// the metadata prefixes (tenant, service, owner, etc) found in the
	// repoRef.
	// Currently only implemented for Exchange Calendars.
	LocationRef string `json:"locationRef,omitempty"`

	// ItemRef contains the stable id of the item itself.  ItemRef is not
	// guaranteed to be unique within a repository.  Uniqueness guarantees
	// maximally inherit from the source item. Eg: Entries for m365 mail items
	// are only as unique as m365 mail item IDs themselves.
	ItemRef string `json:"itemRef,omitempty"`

	ItemInfo
}

// ToLocationIDer takes a backup version and produces the unique location for
// this entry if possible. Reasons it may not be possible to produce the unique
// location include an unsupported backup version or missing information.
//
// TODO(ashmrtn): Remove this function completely if we ever decide to sunset
// older corso versions that didn't populate LocationRef.
func (de Entry) ToLocationIDer(backupVersion int) (LocationIDer, error) {
	if len(de.LocationRef) > 0 {
		baseLoc, err := path.Builder{}.SplitUnescapeAppend(de.LocationRef)
		if err != nil {
			return nil, clues.Wrap(err, "parsing base location info").
				With("location_ref", de.LocationRef)
		}

		// Individual services may add additional info to the base and return that.
		return de.ItemInfo.uniqueLocation(baseLoc)
	}

	rr, err := path.FromDataLayerPath(de.RepoRef, true)
	if err != nil {
		return nil, clues.Wrap(err, "getting item RepoRef").
			With("repo_ref", de.RepoRef)
	}

	var baseLoc *path.Builder

	switch de.ItemInfo.infoType() {
	case ExchangeEvent:
		if backupVersion >= 2 {
			return nil, clues.New("no previous location for calendar entry").
				With("repo_ref", rr)
		}

		fallthrough
	case ExchangeMail, ExchangeContact:
		baseLoc = path.Builder{}.Append(rr.Folders()...)

	case OneDriveItem, SharePointLibrary:
		if backupVersion >= version.OneDrive7LocationRef {
			return nil, clues.New("no previous location for drive entry").
				With("repo_ref", rr)
		}

		p, err := path.ToDrivePath(rr)
		if err != nil {
			return nil, clues.New("converting RepoRef to drive path").
				With("repo_ref", rr)
		}

		baseLoc = path.Builder{}.Append(p.Root).Append(p.Folders...)
	}

	if baseLoc == nil {
		return nil, clues.New("unable to extract LocationRef from RepoRef").
			With("repo_ref", rr)
	}

	// Individual services may add additional info to the base and return that.
	return de.ItemInfo.uniqueLocation(baseLoc)
}

// Check if a file is a metadata file. These are used to store
// additional data like permissions (in case of Drive items) and are
// not to be treated as regular files.
func (de Entry) isMetaFile() bool {
	// sharepoint types not needed, since sharepoint permissions were
	// added after IsMeta was deprecated.
	// Earlier onedrive backups used to store both metafiles and files in details.
	// So filter out just the onedrive items and check for metafiles
	return de.ItemInfo.OneDrive != nil && de.ItemInfo.OneDrive.IsMeta
}

// --------------------------------------------------------------------------------
// CLI Output
// --------------------------------------------------------------------------------

// interface compliance checks
var _ print.Printable = &Entry{}

// MinimumPrintable DetailsEntries is a passthrough func, because no
// reduction is needed for the json output.
func (de Entry) MinimumPrintable() any {
	return de
}

// Headers returns the human-readable names of properties in a DetailsEntry
// for printing out to a terminal in a columnar display.
func (de Entry) Headers(skipID bool) []string {
	hs := []string{}

	if de.ItemInfo.Folder != nil {
		hs = de.ItemInfo.Folder.Headers()
	}

	if de.ItemInfo.Exchange != nil {
		hs = de.ItemInfo.Exchange.Headers()
	}

	if de.ItemInfo.SharePoint != nil {
		hs = de.ItemInfo.SharePoint.Headers()
	}

	if de.ItemInfo.OneDrive != nil {
		hs = de.ItemInfo.OneDrive.Headers()
	}

	if de.ItemInfo.Groups != nil {
		hs = de.ItemInfo.Groups.Headers()
	}

	if skipID {
		return hs
	}

	return append([]string{"ID"}, hs...)
}

// Values returns the values matching the Headers list.
func (de Entry) Values(skipID bool) []string {
	vs := []string{}

	if de.ItemInfo.Folder != nil {
		vs = de.ItemInfo.Folder.Values()
	}

	if de.ItemInfo.Exchange != nil {
		vs = de.ItemInfo.Exchange.Values()
	}

	if de.ItemInfo.SharePoint != nil {
		vs = de.ItemInfo.SharePoint.Values()
	}

	if de.ItemInfo.OneDrive != nil {
		vs = de.ItemInfo.OneDrive.Values()
	}

	if de.ItemInfo.Groups != nil {
		vs = de.ItemInfo.Groups.Values()
	}

	if skipID {
		return vs
	}

	return append([]string{de.ShortRef}, vs...)
}
