package lbl

import (
	"github.com/jesseduffield/lazygit/pkg/commands/patch"
	"github.com/sirupsen/logrus"
)

type State struct {
	selectedLineIdx   int
	rangeStartLineIdx int
	diff              string
	patchParser       *patch.PatchParser
	selectMode        selectMode
}

// these represent what select mode we're in
type selectMode int

const (
	LINE selectMode = iota
	RANGE
	HUNK
)

func NewState(diff string, selectedLineIdx int, oldState *State, log *logrus.Entry) *State {
	patchParser := patch.NewPatchParser(log, diff)

	if len(patchParser.StageableLines) == 0 {
		return nil
	}

	rangeStartLineIdx := 0
	if oldState != nil {
		rangeStartLineIdx = oldState.rangeStartLineIdx
	}

	selectMode := LINE
	// if we have clicked from the outside to focus the main view we'll pass in a non-negative line index so that we can instantly select that line
	if selectedLineIdx >= 0 {
		selectMode = RANGE
		rangeStartLineIdx = selectedLineIdx
	} else if oldState != nil {
		// if we previously had a selectMode of RANGE, we want that to now be line again
		if oldState.selectMode == HUNK {
			selectMode = HUNK
		}
		selectedLineIdx = patchParser.GetNextStageableLineIndex(oldState.selectedLineIdx)
	} else {
		selectedLineIdx = patchParser.StageableLines[0]
	}

	return &State{
		patchParser:       patchParser,
		selectedLineIdx:   selectedLineIdx,
		selectMode:        selectMode,
		rangeStartLineIdx: rangeStartLineIdx,
		diff:              diff,
	}
}

func (s *State) GetSelectedLineIdx() int {
	return s.selectedLineIdx
}

func (s *State) GetDiff() string {
	return s.diff
}

func (s *State) ToggleSelectHunk() {
	if s.selectMode == HUNK {
		s.selectMode = LINE
	} else {
		s.selectMode = HUNK
	}
}

func (s *State) ToggleSelectRange() {
	if s.selectMode == RANGE {
		s.selectMode = LINE
	} else {
		s.selectMode = RANGE
		s.rangeStartLineIdx = s.selectedLineIdx
	}
}

func (s *State) SelectingHunk() bool {
	return s.selectMode == HUNK
}

func (s *State) SelectingRange() bool {
	return s.selectMode == RANGE
}

func (s *State) SelectingLine() bool {
	return s.selectMode == LINE
}

func (s *State) SetLineSelectMode() {
	s.selectMode = LINE
}

func (s *State) SelectLine(newSelectedLineIdx int) {
	if newSelectedLineIdx < 0 {
		newSelectedLineIdx = 0
	} else if newSelectedLineIdx > len(s.patchParser.PatchLines)-1 {
		newSelectedLineIdx = len(s.patchParser.PatchLines) - 1
	}

	s.selectedLineIdx = newSelectedLineIdx
}

func (s *State) SelectNewLineForRange(newSelectedLineIdx int) {
	s.rangeStartLineIdx = newSelectedLineIdx

	s.selectMode = RANGE

	s.SelectLine(newSelectedLineIdx)
}

func (s *State) CycleSelection(forward bool) {
	if s.SelectingHunk() {
		s.CycleHunk(forward)
	} else {
		s.CycleLine(forward)
	}
}

func (s *State) CycleHunk(forward bool) {
	change := 1
	if !forward {
		change = -1
	}

	newHunk := s.patchParser.GetHunkContainingLine(s.selectedLineIdx, change)
	s.selectedLineIdx = s.patchParser.GetNextStageableLineIndex(newHunk.FirstLineIdx)
}

func (s *State) CycleLine(forward bool) {
	change := 1
	if !forward {
		change = -1
	}

	s.SelectLine(s.selectedLineIdx + change)
}

func (s *State) CurrentHunk() *patch.PatchHunk {
	return s.patchParser.GetHunkContainingLine(s.selectedLineIdx, 0)
}

func (s *State) SelectedRange() (int, int) {
	switch s.selectMode {
	case HUNK:
		hunk := s.CurrentHunk()
		return hunk.FirstLineIdx, hunk.LastLineIdx()
	case RANGE:
		if s.rangeStartLineIdx > s.selectedLineIdx {
			return s.selectedLineIdx, s.rangeStartLineIdx
		} else {
			return s.rangeStartLineIdx, s.selectedLineIdx
		}
	case LINE:
		return s.selectedLineIdx, s.selectedLineIdx
	default:
		// should never happen
		return 0, 0
	}
}

func (s *State) CurrentLineNumber() int {
	return s.CurrentHunk().LineNumberOfLine(s.selectedLineIdx)
}

func (s *State) AdjustSelectedLineIdx(change int) {
	s.SelectLine(s.selectedLineIdx + change)
}

func (s *State) RenderForLineIndices(includedLineIndices []int) string {
	firstLineIdx, lastLineIdx := s.SelectedRange()
	return s.patchParser.Render(firstLineIdx, lastLineIdx, includedLineIndices)
}

func (s *State) PlainRenderSelected() string {
	firstLineIdx, lastLineIdx := s.SelectedRange()
	return s.patchParser.PlainRenderLines(firstLineIdx, lastLineIdx)
}

func (s *State) SelectBottom() {
	s.SetLineSelectMode()
	s.SelectLine(len(s.patchParser.PatchLines) - 1)
}

func (s *State) SelectTop() {
	s.SetLineSelectMode()
	s.SelectLine(0)
}

func (s *State) CalculateOrigin(currentOrigin int, bufferHeight int) int {
	firstLineIdx, lastLineIdx := s.SelectedRange()

	return calculateOrigin(currentOrigin, bufferHeight, firstLineIdx, lastLineIdx, s.GetSelectedLineIdx(), s.selectMode)
}
