package mergeconflicts

// mergeConflict : A git conflict with a start, ancestor (if exists), target, and end corresponding to line
// numbers in the file where the conflict markers appear.
// If no ancestor is present (i.e. we're not using the diff3 algorithm), then
// the `ancestor` field's value will be -1
type mergeConflict struct {
	start    int
	ancestor int
	target   int
	end      int
}

func (c *mergeConflict) hasAncestor() bool {
	return c.ancestor >= 0
}

func (c *mergeConflict) isMarkerLine(i int) bool {
	return i == c.start ||
		i == c.ancestor ||
		i == c.target ||
		i == c.end
}

type Selection int

const (
	TOP Selection = iota
	MIDDLE
	BOTTOM
	BOTH
)

func (s Selection) isIndexToKeep(conflict *mergeConflict, i int) bool {
	// we're only handling one conflict at a time so any lines outside this
	// conflict we'll keep
	if i < conflict.start || conflict.end < i {
		return true
	}

	if conflict.isMarkerLine(i) {
		return false
	}

	return s.selected(conflict, i)
}

func (s Selection) bounds(c *mergeConflict) (int, int) {
	switch s {
	case TOP:
		if c.hasAncestor() {
			return c.start, c.ancestor
		}
		return c.start, c.target
	case MIDDLE:
		return c.ancestor, c.target
	case BOTTOM:
		return c.target, c.end
	case BOTH:
		// BOTH spans two disjoint hunks, so it has no single range; callers
		// go through selected() instead of asking for its bounds.
		panic("BOTH has no single range")
	}

	panic("unexpected selection for merge conflict")
}

func (s Selection) selected(c *mergeConflict, idx int) bool {
	// BOTH keeps the top and bottom hunks but drops the common ancestor in
	// between (which is only present with the diff3 conflict style), so it
	// isn't a single contiguous range like the other selections.
	if s == BOTH {
		return TOP.selected(c, idx) || BOTTOM.selected(c, idx)
	}

	start, end := s.bounds(c)
	return start < idx && idx < end
}

func availableSelections(c *mergeConflict) []Selection {
	if c.hasAncestor() {
		return []Selection{TOP, MIDDLE, BOTTOM}
	}
	return []Selection{TOP, BOTTOM}
}
