package gui

import (
	"fmt"
	"strings"

	"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
	"github.com/jesseduffield/lazygit/pkg/commands/models"
)

func (gui *Gui) handleOpenBisectMenu() error {
	if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
		return err
	}

	// no shame in getting this directly rather than using the cached value
	// given how cheap it is to obtain
	info := gui.Git.Bisect.GetInfo()
	commit := gui.getSelectedLocalCommit()
	if info.Started() {
		return gui.openMidBisectMenu(info, commit)
	} else {
		return gui.openStartBisectMenu(info, commit)
	}
}

func (gui *Gui) openMidBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
	// if there is not yet a 'current' bisect commit, or if we have
	// selected the current commit, we need to jump to the next 'current' commit
	// after we perform a bisect action. The reason we don't unconditionally jump
	// is that sometimes the user will want to go and mark a few commits as skipped
	// in a row and they wouldn't want to be jumped back to the current bisect
	// commit each time.
	// Originally we were allowing the user to, from the bisect menu, select whether
	// they were talking about the selected commit or the current bisect commit,
	// and that was a bit confusing (and required extra keypresses).
	selectCurrentAfter := info.GetCurrentSha() == "" || info.GetCurrentSha() == commit.Sha
	// we need to wait to reselect if our bisect commits aren't ancestors of our 'start'
	// ref, because we'll be reloading our commits in that case.
	waitToReselect := selectCurrentAfter && !gui.Git.Bisect.ReachableFromStart(info)

	menuItems := []*menuItem{
		{
			displayString: fmt.Sprintf(gui.Tr.Bisect.Mark, commit.ShortSha(), info.NewTerm()),
			onPress: func() error {
				gui.logAction(gui.Tr.Actions.BisectMark)
				if err := gui.Git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
					return gui.surfaceError(err)
				}

				return gui.afterMark(selectCurrentAfter, waitToReselect)
			},
		},
		{
			displayString: fmt.Sprintf(gui.Tr.Bisect.Mark, commit.ShortSha(), info.OldTerm()),
			onPress: func() error {
				gui.logAction(gui.Tr.Actions.BisectMark)
				if err := gui.Git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
					return gui.surfaceError(err)
				}

				return gui.afterMark(selectCurrentAfter, waitToReselect)
			},
		},
		{
			displayString: fmt.Sprintf(gui.Tr.Bisect.Skip, commit.ShortSha()),
			onPress: func() error {
				gui.logAction(gui.Tr.Actions.BisectSkip)
				if err := gui.Git.Bisect.Skip(commit.Sha); err != nil {
					return gui.surfaceError(err)
				}

				return gui.afterMark(selectCurrentAfter, waitToReselect)
			},
		},
		{
			displayString: gui.Tr.Bisect.ResetOption,
			onPress: func() error {
				return gui.resetBisect()
			},
		},
	}

	return gui.createMenu(
		gui.Tr.Bisect.BisectMenuTitle,
		menuItems,
		createMenuOptions{showCancel: true},
	)
}

func (gui *Gui) openStartBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
	return gui.createMenu(
		gui.Tr.Bisect.BisectMenuTitle,
		[]*menuItem{
			{
				displayString: fmt.Sprintf(gui.Tr.Bisect.MarkStart, commit.ShortSha(), info.NewTerm()),
				onPress: func() error {
					gui.logAction(gui.Tr.Actions.StartBisect)
					if err := gui.Git.Bisect.Start(); err != nil {
						return gui.surfaceError(err)
					}

					if err := gui.Git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
						return gui.surfaceError(err)
					}

					return gui.postBisectCommandRefresh()
				},
			},
			{
				displayString: fmt.Sprintf(gui.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()),
				onPress: func() error {
					gui.logAction(gui.Tr.Actions.StartBisect)
					if err := gui.Git.Bisect.Start(); err != nil {
						return gui.surfaceError(err)
					}

					if err := gui.Git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
						return gui.surfaceError(err)
					}

					return gui.postBisectCommandRefresh()
				},
			},
		},
		createMenuOptions{showCancel: true},
	)
}

func (gui *Gui) resetBisect() error {
	return gui.ask(askOpts{
		title:  gui.Tr.Bisect.ResetTitle,
		prompt: gui.Tr.Bisect.ResetPrompt,
		handleConfirm: func() error {
			gui.logAction(gui.Tr.Actions.ResetBisect)
			if err := gui.Git.Bisect.Reset(); err != nil {
				return gui.surfaceError(err)
			}

			return gui.postBisectCommandRefresh()
		},
	})
}

func (gui *Gui) showBisectCompleteMessage(candidateShas []string) error {
	prompt := gui.Tr.Bisect.CompletePrompt
	if len(candidateShas) > 1 {
		prompt = gui.Tr.Bisect.CompletePromptIndeterminate
	}

	formattedCommits, err := gui.Git.Commit.GetCommitsOneline(candidateShas)
	if err != nil {
		return gui.surfaceError(err)
	}

	return gui.ask(askOpts{
		title:  gui.Tr.Bisect.CompleteTitle,
		prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)),
		handleConfirm: func() error {
			gui.logAction(gui.Tr.Actions.ResetBisect)
			if err := gui.Git.Bisect.Reset(); err != nil {
				return gui.surfaceError(err)
			}

			return gui.postBisectCommandRefresh()
		},
	})
}

func (gui *Gui) afterMark(selectCurrent bool, waitToReselect bool) error {
	done, candidateShas, err := gui.Git.Bisect.IsDone()
	if err != nil {
		return gui.surfaceError(err)
	}

	if err := gui.afterBisectMarkRefresh(selectCurrent, waitToReselect); err != nil {
		return gui.surfaceError(err)
	}

	if done {
		return gui.showBisectCompleteMessage(candidateShas)
	}

	return nil
}

func (gui *Gui) postBisectCommandRefresh() error {
	return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{}})
}

func (gui *Gui) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error {
	selectFn := func() {
		if selectCurrent {
			gui.selectCurrentBisectCommit()
		}
	}

	if waitToReselect {
		return gui.refreshSidePanels(refreshOptions{mode: SYNC, scope: []RefreshableView{}, then: selectFn})
	} else {
		selectFn()

		return gui.postBisectCommandRefresh()
	}
}

func (gui *Gui) selectCurrentBisectCommit() {
	info := gui.Git.Bisect.GetInfo()
	if info.GetCurrentSha() != "" {
		// find index of commit with that sha, move cursor to that.
		for i, commit := range gui.State.Commits {
			if commit.Sha == info.GetCurrentSha() {
				gui.State.Contexts.BranchCommits.GetPanelState().SetSelectedLineIdx(i)
				_ = gui.State.Contexts.BranchCommits.HandleFocus()
				break
			}
		}
	}
}
