package file

import (
	"bytes"
	"image"
	"image/color"
	"image/draw"
	"image/jpeg"
	_ "image/png"
	"math"
	"path"
	"strings"

	xdraw "golang.org/x/image/draw"
	_ "golang.org/x/image/webp"
)

const (
	ImageVariantThumb  = "thumb"
	ImageVariantMedium = "medium"
)

const (
	thumbMaxSide  = 480
	mediumMaxSide = 1280
)

type generatedImageVariant struct {
	Key         string
	Content     []byte
	ContentType string
}

type imageVariantConfig struct {
	Name    string
	MaxSide int
	Quality int
}

var imageVariantConfigs = []imageVariantConfig{
	{Name: ImageVariantThumb, MaxSide: thumbMaxSide, Quality: 76},
	{Name: ImageVariantMedium, MaxSide: mediumMaxSide, Quality: 82},
}

func ImageVariantKey(key string, variant string) string {
	ext := path.Ext(key)
	base := strings.TrimSuffix(key, ext)
	if base == "" {
		base = key
	}
	return base + "." + variant + ".jpg"
}

func ImageVariantFallbackKeys(key string, variant string) []string {
	if key == "" {
		return nil
	}
	keys := []string{ImageVariantKey(key, variant)}
	if strings.HasSuffix(key, ".medium.jpg") && variant == ImageVariantThumb {
		keys = append(keys, strings.TrimSuffix(key, ".medium.jpg")+"."+ImageVariantThumb+".jpg")
	}
	keys = append(keys, key)
	return dedupeKeys(keys)
}

func generateImageVariants(key string, data []byte, contentType string) []generatedImageVariant {
	if !strings.HasPrefix(contentType, "image/") || contentType == "image/svg+xml" {
		return nil
	}
	source, _, err := image.Decode(bytes.NewReader(data))
	if err != nil {
		return nil
	}
	var variants []generatedImageVariant
	for _, config := range imageVariantConfigs {
		rendered := resizeForVariant(source, config.MaxSide)
		encoded, err := encodeJPEG(rendered, config.Quality)
		if err != nil {
			continue
		}
		variants = append(variants, generatedImageVariant{
			Key:         ImageVariantKey(key, config.Name),
			Content:     encoded,
			ContentType: "image/jpeg",
		})
	}
	return variants
}

func resizeForVariant(source image.Image, maxSide int) image.Image {
	bounds := source.Bounds()
	width := bounds.Dx()
	height := bounds.Dy()
	if width <= 0 || height <= 0 || maxSide <= 0 {
		return source
	}
	scale := math.Min(float64(maxSide)/float64(width), float64(maxSide)/float64(height))
	if scale > 1 {
		scale = 1
	}
	targetWidth := max(1, int(math.Round(float64(width)*scale)))
	targetHeight := max(1, int(math.Round(float64(height)*scale)))
	target := image.NewRGBA(image.Rect(0, 0, targetWidth, targetHeight))
	draw.Draw(target, target.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src)
	xdraw.ApproxBiLinear.Scale(target, target.Bounds(), source, bounds, draw.Over, nil)
	return target
}

func encodeJPEG(source image.Image, quality int) ([]byte, error) {
	var buffer bytes.Buffer
	err := jpeg.Encode(&buffer, source, &jpeg.Options{Quality: quality})
	if err != nil {
		return nil, err
	}
	return buffer.Bytes(), nil
}

func dedupeKeys(keys []string) []string {
	seen := make(map[string]struct{}, len(keys))
	result := make([]string, 0, len(keys))
	for _, key := range keys {
		if key == "" {
			continue
		}
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		result = append(result, key)
	}
	return result
}
