package client

import (
	"math"

	"fyne.io/fyne/v2"

	"galaxy/client/world"
)

/*
Client pan integration for Fyne Draggable:

- DragEvent.Dragged provides per-event delta in Fyne logical units.
- Client knows canvasScale (pixels per Fyne unit) and converts to pixels.
- We update camera center in world-fixed (CameraXWorldFp/YWorldFp).

Sign convention (map follows pointer):
- Drag right (dxPx > 0): move world content right => move camera left => CameraXWorldFp -= dxWorldFp
- Drag down  (dyPx > 0): move world content down  => move camera up   => CameraYWorldFp -= dyWorldFp
*/

// draggableClient is the minimal interface we need from your client implementation.
// If your Client already has these methods/fields, you can fold the code directly into it.
type draggableClient interface {
	// CanvasScale returns pixels per Fyne logical unit.
	CanvasScale() float32

	// UpdateParams applies a mutation and schedules refresh through your coalescer.
	UpdateParams(fn func(p *world.RenderParams))

	// RequestRefresh schedules a refresh with current params (no mutation).
	RequestRefresh()

	// ForceFullRedraw forces a full redraw on next Render (used on DragEnd).
	ForceFullRedraw()
}

// PanController holds per-drag transient state.
type PanController struct {
	ed draggableClient

	dragging bool
	lastFx   float32 // last absolute position in Fyne units
	lastFy   float32

	// Remainders to keep subpixel fyne->px conversion stable across many events.
	remPxX float32
	remPxY float32
}

func NewPanController(ed draggableClient) *PanController {
	return &PanController{ed: ed}
}

// Dragged processes one drag event, updates camera center by delta, and schedules redraw.
func (p *PanController) Dragged(ev *fyne.DragEvent) {
	if ev == nil {
		return
	}

	scale := p.ed.CanvasScale()
	if scale <= 0 {
		return
	}

	// DragEvent.Dragged is delta in Fyne logical units (device independent).
	// Convert to pixels by multiplying by canvas scale.
	dxPxF := ev.Dragged.DX * scale
	dyPxF := ev.Dragged.DY * scale

	// accumulate subpixel remainder in pixels
	dxPxF += p.remPxX
	dyPxF += p.remPxY

	dxPx := int(math.Round(float64(dxPxF)))
	dyPx := int(math.Round(float64(dyPxF)))

	p.remPxX = dxPxF - float32(dxPx)
	p.remPxY = dyPxF - float32(dyPx)

	if dxPx == 0 && dyPx == 0 {
		return
	}

	p.ed.UpdateParams(func(rp *world.RenderParams) {
		zoomFp, err := rp.CameraZoomFp()
		if err != nil || zoomFp <= 0 {
			return
		}

		dxWorldFp := world.PixelSpanToWorldFixed(dxPx, zoomFp)
		dyWorldFp := world.PixelSpanToWorldFixed(dyPx, zoomFp)

		// Map follows pointer
		rp.CameraXWorldFp -= dxWorldFp
		rp.CameraYWorldFp -= dyWorldFp
	})
}

// DragEnd ends the drag gesture. We force a full redraw next to eliminate any
// possible artifacts from incremental shifting and to "settle" the final state.
func (p *PanController) DragEnd() {
	p.dragging = false
	p.remPxX = 0
	p.remPxY = 0

	p.ed.ForceFullRedraw()
	p.ed.RequestRefresh()
}
