package controllers

import (
	"github.com/jesseduffield/gocui"
	"github.com/jesseduffield/lazygit/pkg/gui/types"
	"github.com/samber/lo"
)

type PatchBuildingController struct {
	baseController
	c *ControllerCommon
}

var _ types.IController = &PatchBuildingController{}

func NewPatchBuildingController(
	common *ControllerCommon,
) *PatchBuildingController {
	return &PatchBuildingController{
		baseController: baseController{},
		c:              common,
	}
}

func (self *PatchBuildingController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
	return []*types.Binding{
		{
			Key:         opts.GetKey(opts.Config.Universal.OpenFile),
			Handler:     self.OpenFile,
			Description: self.c.Tr.OpenFile,
		},
		{
			Key:         opts.GetKey(opts.Config.Universal.Edit),
			Handler:     self.EditFile,
			Description: self.c.Tr.EditFile,
		},
		{
			Key:         opts.GetKey(opts.Config.Universal.Select),
			Handler:     self.ToggleSelectionAndRefresh,
			Description: self.c.Tr.ToggleSelectionForPatch,
		},
		{
			Key:         opts.GetKey(opts.Config.Universal.Return),
			Handler:     self.Escape,
			Description: self.c.Tr.ExitCustomPatchBuilder,
		},
	}
}

func (self *PatchBuildingController) Context() types.Context {
	return self.c.Contexts().CustomPatchBuilder
}

func (self *PatchBuildingController) context() types.IPatchExplorerContext {
	return self.c.Contexts().CustomPatchBuilder
}

func (self *PatchBuildingController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
	return []*gocui.ViewMouseBinding{}
}

func (self *PatchBuildingController) GetOnFocus() func(types.OnFocusOpts) error {
	return func(opts types.OnFocusOpts) error {
		// no need to change wrap on the secondary view because it can't be interacted with
		self.c.Views().PatchBuilding.Wrap = false

		return self.c.Helpers().PatchBuilding.RefreshPatchBuildingPanel(opts)
	}
}

func (self *PatchBuildingController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
	return func(opts types.OnFocusLostOpts) error {
		self.c.Views().PatchBuilding.Wrap = true

		if self.c.Git().Patch.PatchBuilder.IsEmpty() {
			self.c.Git().Patch.PatchBuilder.Reset()
		}

		return nil
	}
}

func (self *PatchBuildingController) OpenFile() error {
	self.context().GetMutex().Lock()
	defer self.context().GetMutex().Unlock()

	path := self.c.Contexts().CommitFiles.GetSelectedPath()

	if path == "" {
		return nil
	}

	return self.c.Helpers().Files.OpenFile(path)
}

func (self *PatchBuildingController) EditFile() error {
	self.context().GetMutex().Lock()
	defer self.context().GetMutex().Unlock()

	path := self.c.Contexts().CommitFiles.GetSelectedPath()

	if path == "" {
		return nil
	}

	lineNumber := self.context().GetState().CurrentLineNumber()
	return self.c.Helpers().Files.EditFileAtLine(path, lineNumber)
}

func (self *PatchBuildingController) ToggleSelectionAndRefresh() error {
	if err := self.toggleSelection(); err != nil {
		return err
	}

	return self.c.Refresh(types.RefreshOptions{
		Scope: []types.RefreshableView{types.PATCH_BUILDING, types.COMMIT_FILES},
	})
}

func (self *PatchBuildingController) toggleSelection() error {
	self.context().GetMutex().Lock()
	defer self.context().GetMutex().Unlock()

	toggleFunc := self.c.Git().Patch.PatchBuilder.AddFileLineRange
	filename := self.c.Contexts().CommitFiles.GetSelectedPath()
	if filename == "" {
		return nil
	}

	state := self.context().GetState()

	includedLineIndices, err := self.c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename)
	if err != nil {
		return err
	}
	currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedLineIdx())
	if currentLineIsStaged {
		toggleFunc = self.c.Git().Patch.PatchBuilder.RemoveFileLineRange
	}

	// add range of lines to those set for the file
	firstLineIdx, lastLineIdx := state.SelectedRange()

	if err := toggleFunc(filename, firstLineIdx, lastLineIdx); err != nil {
		// might actually want to return an error here
		self.c.Log.Error(err)
	}

	if state.SelectingRange() {
		state.SetLineSelectMode()
	}

	return nil
}

func (self *PatchBuildingController) Escape() error {
	return self.c.Helpers().PatchBuilding.Escape()
}