package pncdsl

import (
	"image/color"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/vector"
)

// engine is the ebiten.Game adapter. Each frame it polls input, advances
// the active script (if any), then lets every registered Widget tick
// (reverse registration order, so the top widget claims input first).
// Whatever survives is offered to the scene as a hotspot click.
type engine struct {
	g *Game
}

func (e *engine) Layout(outsideWidth, outsideHeight int) (int, int) {
	return e.g.Width, e.g.Height
}

func (e *engine) Update() error {
	g := e.g
	dt := 1.0 / 60.0

	g.Input.poll()
	g.transition.update(dt)

	if g.transition.active && g.transition.out {
		return nil
	}

	// Active script consumes the frame.
	if g.scriptRunner != nil {
		ctx := g.scriptCtx
		ctx.DT = dt
		if g.currentScene != "" {
			s := g.SceneManager.MustGet(g.currentScene)
			ctx.Scene = &s
		}
		st := g.scriptRunner.Tick(ctx)
		if st != StatusRunning {
			g.scriptRunner = nil
			g.scriptCtx = nil
		}
		g.tickCharacters(dt)
		return nil
	}

	uictx := &UICtx{Game: g, DT: dt}

	// Clear per-frame transient state that widgets / scene-resolution write.
	g.SetHoverLabel("")

	// Top-down input: the widget drawn last (= registered last) gets the
	// click first, then the next-to-last, etc. A widget signals "I took it"
	// via g.Input.ConsumeLeft / ConsumeRight.
	for _, w := range reversedWidgets(g.UIManager) {
		w.Tick(uictx)
	}

	// Anything still unconsumed is a scene-world interaction.
	e.handleSceneInput()
	g.tickCharacters(dt)
	return nil
}

// handleSceneInput is the legacy "click on hotspot with active verb" flow,
// running only when no widget has claimed the input.
func (e *engine) handleSceneInput() {
	g := e.g
	mp := g.Input.Point()
	// hover label resolution: pointer over a hotspot in the current scene
	if h := e.hotspotAt(mp); h != nil {
		if h.Label != "" {
			g.SetHoverLabel(h.Label)
		} else {
			g.SetHoverLabel(h.Name)
		}
	}

	if g.Input.RightClicked() {
		g.Input.ConsumeRight()
		if g.Inventory.Selected() != "" {
			g.Inventory.Select("")
		} else {
			g.SetSelectedVerb("look")
		}
	}

	if !g.Input.LeftClicked() {
		return
	}
	g.Input.ConsumeLeft()
	h := e.hotspotAt(mp)
	if h == nil {
		return
	}
	sel := g.Inventory.Selected()
	targetLabel := h.Label
	if targetLabel == "" {
		targetLabel = h.Name
	}
	if sel != "" {
		if h.OnUseWith != nil {
			if a, ok := h.OnUseWith[sel]; ok && a != nil {
				g.LogAction("> " + verbLabel(g, "use") + " " + sel + " ezen: " + targetLabel)
				g.queueAction(a, "useWith hotspot")
				g.Inventory.Select("")
				return
			}
		}
		if it, ok := g.ItemManager.Get(sel); ok && it.OnUseWith != nil {
			if a, ok := it.OnUseWith[h.Name]; ok && a != nil {
				g.LogAction("> " + verbLabel(g, "use") + " " + sel + " ezen: " + targetLabel)
				g.queueAction(a, "useWith item")
				g.Inventory.Select("")
				return
			}
		}
		g.FlashLine("Nem ehhez.")
		g.Inventory.Select("")
		return
	}
	a := h.handler(g.SelectedVerb())
	if a == nil {
		if v, ok := g.VerbManager.Get(g.SelectedVerb()); ok && v.Default != nil {
			a = v.Default
		}
	}
	if a == nil {
		g.FlashLine("Semmi említésre méltó.")
		return
	}
	g.LogAction("> " + verbLabel(g, g.SelectedVerb()) + " " + targetLabel)
	g.queueAction(a, "hotspot "+g.SelectedVerb()+" "+h.Name)
}

func verbLabel(g *Game, name string) string {
	if v, ok := g.VerbManager.Get(name); ok {
		return v.Label
	}
	return name
}

func (e *engine) hotspotAt(p Point) *Hotspot {
	g := e.g
	if g.currentScene == "" {
		return nil
	}
	s := g.SceneManager.MustGet(g.currentScene)
	for i := range s.Hotspots {
		h := &s.Hotspots[i]
		if h.Area != nil && h.Area.Contains(p) {
			return h
		}
	}
	return nil
}

func (e *engine) Draw(screen *ebiten.Image) {
	g := e.g
	th := g.Theme()

	// optional backdrop (helps when the background image doesn't fill the screen)
	if th.SceneBackdrop != nil {
		screen.Fill(th.SceneBackdrop)
	}

	// scene background
	if g.currentScene != "" {
		s := g.SceneManager.MustGet(g.currentScene)
		img := g.loaded.image(g.AssetManager, s.Background)
		bw, bh := img.Bounds().Dx(), img.Bounds().Dy()
		if bw > 0 && bh > 0 {
			op := &ebiten.DrawImageOptions{}
			op.GeoM.Scale(float64(g.Width)/float64(bw), float64(g.Height)/float64(bh))
			screen.DrawImage(img, op)
		}
	} else {
		screen.Fill(color.RGBA{0, 0, 0, 255})
	}

	// characters (y-sorted)
	for _, c := range sortedChars(g) {
		drawCharacter(screen, g, c)
	}

	// widgets in registration order
	uictx := &UICtx{Game: g, DT: 1.0 / 60.0}
	for _, w := range orderedWidgets(g.UIManager) {
		w.Draw(screen, uictx)
	}

	// transition overlay on top of everything except cursor (cursor is the
	// last widget, so it has already drawn above the transition? no — the
	// fade should cover the cursor too; draw it after the widgets).
	g.transition.draw(screen, g.Width, g.Height)
}

func sortedChars(g *Game) []*runtimeChar {
	out := make([]*runtimeChar, 0, len(g.chars))
	for _, c := range g.chars {
		out = append(out, c)
	}
	for i := 1; i < len(out); i++ {
		for j := i; j > 0 && out[j].pos.Y < out[j-1].pos.Y; j-- {
			out[j], out[j-1] = out[j-1], out[j]
		}
	}
	return out
}

func drawCharacter(dst *ebiten.Image, g *Game, c *runtimeChar) {
	w := c.def.W
	h := c.def.H
	if w == 0 {
		w = 24
	}
	if h == 0 {
		h = 56
	}
	if c.def.Sprite != "" && !g.loaded.isPlaceholder(c.def.Sprite) {
		img := g.loaded.image(g.AssetManager, c.def.Sprite)
		sw, sh := img.Bounds().Dx(), img.Bounds().Dy()
		if sw > 0 && sh > 0 {
			op := &ebiten.DrawImageOptions{}
			op.GeoM.Scale(w/float64(sw), h/float64(sh))
			op.GeoM.Translate(c.pos.X-w/2, c.pos.Y-h)
			dst.DrawImage(img, op)
			return
		}
	}
	if c.def.Sprite != "" {
		_ = g.loaded.image(g.AssetManager, c.def.Sprite)
	}
	x := c.pos.X - w/2
	y := c.pos.Y - h
	bodyCol := color.RGBA{200, 200, 200, 255}
	if rgba, ok := c.def.SpeechColor.(color.RGBA); ok {
		bodyCol = rgba
	}
	if w > h {
		drawQuadrupedPlaceholder(dst, x, y, w, h, bodyCol)
	} else {
		drawHumanoidPlaceholder(dst, x, y, w, h, bodyCol)
	}
}

func drawHumanoidPlaceholder(dst *ebiten.Image, x, y, w, h float64, body color.RGBA) {
	pants := color.RGBA{40, 50, 90, 255}
	skin := color.RGBA{245, 200, 155, 255}
	outline := color.RGBA{20, 20, 30, 255}

	headR := h * 0.14
	headCY := y + headR + 1

	pantsTop := y + h*0.62
	pantsH := h - (pantsTop - y) - 1
	legW := w * 0.36
	vector.DrawFilledRect(dst, float32(x+w*0.08), float32(pantsTop), float32(legW), float32(pantsH), pants, false)
	vector.DrawFilledRect(dst, float32(x+w*0.56), float32(pantsTop), float32(legW), float32(pantsH), pants, false)

	torsoTop := headCY + headR
	torsoH := pantsTop - torsoTop
	vector.DrawFilledRect(dst, float32(x), float32(torsoTop), float32(w), float32(torsoH), body, false)
	vector.StrokeRect(dst, float32(x), float32(torsoTop), float32(w), float32(torsoH), 1, outline, false)

	vector.DrawFilledCircle(dst, float32(x+w/2), float32(headCY), float32(headR), skin, true)

	footH := 2.0
	vector.DrawFilledRect(dst, float32(x+w*0.05), float32(y+h-footH), float32(legW+2), float32(footH), outline, false)
	vector.DrawFilledRect(dst, float32(x+w*0.55), float32(y+h-footH), float32(legW+2), float32(footH), outline, false)
}

func drawQuadrupedPlaceholder(dst *ebiten.Image, x, y, w, h float64, body color.RGBA) {
	outline := color.RGBA{20, 20, 30, 255}
	dark := color.RGBA{R: body.R / 2, G: body.G / 2, B: body.B / 2, A: 255}

	bodyY := y + h*0.30
	bodyH := h * 0.65
	vector.DrawFilledRect(dst, float32(x), float32(bodyY), float32(w*0.78), float32(bodyH), body, false)

	headR := h * 0.30
	headCX := x + w - headR
	headCY := y + h*0.50
	vector.DrawFilledCircle(dst, float32(headCX), float32(headCY), float32(headR), body, true)

	earW := w * 0.07
	earH := h * 0.30
	vector.DrawFilledRect(dst, float32(headCX-headR*0.7), float32(y), float32(earW), float32(earH), body, false)
	vector.DrawFilledRect(dst, float32(headCX+headR*0.4), float32(y), float32(earW), float32(earH), body, false)

	vector.DrawFilledCircle(dst, float32(headCX+headR*0.25), float32(headCY-1), 1.2, outline, true)

	vector.DrawFilledRect(dst, float32(x-w*0.04), float32(bodyY+1), float32(w*0.08), 2, dark, false)

	legW := w * 0.07
	vector.DrawFilledRect(dst, float32(x+w*0.10), float32(y+h-3), float32(legW), 3, dark, false)
	vector.DrawFilledRect(dst, float32(x+w*0.55), float32(y+h-3), float32(legW), 3, dark, false)
}
