package filetree

import (
	"github.com/jesseduffield/lazygit/pkg/commands/models"
	"github.com/jesseduffield/lazygit/pkg/common"
	"github.com/jesseduffield/lazygit/pkg/gui/types"
	"github.com/samber/lo"
)

type ICommitFileTree interface {
	ITree[models.CommitFile]

	Get(index int) *CommitFileNode
	GetFile(path string) *models.CommitFile
	GetAllItems() []*CommitFileNode
	GetAllFiles() []*models.CommitFile
	GetRoot() *CommitFileNode
	SetTextFilter(filter string, useFuzzySearch bool)
	GetTextFilter() string
}

type CommitFileTree struct {
	getFiles       func() []*models.CommitFile
	tree           *Node[models.CommitFile]
	showTree       bool
	common         *common.Common
	collapsedPaths *CollapsedPaths
	textFilter     string
	useFuzzySearch bool
}

func (self *CommitFileTree) CollapseAll() {
	dirPaths := lo.FilterMap(self.GetAllItems(), func(file *CommitFileNode, index int) (string, bool) {
		return file.path, !file.IsFile()
	})

	for _, path := range dirPaths {
		self.collapsedPaths.Collapse(path)
	}
}

func (self *CommitFileTree) ExpandAll() {
	self.collapsedPaths.ExpandAll()
}

var _ ICommitFileTree = &CommitFileTree{}

func NewCommitFileTree(getFiles func() []*models.CommitFile, common *common.Common, showTree bool) *CommitFileTree {
	return &CommitFileTree{
		getFiles:       getFiles,
		common:         common,
		showTree:       showTree,
		collapsedPaths: NewCollapsedPaths(),
	}
}

func (self *CommitFileTree) ExpandToPath(path string) {
	self.collapsedPaths.ExpandToPath(path)
}

func (self *CommitFileTree) ToggleShowTree() {
	self.showTree = !self.showTree
	self.SetTree()
}

func (self *CommitFileTree) Get(index int) *CommitFileNode {
	// need to traverse the three depth first until we get to the index.
	return NewCommitFileNode(self.tree.GetNodeAtIndex(index+1, self.collapsedPaths)) // ignoring root
}

func (self *CommitFileTree) GetIndexForPath(path string) (int, bool) {
	index, found := self.tree.GetIndexForPath(path, self.collapsedPaths)
	return index - 1, found
}

func (self *CommitFileTree) GetAllItems() []*CommitFileNode {
	if self.tree == nil {
		return nil
	}

	// ignoring root
	return lo.Map(self.tree.Flatten(self.collapsedPaths)[1:], func(node *Node[models.CommitFile], _ int) *CommitFileNode {
		return NewCommitFileNode(node)
	})
}

func (self *CommitFileTree) Len() int {
	return self.tree.Size(self.collapsedPaths) - 1 // ignoring root
}

func (self *CommitFileTree) GetItem(index int) types.HasUrn {
	// Unimplemented because we don't yet need to show inlines statuses in commit file views
	return nil
}

func (self *CommitFileTree) GetAllFiles() []*models.CommitFile {
	return self.getFiles()
}

func (self *CommitFileTree) getFilesForDisplay() []*models.CommitFile {
	files := self.getFiles()
	if self.textFilter != "" {
		files = filterCommitFilesByText(files, self.textFilter, self.useFuzzySearch)
	}
	return files
}

func (self *CommitFileTree) SetTree() {
	filesForDisplay := self.getFilesForDisplay()
	guiConfig := self.common.UserConfig().Gui
	showRootItem := guiConfig.ShowRootItemInFileTree
	cmp := NodeSortComparator[models.CommitFile](guiConfig.FileTreeSortOrder, guiConfig.FileTreeSortCaseSensitive)
	if self.showTree {
		self.tree = BuildTreeFromCommitFiles(filesForDisplay, showRootItem, cmp)
	} else {
		self.tree = BuildFlatTreeFromCommitFiles(filesForDisplay, showRootItem, cmp)
	}
}

func (self *CommitFileTree) SetTextFilter(filter string, useFuzzySearch bool) {
	self.textFilter = filter
	self.useFuzzySearch = useFuzzySearch
	self.SetTree()
}

func (self *CommitFileTree) GetTextFilter() string {
	return self.textFilter
}

func (self *CommitFileTree) IsCollapsed(path string) bool {
	return self.collapsedPaths.IsCollapsed(path)
}

func (self *CommitFileTree) ToggleCollapsed(path string) {
	self.collapsedPaths.ToggleCollapsed(path)
}

func (self *CommitFileTree) GetRoot() *CommitFileNode {
	return NewCommitFileNode(self.tree)
}

func (self *CommitFileTree) CollapsedPaths() *CollapsedPaths {
	return self.collapsedPaths
}

func (self *CommitFileTree) GetFile(path string) *models.CommitFile {
	for _, file := range self.getFiles() {
		if file.Path == path {
			return file
		}
	}

	return nil
}

func (self *CommitFileTree) GetVisualDepth(index int) int {
	return self.tree.GetVisualDepthAtIndex(index+1, self.collapsedPaths) // +1 to skip root
}

func (self *CommitFileTree) InTreeMode() bool {
	return self.showTree
}
