package renderer

import (
	"math"
	"strconv"
	"strings"

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

// SVG rendering constants
const (
	// SVG generation size estimation constants
	svgBaseOverheadBytes   = 150 // Base SVG document overhead
	svgBackgroundRectBytes = 100 // Background rectangle overhead
	svgPathOverheadBytes   = 50  // Per-path element overhead

	// Precision constants
	svgCoordinatePrecision = 10  // Precision factor for SVG coordinates (0.1 precision)
	svgRoundingOffset      = 0.5 // Rounding offset for "round half up" behavior
)

// Note: Previously used polygonBufferPool for intermediate buffering, but eliminated
// to write directly to main builder and avoid unnecessary allocations

// SVGPath represents an SVG path element
type SVGPath struct {
	data strings.Builder
}

// AddPolygon adds a polygon to the SVG path
func (p *SVGPath) AddPolygon(points []engine.Point) {
	if len(points) == 0 {
		return
	}

	// Write directly to main data builder to avoid intermediate allocations
	// Move to first point
	p.data.WriteString("M")
	svgAppendValue(&p.data, points[0].X)
	p.data.WriteString(" ")
	svgAppendValue(&p.data, points[0].Y)

	// Line to subsequent points
	for i := 1; i < len(points); i++ {
		p.data.WriteString("L")
		svgAppendValue(&p.data, points[i].X)
		p.data.WriteString(" ")
		svgAppendValue(&p.data, points[i].Y)
	}

	// Close path
	p.data.WriteString("Z")
}

// AddCircle adds a circle to the SVG path
func (p *SVGPath) AddCircle(topLeft engine.Point, size float64, counterClockwise bool) {
	sweepFlag := "1"
	if counterClockwise {
		sweepFlag = "0"
	}

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

	// Move to start point (left side of circle)
	startX := centerX - radius
	startY := centerY

	// Build circle path directly in main data builder
	p.data.WriteString("M")
	svgAppendValue(&p.data, startX)
	p.data.WriteString(" ")
	svgAppendValue(&p.data, startY)

	// Draw first arc
	p.data.WriteString("a")
	svgAppendValue(&p.data, radius)
	p.data.WriteString(",")
	svgAppendValue(&p.data, radius)
	p.data.WriteString(" 0 1,")
	p.data.WriteString(sweepFlag)
	p.data.WriteString(" ")
	svgAppendValue(&p.data, size)
	p.data.WriteString(",0")

	// Draw second arc
	p.data.WriteString("a")
	svgAppendValue(&p.data, radius)
	p.data.WriteString(",")
	svgAppendValue(&p.data, radius)
	p.data.WriteString(" 0 1,")
	p.data.WriteString(sweepFlag)
	p.data.WriteString(" -")
	svgAppendValue(&p.data, size)
	p.data.WriteString(",0")
}

// DataString returns the SVG path data string
func (p *SVGPath) DataString() string {
	return p.data.String()
}

// SVGRenderer implements the Renderer interface for SVG output
type SVGRenderer struct {
	*BaseRenderer
	pathsByColor map[string]*SVGPath
	colorOrder   []string
}

// NewSVGRenderer creates a new SVG renderer
func NewSVGRenderer(iconSize int) *SVGRenderer {
	return &SVGRenderer{
		BaseRenderer: NewBaseRenderer(iconSize),
		pathsByColor: make(map[string]*SVGPath),
		colorOrder:   make([]string, 0),
	}
}

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

// BeginShape marks the beginning of a new shape with the specified color
func (r *SVGRenderer) BeginShape(color string) {
	// Defense-in-depth validation: ensure color is safe for SVG output
	// Invalid colors are silently ignored to maintain interface compatibility
	if err := engine.ValidateHexColor(color); err != nil {
		// Log validation failure but continue - the shape will not be rendered
		// This prevents breaking the interface while maintaining security
		return
	}

	r.BaseRenderer.BeginShape(color)
	if _, exists := r.pathsByColor[color]; !exists {
		r.pathsByColor[color] = &SVGPath{}
		r.colorOrder = append(r.colorOrder, color)
	}
}

// EndShape marks the end of the currently drawn shape
func (r *SVGRenderer) EndShape() {
	// No action needed for SVG
}

// getCurrentPath returns the current path for the active color
func (r *SVGRenderer) getCurrentPath() *SVGPath {
	currentColor := r.GetCurrentColor()
	if currentColor == "" {
		return nil
	}
	return r.pathsByColor[currentColor]
}

// AddPolygon adds a polygon with the current fill color to the SVG
func (r *SVGRenderer) AddPolygon(points []engine.Point) {
	if path := r.getCurrentPath(); path != nil {
		path.AddPolygon(points)
	}
}

// AddCircle adds a circle with the current fill color to the SVG
func (r *SVGRenderer) AddCircle(topLeft engine.Point, size float64, invert bool) {
	if path := r.getCurrentPath(); path != nil {
		path.AddCircle(topLeft, size, invert)
	}
}

// ToSVG generates the final SVG XML string
func (r *SVGRenderer) ToSVG() string {
	iconSize := r.GetSize()
	background, backgroundOp := r.GetBackground()

	// Estimate capacity to reduce allocations
	capacity := svgBaseOverheadBytes
	if background != "" && backgroundOp > 0 {
		capacity += svgBackgroundRectBytes
	}

	// Estimate path data size
	for _, color := range r.colorOrder {
		path := r.pathsByColor[color]
		if path != nil {
			capacity += svgPathOverheadBytes + path.data.Len()
		}
	}

	var svg strings.Builder
	svg.Grow(capacity)

	// SVG opening tag with namespace and dimensions
	iconSizeStr := strconv.Itoa(iconSize)
	svg.WriteString(`<svg xmlns="http://www.w3.org/2000/svg" width="`)
	svg.WriteString(iconSizeStr)
	svg.WriteString(`" height="`)
	svg.WriteString(iconSizeStr)
	svg.WriteString(`" viewBox="0 0 `)
	svg.WriteString(iconSizeStr)
	svg.WriteString(` `)
	svg.WriteString(iconSizeStr)
	svg.WriteString(`">`)

	// Add background rectangle if specified
	if background != "" && backgroundOp > 0 {
		// Validate background color for safe SVG output
		if err := engine.ValidateHexColor(background); err != nil {
			// Skip invalid background colors to prevent injection
		} else {
			svg.WriteString(`<rect width="100%" height="100%" fill="`)
			svg.WriteString(background) // Now validated
			svg.WriteString(`" opacity="`)
			svg.WriteString(strconv.FormatFloat(backgroundOp, 'f', 2, 64))
			svg.WriteString(`"/>`)
		}
	}

	// Add paths for each color (in insertion order to preserve z-order)
	for _, color := range r.colorOrder {
		path := r.pathsByColor[color]
		dataString := path.DataString()
		if dataString != "" {
			// Final defense-in-depth validation before writing to SVG
			if err := engine.ValidateHexColor(color); err != nil {
				// Skip invalid colors to prevent injection attacks
				continue
			}
			svg.WriteString(`<path fill="`)
			svg.WriteString(color) // Now validated - safe injection point
			svg.WriteString(`" d="`)
			svg.WriteString(dataString)
			svg.WriteString(`"/>`)
		}
	}

	// SVG closing tag
	svg.WriteString("</svg>")

	return svg.String()
}

// svgValue rounds a float64 to one decimal place, mimicking the Jdenticon JS implementation's
// "round half up" behavior. It also formats the number to a minimal string representation.
func svgValue(value float64) string {
	// Use math.Floor to replicate the "round half up" logic from the JS implementation.
	// JavaScript: ((value * 10 + 0.5) | 0) / 10
	rounded := math.Floor(value*svgCoordinatePrecision+svgRoundingOffset) / svgCoordinatePrecision

	// Format to an integer string if there's no fractional part.
	if rounded == math.Trunc(rounded) {
		return strconv.Itoa(int(rounded))
	}

	// Otherwise, format to one decimal place.
	return strconv.FormatFloat(rounded, 'f', 1, 64)
}

// svgAppendValue appends a formatted float64 directly to a strings.Builder to avoid string allocations
func svgAppendValue(buf *strings.Builder, value float64) {
	// Use math.Floor to replicate the "round half up" logic from the JS implementation.
	// JavaScript: ((value * 10 + 0.5) | 0) / 10
	rounded := math.Floor(value*svgCoordinatePrecision+svgRoundingOffset) / svgCoordinatePrecision

	// Use stack-allocated buffer for AppendFloat to avoid heap allocations
	var tempBuf [32]byte

	// Format to an integer string if there's no fractional part.
	if rounded == math.Trunc(rounded) {
		result := strconv.AppendInt(tempBuf[:0], int64(rounded), 10)
		buf.Write(result)
	} else {
		// Otherwise, format to one decimal place using AppendFloat
		result := strconv.AppendFloat(tempBuf[:0], rounded, 'f', 1, 64)
		buf.Write(result)
	}
}
