package model

import (
	"io"
	"sort"
	"strings"
	"time"

	"github.com/alist-org/alist/v3/pkg/http_range"
	"github.com/alist-org/alist/v3/pkg/utils"
	"github.com/dlclark/regexp2"

	mapset "github.com/deckarep/golang-set/v2"

	"github.com/maruel/natural"
)

type ObjUnwrap interface {
	Unwrap() Obj
}

type Obj interface {
	GetSize() int64
	GetName() string
	ModTime() time.Time
	CreateTime() time.Time
	IsDir() bool
	GetHash() utils.HashInfo

	// The internal information of the driver.
	// If you want to use it, please understand what it means
	GetID() string
	GetPath() string
}

// FileStreamer ->check FileStream for more comments
type FileStreamer interface {
	io.Reader
	io.Closer
	Obj
	GetMimetype() string
	//SetReader(io.Reader)
	NeedStore() bool
	IsForceStreamUpload() bool
	GetExist() Obj
	SetExist(Obj)
	//for a non-seekable Stream, RangeRead supports peeking some data, and CacheFullInTempFile still works
	RangeRead(http_range.Range) (io.Reader, error)
	//for a non-seekable Stream, if Read is called, this function won't work
	CacheFullInTempFile() (File, error)
}

type URL interface {
	URL() string
}

type Thumb interface {
	Thumb() string
}

type SetPath interface {
	SetPath(path string)
}

func SortFiles(objs []Obj, orderBy, orderDirection string) {
	if orderBy == "" {
		return
	}
	sort.Slice(objs, func(i, j int) bool {
		switch orderBy {
		case "name":
			{
				c := natural.Less(objs[i].GetName(), objs[j].GetName())
				if orderDirection == "desc" {
					return !c
				}
				return c
			}
		case "size":
			{
				if orderDirection == "desc" {
					return objs[i].GetSize() >= objs[j].GetSize()
				}
				return objs[i].GetSize() <= objs[j].GetSize()
			}
		case "modified":
			if orderDirection == "desc" {
				return objs[i].ModTime().After(objs[j].ModTime())
			}
			return objs[i].ModTime().Before(objs[j].ModTime())
		}
		return false
	})
}

func ExtractFolder(objs []Obj, extractFolder string) {
	if extractFolder == "" {
		return
	}
	front := extractFolder == "front"
	sort.SliceStable(objs, func(i, j int) bool {
		if objs[i].IsDir() || objs[j].IsDir() {
			if !objs[i].IsDir() {
				return !front
			}
			if !objs[j].IsDir() {
				return front
			}
		}
		return false
	})
}

func WrapObjName(objs Obj) Obj {
	return &ObjWrapName{Obj: objs}
}

func WrapObjsName(objs []Obj) {
	for i := 0; i < len(objs); i++ {
		objs[i] = &ObjWrapName{Obj: objs[i]}
	}
}

func UnwrapObj(obj Obj) Obj {
	if unwrap, ok := obj.(ObjUnwrap); ok {
		obj = unwrap.Unwrap()
	}
	return obj
}

func GetThumb(obj Obj) (thumb string, ok bool) {
	if obj, ok := obj.(Thumb); ok {
		return obj.Thumb(), true
	}
	if unwrap, ok := obj.(ObjUnwrap); ok {
		return GetThumb(unwrap.Unwrap())
	}
	return thumb, false
}

func GetUrl(obj Obj) (url string, ok bool) {
	if obj, ok := obj.(URL); ok {
		return obj.URL(), true
	}
	if unwrap, ok := obj.(ObjUnwrap); ok {
		return GetUrl(unwrap.Unwrap())
	}
	return url, false
}

func GetRawObject(obj Obj) *Object {
	switch v := obj.(type) {
	case *ObjThumbURL:
		return &v.Object
	case *ObjThumb:
		return &v.Object
	case *ObjectURL:
		return &v.Object
	case *Object:
		return v
	}
	return nil
}

// Merge
func NewObjMerge() *ObjMerge {
	return &ObjMerge{
		set: mapset.NewSet[string](),
	}
}

type ObjMerge struct {
	regs []*regexp2.Regexp
	set  mapset.Set[string]
}

func (om *ObjMerge) Merge(objs []Obj, objs_ ...Obj) []Obj {
	newObjs := make([]Obj, 0, len(objs)+len(objs_))
	newObjs = om.insertObjs(om.insertObjs(newObjs, objs...), objs_...)
	return newObjs
}

func (om *ObjMerge) insertObjs(objs []Obj, objs_ ...Obj) []Obj {
	for _, obj := range objs_ {
		if om.clickObj(obj) {
			objs = append(objs, obj)
		}
	}
	return objs
}

func (om *ObjMerge) clickObj(obj Obj) bool {
	for _, reg := range om.regs {
		if isMatch, _ := reg.MatchString(obj.GetName()); isMatch {
			return false
		}
	}
	return om.set.Add(obj.GetName())
}

func (om *ObjMerge) InitHideReg(hides string) {
	rs := strings.Split(hides, "\n")
	om.regs = make([]*regexp2.Regexp, 0, len(rs))
	for _, r := range rs {
		om.regs = append(om.regs, regexp2.MustCompile(r, regexp2.None))
	}
}

func (om *ObjMerge) Reset() {
	om.set.Clear()
}