package context

import (
	"github.com/jesseduffield/lazygit/pkg/gui/presentation"
	"github.com/jesseduffield/lazygit/pkg/gui/types"
	"github.com/jesseduffield/lazygit/pkg/tasks"
)

type SuggestionsContext struct {
	*ListViewModel[*types.Suggestion]
	*ListContextTrait

	State *SuggestionsContextState
}

type SuggestionsContextState struct {
	Suggestions        []*types.Suggestion
	OnConfirm          func() error
	OnClose            func() error
	OnDeleteSuggestion func() error
	AsyncHandler       *tasks.AsyncHandler

	AllowEditSuggestion bool

	// FindSuggestions will take a string that the user has typed into a prompt
	// and return a slice of suggestions which match that string.
	FindSuggestions func(string) []*types.Suggestion
}

var _ types.IListContext = (*SuggestionsContext)(nil)

func NewSuggestionsContext(
	c *ContextCommon,
) *SuggestionsContext {
	state := &SuggestionsContextState{
		AsyncHandler: tasks.NewAsyncHandler(c.OnWorker),
	}
	getModel := func() []*types.Suggestion {
		return state.Suggestions
	}

	getDisplayStrings := func(_ int, _ int) [][]string {
		return presentation.GetSuggestionListDisplayStrings(state.Suggestions)
	}

	viewModel := NewListViewModel(getModel)

	return &SuggestionsContext{
		State:         state,
		ListViewModel: viewModel,
		ListContextTrait: &ListContextTrait{
			Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
				View:                  c.Views().Suggestions,
				WindowName:            "suggestions",
				Key:                   SUGGESTIONS_CONTEXT_KEY,
				Kind:                  types.PERSISTENT_POPUP,
				Focusable:             true,
				HasUncontrolledBounds: true,
			})),
			ListRenderer: ListRenderer{
				list:              viewModel,
				getDisplayStrings: getDisplayStrings,
			},
			c: c,
		},
	}
}

func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion) {
	// SetSuggestions is invoked from AsyncHandler (a worker goroutine) when
	// the prompt input changes, as well as from prepareConfirmationPanel on
	// the UI thread. Bounce to the UI thread either way so the worker path
	// keeps flushing once HandleRender stops calling Render() itself.
	self.c.OnUIThread(func() error {
		self.State.Suggestions = suggestions
		self.SetSelection(0)
		self.c.ResetViewOrigin(self.GetView())
		self.HandleRender()
		return nil
	})
}

func (self *SuggestionsContext) RefreshSuggestions() {
	// Capture the suggestions function and the prompt input here, on the UI
	// thread, rather than inside the worker below: the main thread rewrites both
	// (State.FindSuggestions and the prompt's TextArea) when it (re)creates a
	// prompt panel, so reading them from the worker races those writes. It's
	// also more correct -- we search for the input as it was when dispatched,
	// which is what this request's AsyncHandler id corresponds to.
	findSuggestionsFn := self.State.FindSuggestions
	promptInput := self.c.GetPromptInput()
	self.State.AsyncHandler.Do(func() func() {
		if findSuggestionsFn != nil {
			suggestions := findSuggestionsFn(promptInput)
			return func() { self.SetSuggestions(suggestions) }
		}
		return func() {}
	})
}

// There is currently no need to use range-select in the suggestions view so we're disabling it.
func (self *SuggestionsContext) RangeSelectEnabled() bool {
	return false
}

func (self *SuggestionsContext) GetOnDoubleClick() func() error {
	return self.State.OnConfirm
}
