package templates
import (
"bookhoard/internal/database"
"fmt"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
func activeClass(current, target string) string {
base := "block px-4 py-2 rounded-lg "
if current == target {
return base + "bg-accent text-bg-primary"
}
return base + "hover:opacity-80"
}
func ContainsString(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
func uuidToString(id pgtype.UUID) string {
if !id.Valid {
return ""
}
u, err := uuid.FromBytes(id.Bytes[0:16])
if err != nil {
return ""
}
return u.String()
}
// renderStars converts rating (1-10 scale) to star display
// Always displays 5 stars total with filled stars in yellow
// and empty stars in grey (using theme variable)
// Rating scale: 1-10 where each 2 points = 1 full star, odd numbers = half stars
func renderStars(rating int32) string {
const totalStars = 5
fullStars := rating / 2
hasHalf := rating%2 != 0
var stars strings.Builder
for i := int32(0); i < totalStars; i++ {
if i < fullStars {
// Filled star - accent color (theme-aware highlight)
stars.WriteString(`★`)
} else if i == fullStars && hasHalf {
// Half star - gradient from accent (left) to grey (right)
stars.WriteString(`★`)
} else {
// Empty star - grey using theme variable
stars.WriteString(`★`)
}
}
return stars.String()
}
// formatFileSize converts bytes to human-readable format
func formatFileSize(bytes int64) string {
const (
KB = 1024
MB = KB * 1024
GB = MB * 1024
)
switch {
case bytes >= GB:
return fmt.Sprintf("%.2f GB", float64(bytes)/float64(GB))
case bytes >= MB:
return fmt.Sprintf("%.2f MB", float64(bytes)/float64(MB))
case bytes >= KB:
return fmt.Sprintf("%.2f KB", float64(bytes)/float64(KB))
default:
return fmt.Sprintf("%d B", bytes)
}
}
// getExternalURL generates URL for external book services
// Priority: ID > ISBN > Title+Author search
func getExternalURL(service string, id string, isbn pgtype.Text, title string, author pgtype.Text) string {
baseURL := ""
searchTerm := ""
// Determine search term: ID > ISBN > Title+Author
if id != "" {
searchTerm = id
} else if isbn.Valid && isbn.String != "" {
searchTerm = isbn.String
} else {
// Build title+author search query
if author.Valid && author.String != "" {
searchTerm = fmt.Sprintf("%s %s", title, author.String)
} else {
searchTerm = title
}
}
// Build URL based on service
switch service {
case "goodreads":
if id != "" {
baseURL = "https://www.goodreads.com/book/show/"
} else {
baseURL = "https://www.goodreads.com/search?q="
}
case "openlibrary":
if id != "" {
baseURL = "https://openlibrary.org/books/"
} else {
baseURL = "https://openlibrary.org/search?q="
}
case "googlebooks":
if id != "" {
baseURL = "https://books.google.com/books?id="
} else {
baseURL = "https://www.google.com/search?tbm=bks&q="
}
case "amazon":
// Amazon doesn't have direct book IDs, always search
baseURL = "https://www.amazon.com/s?k="
if isbn.Valid && isbn.String != "" {
searchTerm = isbn.String
}
}
return baseURL + searchTerm
}
// getBookRating returns the rating value or 0 if not rated
func getBookRating(rating *database.MediaRatings) int32 {
if rating != nil {
return rating.Rating
}
return 0
}