package d2scene

import (
	
	
)

// Primitive is the closed set of paintable scene leaves. Groups are Nodes
// with a nil Primitive and one or more Children.
type Primitive interface {
	isPrimitive()
}

type Rect struct {
	Box     Box
	RadiusX float64
	RadiusY float64
	Fill    Paint
	Stroke  *Stroke
}

func (Rect) () {}

type Ellipse struct {
	Center  Point
	RadiusX float64
	RadiusY float64
	Fill    Paint
	Stroke  *Stroke
}

func (Ellipse) () {}

type Font struct {
	Family string
	Style  string
	Weight int
	Size   float64
	Asset  AssetID
}

type TextAnchor uint8

const (
	AnchorStart TextAnchor = iota
	AnchorMiddle
	AnchorEnd
)

// Glyph optionally carries a shaper's exact glyph placement. Ink is relative
// to Origin+Position. A renderer may shape Text itself when Glyphs is empty.
type Glyph struct {
	ID uint32
	// Empty retains an invisible shaper output (for example a default-
	// ignorable control) so its placement and advance survive in the scene.
	// Empty glyphs use ID zero and are never sent to the outline rasterizer.
	Empty bool
	// Asset overrides TextRun.Font.Asset for this glyph. An empty value keeps
	// the primary asset. This makes explicit shaping capable of retaining
	// mixed-font fallback decisions in the scene itself.
	Asset    AssetID
	Position Point
	Advance  float64
	Ink      Bounds
}

// TextRun is one consistently styled baseline run. Ink is the exact measured
// node-local ink bounds when available. Underline and Strike are explicit so
// link and label decoration does not require renderer-specific child nodes.
type TextRun struct {
	Text   string
	Origin Point
	Anchor TextAnchor
	Font   Font
	// Fallbacks is an ordered list of already-resolved font assets. Renderers
	// may use them for missing glyphs but must never perform font discovery or
	// filesystem I/O while painting a document. It lives on TextRun rather than
	// Font so Font remains comparable and usable as a map key.
	Fallbacks []AssetID
	Fill      Paint
	Stroke    *Stroke
	Underline bool
	Strike    bool
	Glyphs    []Glyph
	Ink       Bounds
}

func (TextRun) () {}

type AspectAlign uint8

const (
	AlignNone AspectAlign = iota
	AlignXMinYMin
	AlignXMidYMin
	AlignXMaxYMin
	AlignXMinYMid
	AlignXMidYMid
	AlignXMaxYMid
	AlignXMinYMax
	AlignXMidYMax
	AlignXMaxYMax
)

type AspectFit uint8

const (
	AspectMeet AspectFit = iota
	AspectSlice
)

type AspectRatio struct {
	Align AspectAlign
	Fit   AspectFit
}

type Image struct {
	Asset  AssetID
	Box    Box
	Aspect AspectRatio
}

func (Image) () {}

// PrimitiveBounds returns conservative painted bounds after applying m. Path
// and ellipse geometry extrema are analytic; stroke expansion is deliberately
// conservative until the stroker computes its exact outline.
func ( Primitive,  Matrix) (Bounds, error) {
	if  == nil {
		return Bounds{}, nil
	}
	if !.IsFinite() {
		return Bounds{}, fmt.Errorf("d2scene: non-finite primitive transform")
	}
	switch primitive := .(type) {
	case Rect:
		return rectPrimitiveBounds(, )
	case *Rect:
		if  == nil {
			return Bounds{}, nil
		}
		return rectPrimitiveBounds(*, )
	case Ellipse:
		return ellipsePrimitiveBounds(, )
	case *Ellipse:
		if  == nil {
			return Bounds{}, nil
		}
		return ellipsePrimitiveBounds(*, )
	case Path:
		return pathPrimitiveBounds(, )
	case *Path:
		if  == nil {
			return Bounds{}, nil
		}
		return pathPrimitiveBounds(*, )
	case TextRun:
		return textPrimitiveBounds(, )
	case *TextRun:
		if  == nil {
			return Bounds{}, nil
		}
		return textPrimitiveBounds(*, )
	case Image:
		return imagePrimitiveBounds(, )
	case *Image:
		if  == nil {
			return Bounds{}, nil
		}
		return imagePrimitiveBounds(*, )
	default:
		return Bounds{}, fmt.Errorf("d2scene: unsupported primitive %T", )
	}
}

func rectPrimitiveBounds( Rect,  Matrix) (Bounds, error) {
	if  := validateBox(.Box);  != nil {
		return Bounds{}, fmt.Errorf("d2scene: rectangle: %w", )
	}
	if .RadiusX < 0 || .RadiusY < 0 || !finite(.RadiusX) || !finite(.RadiusY) {
		return Bounds{}, fmt.Errorf("d2scene: rectangle: invalid corner radius")
	}
	if .Fill == nil && !.Stroke.visible() {
		return Bounds{}, nil
	}
	return expandStroke(.Box.Bounds().Transform(), .Stroke, ), nil
}

func ellipsePrimitiveBounds( Ellipse,  Matrix) (Bounds, error) {
	if !finitePoint(.Center) || !finite(.RadiusX) || !finite(.RadiusY) || .RadiusX < 0 || .RadiusY < 0 {
		return Bounds{}, fmt.Errorf("d2scene: ellipse: invalid geometry")
	}
	if .Fill == nil && !.Stroke.visible() {
		return Bounds{}, nil
	}
	 := .Point(.Center)
	 := hypot(.A*.RadiusX, .C*.RadiusY)
	 := hypot(.B*.RadiusX, .D*.RadiusY)
	 := NewBounds(.X-, .Y-, .X+, .Y+)
	return expandStroke(, .Stroke, ), nil
}

func pathPrimitiveBounds( Path,  Matrix) (Bounds, error) {
	if .Fill == nil && !.Stroke.visible() {
		return Bounds{}, nil
	}
	,  := .transformedGeometryBounds()
	if  != nil {
		return Bounds{}, 
	}
	return expandStroke(, .Stroke, ), nil
}

func textPrimitiveBounds( TextRun,  Matrix) (Bounds, error) {
	if .Fill == nil && !.Stroke.visible() {
		return Bounds{}, nil
	}
	if .Font.Size <= 0 || !finite(.Font.Size) || !finitePoint(.Origin) {
		return Bounds{}, fmt.Errorf("d2scene: text: invalid font size or origin")
	}
	 := .Ink
	if !.Valid {
		for ,  := range .Glyphs {
			if !finitePoint(.Position) || !finite(.Advance) || !.Ink.IsFinite() {
				return Bounds{}, fmt.Errorf("d2scene: text: invalid glyph geometry")
			}
			 = .Union(.Ink.Translate(Point{
				X: .Origin.X + .Position.X,
				Y: .Origin.Y + .Position.Y,
			}))
		}
	}
	if !.IsFinite() {
		return Bounds{}, fmt.Errorf("d2scene: text: invalid ink bounds")
	}
	if !.Valid {
		return Bounds{}, fmt.Errorf("d2scene: text: missing ink or glyph bounds")
	}
	return expandStroke(.Transform(), .Stroke, ), nil
}

func imagePrimitiveBounds( Image,  Matrix) (Bounds, error) {
	if .Asset == "" {
		return Bounds{}, fmt.Errorf("d2scene: image: empty asset ID")
	}
	if  := validateBox(.Box);  != nil {
		return Bounds{}, fmt.Errorf("d2scene: image: %w", )
	}
	return .Box.Bounds().Transform(), nil
}

func expandStroke( Bounds,  *Stroke,  Matrix) Bounds {
	if !.Valid || !.visible() {
		return 
	}
	 := .expansion() * .MaxScale()
	return .Expand(, )
}

func validateBox( Box) error {
	if !finite(.X) || !finite(.Y) || !finite(.Width) || !finite(.Height) {
		return fmt.Errorf("non-finite box")
	}
	if .Width < 0 || .Height < 0 {
		return fmt.Errorf("negative box size")
	}
	return nil
}

func hypot(,  float64) float64 {
	// Kept behind one helper so every analytic bounds path uses the same
	// deterministic standard-library operation.
	return math.Hypot(, )
}