package renderer

import (
	"gitea.dockr.co/kev/go-jdenticon/internal/engine"
)

// Renderer defines the interface for rendering identicons to various output formats.
// It provides a set of drawing primitives that can be implemented by concrete renderers
// such as SVG, PNG, or other custom formats.
type Renderer interface {
	// Drawing primitives
	MoveTo(x, y float64)
	LineTo(x, y float64)
	CurveTo(x1, y1, x2, y2, x, y float64)
	ClosePath()

	// Fill and stroke operations
	Fill(color string)
	Stroke(color string, width float64)

	// Shape management
	BeginShape(color string)
	EndShape()

	// Background and configuration
	SetBackground(fillColor string, opacity float64)

	// High-level shape methods
	AddPolygon(points []engine.Point)
	AddCircle(topLeft engine.Point, size float64, invert bool)
	AddRectangle(x, y, width, height float64)
	AddTriangle(p1, p2, p3 engine.Point)

	// Utility methods
	GetSize() int
	Clear()
}

// BaseRenderer provides default implementations for common renderer functionality.
// Concrete renderers can embed this struct and override specific methods as needed.
type BaseRenderer struct {
	iconSize     int
	currentColor string
	background   string
	backgroundOp float64

	// Current path state for primitive operations
	currentPath []PathCommand
	pathStart   engine.Point
	currentPos  engine.Point
}

// PathCommandType represents the type of path command
type PathCommandType int

const (
	MoveToCommand PathCommandType = iota
	LineToCommand
	CurveToCommand
	ClosePathCommand
)

// PathCommand represents a single drawing command in a path
type PathCommand struct {
	Type   PathCommandType
	Points []engine.Point
}

// NewBaseRenderer creates a new base renderer with the specified icon size
func NewBaseRenderer(iconSize int) *BaseRenderer {
	return &BaseRenderer{
		iconSize:    iconSize,
		currentPath: make([]PathCommand, 0),
	}
}

// MoveTo moves the current drawing position to the specified coordinates
func (r *BaseRenderer) MoveTo(x, y float64) {
	pos := engine.Point{X: x, Y: y}
	r.currentPos = pos
	r.pathStart = pos
	r.currentPath = append(r.currentPath, PathCommand{
		Type:   MoveToCommand,
		Points: []engine.Point{pos},
	})
}

// LineTo draws a line from the current position to the specified coordinates
func (r *BaseRenderer) LineTo(x, y float64) {
	pos := engine.Point{X: x, Y: y}
	r.currentPos = pos
	r.currentPath = append(r.currentPath, PathCommand{
		Type:   LineToCommand,
		Points: []engine.Point{pos},
	})
}

// CurveTo draws a cubic Bézier curve from the current position to (x, y) using (x1, y1) and (x2, y2) as control points
func (r *BaseRenderer) CurveTo(x1, y1, x2, y2, x, y float64) {
	endPos := engine.Point{X: x, Y: y}
	r.currentPos = endPos
	r.currentPath = append(r.currentPath, PathCommand{
		Type: CurveToCommand,
		Points: []engine.Point{
			{X: x1, Y: y1},
			{X: x2, Y: y2},
			endPos,
		},
	})
}

// ClosePath closes the current path by drawing a line back to the path start
func (r *BaseRenderer) ClosePath() {
	r.currentPos = r.pathStart
	r.currentPath = append(r.currentPath, PathCommand{
		Type:   ClosePathCommand,
		Points: []engine.Point{},
	})
}

// Fill fills the current path with the specified color
func (r *BaseRenderer) Fill(color string) {
	// Default implementation - concrete renderers should override
}

// Stroke strokes the current path with the specified color and width
func (r *BaseRenderer) Stroke(color string, width float64) {
	// Default implementation - concrete renderers should override
}

// BeginShape starts a new shape with the specified color
func (r *BaseRenderer) BeginShape(color string) {
	r.currentColor = color
	r.currentPath = make([]PathCommand, 0)
}

// EndShape ends the current shape
func (r *BaseRenderer) EndShape() {
	// Default implementation - concrete renderers should override
}

// SetBackground sets the background color and opacity
func (r *BaseRenderer) SetBackground(fillColor string, opacity float64) {
	r.background = fillColor
	r.backgroundOp = opacity
}

// AddPolygon adds a polygon to the renderer using the current fill color
func (r *BaseRenderer) AddPolygon(points []engine.Point) {
	if len(points) == 0 {
		return
	}

	// Move to first point
	r.MoveTo(points[0].X, points[0].Y)

	// Line to subsequent points
	for i := 1; i < len(points); i++ {
		r.LineTo(points[i].X, points[i].Y)
	}

	// Close the path
	r.ClosePath()

	// Fill with current color
	r.Fill(r.currentColor)
}

// AddCircle adds a circle to the renderer using the current fill color
func (r *BaseRenderer) AddCircle(topLeft engine.Point, size float64, invert bool) {
	// Approximate circle using cubic Bézier curves
	// Magic number for circle approximation with Bézier curves
	const kappa = 0.5522847498307936 // 4/3 * (sqrt(2) - 1)

	radius := size / 2
	centerX := topLeft.X + radius
	centerY := topLeft.Y + radius

	cp := kappa * radius // Control point distance

	// Start at rightmost point
	r.MoveTo(centerX+radius, centerY)

	// Four cubic curves to approximate circle
	r.CurveTo(centerX+radius, centerY+cp, centerX+cp, centerY+radius, centerX, centerY+radius)
	r.CurveTo(centerX-cp, centerY+radius, centerX-radius, centerY+cp, centerX-radius, centerY)
	r.CurveTo(centerX-radius, centerY-cp, centerX-cp, centerY-radius, centerX, centerY-radius)
	r.CurveTo(centerX+cp, centerY-radius, centerX+radius, centerY-cp, centerX+radius, centerY)

	r.ClosePath()
	r.Fill(r.currentColor)
}

// AddRectangle adds a rectangle to the renderer
func (r *BaseRenderer) AddRectangle(x, y, width, height float64) {
	points := []engine.Point{
		{X: x, Y: y},
		{X: x + width, Y: y},
		{X: x + width, Y: y + height},
		{X: x, Y: y + height},
	}
	r.AddPolygon(points)
}

// AddTriangle adds a triangle to the renderer
func (r *BaseRenderer) AddTriangle(p1, p2, p3 engine.Point) {
	points := []engine.Point{p1, p2, p3}
	r.AddPolygon(points)
}

// GetSize returns the icon size
func (r *BaseRenderer) GetSize() int {
	return r.iconSize
}

// Clear clears the renderer state
func (r *BaseRenderer) Clear() {
	r.currentPath = make([]PathCommand, 0)
	r.currentColor = ""
	r.background = ""
	r.backgroundOp = 0
}

// GetCurrentPath returns the current path commands
func (r *BaseRenderer) GetCurrentPath() []PathCommand {
	return r.currentPath
}

// GetCurrentColor returns the current drawing color
func (r *BaseRenderer) GetCurrentColor() string {
	return r.currentColor
}

// GetBackground returns the background color and opacity
func (r *BaseRenderer) GetBackground() (string, float64) {
	return r.background, r.backgroundOp
}
