package main

import (
	"regexp"
	"strings"
)

func matchLine(pattern, actual string) bool {
	if !strings.Contains(pattern, "...") {
		return pattern == actual
	}

	parts := strings.Split(pattern, "...")
	escaped := make([]string, len(parts))
	for i, p := range parts {
		escaped[i] = regexp.QuoteMeta(p)
	}
	re := regexp.MustCompile("^" + strings.Join(escaped, ".*") + "$")
	return re.MatchString(actual)
}

func matchOutput(expected, actual []string) bool {
	return doMatch(expected, 0, actual, 0)
}

func doMatch(expected []string, ei int, actual []string, ai int) bool {
	if ei == len(expected) && ai == len(actual) {
		return true
	}
	if ei == len(expected) {
		return false
	}

	exp := expected[ei]

	// Multi-line wildcard
	if exp == "..." {
		for skip := ai; skip <= len(actual); skip++ {
			if doMatch(expected, ei+1, actual, skip) {
				return true
			}
		}
		return false
	}

	if ai == len(actual) {
		return false
	}

	if matchLine(exp, actual[ai]) {
		return doMatch(expected, ei+1, actual, ai+1)
	}

	return false
}

func diff(expected, actual []string) []DiffLine {
	var result []DiffLine
	ei, ai := 0, 0

	for ei < len(expected) || ai < len(actual) {
		if ei < len(expected) && expected[ei] == "..." {
			nextExp := ""
			hasNext := ei+1 < len(expected)
			if hasNext {
				nextExp = expected[ei+1]
			}

			if !hasNext {
				result = append(result, DiffLine{Kind: "context", Text: "..."})
				for ai < len(actual) {
					result = append(result, DiffLine{Kind: "context", Text: actual[ai]})
					ai++
				}
				break
			}

			result = append(result, DiffLine{Kind: "context", Text: "..."})
			ei++
			for ai < len(actual) && !matchLine(nextExp, actual[ai]) {
				result = append(result, DiffLine{Kind: "context", Text: actual[ai]})
				ai++
			}
			continue
		}

		if ei < len(expected) && ai < len(actual) {
			if matchLine(expected[ei], actual[ai]) {
				result = append(result, DiffLine{Kind: "equal", Text: actual[ai]})
				ei++
				ai++
			} else {
				result = append(result, DiffLine{Kind: "expected", Text: expected[ei]})
				result = append(result, DiffLine{Kind: "actual", Text: actual[ai]})
				ei++
				ai++
			}
		} else if ei < len(expected) {
			result = append(result, DiffLine{Kind: "expected", Text: expected[ei]})
			ei++
		} else {
			result = append(result, DiffLine{Kind: "actual", Text: actual[ai]})
			ai++
		}
	}

	return result
}
