package utils

import (
	"sort"
	"strings"
	"unicode"
)

// titlecase converts a string to title case while preserving hyphenation and apostrophes
// Example: "science fiction" → "Science Fiction", "non-fiction" → "Non-Fiction", "o'reilly" → "O'Reilly"
func titlecase(s string) string {
	words := strings.Fields(s)
	for i, word := range words {
		// Handle words with hyphens or apostrophes
		if strings.ContainsAny(word, "-'") {
			var result strings.Builder
			capitalizeNext := true
			for _, r := range word {
				if r == '-' || r == '\'' {
					result.WriteRune(r)
					capitalizeNext = true
				} else if capitalizeNext {
					result.WriteRune(unicode.ToUpper(r))
					capitalizeNext = false
				} else {
					result.WriteRune(unicode.ToLower(r))
				}
			}
			words[i] = result.String()
		} else {
			runes := []rune(word)
			runes[0] = unicode.ToUpper(runes[0])
			for j := 1; j < len(runes); j++ {
				runes[j] = unicode.ToLower(runes[j])
			}
			words[i] = string(runes)
		}
	}
	return strings.Join(words, " ")
}

// hasPunctuation returns true if string contains any punctuation character
func hasPunctuation(s string) bool {
	for _, r := range s {
		if unicode.IsPunct(r) {
			return true
		}
	}
	return false
}

// removePunctuation removes all punctuation characters from a string
// Replaces hyphens with spaces for search field normalization
func removePunctuation(s string) string {
	var result strings.Builder
	for _, r := range s {
		if unicode.IsPunct(r) {
			// Replace hyphens with spaces, remove other punctuation
			if r == '-' {
				result.WriteRune(' ')
			}
		} else {
			result.WriteRune(r)
		}
	}

	// Clean up multiple spaces
	return strings.Join(strings.Fields(result.String()), " ")
}

// candidate represents a potential tag/contributor value with metadata
type candidate struct {
	value      string
	hasPunct   bool
	firstIndex int
}

// NormalizeTags normalizes an array of tags for display:
// 1. Trim whitespace
// 2. Titlecase (preserves hyphenation)
// 3. Case-insensitive deduplication with punctuation preference
// 4. Remove empty strings
func NormalizeTags(tags []string) []string {
	candidates := make(map[string]candidate)

	for i, tag := range tags {
		// Trim whitespace
		tag = strings.TrimSpace(tag)

		// Skip empty tags
		if tag == "" {
			continue
		}

		// Titlecase for display (preserves hyphenation: "Non-Fiction")
		titlecasedTag := titlecase(tag)

		// Case-insensitive deduplication with punctuation preference
		// Remove punctuation and spaces for comparison to handle "nonfiction" == "non-fiction"
		key := strings.ReplaceAll(removePunctuation(strings.ToLower(titlecasedTag)), " ", "")
		if existing, exists := candidates[key]; !exists {
			// First occurrence - store it
			candidates[key] = candidate{value: titlecasedTag, hasPunct: hasPunctuation(titlecasedTag), firstIndex: i}
		} else {
			// Duplicate - upgrade if new version has punctuation and existing doesn't
			if hasPunctuation(titlecasedTag) && !existing.hasPunct {
				candidates[key] = candidate{value: titlecasedTag, hasPunct: true, firstIndex: existing.firstIndex}
			}
			// Otherwise keep first occurrence
		}
	}

	// Extract values in order of first appearance
	result := make([]pair, 0, len(candidates))
	for _, c := range candidates {
		result = append(result, pair{value: c.value, index: c.firstIndex})
	}

	// Sort by first appearance
	sort.Slice(result, func(i, j int) bool {
		return result[i].index < result[j].index
	})

	// Extract sorted values
	normalized := make([]string, len(result))
	for i, p := range result {
		normalized[i] = p.value
	}

	return normalized
}

// pair is a helper for sorting values by their original index
type pair struct {
	value string
	index int
}

// NormalizeTagsSearch normalizes an array of tags for searching:
// 1. Trim whitespace
// 2. Remove punctuation
// 3. Lowercase
// 4. Case-insensitive deduplication
// 5. Remove empty strings
func NormalizeTagsSearch(tags []string) []string {
	seen := make(map[string]struct{})
	normalized := make([]string, 0)

	for _, tag := range tags {
		// Trim whitespace
		tag = strings.TrimSpace(tag)

		// Skip empty tags
		if tag == "" {
			continue
		}

		// Remove punctuation for search
		tag = removePunctuation(tag)

		// Lowercase for search
		tag = strings.ToLower(tag)

		// Skip empty after removal
		if tag == "" {
			continue
		}

		// Case-insensitive deduplication
		if _, exists := seen[tag]; !exists {
			seen[tag] = struct{}{}
			normalized = append(normalized, tag)
		}
	}

	return normalized
}

// NormalizeContributors normalizes an array of contributors for display:
// 1. Trim whitespace
// 2. Preserve original casing (including CAPSLOCK companies)
// 3. Preserve original punctuation for display
// 4. Case-insensitive deduplication with punctuation preference (removes punctuation for comparison only)
// 5. Remove empty strings
func NormalizeContributors(contributors []string) []string {
	candidates := make(map[string]candidate)

	for i, contributor := range contributors {
		// Trim whitespace
		contributor = strings.TrimSpace(contributor)

		// Skip empty contributors
		if contributor == "" {
			continue
		}

		// Case-insensitive deduplication (remove punctuation for dedup check only)
		dedupKey := removePunctuation(strings.ToLower(contributor))

		// Keep original case and punctuation for display with punctuation preference
		if existing, exists := candidates[dedupKey]; !exists {
			// First occurrence - store it
			candidates[dedupKey] = candidate{value: contributor, hasPunct: hasPunctuation(contributor), firstIndex: i}
		} else {
			// Duplicate - upgrade if new version has punctuation and existing doesn't
			if hasPunctuation(contributor) && !existing.hasPunct {
				candidates[dedupKey] = candidate{value: contributor, hasPunct: true, firstIndex: existing.firstIndex}
			}
			// Otherwise keep first occurrence
		}
	}

	// Extract values in order of first appearance
	result := make([]pair, 0, len(candidates))
	for _, c := range candidates {
		result = append(result, pair{value: c.value, index: c.firstIndex})
	}

	// Sort by first appearance
	sort.Slice(result, func(i, j int) bool {
		return result[i].index < result[j].index
	})

	// Extract sorted values
	normalized := make([]string, len(result))
	for i, p := range result {
		normalized[i] = p.value
	}

	return normalized
}

// NormalizeContributorsSearch normalizes an array of contributors for searching:
// 1. Trim whitespace
// 2. Remove punctuation
// 3. Lowercase
// 4. Case-insensitive deduplication
// 5. Remove empty strings
func NormalizeContributorsSearch(contributors []string) []string {
	seen := make(map[string]struct{})
	normalized := make([]string, 0)

	for _, contributor := range contributors {
		// Trim whitespace
		contributor = strings.TrimSpace(contributor)

		// Skip empty contributors
		if contributor == "" {
			continue
		}

		// Remove punctuation for search
		contributor = removePunctuation(contributor)

		// Lowercase for search
		contributor = strings.ToLower(contributor)

		// Skip empty after removal
		if contributor == "" {
			continue
		}

		// Case-insensitive deduplication
		if _, exists := seen[contributor]; !exists {
			seen[contributor] = struct{}{}
			normalized = append(normalized, contributor)
		}
	}

	return normalized
}

// JoinTags converts a string array to a comma-separated string
// Maintained for backward compatibility with external systems
func JoinTags(tags []string) string {
	return strings.Join(tags, ", ")
}

// SplitTags converts a comma-separated string to a normalized display array
func SplitTags(tags string) []string {
	if tags == "" {
		return []string{}
	}

	parts := strings.Split(tags, ",")
	return NormalizeTags(parts)
}
