package model

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

	"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
	"github.com/OpenListTeam/OpenList/v4/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
	utils.ClosersIF
	Obj
	GetMimetype() string
	NeedStore() bool
	IsForceStreamUpload() bool
	GetExist() Obj
	SetExist(Obj)
	// for a non-seekable Stream, RangeRead supports peeking some data, and CacheFullAndWriter still works
	RangeRead(http_range.Range) (io.Reader, error)
	// for a non-seekable Stream, if Read is called, this function won't work.
	// caches the full Stream and writes it to writer (if provided, even if the stream is already cached).
	CacheFullAndWriter(up *UpdateProgress, writer io.Writer) (File, error)
	// if the Stream is not a File and is not cached, returns nil.
	GetFile() File
}

type UpdateProgress func(percentage float64)

func UpdateProgressWithRange(inner UpdateProgress, start, end float64) UpdateProgress {
	return func(p float64) {
		if p < 0 {
			p = 0
		}
		if p > 100 {
			p = 100
		}
		scaled := start + (end-start)*(p/100.0)
		inner(scaled)
	}
}

type URL interface {
	URL() string
}

type Thumb interface {
	Thumb() string
}

type SetPath interface {
	SetPath(path string)
}

type ObjWithProvider interface {
	GetProvider() 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{Name: utils.MappingName(objs.GetName()), Obj: objs}
}

func WrapObjsName(objs []Obj) {
	for i := range objs {
		objs[i] = &ObjWrapName{Name: utils.MappingName(objs[i].GetName()), Obj: objs[i]}
	}
}

func UnwrapObjName(obj Obj) Obj {
	if n, ok := obj.(*ObjWrapName); ok {
		return n.Obj
	}
	return obj
}

func GetThumb(obj Obj) (thumb string, ok bool) {
	for {
		switch o := obj.(type) {
		case Thumb:
			return o.Thumb(), true
		case ObjUnwrap:
			obj = o.Unwrap()
		default:
			return
		}
	}
}

func GetUrl(obj Obj) (url string, ok bool) {
	for {
		switch o := obj.(type) {
		case URL:
			return o.URL(), true
		case ObjUnwrap:
			obj = o.Unwrap()
		default:
			return
		}
	}
}

func GetProvider(obj Obj) (string, bool) {
	for {
		switch o := obj.(type) {
		case ObjWithProvider:
			return o.GetProvider(), true
		case ObjUnwrap:
			obj = o.Unwrap()
		default:
			return "unknown", false
		}
	}
}

// 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()
}

type ObjMask uint8

func (m ObjMask) GetObjMask() ObjMask {
	return m
}

const (
	Virtual ObjMask = 1 << iota
	NoRename
	NoRemove
	NoMove
	NoCopy
	NoWrite
	Temp
)
const (
	Locked   = NoRename | NoRemove | NoMove
	ReadOnly = Locked | NoWrite // NoRename | NoDelete | NoMove | NoWrite
)

type ObjWrapMask struct {
	Obj
	Mask ObjMask
}

func (m *ObjWrapMask) Unwrap() Obj {
	return m.Obj
}
func (m *ObjWrapMask) GetObjMask() ObjMask {
	return m.Mask
}

func GetObjMask(obj Obj) ObjMask {
	for {
		switch o := obj.(type) {
		case interface{ GetObjMask() ObjMask }:
			return o.GetObjMask()
		case ObjUnwrap:
			obj = o.Unwrap()
		default:
			return 0
		}
	}
}

func ObjHasMask(obj Obj, mask ObjMask) bool {
	return GetObjMask(obj)&mask != 0
}
