package main

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

	"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 (
	RawDir  = "./assets_raw"
	OutFile = "./cmd/client/assets/assets.json"

	WidthList    = 280 // Etwas breiter für die Bilder
	WidthInspect = 300

	CanvasWidth  = 1280
	CanvasHeight = 720

	LineHeight   = 35 // Höher für Thumbnails
	HeaderHeight = 30
	TextOffset   = 22 // Text vertikal zentrieren
)

// Farben
var (
	ColBg        = color.RGBA{30, 30, 30, 255}
	ColPanel     = color.RGBA{40, 44, 52, 255}
	ColText      = color.RGBA{220, 220, 220, 255}
	ColHighlight = color.RGBA{80, 120, 200, 255}
	ColNewFile   = color.RGBA{150, 150, 150, 255}
	ColAxis      = color.RGBA{100, 255, 100, 255}
	ColInput     = color.RGBA{20, 20, 20, 255}
	ColDelete    = color.RGBA{255, 100, 100, 255}
	ColPlayerRef = color.RGBA{0, 255, 255, 100}
)

var AssetTypes = []string{"obstacle", "platform", "powerup", "enemy", "deco", "coin"}

// --- HILFSFUNKTIONEN ---

func generateBrickTexture(w, h int) *ebiten.Image {
	img := ebiten.NewImage(w, h)
	img.Fill(color.RGBA{80, 80, 90, 255})
	brickColor := color.RGBA{160, 80, 40, 255}
	brickHighlight := color.RGBA{180, 100, 50, 255}
	rows := 2
	cols := 4
	brickH := float32(h) / float32(rows)
	brickW := float32(w) / float32(cols)
	padding := float32(2)

	for row := 0; row < rows; row++ {
		for col := 0; col < cols; col++ {
			xOffset := float32(0)
			if row%2 != 0 {
				xOffset = brickW / 2
			}
			x := float32(col)*brickW + xOffset
			y := float32(row) * brickH
			drawBrick := func(bx, by float32) {
				vector.DrawFilledRect(img, bx+padding, by+padding, brickW-padding*2, brickH-padding*2, brickColor, false)
				vector.DrawFilledRect(img, bx+padding, by+padding, brickW-padding*2, 2, brickHighlight, false)
			}
			drawBrick(x, y)
			if x+brickW > float32(w) {
				drawBrick(x-float32(w), y)
			}
		}
	}
	return img
}

func saveImageToDisk(img *ebiten.Image, filename string) error {
	stdImg := img.SubImage(img.Bounds())
	assetDir := filepath.Dir(OutFile)
	fullPath := filepath.Join(assetDir, filename)
	f, err := os.Create(fullPath)
	if err != nil {
		return err
	}
	defer f.Close()
	return png.Encode(f, stdImg)
}

// --- EDITOR STRUCT ---

type Editor struct {
	manifest     game.AssetManifest
	assetsImages map[string]*ebiten.Image

	rawFiles  []string
	sortedIDs []string

	selectedID string

	inputBuffer string
	activeField string

	listScroll    float64
	showPlayerRef bool

	isDraggingImage  bool
	isDraggingHitbox bool
	dragStart        game.Vec2
}

func NewEditor() *Editor {
	e := &Editor{
		assetsImages:  make(map[string]*ebiten.Image),
		manifest:      game.AssetManifest{Assets: make(map[string]game.AssetDefinition)},
		showPlayerRef: true,
	}
	e.ScanRawFiles()
	e.LoadManifest()
	e.RebuildList()

	if len(e.sortedIDs) > 0 {
		e.selectedID = e.sortedIDs[0]
	}
	return e
}

func (e *Editor) ScanRawFiles() {
	e.rawFiles = []string{}
	entries, _ := os.ReadDir(RawDir)
	for _, f := range entries {
		if f.IsDir() {
			continue
		}
		ext := strings.ToLower(filepath.Ext(f.Name()))
		if ext != ".png" && ext != ".jpg" {
			continue
		}

		e.rawFiles = append(e.rawFiles, f.Name())

		id := strings.TrimSuffix(f.Name(), ext)
		path := filepath.Join(RawDir, f.Name())
		img, _, err := ebitenutil.NewImageFromFile(path)
		if err == nil {
			e.assetsImages[id] = img
		}
	}
	sort.Strings(e.rawFiles)
}

func (e *Editor) LoadManifest() {
	data, err := ioutil.ReadFile(OutFile)
	if err == nil {
		var loaded game.AssetManifest
		json.Unmarshal(data, &loaded)
		for k, v := range loaded.Assets {
			e.manifest.Assets[k] = v
			// Laden der Bilder für existierende Assets
			if v.Filename != "" {
				assetPath := filepath.Join(filepath.Dir(OutFile), v.Filename)
				if img, _, err := ebitenutil.NewImageFromFile(assetPath); err == nil {
					e.assetsImages[v.ID] = img
				}
			}
		}
	}
}

func (e *Editor) RebuildList() {
	e.sortedIDs = []string{}
	for k := range e.manifest.Assets {
		e.sortedIDs = append(e.sortedIDs, k)
	}
	sort.Strings(e.sortedIDs)
}

func (e *Editor) Save() {
	data, _ := json.MarshalIndent(e.manifest, "", "  ")
	_ = ioutil.WriteFile(OutFile, data, 0644)
	log.Println("Gespeichert.")
}

func (e *Editor) DeleteAsset(id string) {
	delete(e.manifest.Assets, id)
	e.RebuildList()
	if e.selectedID == id {
		if len(e.sortedIDs) > 0 {
			e.selectedID = e.sortedIDs[0]
		} else {
			e.selectedID = ""
		}
	}
	log.Printf("Asset gelöscht: %s", id)
}

func (e *Editor) CreateAssetFromFile(filename string) {
	ext := filepath.Ext(filename)
	id := strings.TrimSuffix(filename, ext)

	// Prüfen ob es schon existiert
	if _, ok := e.manifest.Assets[id]; ok {
		// Neuen Namen generieren um Überschreiben zu vermeiden
		id = fmt.Sprintf("%s_%d", id, time.Now().Unix())
	}

	img := e.assetsImages[strings.TrimSuffix(filename, ext)] // Versuch Raw Image zu finden
	// Falls nicht gefunden (weil neu reinkopiert), versuchen wir es zu laden
	if img == nil {
		path := filepath.Join(RawDir, filename)
		img, _, _ = ebitenutil.NewImageFromFile(path)
	}

	w, h := 64, 64
	if img != nil {
		w, h = img.Bounds().Dx(), img.Bounds().Dy()
		// Speichern wir das Bild auch in e.assetsImages für die Vorschau
		e.assetsImages[id] = img
	}

	// Wichtig: Wir kopieren das Raw File in den Client Assets Ordner!
	// (Simuliert durch Laden und Speichern, oder wir setzen nur den Pfad wenn wir faul sind.
	// Sauberer: Wir gehen davon aus, dass der User es händisch kopiert hat ODER wir kopieren es hier.)
	// Für diesen Editor nehmen wir an, der User nutzt "RawDir" als Quelle und wir müssten es eigentlich kopieren.
	// Simpler Hack: Wir nutzen das File aus RawDir und speichern es als neues PNG im Zielordner.
	if img != nil {
		saveImageToDisk(img, filename)
	}

	e.manifest.Assets[id] = game.AssetDefinition{
		ID: id, Type: "obstacle", Filename: filename, Scale: 1.0,
		Color:    game.HexColor{R: 255, G: 0, B: 255, A: 255},
		DrawOffX: float64(-w) / 2,
		DrawOffY: float64(-h),
		Hitbox:   game.Rect{W: float64(w), H: float64(h), OffsetX: float64(-w) / 2, OffsetY: float64(-h)},
	}
	e.RebuildList()
	e.selectedID = id
}

func (e *Editor) CreatePlatform() {
	w, h := 128, 32
	texImg := generateBrickTexture(w, h)
	timestamp := time.Now().Unix()
	filename := fmt.Sprintf("gen_plat_%d.png", timestamp)
	id := fmt.Sprintf("platform_%d", timestamp)

	if err := saveImageToDisk(texImg, filename); err != nil {
		log.Printf("Fehler beim Speichern: %v", err)
		return
	}

	e.assetsImages[id] = texImg
	e.manifest.Assets[id] = game.AssetDefinition{
		ID:       id,
		Type:     "platform",
		Filename: filename,
		Scale:    1.0,
		Color:    game.HexColor{R: 255, G: 255, B: 255, A: 255},
		DrawOffX: float64(-w) / 2,
		DrawOffY: float64(-h) / 2,
		Hitbox:   game.Rect{W: float64(w), H: float64(h), OffsetX: float64(-w) / 2, OffsetY: float64(-h) / 2},
	}
	e.RebuildList()
	e.selectedID = id
}

func (e *Editor) Update() error {
	if inpututil.IsKeyJustPressed(ebiten.KeyS) && e.activeField == "" {
		e.Save()
	}
	if inpututil.IsKeyJustPressed(ebiten.KeyP) && e.activeField == "" {
		e.showPlayerRef = !e.showPlayerRef
	}

	mx, my := ebiten.CursorPosition()

	// --- LISTE (LINKS) ---
	if mx < WidthList {
		_, wy := ebiten.Wheel()
		e.listScroll -= wy * 20
		if e.listScroll < 0 {
			e.listScroll = 0
		}

		if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) || inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
			currentY := 40.0 - e.listScroll

			// ASSETS
			currentY += float64(LineHeight)
			for _, id := range e.sortedIDs {
				if float64(my) >= currentY && float64(my) < currentY+float64(LineHeight) {
					if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
						e.selectedID = id
						e.activeField = ""
					} else {
						e.DeleteAsset(id)
						return nil
					}
				}
				currentY += float64(LineHeight)
			}

			// RAW FILES
			currentY += 40
			for _, f := range e.rawFiles {
				ext := filepath.Ext(f)
				id := strings.TrimSuffix(f, ext)
				if _, exists := e.manifest.Assets[id]; exists {
					continue
				}

				if float64(my) >= currentY && float64(my) < currentY+float64(LineHeight) {
					if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
						e.CreateAssetFromFile(f)
					}
				}
				currentY += float64(LineHeight)
			}

			if my > CanvasHeight-40 {
				e.CreatePlatform()
			}
		}
		return nil
	}

	// --- INSPECTOR (RECHTS) ---
	if mx > CanvasWidth-WidthInspect {
		e.UpdateInspector(mx, my)
		return nil
	}

	// --- CANVAS (MITTE) ---
	if e.selectedID != "" {
		def := e.manifest.Assets[e.selectedID]
		centerX := float64(WidthList) + float64(CanvasWidth-WidthList-WidthInspect)/2
		centerY := float64(CanvasHeight) * 0.75

		relMX := float64(mx) - centerX - def.DrawOffX
		relMY := float64(my) - centerY - def.DrawOffY

		// Hitbox Drag
		if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
			e.isDraggingHitbox = true
			e.dragStart = game.Vec2{X: relMX, Y: relMY}
			def.Hitbox.OffsetX = relMX
			def.Hitbox.OffsetY = relMY
			def.Hitbox.W = 0
			def.Hitbox.H = 0
		}

		if e.isDraggingHitbox {
			if ebiten.IsMouseButtonPressed(ebiten.MouseButtonRight) {
				def.Hitbox.W = relMX - def.Hitbox.OffsetX
				def.Hitbox.H = relMY - def.Hitbox.OffsetY
			} else {
				e.isDraggingHitbox = false
				// Negative Größen korrigieren
				if def.Hitbox.W < 0 {
					def.Hitbox.OffsetX += def.Hitbox.W
					def.Hitbox.W = math.Abs(def.Hitbox.W)
				}
				if def.Hitbox.H < 0 {
					def.Hitbox.OffsetY += def.Hitbox.H
					def.Hitbox.H = math.Abs(def.Hitbox.H)
				}
			}
			e.manifest.Assets[e.selectedID] = def
			return nil
		}

		// Image Drag
		if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
			e.isDraggingImage = true
			e.dragStart = game.Vec2{X: float64(mx), Y: float64(my)}
			e.activeField = ""
		}

		if e.isDraggingImage && ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
			dx := float64(mx) - e.dragStart.X
			dy := float64(my) - e.dragStart.Y
			def.DrawOffX += dx
			def.DrawOffY += dy
			e.dragStart = game.Vec2{X: float64(mx), Y: float64(my)}
			e.manifest.Assets[e.selectedID] = def
		} else {
			e.isDraggingImage = false
		}
	}

	return nil
}

func (e *Editor) UpdateInspector(mx, my int) {
	if e.selectedID == "" {
		return
	}
	def := e.manifest.Assets[e.selectedID]
	y := 40
	y += HeaderHeight + 20

	// Type toggle
	if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) && my >= y && my < y+20 {
		idx := 0
		for i, t := range AssetTypes {
			if t == def.Type {
				idx = i
			}
		}
		def.Type = AssetTypes[(idx+1)%len(AssetTypes)]
	}
	y += HeaderHeight

	updateFloat := func(fieldID string, val *float64) {
		if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) && my >= y && my < y+20 {
			e.activeField = fieldID
			e.inputBuffer = fmt.Sprintf("%.2f", *val)
			ebiten.InputChars()
		}
		if e.activeField == fieldID {
			if inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
				if v, err := strconv.ParseFloat(e.inputBuffer, 64); err == nil {
					*val = v
				}
				e.activeField = ""
			} else if inpututil.IsKeyJustPressed(ebiten.KeyBackspace) {
				if len(e.inputBuffer) > 0 {
					e.inputBuffer = e.inputBuffer[:len(e.inputBuffer)-1]
				}
			} else {
				e.inputBuffer += string(ebiten.InputChars())
			}
		}
		y += LineHeight
	}

	if def.Filename == "" {
		y += 20
		updateFloat("proc_w", &def.ProcWidth)
		updateFloat("proc_h", &def.ProcHeight)
		def.Hitbox.W = def.ProcWidth
		def.Hitbox.H = def.ProcHeight
	} else {
		y += 20
		updateFloat("scale", &def.Scale)
	}

	y += 20
	updateFloat("off_x", &def.DrawOffX)
	updateFloat("off_y", &def.DrawOffY)
	y += 20
	updateFloat("hb_x", &def.Hitbox.OffsetX)
	updateFloat("hb_y", &def.Hitbox.OffsetY)
	updateFloat("hb_w", &def.Hitbox.W)
	updateFloat("hb_h", &def.Hitbox.H)

	y += 20
	updateColorSlider := func(val *uint8) {
		sliderRect := image.Rect(CanvasWidth-WidthInspect+10, y, CanvasWidth-10, y+15)
		if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) && my >= y && my < y+15 {
			relX := float64(mx - sliderRect.Min.X)
			pct := relX / float64(sliderRect.Dx())
			if pct < 0 {
				pct = 0
			}
			if pct > 1 {
				pct = 1
			}
			*val = uint8(pct * 255)
		}
		y += 20
	}
	updateColorSlider(&def.Color.R)
	updateColorSlider(&def.Color.G)
	updateColorSlider(&def.Color.B)
	updateColorSlider(&def.Color.A)

	e.manifest.Assets[e.selectedID] = def
}

func (e *Editor) Draw(screen *ebiten.Image) {
	// --- 1. LISTE LINKS ---
	vector.DrawFilledRect(screen, 0, 0, WidthList, CanvasHeight, ColPanel, false)

	// Button Neu
	btnRect := image.Rect(10, CanvasHeight-35, WidthList-10, CanvasHeight-10)
	vector.DrawFilledRect(screen, float32(btnRect.Min.X), float32(btnRect.Min.Y), float32(btnRect.Dx()), float32(btnRect.Dy()), ColHighlight, false)
	text.Draw(screen, "+ NEW PLATFORM", basicfont.Face7x13, 20, CanvasHeight-18, color.RGBA{255, 255, 255, 255})

	// SCROLL BEREICH
	startY := 40.0 - e.listScroll
	currentY := startY

	// Helper Funktion zum Zeichnen von Listeneinträgen mit Bild
	drawListItem := func(label string, id string, col color.Color, img *ebiten.Image) {
		if currentY > -float64(LineHeight) && currentY < CanvasHeight-50 {
			// Bild Vorschau (Thumbnail)
			if img != nil {
				// Skalierung berechnen (max 28px hoch/breit)
				iconSize := float64(LineHeight - 4) // etwas Rand lassen
				iw, ih := img.Bounds().Dx(), img.Bounds().Dy()
				scale := iconSize / float64(ih)
				if float64(iw)*scale > iconSize {
					scale = iconSize / float64(iw)
				}

				op := &ebiten.DrawImageOptions{}
				op.GeoM.Scale(scale, scale)
				// Zentrieren im Icon-Bereich (links)
				op.GeoM.Translate(10, currentY+2)
				screen.DrawImage(img, op)
			} else {
				// Platzhalter Box wenn kein Bild
				vector.DrawFilledRect(screen, 10, float32(currentY+2), 28, 28, color.RGBA{60, 60, 60, 255}, false)
			}

			// Text daneben
			text.Draw(screen, label, basicfont.Face7x13, 45, int(currentY+float64(TextOffset)), col)
		}
		currentY += float64(LineHeight)
	}

	text.Draw(screen, "--- ASSETS ---", basicfont.Face7x13, 10, int(currentY), color.RGBA{150, 150, 150, 255})
	currentY += float64(LineHeight)

	for _, id := range e.sortedIDs {
		col := ColText
		if id == e.selectedID {
			col = ColHighlight
		}
		// Bild holen aus e.assetsImages
		img := e.assetsImages[id]
		// Falls AssetDefinition sagt es gibt ein Filename, aber img nil ist, versuchen wir es via ID
		if img == nil {
			if def, ok := e.manifest.Assets[id]; ok && def.Filename != "" {
				// ID könnte abweichen wenn Filename anders heißt, wir suchen in assetsImages nach ID
				img = e.assetsImages[def.ID]
			}
		}
		drawListItem(id, id, col, img)
	}

	currentY += 20
	text.Draw(screen, "--- RAW FILES ---", basicfont.Face7x13, 10, int(currentY), color.RGBA{150, 150, 150, 255})
	currentY += float64(LineHeight)

	for _, f := range e.rawFiles {
		ext := filepath.Ext(f)
		id := strings.TrimSuffix(f, ext)
		if _, exists := e.manifest.Assets[id]; exists {
			continue
		}

		// Bild für Raw File
		img := e.assetsImages[id]
		drawListItem("+ "+f, id, ColNewFile, img)
	}

	// --- 2. CANVAS MITTE ---
	viewX := float64(WidthList)
	centerX := float64(WidthList) + float64(CanvasWidth-WidthList-WidthInspect)/2
	centerY := float64(CanvasHeight) * 0.75

	vector.StrokeLine(screen, float32(viewX), float32(centerY), float32(CanvasWidth-WidthInspect), float32(centerY), 2, ColAxis, false)
	text.Draw(screen, "BODEN (Y=0) | [P] Player Ref Toggle", basicfont.Face7x13, int(viewX)+10, int(centerY)-10, ColAxis)
	vector.StrokeLine(screen, float32(centerX), 0, float32(centerX), CanvasHeight, 1, color.RGBA{100, 100, 100, 255}, false)

	// A. PLAYER GHOST
	if e.showPlayerRef && e.selectedID != "player" {
		if playerDef, ok := e.manifest.Assets["player"]; ok {
			posX := centerX + playerDef.DrawOffX
			posY := centerY + playerDef.DrawOffY
			col := ColPlayerRef
			if playerDef.Filename != "" {
				if img := e.assetsImages[playerDef.ID]; img != nil {
					op := &ebiten.DrawImageOptions{}
					op.GeoM.Scale(playerDef.Scale, playerDef.Scale)
					op.GeoM.Translate(posX, posY)
					op.ColorScale.ScaleAlpha(0.5)
					screen.DrawImage(img, op)
				}
			} else {
				vector.DrawFilledRect(screen, float32(posX), float32(posY), float32(playerDef.ProcWidth), float32(playerDef.ProcHeight), col, false)
			}
			hx := float32(centerX + playerDef.DrawOffX + playerDef.Hitbox.OffsetX)
			hy := float32(centerY + playerDef.DrawOffY + playerDef.Hitbox.OffsetY)
			vector.StrokeRect(screen, hx, hy, float32(playerDef.Hitbox.W), float32(playerDef.Hitbox.H), 1, col, false)
		}
	}

	// B. CURRENT OBJECT
	if e.selectedID != "" {
		def := e.manifest.Assets[e.selectedID]
		posX := centerX + def.DrawOffX
		posY := centerY + def.DrawOffY
		img := e.assetsImages[def.ID]

		if def.Filename != "" {
			if img != nil {
				op := &ebiten.DrawImageOptions{}
				op.GeoM.Scale(def.Scale, def.Scale)
				op.GeoM.Translate(posX, posY)
				screen.DrawImage(img, op)
			} else {
				fallbackCol := def.Color.ToRGBA()
				vector.DrawFilledRect(screen, float32(posX+def.Hitbox.OffsetX), float32(posY+def.Hitbox.OffsetY), float32(def.Hitbox.W), float32(def.Hitbox.H), fallbackCol, false)
				text.Draw(screen, "MISSING IMG", basicfont.Face7x13, int(posX), int(posY), color.RGBA{255, 255, 255, 255})
			}
		} else {
			col := def.Color.ToRGBA()
			vector.DrawFilledRect(screen, float32(posX), float32(posY), float32(def.ProcWidth), float32(def.ProcHeight), col, false)
		}

		// Hitbox Current
		hx := float32(centerX + def.DrawOffX + def.Hitbox.OffsetX)
		hy := float32(centerY + def.DrawOffY + def.Hitbox.OffsetY)
		vector.DrawFilledRect(screen, hx, hy, float32(def.Hitbox.W), float32(def.Hitbox.H), color.RGBA{255, 0, 0, 80}, false)
		vector.StrokeRect(screen, hx, hy, float32(def.Hitbox.W), float32(def.Hitbox.H), 2, color.RGBA{255, 0, 0, 255}, false)
	}

	// --- 3. INSPECTOR RECHTS ---
	panelX := float64(CanvasWidth - WidthInspect)
	vector.DrawFilledRect(screen, float32(panelX), 0, WidthInspect, CanvasHeight, ColPanel, false)

	if e.selectedID != "" {
		def := e.manifest.Assets[e.selectedID]
		y := 40

		drawLabel := func(txt string) {
			text.Draw(screen, txt, basicfont.Face7x13, int(panelX)+10, y+TextOffset, ColHighlight)
		}

		text.Draw(screen, "ID: "+def.ID, basicfont.Face7x13, int(panelX)+10, y+TextOffset, color.RGBA{255, 255, 255, 255})
		y += HeaderHeight

		drawLabel("TYPE: [Click to change]")
		y += 20
		vector.DrawFilledRect(screen, float32(panelX)+10, float32(y), WidthInspect-20, 20, ColInput, false)
		text.Draw(screen, strings.ToUpper(def.Type), basicfont.Face7x13, int(panelX)+20, y+14, color.RGBA{255, 255, 255, 255})
		y += HeaderHeight

		drawVal := func(lbl string, val float64, fieldID string) {
			str := fmt.Sprintf("%.2f", val)
			col := color.RGBA{255, 255, 255, 255}
			if e.activeField == fieldID {
				str = e.inputBuffer + "_"
				col = ColHighlight
			}
			text.Draw(screen, lbl+": "+str, basicfont.Face7x13, int(panelX)+20, y+TextOffset, col)
			y += LineHeight
		}

		if def.Filename == "" {
			drawLabel("--- PROCEDURAL ---")
			y += 20
			drawVal("W", def.ProcWidth, "proc_w")
			drawVal("H", def.ProcHeight, "proc_h")
		} else {
			drawLabel("--- IMAGE ---")
			y += 20
			drawVal("Scale", def.Scale, "scale")
		}

		drawLabel("--- OFFSET (L-Click Drag) ---")
		y += 20
		drawVal("X", def.DrawOffX, "off_x")
		drawVal("Y", def.DrawOffY, "off_y")
		drawLabel("--- HITBOX (R-Click Drag) ---")
		y += 20
		drawVal("X", def.Hitbox.OffsetX, "hb_x")
		drawVal("Y", def.Hitbox.OffsetY, "hb_y")
		drawVal("W", def.Hitbox.W, "hb_w")
		drawVal("H", def.Hitbox.H, "hb_h")

		drawLabel("--- COLOR ---")
		y += 20
		drawBar := func(c color.RGBA) {
			vector.DrawFilledRect(screen, float32(panelX)+10, float32(y), WidthInspect-30, 10, c, false)
			y += 20
		}
		drawBar(color.RGBA{def.Color.R, 0, 0, 255})
		drawBar(color.RGBA{0, def.Color.G, 0, 255})
		drawBar(color.RGBA{0, 0, def.Color.B, 255})
		vector.DrawFilledRect(screen, float32(panelX)+WidthInspect-30, float32(y-60), 20, 50, def.Color.ToRGBA(), false)

		text.Draw(screen, "[Enter] Confirm | [S] Save", basicfont.Face7x13, int(panelX)+10, CanvasHeight-20, color.RGBA{100, 100, 100, 255})
	}
}

func (e *Editor) Layout(w, h int) (int, int) { return CanvasWidth, CanvasHeight }

func main() {
	os.MkdirAll(RawDir, 0755)
	os.MkdirAll(filepath.Dir(OutFile), 0755)
	ebiten.SetWindowSize(CanvasWidth, CanvasHeight)
	ebiten.SetWindowTitle("Escape Prefab Editor - Pro")
	if err := ebiten.RunGame(NewEditor()); err != nil {
		log.Fatal(err)
	}
}
