package context

import (
	"github.com/jesseduffield/gocui"
	"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
	"github.com/jesseduffield/lazygit/pkg/gui/types"
	deadlock "github.com/sasha-s/go-deadlock"
)

type PatchExplorerContext struct {
	*SimpleContext
	*SearchTrait

	state                  *patch_exploring.State
	viewTrait              *ViewTrait
	getIncludedLineIndices func() []int
	c                      *ContextCommon
	mutex                  *deadlock.Mutex
}

var _ types.IPatchExplorerContext = (*PatchExplorerContext)(nil)

func NewPatchExplorerContext(
	view *gocui.View,
	windowName string,
	key types.ContextKey,

	getIncludedLineIndices func() []int,

	c *ContextCommon,
) *PatchExplorerContext {
	ctx := &PatchExplorerContext{
		state:                  nil,
		viewTrait:              NewViewTrait(view),
		c:                      c,
		mutex:                  &deadlock.Mutex{},
		getIncludedLineIndices: getIncludedLineIndices,
		SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
			View:             view,
			WindowName:       windowName,
			Key:              key,
			Kind:             types.MAIN_CONTEXT,
			Focusable:        true,
			HighlightOnFocus: true,
		})),
		SearchTrait: NewSearchTrait(c),
	}

	ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(
		func(selectedLineIdx int) error {
			ctx.GetMutex().Lock()
			defer ctx.GetMutex().Unlock()
			return ctx.NavigateTo(ctx.c.IsCurrentContext(ctx), selectedLineIdx)
		}),
	)

	return ctx
}

func (self *PatchExplorerContext) IsPatchExplorerContext() {}

func (self *PatchExplorerContext) GetState() *patch_exploring.State {
	return self.state
}

func (self *PatchExplorerContext) SetState(state *patch_exploring.State) {
	self.state = state
}

func (self *PatchExplorerContext) GetViewTrait() types.IViewTrait {
	return self.viewTrait
}

func (self *PatchExplorerContext) GetIncludedLineIndices() []int {
	return self.getIncludedLineIndices()
}

func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) error {
	self.setContent(isFocused)

	self.FocusSelection()
	self.c.Render()

	return nil
}

func (self *PatchExplorerContext) Render(isFocused bool) error {
	self.setContent(isFocused)

	self.c.Render()

	return nil
}

func (self *PatchExplorerContext) Focus() error {
	self.FocusSelection()
	self.c.Render()

	return nil
}

func (self *PatchExplorerContext) setContent(isFocused bool) {
	self.GetView().SetContent(self.GetContentToRender(isFocused))
}

func (self *PatchExplorerContext) FocusSelection() {
	view := self.GetView()
	state := self.GetState()
	_, viewHeight := view.Size()
	bufferHeight := viewHeight - 1
	_, origin := view.Origin()
	numLines := view.LinesHeight()

	newOriginY := state.CalculateOrigin(origin, bufferHeight, numLines)

	_ = view.SetOriginY(newOriginY)

	startIdx, endIdx := state.SelectedRange()
	// As far as the view is concerned, we are always selecting a range
	view.SetRangeSelectStart(startIdx)
	view.SetCursorY(endIdx - newOriginY)
}

func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {
	if self.GetState() == nil {
		return ""
	}

	return self.GetState().RenderForLineIndices(isFocused, self.GetIncludedLineIndices())
}

func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int) error {
	self.GetState().SetLineSelectMode()
	self.GetState().SelectLine(selectedLineIdx)

	return self.RenderAndFocus(isFocused)
}

func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
	return self.mutex
}
