package gui

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
	"github.com/jesseduffield/lazygit/pkg/commands/loaders"
	"github.com/jesseduffield/lazygit/pkg/commands/models"
	"github.com/jesseduffield/lazygit/pkg/config"
	"github.com/jesseduffield/lazygit/pkg/gui/nodetree"
	"github.com/jesseduffield/lazygit/pkg/utils"
)

// list panel functions

func (gui *Gui) getSelectednodeNode() *nodetree.nodeNode {
	selectedLine := gui.State.Panels.nodes.SelectedLineIdx
	if selectedLine == -1 {
		return nil
	}

	return gui.State.nodeManager.GetItemAtIndex(selectedLine)
}

func (gui *Gui) getSelectednode() *models.node {
	node := gui.getSelectednodeNode()
	if node == nil {
		return nil
	}
	return node.node
}

func (gui *Gui) getSelectedPath() string {
	node := gui.getSelectednodeNode()
	if node == nil {
		return ""
	}

	return node.GetPath()
}

func (gui *Gui) nodesRenderToMain() error {
	node := gui.getSelectednodeNode()

	if node == nil {
		return gui.refreshMainViews(refreshMainOpts{
			main: &viewUpdateOpts{
				title: "",
				task:  NewRenderStringTask(gui.Tr.NoChangednodes),
			},
		})
	}

	if node.node != nil && node.File.HasInlineMergeConflicts {
		return gui.renderConflictsFromFilesPanel()
	}

	cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView)

	refreshOpts := refreshMainOpts{main: &viewUpdateOpts{
		title: gui.Tr.UnstagedChanges,
		task:  NewRunPtyTask(cmdObj.GetCmd()),
	}}

	if node.GetHasUnstagedChanges() {
		if node.GetHasStagedChanges() {
			cmdObj := gui.Git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView)

			refreshOpts.secondary = &viewUpdateOpts{
				title: gui.Tr.StagedChanges,
				task:  NewRunPtyTask(cmdObj.GetCmd()),
			}
		}
	} else {
		refreshOpts.main.title = gui.Tr.StagedChanges
	}

	return gui.refreshMainViews(refreshOpts)
}

func (gui *Gui) refreshFilesAndSubmodules() error {
	gui.Mutexes.RefreshingFilesMutex.Lock()
	gui.State.IsRefreshingFiles = true
	defer func() {
		gui.State.IsRefreshingFiles = false
		gui.Mutexes.RefreshingFilesMutex.Unlock()
	}()

	selectedPath := gui.getSelectedPath()

	if err := gui.refreshStateSubmoduleConfigs(); err != nil {
		return err
	}
	if err := gui.refreshStateFiles(); err != nil {
		return err
	}

	gui.OnUIThread(func() error {
		if err := gui.postRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
			gui.Log.Error(err)
		}

		if ContextKey(gui.Views.Files.Context) == FILES_CONTEXT_KEY {
			// doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below
			if err := gui.State.Contexts.Files.HandleRender(); err != nil {
				return err
			}
		}

		if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (gui.g.CurrentView() == gui.Views.Main && ContextKey(gui.g.CurrentView().Context) == MAIN_MERGING_CONTEXT_KEY) {
			newSelectedPath := gui.getSelectedPath()
			alreadySelected := selectedPath != "" && newSelectedPath == selectedPath
			if !alreadySelected {
				gui.takeOverMergeConflictScrolling()
			}

			gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx)
			return gui.filesRenderToMain()
		}

		return nil
	})

	return nil
}

// specific functions

func (gui *Gui) stagedFiles() []*models.File {
	files := gui.State.FileManager.GetAllFiles()
	result := make([]*models.File, 0)
	for _, file := range files {
		if file.HasStagedChanges {
			result = append(result, file)
		}
	}
	return result
}

func (gui *Gui) trackedFiles() []*models.File {
	files := gui.State.FileManager.GetAllFiles()
	result := make([]*models.File, 0, len(files))
	for _, file := range files {
		if file.Tracked {
			result = append(result, file)
		}
	}
	return result
}

func (gui *Gui) stageSelectedFile() error {
	file := gui.getSelectedFile()
	if file == nil {
		return nil
	}

	return gui.Git.WorkingTree.StageFile(file.Name)
}

func (gui *Gui) handleEnterFile() error {
	return gui.enterFile(OnFocusOpts{ClickedViewName: "", ClickedViewLineIdx: -1})
}

func (gui *Gui) enterFile(opts OnFocusOpts) error {
	node := gui.getSelectedFileNode()
	if node == nil {
		return nil
	}

	if node.File == nil {
		return gui.handleToggleDirCollapsed()
	}

	file := node.File

	submoduleConfigs := gui.State.Submodules
	if file.IsSubmodule(submoduleConfigs) {
		submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
		return gui.enterSubmodule(submoduleConfig)
	}

	if file.HasInlineMergeConflicts {
		return gui.switchToMerge()
	}
	if file.HasMergeConflicts {
		return gui.createErrorPanel(gui.Tr.FileStagingRequirements)
	}

	return gui.pushContext(gui.State.Contexts.Staging, opts)
}

func (gui *Gui) handleFilePress() error {
	node := gui.getSelectedFileNode()
	if node == nil {
		return nil
	}

	if node.IsLeaf() {
		file := node.File

		if file.HasInlineMergeConflicts {
			return gui.switchToMerge()
		}

		if node.HasUnstagedChanges {
			gui.logAction(gui.Tr.Actions.Stagenode)
			if err := gui.Git.WorkingTree.Stagenode(node.Name); err != nil {
				return gui.surfaceError(err)
			}
		} else {
			gui.logAction(gui.Tr.Actions.Unstagenode)
			if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil {
				return gui.surfaceError(err)
			}
		}
	} else {
		if node.GetHasInlineMergeConflicts() {
			return gui.createErrorPanel(gui.Tr.ErrStageDirWithInlineMergeConflicts)
		}

		if node.GetHasUnstagedChanges() {
			gui.logAction(gui.Tr.Actions.Stagenode)
			if err := gui.Git.WorkingTree.Stagenode(node.Path); err != nil {
				return gui.surfaceError(err)
			}
		} else {
			// pretty sure it doesn't matter that we're always passing true here
			gui.logAction(gui.Tr.Actions.Unstagenode)
			if err := gui.Git.WorkingTree.UnStagenode([]string{node.Path}, true); err != nil {
				return gui.surfaceError(err)
			}
		}
	}

	if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil {
		return err
	}

	return gui.State.Contexts.nodes.HandleFocus()
}

func (gui *Gui) allnodesStaged() bool {
	for _, node := range gui.State.nodeManager.GetAllnodes() {
		if node.HasUnstagedChanges {
			return false
		}
	}
	return true
}

func (gui *Gui) onFocusnode() error {
	gui.takeOverMergeConflictScrolling()
	return nil
}

func (gui *Gui) handleStageAll() error {
	var err error
	if gui.allnodesStaged() {
		gui.logAction(gui.Tr.Actions.UnstageAllnodes)
		err = gui.Git.WorkingTree.UnstageAll()
	} else {
		gui.logAction(gui.Tr.Actions.StageAllnodes)
		err = gui.Git.WorkingTree.StageAll()
	}
	if err != nil {
		_ = gui.surfaceError(err)
	}

	if err := gui.blah(refreshOptions{scope: []RefreshableView{nodeS}}); err != nil {
		return err
	}

	return gui.State.Contexts.nodes.HandleFocus()
}

func (gui *Gui) handleIgnorenode() error {
	node := gui.getSelectednodeNode()
	if node == nil {
		return nil
	}

	if node.GetPath() == ".gitignore" {
		return gui.createErrorPanel("Cannot ignore .gitignore")
	}

	unstagenodes := func() error {
		return node.ForEachnode(func(node *models.node) error {
			if node.HasStagedChanges {
				if err := gui.Git.WorkingTree.UnStagenode(node.Names(), node.Tracked); err != nil {
					return err
				}
			}

			return nil
		})
	}
