package tools

import (
	"fmt"
	"strings"
	"unicode"
)

func applyEdit(raw, filePath, oldText, newText string) (string, error) {
	bom, content := stripBOM(raw)
	originalEnding := detectLineEnding(content)
	normalizedContent := normalizeToLF(content)
	normalizedOld := normalizeToLF(oldText)
	normalizedNew := normalizeToLF(newText)
	match := fuzzyFindText(normalizedContent, normalizedOld)
	if !match.Found {
		return "", fmt.Errorf(
			"could not find the exact text in %s. the old text must match exactly including all whitespace and newlines",
			filePath,
		)
	}
	fuzzyContent := normalizeForFuzzyMatch(normalizedContent)
	fuzzyOld := normalizeForFuzzyMatch(normalizedOld)
	occurrences := strings.Count(fuzzyContent, fuzzyOld)
	if occurrences > 1 {
		return "", fmt.Errorf(
			"found %d occurrences of the text in %s. the text must be unique. please provide more context to make it unique",
			occurrences, filePath,
		)
	}
	baseContent := match.ContentForReplacement
	updated := baseContent[:match.Index] + normalizedNew + baseContent[match.Index+match.MatchLength:]
	if baseContent == updated {
		return "", fmt.Errorf(
			"no changes made to %s. the replacement produced identical content. this might indicate an issue with special characters or the text not existing as expected",
			filePath,
		)
	}
	return bom + restoreLineEndings(updated, originalEnding), nil
}

type fuzzyMatchResult struct {
	Found                 bool
	Index                 int
	MatchLength           int
	ContentForReplacement string
}

func detectLineEnding(content string) string {
	crlfIdx := strings.Index(content, "\r\n")
	lfIdx := strings.Index(content, "\n")
	if lfIdx == -1 {
		return "\n"
	}
	if crlfIdx == -1 {
		return "\n"
	}
	if crlfIdx < lfIdx {
		return "\r\n"
	}
	return "\n"
}

func normalizeToLF(text string) string {
	text = strings.ReplaceAll(text, "\r\n", "\n")
	return strings.ReplaceAll(text, "\r", "\n")
}

func restoreLineEndings(text, ending string) string {
	if ending == "\r\n" {
		return strings.ReplaceAll(text, "\n", "\r\n")
	}
	return text
}

func stripBOM(content string) (string, string) {
	const bom = "\uFEFF"
	if strings.HasPrefix(content, bom) {
		return bom, content[len(bom):]
	}
	return "", content
}

func normalizeForFuzzyMatch(text string) string {
	lines := strings.Split(text, "\n")
	for i, line := range lines {
		lines[i] = strings.TrimRightFunc(line, unicode.IsSpace)
	}
	trimmed := strings.Join(lines, "\n")
	return strings.Map(func(r rune) rune {
		switch r {
		case '\u2018', '\u2019', '\u201A', '\u201B':
			return '\''
		case '\u201C', '\u201D', '\u201E', '\u201F':
			return '"'
		case '\u2010', '\u2011', '\u2012', '\u2013', '\u2014', '\u2015', '\u2212':
			return '-'
		case '\u00A0', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006', '\u2007', '\u2008', '\u2009', '\u200A', '\u202F', '\u205F', '\u3000':
			return ' '
		default:
			return r
		}
	}, trimmed)
}

func fuzzyFindText(content, oldText string) fuzzyMatchResult {
	exactIndex := strings.Index(content, oldText)
	if exactIndex != -1 {
		return fuzzyMatchResult{Found: true, Index: exactIndex, MatchLength: len(oldText), ContentForReplacement: content}
	}
	fuzzyContent := normalizeForFuzzyMatch(content)
	fuzzyOld := normalizeForFuzzyMatch(oldText)
	fuzzyIndex := strings.Index(fuzzyContent, fuzzyOld)
	if fuzzyIndex == -1 {
		return fuzzyMatchResult{Found: false, Index: -1, ContentForReplacement: content}
	}
	return fuzzyMatchResult{Found: true, Index: fuzzyIndex, MatchLength: len(fuzzyOld), ContentForReplacement: fuzzyContent}
}
