package data

import (
	"context"

	"github.com/alcionai/clues"

	"github.com/alcionai/corso/src/pkg/control"
	"github.com/alcionai/corso/src/pkg/count"
	"github.com/alcionai/corso/src/pkg/fault"
	"github.com/alcionai/corso/src/pkg/path"
)

var (
	ErrNotFound = clues.New("not found")
	ErrNoData   = clues.New("no data")
)

type CollectionState int

const (
	NewState      CollectionState = 0
	NotMovedState CollectionState = 1
	MovedState    CollectionState = 2
	DeletedState  CollectionState = 3
)

func (cs CollectionState) String() string {
	s := "Unknown State"

	switch cs {
	case 0:
		s = "New"
	case 1:
		s = "Not Moved"
	case 2:
		s = "Moved"
	case 3:
		s = "Deleted"
	}

	return s
}

type FetchRestoreCollection struct {
	Collection
	FetchItemByNamer
}

// NoFetchRestoreCollection is a wrapper for a Collection that returns
// ErrNotFound for all Fetch calls.
type NoFetchRestoreCollection struct {
	Collection
}

func (c NoFetchRestoreCollection) FetchItemByName(context.Context, string) (Item, error) {
	return nil, ErrNotFound
}

// StateOf lets us figure out the state of the collection from the
// previous and current path
func StateOf(
	prev, curr path.Path,
	counter *count.Bus,
) CollectionState {
	if curr == nil || len(curr.String()) == 0 {
		counter.Inc(count.CollectionTombstoned)
		return DeletedState
	}

	if prev == nil || len(prev.String()) == 0 {
		counter.Inc(count.CollectionNew)
		return NewState
	}

	if curr.String() != prev.String() {
		counter.Inc(count.CollectionMoved)
		return MovedState
	}

	counter.Inc(count.CollectionNotMoved)

	return NotMovedState
}

// -----------------------------------------------------------------------------
// BaseCollection
// -----------------------------------------------------------------------------

func NewBaseCollection(
	curr, prev path.Path,
	location *path.Builder,
	ctrlOpts control.Options,
	doNotMergeItems bool,
	counter *count.Bus,
) BaseCollection {
	return BaseCollection{
		opts:            ctrlOpts,
		doNotMergeItems: doNotMergeItems,
		fullPath:        curr,
		locationPath:    location,
		prevPath:        prev,
		state:           StateOf(prev, curr, counter),
		Counter:         counter.Local(),
	}
}

// BaseCollection contains basic functionality like returning path, location,
// and state information. It can be embedded in other implementations to provide
// this functionality.
//
// Functionality like how items are fetched is left to the embedding struct.
type BaseCollection struct {
	opts control.Options

	// FullPath is the current hierarchical path used by this collection.
	fullPath path.Path

	// PrevPath is the previous hierarchical path used by this collection.
	// It may be the same as fullPath, if the folder was not renamed or
	// moved.  It will be empty on its first retrieval.
	prevPath path.Path

	// LocationPath contains the path with human-readable display names.
	// IE: "/Inbox/Important" instead of "/abcdxyz123/algha=lgkhal=t"
	locationPath *path.Builder

	state CollectionState

	// doNotMergeItems should only be true if the old delta token expired.
	doNotMergeItems bool

	Counter *count.Bus
}

// FullPath returns the BaseCollection's fullPath []string
func (col *BaseCollection) FullPath() path.Path {
	return col.fullPath
}

// LocationPath produces the BaseCollection's full path, but with display names
// instead of IDs in the folders.  Only populated for Calendars.
func (col *BaseCollection) LocationPath() *path.Builder {
	return col.locationPath
}

func (col BaseCollection) PreviousPath() path.Path {
	return col.prevPath
}

func (col BaseCollection) State() CollectionState {
	return col.state
}

func (col BaseCollection) Category() path.CategoryType {
	switch {
	case col.FullPath() != nil:
		return col.FullPath().Category()
	case col.PreviousPath() != nil:
		return col.PreviousPath().Category()
	}

	return path.UnknownCategory
}

func (col BaseCollection) DoNotMergeItems() bool {
	return col.doNotMergeItems
}

func (col BaseCollection) Opts() control.Options {
	return col.opts
}

// -----------------------------------------------------------------------------
// tombstoneCollection
// -----------------------------------------------------------------------------

func NewTombstoneCollection(
	prev path.Path,
	opts control.Options,
	counter *count.Bus,
) *tombstoneCollection {
	return &tombstoneCollection{
		BaseCollection: NewBaseCollection(
			nil,
			prev,
			nil,
			opts,
			false,
			counter),
	}
}

// tombstoneCollection is a collection that marks a folder (and folders under it
// if they aren't explicitly noted in other collecteds) as deleted. It doesn't
// contain any items.
type tombstoneCollection struct {
	BaseCollection
}

// Items never returns any data for tombstone collections because the collection
// only denotes the deletion of the current folder and possibly subfolders. All
// items contained in the deleted folder are also deleted.
func (col *tombstoneCollection) Items(context.Context, *fault.Bus) <-chan Item {
	return nil
}
