package analyze

import (
	"iter"
	"path/filepath"
	"sort"
	"sync"
	"time"

	"github.com/dundee/gdu/v5/pkg/fs"
)

// File struct
type File struct {
	Mtime  time.Time
	Parent fs.Item
	Name   string
	Size   int64
	Usage  int64
	Mli    uint64
	Flag   rune
}

// GetName returns name of dir
func (f *File) GetName() string {
	return f.Name
}

// IsDir returns false for file
func (f *File) IsDir() bool {
	return false
}

// GetParent returns parent dir
func (f *File) GetParent() fs.Item {
	return f.Parent
}

// SetParent sets parent dir
func (f *File) SetParent(parent fs.Item) {
	f.Parent = parent
}

// GetPath returns absolute Get of the file
func (f *File) GetPath() string {
	return filepath.Join(f.Parent.GetPath(), f.Name)
}

// GetFlag returns flag of the file
func (f *File) GetFlag() rune {
	return f.Flag
}

// GetSize returns size of the file
func (f *File) GetSize() int64 {
	return f.Size
}

// GetUsage returns usage of the file
func (f *File) GetUsage() int64 {
	return f.Usage
}

// GetMtime returns mtime of the file
func (f *File) GetMtime() time.Time {
	return f.Mtime
}

// GetType returns name type of item
func (f *File) GetType() string {
	if f.Flag == '@' {
		return "Other"
	}
	return "File"
}

// GetItemCount returns 1 for file
func (f *File) GetItemCount() int64 {
	return 1
}

// GetMultiLinkedInode returns inode number of multilinked file
func (f *File) GetMultiLinkedInode() uint64 {
	return f.Mli
}

func (f *File) alreadyCounted(linkedItems fs.HardLinkedItems) bool {
	mli := f.Mli
	counted := false
	if mli > 0 {
		f.Flag = 'H'
		if _, ok := linkedItems[mli]; ok {
			counted = true
		}
		linkedItems[mli] = append(linkedItems[mli], f)
	}
	return counted
}

// GetItemStats returns 1 as count of items, apparent usage and real usage of this file
func (f *File) GetItemStats(linkedItems fs.HardLinkedItems) (itemCount, size, usage int64) {
	if f.alreadyCounted(linkedItems) {
		return 1, 0, 0
	}
	return 1, f.GetSize(), f.GetUsage()
}

// UpdateStats does nothing on file
func (f *File) UpdateStats(linkedItems fs.HardLinkedItems) {}

// GetFiles returns all files in directory
func (f *File) GetFiles(sortBy fs.SortBy, order fs.SortOrder) iter.Seq[fs.Item] {
	return func(yield func(fs.Item) bool) {}
}

// GetFilesLocked returns all files in directory
func (f *File) GetFilesLocked(sortBy fs.SortBy, order fs.SortOrder) iter.Seq[fs.Item] {
	return f.GetFiles(sortBy, order)
}

// RLock panics on file
func (f *File) RLock() func() {
	panic("RLock should not be called on file")
}

// AddFile panics on file
func (f *File) AddFile(item fs.Item) {
	panic("AddFile should not be called on file")
}

// RemoveFile panics on file
func (f *File) RemoveFile(item fs.Item) {
	panic("RemoveFile should not be called on file")
}

// RemoveFileByName panics on file
func (f *File) RemoveFileByName(name string) {
	panic("RemoveFileByName should not be called on file")
}

// Dir struct
type Dir struct {
	*File
	BasePath  string
	Files     fs.Files
	ItemCount int64
	m         sync.RWMutex
}

// AddFile add item to files
func (f *Dir) AddFile(item fs.Item) {
	f.Files = append(f.Files, item)
}

// GetFiles returns all files in directory as a sorted iterator
func (f *Dir) GetFiles(sortBy fs.SortBy, order fs.SortOrder) iter.Seq[fs.Item] {
	return func(yield func(fs.Item) bool) {
		// Make a copy to avoid modifying the original slice
		sorted := make(fs.Files, len(f.Files))
		copy(sorted, f.Files)
		sortFiles(sorted, sortBy, order)

		for _, item := range sorted {
			if !yield(item) {
				return
			}
		}
	}
}

// GetFilesLocked returns all files in directory as a sorted iterator
// It is safe to call this function from multiple goroutines
func (f *Dir) GetFilesLocked(sortBy fs.SortBy, order fs.SortOrder) iter.Seq[fs.Item] {
	return func(yield func(fs.Item) bool) {
		f.m.RLock()
		defer f.m.RUnlock()

		// Make a copy to avoid modifying the original slice
		sorted := make(fs.Files, len(f.Files))
		copy(sorted, f.Files)
		sortFiles(sorted, sortBy, order)

		for _, item := range sorted {
			if !yield(item) {
				return
			}
		}
	}
}

// GetType returns name type of item
func (f *Dir) GetType() string {
	return "Directory"
}

// GetItemCount returns number of files in dir
func (f *Dir) GetItemCount() int64 {
	f.m.RLock()
	defer f.m.RUnlock()
	return f.ItemCount
}

// IsDir returns true for dir
func (f *Dir) IsDir() bool {
	return true
}

// GetPath returns absolute path of the file
func (f *Dir) GetPath() string {
	if f.BasePath != "" {
		return filepath.Join(f.BasePath, f.Name)
	}
	if f.Parent != nil {
		return filepath.Join(f.Parent.GetPath(), f.Name)
	}
	return f.Name
}

// GetItemStats returns item count, apparent usage and real usage of this dir
func (f *Dir) GetItemStats(linkedItems fs.HardLinkedItems) (itemCount, size, usage int64) {
	f.UpdateStats(linkedItems)
	return f.ItemCount, f.GetSize(), f.GetUsage()
}

// UpdateStats recursively updates size and item count
func (f *Dir) UpdateStats(linkedItems fs.HardLinkedItems) {
	totalSize := int64(4096)
	totalUsage := int64(4096)
	var itemCount int64
	for _, entry := range f.Files {
		count, size, usage := entry.GetItemStats(linkedItems)
		totalSize += size
		totalUsage += usage
		itemCount += count

		if entry.GetMtime().After(f.Mtime) {
			f.Mtime = entry.GetMtime()
		}

		switch entry.GetFlag() {
		case '!', '.':
			if f.Flag != '!' {
				f.Flag = '.'
			}
		}
	}
	f.ItemCount = itemCount + 1
	f.Size = totalSize
	f.Usage = totalUsage
}

// RemoveFile removes item from dir, updates size and item count
func (f *Dir) RemoveFile(item fs.Item) {
	f.m.Lock()
	defer f.m.Unlock()

	f.Files = f.Files.Remove(item)

	cur := f
	for {
		cur.ItemCount -= item.GetItemCount()
		cur.Size -= item.GetSize()
		cur.Usage -= item.GetUsage()

		if cur.Parent == nil {
			break
		}
		cur = cur.Parent.(*Dir)
	}
}

// sortFiles sorts files in place according to sortBy and order
func sortFiles(files fs.Files, sortBy fs.SortBy, order fs.SortOrder) {
	var sorter sort.Interface
	switch sortBy {
	case fs.SortByName:
		sorter = fs.ByName(files)
	case fs.SortByItemCount:
		sorter = fs.ByItemCount(files)
	case fs.SortByMtime:
		sorter = fs.ByMtime(files)
	case fs.SortByApparentSize:
		sorter = fs.ByApparentSize(files)
	case fs.SortBySize:
		sorter = files
	}

	if order == fs.SortDesc {
		sort.Sort(sort.Reverse(sorter))
	} else {
		sort.Sort(sorter)
	}
}

// RLock read locks dir
func (f *Dir) RLock() func() {
	f.m.RLock()
	return f.m.RUnlock
}

// RemoveFileByName removes item by name from dir
func (f *Dir) RemoveFileByName(name string) {
	f.m.Lock()
	defer f.m.Unlock()

	idx, ok := f.Files.FindByName(name)
	if !ok {
		return
	}
	item := f.Files[idx]
	f.Files = append(f.Files[:idx], f.Files[idx+1:]...)

	cur := f
	for {
		cur.ItemCount -= item.GetItemCount()
		cur.Size -= item.GetSize()
		cur.Usage -= item.GetUsage()

		if cur.Parent == nil {
			break
		}
		cur = cur.Parent.(*Dir)
	}
}
