package context

import (
	"fmt"
	"time"

	"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
	"github.com/jesseduffield/lazygit/pkg/commands/models"
	"github.com/jesseduffield/lazygit/pkg/gui/presentation"
	"github.com/jesseduffield/lazygit/pkg/gui/types"
	"github.com/samber/lo"
)

type SubCommitsContext struct {
	c *ContextCommon

	*SubCommitsViewModel
	*ListContextTrait
	*DynamicTitleBuilder
	*SearchTrait
}

var (
	_ types.IListContext    = (*SubCommitsContext)(nil)
	_ types.DiffableContext = (*SubCommitsContext)(nil)
)

func NewSubCommitsContext(
	c *ContextCommon,
) *SubCommitsContext {
	viewModel := &SubCommitsViewModel{
		ListViewModel: NewListViewModel(
			func() []*models.Commit { return c.Model().SubCommits },
		),
		ref:          nil,
		limitCommits: true,
	}

	getDisplayStrings := func(startIdx int, endIdx int) [][]string {
		// This can happen if a sub-commits view is asked to be rerendered while
		// it is invisble; for example when switching screen modes, which
		// rerenders all views.
		if viewModel.GetRef() == nil {
			return [][]string{}
		}

		selectedCommitSha := ""
		if c.CurrentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY {
			selectedCommit := viewModel.GetSelected()
			if selectedCommit != nil {
				selectedCommitSha = selectedCommit.Sha
			}
		}
		branches := []*models.Branch{}
		if viewModel.GetShowBranchHeads() {
			branches = c.Model().Branches
		}
		hasRebaseUpdateRefsConfig := c.Git().Config.GetRebaseUpdateRefs()
		return presentation.GetCommitListDisplayStrings(
			c.Common,
			c.Model().SubCommits,
			branches,
			viewModel.GetRef().RefName(),
			hasRebaseUpdateRefsConfig,
			c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
			c.Modes().CherryPicking.SelectedShaSet(),
			c.Modes().Diffing.Ref,
			"",
			c.UserConfig.Gui.TimeFormat,
			c.UserConfig.Gui.ShortTimeFormat,
			time.Now(),
			c.UserConfig.Git.ParseEmoji,
			selectedCommitSha,
			startIdx,
			endIdx,
			// Don't show the graph in the left/right view; we'd like to, but
			// it's too complicated:
			shouldShowGraph(c) && viewModel.GetRefToShowDivergenceFrom() == "",
			git_commands.NewNullBisectInfo(),
			false,
		)
	}

	getNonModelItems := func() []*NonModelItem {
		result := []*NonModelItem{}
		if viewModel.GetRefToShowDivergenceFrom() != "" {
			_, upstreamIdx, found := lo.FindIndexOf(
				c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceRight })
			if !found {
				upstreamIdx = 0
			}
			result = append(result, &NonModelItem{
				Index:   upstreamIdx,
				Content: fmt.Sprintf("--- %s ---", c.Tr.DivergenceSectionHeaderRemote),
			})

			_, localIdx, found := lo.FindIndexOf(
				c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceLeft })
			if !found {
				localIdx = len(c.Model().SubCommits)
			}
			result = append(result, &NonModelItem{
				Index:   localIdx,
				Content: fmt.Sprintf("--- %s ---", c.Tr.DivergenceSectionHeaderLocal),
			})
		}

		return result
	}

	ctx := &SubCommitsContext{
		c:                   c,
		SubCommitsViewModel: viewModel,
		SearchTrait:         NewSearchTrait(c),
		DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle),
		ListContextTrait: &ListContextTrait{
			Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
				View:                       c.Views().SubCommits,
				WindowName:                 "branches",
				Key:                        SUB_COMMITS_CONTEXT_KEY,
				Kind:                       types.SIDE_CONTEXT,
				Focusable:                  true,
				Transient:                  true,
				NeedsRerenderOnWidthChange: true,
			})),
			ListRenderer: ListRenderer{
				list:              viewModel,
				getDisplayStrings: getDisplayStrings,
				getNonModelItems:  getNonModelItems,
			},
			c:                       c,
			refreshViewportOnChange: true,
		},
	}

	ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(func(selectedLineIdx int) error {
		ctx.GetList().SetSelection(selectedLineIdx)
		return ctx.HandleFocus(types.OnFocusOpts{})
	}))

	return ctx
}

type SubCommitsViewModel struct {
	// name of the ref that the sub-commits are shown for
	ref                     types.Ref
	refToShowDivergenceFrom string
	*ListViewModel[*models.Commit]

	limitCommits    bool
	showBranchHeads bool
}

func (self *SubCommitsViewModel) SetRef(ref types.Ref) {
	self.ref = ref
}

func (self *SubCommitsViewModel) GetRef() types.Ref {
	return self.ref
}

func (self *SubCommitsViewModel) SetRefToShowDivergenceFrom(ref string) {
	self.refToShowDivergenceFrom = ref
}

func (self *SubCommitsViewModel) GetRefToShowDivergenceFrom() string {
	return self.refToShowDivergenceFrom
}

func (self *SubCommitsViewModel) SetShowBranchHeads(value bool) {
	self.showBranchHeads = value
}

func (self *SubCommitsViewModel) GetShowBranchHeads() bool {
	return self.showBranchHeads
}

func (self *SubCommitsContext) CanRebase() bool {
	return false
}

func (self *SubCommitsContext) GetSelectedRef() types.Ref {
	commit := self.GetSelected()
	if commit == nil {
		return nil
	}
	return commit
}

func (self *SubCommitsContext) GetCommits() []*models.Commit {
	return self.getModel()
}

func (self *SubCommitsContext) SetLimitCommits(value bool) {
	self.limitCommits = value
}

func (self *SubCommitsContext) GetLimitCommits() bool {
	return self.limitCommits
}

func (self *SubCommitsContext) GetDiffTerminals() []string {
	itemId := self.GetSelectedItemId()

	return []string{itemId}
}
