package main

import (
	"encoding/json"
	"fmt"
	"image/color"
	"io/ioutil"
	"log"
	"math"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"
	"github.com/hajimehoshi/ebiten/v2/inpututil"
	"github.com/hajimehoshi/ebiten/v2/text"
	"github.com/hajimehoshi/ebiten/v2/vector"
	"golang.org/x/image/font/basicfont"

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

// --- CONFIG ---
const (
	AssetFile = "./cmd/client/assets/assets.json"
	ChunkDir  = "./cmd/client/assets/chunks"

	SidebarWidth = 250
	TopBarHeight = 40
	CanvasHeight = 720
	CanvasWidth  = 1280

	TileSize  = 64
	RefFloorY = 540
)

var (
	ColBgSidebar  = color.RGBA{40, 44, 52, 255}
	ColBgTop      = color.RGBA{35, 35, 40, 255}
	ColBgCanvas   = color.RGBA{30, 30, 30, 255}
	ColGrid       = color.RGBA{60, 60, 60, 255}
	ColFloor      = color.RGBA{0, 255, 0, 150}
	ColText       = color.RGBA{220, 220, 220, 255}
	ColHighlight  = color.RGBA{80, 120, 200, 255}
	ColHitbox     = color.RGBA{255, 0, 0, 200}
	ColHitboxFill = color.RGBA{255, 0, 0, 50}
	ColOrigin     = color.RGBA{255, 255, 0, 255} // Gelb für Referenzpunkt
)

type LevelEditor struct {
	assetManifest game.AssetManifest
	assetList     []string
	assetsImages  map[string]*ebiten.Image

	currentChunk game.Chunk

	scrollX    float64
	zoom       float64
	listScroll float64
	statusMsg  string

	showGrid      bool
	enableSnap    bool
	showHitbox    bool
	showPlayerRef bool // NEU: Spieler Ghost anzeigen

	activeField string
	inputBuffer string

	isDragging      bool
	dragType        string
	dragAssetID     string
	dragTargetIndex int
	dragOffset      game.Vec2
}

func NewLevelEditor() *LevelEditor {
	le := &LevelEditor{
		assetsImages:  make(map[string]*ebiten.Image),
		currentChunk:  game.Chunk{ID: "chunk_new", Width: 50, Objects: []game.LevelObject{}},
		zoom:          1.0,
		showGrid:      true,
		enableSnap:    true,
		showHitbox:    true,
		showPlayerRef: true, // Standardmäßig an
	}
	le.LoadAssets()
	le.LoadChunk("chunk_01.json")
	return le
}

func (le *LevelEditor) LoadAssets() {
	data, err := ioutil.ReadFile(AssetFile)
	if err != nil {
		fmt.Println("⚠️ Konnte Assets nicht laden:", AssetFile)
		le.assetManifest = game.AssetManifest{Assets: make(map[string]game.AssetDefinition)}
	} else {
		json.Unmarshal(data, &le.assetManifest)
	}

	baseDir := "./cmd/client/assets"
	le.assetList = []string{}

	for id, def := range le.assetManifest.Assets {
		le.assetList = append(le.assetList, id)
		if def.Filename != "" {
			fullPath := filepath.Join(baseDir, def.Filename)
			img, _, err := ebitenutil.NewImageFromFile(fullPath)
			if err == nil {
				le.assetsImages[id] = img
			}
		}
	}
	sort.Strings(le.assetList)
}

func (le *LevelEditor) LoadChunk(filename string) {
	path := filepath.Join(ChunkDir, filename)
	data, err := ioutil.ReadFile(path)
	if err == nil {
		json.Unmarshal(data, &le.currentChunk)
		le.statusMsg = "Geladen: " + filename
	} else {
		le.currentChunk.ID = strings.TrimSuffix(filename, filepath.Ext(filename))
		le.statusMsg = "Neu erstellt: " + le.currentChunk.ID
	}
}

func (le *LevelEditor) SaveChunk() {
	os.MkdirAll(ChunkDir, 0755)
	filename := le.currentChunk.ID + ".json"
	path := filepath.Join(ChunkDir, filename)
	data, _ := json.MarshalIndent(le.currentChunk, "", "  ")
	ioutil.WriteFile(path, data, 0644)
	le.statusMsg = "GESPEICHERT als " + filename
}

func (le *LevelEditor) ScreenToWorld(mx, my int) (float64, float64) {
	screenX := float64(mx - SidebarWidth)
	screenY := float64(my - TopBarHeight)
	worldX := (screenX / le.zoom) + le.scrollX
	worldY := screenY / le.zoom
	return worldX, worldY
}

func (le *LevelEditor) GetSmartSnap(x, y float64, objHeight float64) (float64, float64) {
	shouldSnap := le.enableSnap
	if ebiten.IsKeyPressed(ebiten.KeyShift) {
		shouldSnap = !shouldSnap
	}

	if shouldSnap {
		sx := math.Floor(x/TileSize) * TileSize

		// Y Smart Snap: Unterkante ans Raster
		gridLine := math.Round(y/TileSize) * TileSize
		sy := gridLine - objHeight

		return sx, sy
	}
	return x, y
}

func (le *LevelEditor) GetAssetSize(id string) (float64, float64) {
	def, ok := le.assetManifest.Assets[id]
	if !ok {
		return 64, 64
	}
	w := def.Hitbox.W
	h := def.Hitbox.H
	if w == 0 {
		w = def.ProcWidth
	}
	if h == 0 {
		h = def.ProcHeight
	}
	if w == 0 {
		w = 64
	}
	if h == 0 {
		h = 64
	}
	return w, h
}

func (le *LevelEditor) Update() error {
	mx, my := ebiten.CursorPosition()

	// Text Input
	if le.activeField != "" {
		if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
			if le.activeField == "id" {
				le.currentChunk.ID = le.inputBuffer
			}
			if le.activeField == "width" {
				if v, err := strconv.Atoi(le.inputBuffer); err == nil {
					le.currentChunk.Width = v
				}
			}
			le.activeField = ""
		} else if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
			le.activeField = ""
		} else if inpututil.IsKeyJustPressed(ebiten.KeyBackspace) {
			if len(le.inputBuffer) > 0 {
				le.inputBuffer = le.inputBuffer[:len(le.inputBuffer)-1]
			}
		} else {
			le.inputBuffer += string(ebiten.InputChars())
		}
		return nil
	}

	// Hotkeys
	if mx > SidebarWidth {
		_, wy := ebiten.Wheel()
		if wy != 0 {
			le.zoom += wy * 0.1
			if le.zoom < 0.2 {
				le.zoom = 0.2
			}
			if le.zoom > 3.0 {
				le.zoom = 3.0
			}
		}
	}
	if inpututil.IsKeyJustPressed(ebiten.KeyS) {
		le.SaveChunk()
	}
	if inpututil.IsKeyJustPressed(ebiten.KeyG) {
		le.showGrid = !le.showGrid
	}
	if inpututil.IsKeyJustPressed(ebiten.KeyH) {
		le.showHitbox = !le.showHitbox
	}
	if inpututil.IsKeyJustPressed(ebiten.KeyP) {
		le.showPlayerRef = !le.showPlayerRef
	} // NEU: Toggle Player
	if ebiten.IsKeyPressed(ebiten.KeyRight) {
		le.scrollX += 10 / le.zoom
	}
	if ebiten.IsKeyPressed(ebiten.KeyLeft) {
		le.scrollX -= 10 / le.zoom
		if le.scrollX < 0 {
			le.scrollX = 0
		}
	}

	// UI
	if my < TopBarHeight {
		if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
			if mx >= 70 && mx < 220 {
				le.activeField = "id"
				le.inputBuffer = le.currentChunk.ID
			}
			if mx >= 300 && mx < 360 {
				le.activeField = "width"
				le.inputBuffer = fmt.Sprintf("%d", le.currentChunk.Width)
			}
		}
		return nil
	}

	// Palette
	if mx < SidebarWidth {
		_, wy := ebiten.Wheel()
		le.listScroll -= wy * 20
		if le.listScroll < 0 {
			le.listScroll = 0
		}
		if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
			clickY := float64(my-TopBarHeight) + le.listScroll - 40.0
			if clickY >= 0 {
				idx := int(clickY / 25)
				if idx >= 0 && idx < len(le.assetList) {
					le.isDragging = true
					le.dragType = "new"
					le.dragAssetID = le.assetList[idx]
				}
			}
		}
		return nil
	}

	// Canvas Logic
	worldX, worldY := le.ScreenToWorld(mx, my)

	// DELETE
	if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
		for i := len(le.currentChunk.Objects) - 1; i >= 0; i-- {
			obj := le.currentChunk.Objects[i]
			w, h := le.GetAssetSize(obj.AssetID)
			if worldX >= obj.X && worldX <= obj.X+w && worldY >= obj.Y && worldY <= obj.Y+h {
				le.currentChunk.Objects = append(le.currentChunk.Objects[:i], le.currentChunk.Objects[i+1:]...)
				return nil
			}
		}
	}

	// MOVE
	if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) && !le.isDragging {
		for i := len(le.currentChunk.Objects) - 1; i >= 0; i-- {
			obj := le.currentChunk.Objects[i]
			w, h := le.GetAssetSize(obj.AssetID)
			if worldX >= obj.X && worldX <= obj.X+w && worldY >= obj.Y && worldY <= obj.Y+h {
				le.isDragging = true
				le.dragType = "move"
				le.dragTargetIndex = i
				le.dragAssetID = obj.AssetID
				le.dragOffset = game.Vec2{X: worldX - obj.X, Y: worldY - obj.Y}
				return nil
			}
		}
	}

	// DRAGGING
	if le.isDragging {
		rawWX, rawWY := le.ScreenToWorld(mx, my)
		_, h := le.GetAssetSize(le.dragAssetID)

		if le.dragType == "move" {
			targetX := rawWX - le.dragOffset.X
			targetY := rawWY - le.dragOffset.Y
			snapX, snapY := le.GetSmartSnap(targetX, targetY+h, h)
			if !le.enableSnap {
				snapX = targetX
				snapY = targetY
			}

			if le.dragTargetIndex < len(le.currentChunk.Objects) {
				le.currentChunk.Objects[le.dragTargetIndex].X = snapX
				le.currentChunk.Objects[le.dragTargetIndex].Y = snapY
			}
		}

		if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
			if le.dragType == "new" {
				finalX, finalY := le.GetSmartSnap(rawWX, rawWY, h)
				newObj := game.LevelObject{AssetID: le.dragAssetID, X: finalX, Y: finalY}
				le.currentChunk.Objects = append(le.currentChunk.Objects, newObj)
			}
			le.isDragging = false
			le.dragType = ""
		}
	}

	return nil
}

func (le *LevelEditor) Draw(screen *ebiten.Image) {
	// UI HINTERGRUND
	vector.DrawFilledRect(screen, 0, 0, CanvasWidth, TopBarHeight, ColBgTop, false)
	vector.DrawFilledRect(screen, 0, TopBarHeight, SidebarWidth, CanvasHeight-TopBarHeight, ColBgSidebar, false)

	text.Draw(screen, "ID: "+le.currentChunk.ID, basicfont.Face7x13, 75, 25, color.White)

	// ASSET LISTE
	startY := float64(TopBarHeight+40) - le.listScroll
	for i, id := range le.assetList {
		y := startY + float64(i*25)
		if y < float64(TopBarHeight) || y > CanvasHeight {
			continue
		}
		col := ColText
		if le.isDragging && le.dragType == "new" && le.dragAssetID == id {
			col = ColHighlight
		}
		text.Draw(screen, id, basicfont.Face7x13, 10, int(y+15), col)
	}

	// CANVAS
	canvasOffX := float64(SidebarWidth)
	canvasOffY := float64(TopBarHeight)

	// GRID
	if le.showGrid {
		startGridX := int(le.scrollX/TileSize) * TileSize
		for x := startGridX; x < startGridX+int(CanvasWidth/le.zoom)+TileSize; x += TileSize {
			drawX := float32((float64(x)-le.scrollX)*le.zoom + canvasOffX)
			vector.StrokeLine(screen, drawX, float32(canvasOffY), drawX, CanvasHeight, 1, ColGrid, false)
		}
		for y := 0; y < int(CanvasHeight/le.zoom); y += TileSize {
			drawY := float32(float64(y)*le.zoom + canvasOffY)
			vector.StrokeLine(screen, float32(canvasOffX), drawY, CanvasWidth, drawY, 1, ColGrid, false)
		}
	}

	// BODEN LINIE
	floorScreenY := float32((RefFloorY * le.zoom) + canvasOffY)
	vector.StrokeLine(screen, float32(canvasOffX), floorScreenY, float32(CanvasWidth), floorScreenY, 2, ColFloor, false)

	// PLAYER REFERENCE (GHOST)
	// PLAYER REFERENCE (GHOST)
	if le.showPlayerRef {
		playerDef, ok := le.assetManifest.Assets["player"]
		if ok {
			// 1. Richtige Y-Position berechnen, damit Füße auf dem Boden stehen
			// Formel: Boden - HitboxHöhe - Alle Offsets
			// Weil: (Pos + DrawOff + HitboxOff) + HitboxH = Boden

			pH := playerDef.Hitbox.H
			if pH == 0 {
				pH = 64
			} // Fallback

			// Hier ist die korrigierte Mathe:
			pY := float64(RefFloorY) - pH - playerDef.Hitbox.OffsetY - playerDef.DrawOffY

			// Zeichne Referenz-Spieler bei Welt-X = ScrollX + 200
			refWorldX := le.scrollX + 200

			// Wir übergeben pY direkt. DrawAsset addiert dann wieder DrawOffY dazu.
			// Dadurch gleicht es sich aus und die Hitbox landet exakt auf dem Boden.
			le.DrawAsset(screen, "player", refWorldX, pY, canvasOffX, canvasOffY, 0.5)

			// Label
			sX := (refWorldX-le.scrollX)*le.zoom + canvasOffX
			text.Draw(screen, "REF PLAYER", basicfont.Face7x13, int(sX), int(floorScreenY)+20, ColHighlight)
		}
	}

	// OBJEKTE
	for _, obj := range le.currentChunk.Objects {
		le.DrawAsset(screen, obj.AssetID, obj.X, obj.Y, canvasOffX, canvasOffY, 1.0)
	}

	// DRAG GHOST
	if le.isDragging && le.dragType == "new" {
		mx, my := ebiten.CursorPosition()
		if mx > SidebarWidth && my > TopBarHeight {
			wRawX, wRawY := le.ScreenToWorld(mx, my)
			_, h := le.GetAssetSize(le.dragAssetID)
			snapX, snapY := le.GetSmartSnap(wRawX, wRawY, h)
			le.DrawAsset(screen, le.dragAssetID, snapX, snapY, canvasOffX, canvasOffY, 0.6)

			txt := fmt.Sprintf("Y: %.0f", snapY)
			text.Draw(screen, txt, basicfont.Face7x13, mx+10, my, ColHighlight)
		}
	}

	// STATUS
	text.Draw(screen, "[S]ave | [G]rid | [H]itbox | [P]layer Ref | R-Click=Del", basicfont.Face7x13, 400, 25, color.Gray{100})
	text.Draw(screen, le.statusMsg, basicfont.Face7x13, SidebarWidth+10, CanvasHeight-10, ColHighlight)
}

func (le *LevelEditor) DrawAsset(screen *ebiten.Image, id string, wX, wY, offX, offY float64, alpha float32) {
	sX := (wX-le.scrollX)*le.zoom + offX
	sY := wY*le.zoom + offY

	if sX < SidebarWidth-100 || sX > CanvasWidth {
		return
	}

	def, ok := le.assetManifest.Assets[id]
	if !ok {
		return
	}

	// 1. SPRITE
	if def.Filename != "" {
		img := le.assetsImages[id]
		if img != nil {
			op := &ebiten.DrawImageOptions{}
			op.GeoM.Scale(def.Scale*le.zoom, def.Scale*le.zoom)
			drawX := sX + (def.DrawOffX * le.zoom)
			drawY := sY + (def.DrawOffY * le.zoom)
			op.GeoM.Translate(drawX, drawY)
			op.ColorScale.ScaleAlpha(alpha)
			screen.DrawImage(img, op)
		}
	} else {
		col := def.Color.ToRGBA()
		col.A = uint8(float32(col.A) * alpha)
		w := float32((def.ProcWidth) * le.zoom)
		h := float32((def.ProcHeight) * le.zoom)
		if w == 0 {
			w = 64 * float32(le.zoom)
			h = 64 * float32(le.zoom)
		}
		vector.DrawFilledRect(screen, float32(sX), float32(sY), w, h, col, false)
	}

	// 2. HITBOX (Rot)
	if le.showHitbox {
		hx := float32(sX + (def.DrawOffX * le.zoom) + (def.Hitbox.OffsetX * le.zoom))
		hy := float32(sY + (def.DrawOffY * le.zoom) + (def.Hitbox.OffsetY * le.zoom))
		hw := float32(def.Hitbox.W * le.zoom)
		hh := float32(def.Hitbox.H * le.zoom)
		if hw == 0 {
			if def.ProcWidth > 0 {
				hw = float32(def.ProcWidth * le.zoom)
				hh = float32(def.ProcHeight * le.zoom)
			} else {
				hw = 64 * float32(le.zoom)
				hh = 64 * float32(le.zoom)
			}
		}
		vector.StrokeRect(screen, hx, hy, hw, hh, 2, ColHitbox, false)
		vector.DrawFilledRect(screen, hx, hy, hw, hh, ColHitboxFill, false)
	}

	// 3. REFERENZPUNKT (Gelbes Kreuz) <-- DAS WOLLTEST DU!
	// Zeigt exakt die Koordinate (X, Y) des Objekts
	cX := float32(sX)
	cY := float32(sY)
	vector.StrokeLine(screen, cX-5, cY, cX+5, cY, 2, ColOrigin, false)
	vector.StrokeLine(screen, cX, cY-5, cX, cY+5, 2, ColOrigin, false)
}

func (le *LevelEditor) Layout(w, h int) (int, int) { return CanvasWidth, CanvasHeight }

func main() {
	os.MkdirAll(ChunkDir, 0755)
	ebiten.SetWindowSize(CanvasWidth, CanvasHeight)
	ebiten.SetWindowTitle("Escape Level Editor - Reference Point Added")
	if err := ebiten.RunGame(NewLevelEditor()); err != nil {
		log.Fatal(err)
	}
}
