package patch

import (
	"fmt"
	"strings"

	"github.com/jesseduffield/lazygit/pkg/utils"
)

type PatchHunk struct {
	FirstLineIdx int
	oldStart     int
	newStart     int
	heading      string
	bodyLines    []string
}

func (hunk *PatchHunk) LastLineIdx() int {
	return hunk.FirstLineIdx + len(hunk.bodyLines)
}

func newHunk(lines []string, firstLineIdx int) *PatchHunk {
	header := lines[0]
	bodyLines := lines[1:]

	oldStart, newStart, heading := headerInfo(header)

	return &PatchHunk{
		oldStart:     oldStart,
		newStart:     newStart,
		heading:      heading,
		FirstLineIdx: firstLineIdx,
		bodyLines:    bodyLines,
	}
}

func headerInfo(header string) (int, int, string) {
	match := hunkHeaderRegexp.FindStringSubmatch(header)

	oldStart := utils.MustConvertToInt(match[1])
	newStart := utils.MustConvertToInt(match[2])
	heading := match[3]

	return oldStart, newStart, heading
}

func (hunk *PatchHunk) updatedLines(lineIndices []int, reverse bool) []string {
	skippedNewlineMessageIndex := -1
	newLines := []string{}

	lineIdx := hunk.FirstLineIdx
	for _, line := range hunk.bodyLines {
		lineIdx++ // incrementing at the start to skip the header line
		if line == "" {
			break
		}
		isLineSelected := utils.IncludesInt(lineIndices, lineIdx)

		firstChar, content := line[:1], line[1:]
		transformedFirstChar := transformedFirstChar(firstChar, reverse, isLineSelected)

		if isLineSelected || (transformedFirstChar == "\\" && skippedNewlineMessageIndex != lineIdx) || transformedFirstChar == " " {
			newLines = append(newLines, transformedFirstChar+content)
			continue
		}

		if transformedFirstChar == "+" {
			// we don't want to include the 'newline at end of file' line if it involves an addition we're not including
			skippedNewlineMessageIndex = lineIdx + 1
		}
	}

	return newLines
}

func transformedFirstChar(firstChar string, reverse bool, isLineSelected bool) string {
	if reverse {
		if !isLineSelected && firstChar == "+" {
			return " "
		} else if firstChar == "-" {
			return "+"
		} else if firstChar == "+" {
			return "-"
		} else {
			return firstChar
		}
	}

	if !isLineSelected && firstChar == "-" {
		return " "
	}

	return firstChar
}

func (hunk *PatchHunk) formatHeader(oldStart int, oldLength int, newStart int, newLength int, heading string) string {
	return fmt.Sprintf("@@ -%d,%d +%d,%d @@%s\n", oldStart, oldLength, newStart, newLength, heading)
}

func (hunk *PatchHunk) formatWithChanges(lineIndices []int, reverse bool, startOffset int) (int, string) {
	bodyLines := hunk.updatedLines(lineIndices, reverse)
	startOffset, header, ok := hunk.updatedHeader(bodyLines, startOffset, reverse)
	if !ok {
		return startOffset, ""
	}
	return startOffset, header + strings.Join(bodyLines, "")
}

func (hunk *PatchHunk) updatedHeader(newBodyLines []string, startOffset int, reverse bool) (int, string, bool) {
	changeCount := nLinesWithPrefix(newBodyLines, []string{"+", "-"})
	oldLength := nLinesWithPrefix(newBodyLines, []string{" ", "-"})
	newLength := nLinesWithPrefix(newBodyLines, []string{"+", " "})

	if changeCount == 0 {
		// if nothing has changed we just return nothing
		return startOffset, "", false
	}

	var oldStart int
	if reverse {
		oldStart = hunk.newStart
	} else {
		oldStart = hunk.oldStart
	}

	var newStartOffset int
	// if the hunk went from zero to positive length, we need to increment the starting point by one
	// if the hunk went from positive to zero length, we need to decrement the starting point by one
	if oldLength == 0 {
		newStartOffset = 1
	} else if newLength == 0 {
		newStartOffset = -1
	} else {
		newStartOffset = 0
	}

	newStart := oldStart + startOffset + newStartOffset

	newStartOffset = startOffset + newLength - oldLength
	formattedHeader := hunk.formatHeader(oldStart, oldLength, newStart, newLength, hunk.heading)
	return newStartOffset, formattedHeader, true
}