package main

import (
	"image"
	"log"
	"sort"

	"github.com/hajimehoshi/ebiten/v2"

	"git.zb-server.de/ZB-Server/EscapeFromTeacher/pkg/game"
)

const (
	// 2px Padding verhindert Linear-Filter-Bleeding an Sprite-Grenzen
	atlasPadding = 2
	// Maximale Atlas-Größe; 2048 deckt alle aktuellen Sprites locker ab
	atlasMaxSize = 2048
)

// buildAtlas packt alle Sprite-Bilder in eine einzige GPU-Textur (Texture Atlas).
// Ebiten erkennt zusammenhängende DrawImage-Calls auf derselben Quell-Textur
// und fasst sie in einen einzigen GPU-Draw-Call zusammen ("internal batching").
//
// Bilder vom Typ "background" und Bilder die größer als atlasMaxSize/2 sind
// bleiben als eigenständige Texturen und werden direkt in das Ergebnis übernommen.
//
// Alle anderen Sprites werden per Shelf-Packing-Algorithmus in den Atlas gepackt.
// Der Rückgabewert ist eine neue Map, in der jeder Eintrag entweder ein
// atlas.SubImage()-Ausschnitt oder das originale Einzelbild ist.
func buildAtlas(images map[string]*ebiten.Image, manifest game.AssetManifest) map[string]*ebiten.Image {
	result := make(map[string]*ebiten.Image, len(images))

	type spriteEntry struct {
		id string
		w  int
		h  int
	}

	var topack []spriteEntry
	standalone := make(map[string]*ebiten.Image)

	for id, img := range images {
		if img == nil {
			continue
		}
		def, ok := manifest.Assets[id]
		if ok && def.Type == "background" {
			standalone[id] = img
			continue
		}
		w, h := img.Bounds().Dx(), img.Bounds().Dy()
		if w > atlasMaxSize/2 || h > atlasMaxSize/2 {
			standalone[id] = img
			continue
		}
		topack = append(topack, spriteEntry{id: id, w: w, h: h})
	}

	if len(topack) == 0 {
		for id, img := range standalone {
			result[id] = img
		}
		return result
	}

	// Shelf-Packing: nach Höhe absteigend sortieren → dichtere Packung
	sort.Slice(topack, func(i, j int) bool {
		return topack[i].h > topack[j].h
	})

	type placement struct {
		id   string
		rect image.Rectangle
	}
	var placements []placement

	curX := atlasPadding
	curY := atlasPadding
	shelfH := 0
	atlasW := 0
	atlasH := 0

	for _, s := range topack {
		pw := s.w + atlasPadding
		ph := s.h + atlasPadding

		// Neue Zeile (Shelf) wenn kein Platz mehr in aktueller Zeile
		if curX+pw > atlasMaxSize {
			curX = atlasPadding
			curY += shelfH + atlasPadding
			shelfH = 0
		}

		// Passt nicht mehr in den Atlas → als Standalone behalten
		if curY+ph > atlasMaxSize {
			standalone[s.id] = images[s.id]
			continue
		}

		r := image.Rect(curX, curY, curX+s.w, curY+s.h)
		placements = append(placements, placement{id: s.id, rect: r})

		if s.h > shelfH {
			shelfH = s.h
		}
		if curX+pw > atlasW {
			atlasW = curX + pw
		}
		if curY+s.h+atlasPadding > atlasH {
			atlasH = curY + s.h + atlasPadding
		}
		curX += pw
	}

	// Atlas-Textur anlegen und Sprites hineinzeichnen
	atlas := ebiten.NewImage(atlasW, atlasH)
	for _, p := range placements {
		op := &ebiten.DrawImageOptions{}
		op.GeoM.Translate(float64(p.rect.Min.X), float64(p.rect.Min.Y))
		atlas.DrawImage(images[p.id], op)
		result[p.id] = atlas.SubImage(p.rect).(*ebiten.Image)
	}

	for id, img := range standalone {
		result[id] = img
	}

	log.Printf("🗺️  Texture Atlas: %dx%d px | %d Sprites gepackt | %d standalone",
		atlasW, atlasH, len(placements), len(standalone))

	return result
}
