package types

import (
	"github.com/bluekeyes/go-gitdiff/gitdiff"
)

type SplitLine struct {
	LineNumber int            `json:"line_number,omitempty"`
	Content    string         `json:"content"`
	Op         gitdiff.LineOp `json:"op"`
	IsEmpty    bool           `json:"is_empty"`
}

type SplitFragment struct {
	Header     string      `json:"header"`
	LeftLines  []SplitLine `json:"left_lines"`
	RightLines []SplitLine `json:"right_lines"`
}

type SplitDiff struct {
	Name          string          `json:"name"`
	TextFragments []SplitFragment `json:"fragments"`
}

func (d SplitDiff) Id() string {
	return d.Name
}

// separate lines into left and right, this includes additional logic to
// group consecutive runs of additions and deletions in order to align them
// properly in the final output
//
// TODO: move all diff stuff to a single package, we are spread across patchutil and types right now
func SeparateLines(fragment *gitdiff.TextFragment) ([]SplitLine, []SplitLine) {
	lines := fragment.Lines
	var leftLines, rightLines []SplitLine
	oldLineNum := fragment.OldPosition
	newLineNum := fragment.NewPosition

	// process deletions and additions in groups for better alignment
	i := 0
	for i < len(lines) {
		line := lines[i]

		switch line.Op {
		case gitdiff.OpContext:
			leftLines = append(leftLines, SplitLine{
				LineNumber: int(oldLineNum),
				Content:    line.Line,
				Op:         gitdiff.OpContext,
				IsEmpty:    false,
			})
			rightLines = append(rightLines, SplitLine{
				LineNumber: int(newLineNum),
				Content:    line.Line,
				Op:         gitdiff.OpContext,
				IsEmpty:    false,
			})
			oldLineNum++
			newLineNum++
			i++

		case gitdiff.OpDelete:
			deletionCount := 0
			for j := i; j < len(lines) && lines[j].Op == gitdiff.OpDelete; j++ {
				leftLines = append(leftLines, SplitLine{
					LineNumber: int(oldLineNum),
					Content:    lines[j].Line,
					Op:         gitdiff.OpDelete,
					IsEmpty:    false,
				})
				oldLineNum++
				deletionCount++
			}
			i += deletionCount

			additionCount := 0
			for j := i; j < len(lines) && lines[j].Op == gitdiff.OpAdd; j++ {
				rightLines = append(rightLines, SplitLine{
					LineNumber: int(newLineNum),
					Content:    lines[j].Line,
					Op:         gitdiff.OpAdd,
					IsEmpty:    false,
				})
				newLineNum++
				additionCount++
			}
			i += additionCount

			// add empty lines to balance the sides
			if deletionCount > additionCount {
				// more deletions than additions - pad right side
				for k := 0; k < deletionCount-additionCount; k++ {
					rightLines = append(rightLines, SplitLine{
						Content: "",
						Op:      gitdiff.OpContext,
						IsEmpty: true,
					})
				}
			} else if additionCount > deletionCount {
				// more additions than deletions - pad left side
				for k := 0; k < additionCount-deletionCount; k++ {
					leftLines = append(leftLines, SplitLine{
						Content: "",
						Op:      gitdiff.OpContext,
						IsEmpty: true,
					})
				}
			}

		case gitdiff.OpAdd:
			// standalone addition (not preceded by deletion)
			leftLines = append(leftLines, SplitLine{
				Content: "",
				Op:      gitdiff.OpContext,
				IsEmpty: true,
			})
			rightLines = append(rightLines, SplitLine{
				LineNumber: int(newLineNum),
				Content:    line.Line,
				Op:         gitdiff.OpAdd,
				IsEmpty:    false,
			})
			newLineNum++
			i++
		}
	}

	return leftLines, rightLines
}
