package ui

import (
	"bytes"
	"embed"
	"image"
	"image/color"
	_ "image/png"

	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/clip"
	"gioui.org/op/paint"
	"gioui.org/text"
	"gioui.org/unit"
	"gioui.org/widget/material"
)

//go:embed icons/*.png
var iconFS embed.FS

// ScaleProvider provides access to the current scale factor.
type ScaleProvider interface {
	Scale() float32
}

// Renderer consumes a slice of elements and draws them.
type Renderer struct {
	theme Theme
	shp   *text.Shaper
	scale ScaleProvider
	icons map[string]image.Image
}

// New creates a new Renderer.
func New(th Theme, shp *text.Shaper, scale ScaleProvider) *Renderer {
	r := &Renderer{theme: th, shp: shp, scale: scale, icons: make(map[string]image.Image)}
	r.loadIcons()
	return r
}

// loadIcons loads PNG icons from the embedded filesystem.
func (r *Renderer) loadIcons() {
	data, err := iconFS.ReadFile("icons/cut.png")
	if err != nil {
		return
	}
	img, _, err := image.Decode(bytes.NewReader(data))
	if err != nil {
		return
	}
	r.icons["cut"] = img
}

// icon returns a loaded icon image by name, or nil if not found.
func (r *Renderer) icon(name string) image.Image {
	return r.icons[name]
}

// toPx converts Dp to physical pixels using State's scale.
func (r *Renderer) toPx(dp Dp) Px {
	return ToPx(dp, r.scale.Scale())
}

// toDp converts physical pixels to Dp using State's scale.
func (r *Renderer) toDp(px Px) Dp {
	return ToDp(px, r.scale.Scale())
}

// Draw iterates elements and draws each in slice order (back-to-front).
func (r *Renderer) Draw(gtx layout.Context, elems []Element) {
	// Clip to window bounds
	clipRect := clip.Rect{
		Min: gtx.Constraints.Min,
		Max: gtx.Constraints.Max,
	}.Push(gtx.Ops)

	for _, e := range elems {
		if !e.Visible() {
			continue
		}
		r.drawElement(gtx, e)
	}

	clipRect.Pop()
}

// drawElement clips to the element's bounds, draws background if any,
// then dispatches to the element's Draw method.
func (r *Renderer) drawElement(gtx layout.Context, e Element) {
	reg := e.Region()

	// Clip to element bounds
	clipRect := clip.Rect{
		Min: image.Point{X: int(r.toPx(reg.X)), Y: int(r.toPx(reg.Y))},
		Max: image.Point{X: int(r.toPx(reg.X + reg.W)), Y: int(r.toPx(reg.Y + reg.H))},
	}.Push(gtx.Ops)

	// Draw children if this is a container
	if container, ok := e.(Container); ok {
		for _, child := range container.children {
			child.Draw(gtx, r)
		}
	}

	// Dispatch to element's Draw method
	e.Draw(gtx, r)

	clipRect.Pop()
}

// drawBg draws a solid-color background for the given region.
func (r *Renderer) drawBg(gtx layout.Context, reg Region, col Color) {
	bgClip := clip.Rect{
		Min: image.Point{X: int(r.toPx(reg.X)), Y: int(r.toPx(reg.Y))},
		Max: image.Point{X: int(r.toPx(reg.X + reg.W)), Y: int(r.toPx(reg.Y + reg.H))},
	}.Push(gtx.Ops)
	paint.ColorOp{Color: color.NRGBA{R: col.R, G: col.G, B: col.B, A: col.A}}.Add(gtx.Ops)
	paint.PaintOp{}.Add(gtx.Ops)
	bgClip.Pop()
}

// drawText draws text using Gio's textView approach.
func (r *Renderer) drawText(gtx layout.Context, str string, size unit.Sp, reg Region, col Color) {
	th := material.NewTheme()
	th.Shaper = r.shp
	gtx.Constraints.Min.X = int(r.toPx(reg.X))
	gtx.Constraints.Min.Y = int(r.toPx(reg.Y))
	material.Label(th, size, str).Layout(gtx)
}

// drawPng draws a PNG image at the given region and size.
func (r *Renderer) drawPng(gtx layout.Context, img image.Image, reg Region, width, height Dp) {
	if img == nil {
		return
	}

	xPx := int(r.toPx(reg.X))
	yPx := int(r.toPx(reg.Y))
	wPx := int(r.toPx(width))
	hPx := int(r.toPx(height))
	_ = image.Rect(xPx, yPx, xPx+wPx, yPx+hPx)

	stack := op.Offset(image.Pt(xPx, yPx)).Push(gtx.Ops)
	paint.NewImageOp(img).Add(gtx.Ops)
	paint.PaintOp{}.Add(gtx.Ops)
	stack.Pop()
}
