package main

import (
	"bufio"
	"image"
	"image/color"
	"image/draw"
	"math"
	"os"
	"regexp"
	"strconv"
	"strings"
)

// Aperture types
const (
	ApertureCircle  = "C"
	ApertureRect    = "R"
	ApertureObround = "O"
	AperturePolygon = "P"
)

type Aperture struct {
	Type      string
	Modifiers []float64
}

type MacroPrimitive struct {
	Code      int
	Modifiers []string // Store as strings to handle $1, $2, expressions like $1+$1
}

type Macro struct {
	Name       string
	Primitives []MacroPrimitive
}

type GerberState struct {
	Apertures        map[int]Aperture
	Macros           map[string]Macro
	CurrentAperture  int
	X, Y             float64 // Current coordinates in mm
	FormatX, FormatY struct {
		Integer, Decimal int
	}
	Units            string // "MM" or "IN"
	CurrentFootprint string // Stored from %TO.C,Footprint,...*%
}

type GerberCommand struct {
	Type      string // "D01", "D02", "D03", "AD", "FS", etc.
	X, Y      *float64
	I, J      *float64
	D         *int
	Footprint string
}

type GerberFile struct {
	Commands []GerberCommand
	State    GerberState
}

// Footprint represents a component bounding area deduced from Gerber X2 attributes
type Footprint struct {
	Name    string  `json:"name"`
	MinX    float64 `json:"minX"`
	MinY    float64 `json:"minY"`
	MaxX    float64 `json:"maxX"`
	MaxY    float64 `json:"maxY"`
	CenterX float64 `json:"centerX"`
	CenterY float64 `json:"centerY"`
}

func ExtractFootprints(gf *GerberFile) []Footprint {
	fps := make(map[string]*Footprint)

	for _, cmd := range gf.Commands {
		if cmd.Footprint == "" {
			continue
		}
		if cmd.X == nil || cmd.Y == nil {
			continue
		}

		fp, exists := fps[cmd.Footprint]
		if !exists {
			fp = &Footprint{
				Name: cmd.Footprint,
				MinX: *cmd.X,
				MaxX: *cmd.X,
				MinY: *cmd.Y,
				MaxY: *cmd.Y,
			}
			fps[cmd.Footprint] = fp
		} else {
			if *cmd.X < fp.MinX {
				fp.MinX = *cmd.X
			}
			if *cmd.X > fp.MaxX {
				fp.MaxX = *cmd.X
			}
			if *cmd.Y < fp.MinY {
				fp.MinY = *cmd.Y
			}
			if *cmd.Y > fp.MaxY {
				fp.MaxY = *cmd.Y
			}
		}
	}

	var result []Footprint
	for _, fp := range fps {
		fp.CenterX = (fp.MinX + fp.MaxX) / 2.0
		fp.CenterY = (fp.MinY + fp.MaxY) / 2.0
		result = append(result, *fp)
	}
	return result
}

func NewGerberFile() *GerberFile {
	return &GerberFile{
		State: GerberState{
			Apertures: make(map[int]Aperture),
			Macros:    make(map[string]Macro),
			Units:     "MM", // Default, usually set by MO
		},
	}
}

// ParseGerber parses a simple RS-274X file
func ParseGerber(filename string) (*GerberFile, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	gf := NewGerberFile()
	scanner := bufio.NewScanner(file)

	// Regex for coordinates: X123Y456D01
	reCoord := regexp.MustCompile(`([XYDIJ])([\d\.\-]+)`)
	// Regex for Aperture Definition: %ADD10C,0.5*% or %ADD10RoundRect,0.25X-0.75X...*%
	reAD := regexp.MustCompile(`%ADD(\d+)([A-Za-z0-9_]+),?([\d\.\-X]+)?\*%`)
	// Regex for Format Spec: %FSLAX24Y24*%
	reFS := regexp.MustCompile(`%FSLAX(\d)(\d)Y(\d)(\d)\*%`)

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		// Handle Parameters
		if strings.HasPrefix(line, "%") {
			if strings.HasPrefix(line, "%FS") {
				matches := reFS.FindStringSubmatch(line)
				if len(matches) == 5 {
					gf.State.FormatX.Integer, _ = strconv.Atoi(matches[1])
					gf.State.FormatX.Decimal, _ = strconv.Atoi(matches[2])
					gf.State.FormatY.Integer, _ = strconv.Atoi(matches[3])
					gf.State.FormatY.Decimal, _ = strconv.Atoi(matches[4])
				}
			} else if strings.HasPrefix(line, "%AD") {
				matches := reAD.FindStringSubmatch(line)
				if len(matches) >= 3 {
					dCode, _ := strconv.Atoi(matches[1])
					apType := matches[2]
					var mods []float64
					if len(matches) > 3 && matches[3] != "" {
						parts := strings.Split(matches[3], "X")
						for _, p := range parts {
							val, _ := strconv.ParseFloat(p, 64)
							mods = append(mods, val)
						}
					}
					gf.State.Apertures[dCode] = Aperture{Type: apType, Modifiers: mods}
				}
			} else if strings.HasPrefix(line, "%AM") {
				// Parse Macro
				name := strings.TrimPrefix(line, "%AM")
				name = strings.TrimSuffix(name, "*")

				var primitives []MacroPrimitive

				for scanner.Scan() {
					mLine := strings.TrimSpace(scanner.Text())
					// Skip comment lines (start with "0 ")
					if strings.HasPrefix(mLine, "0 ") {
						continue
					}
					// Skip empty lines
					if mLine == "" {
						continue
					}

					// Check if this line ends the macro definition
					// Standard allows ending with *% at end of last primitive OR a separate line with %
					trimmedLine := strings.TrimSpace(mLine)
					if trimmedLine == "%" {
						break
					}

					endsWithPercent := strings.HasSuffix(mLine, "*%")

					// Remove trailing *% or just *
					mLine = strings.TrimSuffix(mLine, "*%")
					mLine = strings.TrimSuffix(mLine, "*")

					// Parse primitive
					parts := strings.Split(mLine, ",")
					if len(parts) > 0 && parts[0] != "" {
						code, err := strconv.Atoi(parts[0])
						if err == nil && code > 0 {
							// Store modifiers as strings to handle $1, $2, expressions
							var mods []string
							for _, p := range parts[1:] {
								mods = append(mods, strings.TrimSpace(p))
							}
							primitives = append(primitives, MacroPrimitive{Code: code, Modifiers: mods})
						}
					}

					// If line ended with *%, macro definition is complete
					if endsWithPercent {
						break
					}
				}
				gf.State.Macros[name] = Macro{Name: name, Primitives: primitives}
			} else if strings.HasPrefix(line, "%MO") {
				if strings.Contains(line, "IN") {
					gf.State.Units = "IN"
				} else {
					gf.State.Units = "MM"
				}
			} else if strings.HasPrefix(line, "%TO") {
				parts := strings.Split(line, ",")
				if len(parts) >= 2 && strings.HasPrefix(parts[0], "%TO.C") {
					refDes := strings.TrimSuffix(parts[1], "*%")
					if refDes != "" {
						gf.State.CurrentFootprint = refDes
					}
				}
			} else if strings.HasPrefix(line, "%TD") {
				if strings.Contains(line, "%TD*%") || strings.Contains(line, "%TD,C*%") || strings.Contains(line, "%TD,Footprint*%") {
					gf.State.CurrentFootprint = ""
				}
			}
			continue
		}

		// Handle Standard Commands
		// Split by *
		parts := strings.Split(line, "*")
		for _, part := range parts {
			part = strings.TrimSpace(part)
			if part == "" {
				continue
			}

			// Check for G-codes
			if strings.HasPrefix(part, "G") {
				if part == "G01" {
					// Linear interpolation (default)
					gf.Commands = append(gf.Commands, GerberCommand{Type: "G01"})
				} else if part == "G02" {
					// Clockwise circular interpolation
					gf.Commands = append(gf.Commands, GerberCommand{Type: "G02"})
				} else if part == "G03" {
					// Counter-clockwise circular interpolation
					gf.Commands = append(gf.Commands, GerberCommand{Type: "G03"})
				} else if part == "G36" {
					// Region fill start
					gf.Commands = append(gf.Commands, GerberCommand{Type: "G36"})
				} else if part == "G37" {
					// Region fill end
					gf.Commands = append(gf.Commands, GerberCommand{Type: "G37"})
				}
				continue
			}

			// Handle Aperture Selection (e.g., D10*)
			if strings.HasPrefix(part, "D") && len(part) >= 2 {
				// Likely D10, D11 etc.
				dCode, err := strconv.Atoi(part[1:])
				if err == nil && dCode >= 10 {
					gf.Commands = append(gf.Commands, GerberCommand{Type: "APERTURE", D: &dCode})
					continue
				}
			}

			// Handle Coordinates and Draw/Flash commands
			// X...Y...D01*
			matches := reCoord.FindAllStringSubmatch(part, -1)
			if len(matches) > 0 {
				cmd := GerberCommand{Type: "MOVE", Footprint: gf.State.CurrentFootprint}
				for _, m := range matches {
					valStr := m[2]

					switch m[1] {
					case "X":
						v := gf.parseCoordinate(valStr, gf.State.FormatX)
						cmd.X = &v
					case "Y":
						v := gf.parseCoordinate(valStr, gf.State.FormatY)
						cmd.Y = &v
					case "I":
						v := gf.parseCoordinate(valStr, gf.State.FormatX)
						cmd.I = &v
					case "J":
						v := gf.parseCoordinate(valStr, gf.State.FormatY)
						cmd.J = &v
					case "D":
						val, _ := strconv.ParseFloat(valStr, 64)
						d := int(val)
						cmd.D = &d
						if d == 1 {
							cmd.Type = "DRAW"
						} else if d == 2 {
							cmd.Type = "MOVE"
						} else if d == 3 {
							cmd.Type = "FLASH"
						}
					}
				}
				gf.Commands = append(gf.Commands, cmd)
			}
		}
	}

	return gf, nil
}

func (gf *GerberFile) parseCoordinate(valStr string, fmtSpec struct{ Integer, Decimal int }) float64 {
	if strings.Contains(valStr, ".") {
		val, _ := strconv.ParseFloat(valStr, 64)
		return val
	}
	val, _ := strconv.ParseFloat(valStr, 64)
	divisor := math.Pow(10, float64(fmtSpec.Decimal))
	return val / divisor
}

// evaluateMacroExpression evaluates a macro expression like "$1", "$1+$1", "0.5", etc.
// with variable substitution from aperture modifiers
func evaluateMacroExpression(expr string, params []float64) float64 {
	expr = strings.TrimSpace(expr)

	// Handle simple addition (e.g., "$1+$1")
	if strings.Contains(expr, "+") {
		parts := strings.Split(expr, "+")
		result := 0.0
		for _, part := range parts {
			result += evaluateMacroExpression(strings.TrimSpace(part), params)
		}
		return result
	}

	// Handle simple subtraction (e.g., "$1-$2")
	if strings.Contains(expr, "-") && !strings.HasPrefix(expr, "-") {
		parts := strings.Split(expr, "-")
		if len(parts) == 2 {
			left := evaluateMacroExpression(strings.TrimSpace(parts[0]), params)
			right := evaluateMacroExpression(strings.TrimSpace(parts[1]), params)
			return left - right
		}
	}

	// Handle variable substitution (e.g., "$1", "$2")
	if strings.HasPrefix(expr, "$") {
		varNum, err := strconv.Atoi(expr[1:])
		if err == nil && varNum > 0 && varNum <= len(params) {
			return params[varNum-1]
		}
		return 0.0
	}

	// Handle literal numbers
	val, _ := strconv.ParseFloat(expr, 64)
	return val
}

// rotatePoint rotates a point (x, y) around the origin by angleDegrees
func rotatePoint(x, y, angleDegrees float64) (float64, float64) {
	if angleDegrees == 0 {
		return x, y
	}
	angleRad := angleDegrees * math.Pi / 180.0
	cosA := math.Cos(angleRad)
	sinA := math.Sin(angleRad)
	return x*cosA - y*sinA, x*sinA + y*cosA
}

type Bounds struct {
	MinX, MinY, MaxX, MaxY float64
}

func (gf *GerberFile) CalculateBounds() Bounds {
	minX, minY := 1e9, 1e9
	maxX, maxY := -1e9, -1e9

	updateBounds := func(x, y float64) {
		if x < minX {
			minX = x
		}
		if y < minY {
			minY = y
		}
		if x > maxX {
			maxX = x
		}
		if y > maxY {
			maxY = y
		}
	}

	curX, curY := 0.0, 0.0
	for _, cmd := range gf.Commands {
		prevX, prevY := curX, curY
		if cmd.X != nil {
			curX = *cmd.X
		}
		if cmd.Y != nil {
			curY = *cmd.Y
		}

		if cmd.Type == "FLASH" {
			updateBounds(curX, curY)
		} else if cmd.Type == "DRAW" {
			updateBounds(prevX, prevY)
			updateBounds(curX, curY)
		}
	}

	if minX == 1e9 {
		// No drawing commands found, default to 0,0
		minX, minY = 0, 0
		maxX, maxY = 10, 10 // Arbitrary small size
	}

	// Add some padding
	padding := 2.0 // mm
	minX -= padding
	minY -= padding
	maxX += padding
	maxY += padding

	return Bounds{MinX: minX, MinY: minY, MaxX: maxX, MaxY: maxY}
}

// Render generates an image from the parsed Gerber commands
func (gf *GerberFile) Render(dpi float64, bounds *Bounds) image.Image {
	var b Bounds
	if bounds != nil {
		b = *bounds
	} else {
		b = gf.CalculateBounds()
	}

	widthMM := b.MaxX - b.MinX
	heightMM := b.MaxY - b.MinY

	var scale float64
	if gf.State.Units == "IN" {
		scale = dpi
	} else {
		scale = dpi / 25.4
	}

	imgWidth := int(widthMM * scale)
	imgHeight := int(heightMM * scale)

	img := image.NewRGBA(image.Rect(0, 0, imgWidth, imgHeight))

	// Fill black (stencil material)
	draw.Draw(img, img.Bounds(), &image.Uniform{color.Black}, image.Point{}, draw.Src)

	// White for holes
	white := &image.Uniform{color.White}

	// Helper to convert mm to pixels
	toPix := func(x, y float64) (int, int) {
		px := int((x - b.MinX) * scale)
		py := int((heightMM - (y - b.MinY)) * scale) // Flip Y for image coords
		return px, py
	}

	curX, curY := 0.0, 0.0
	curDCode := 0
	interpolationMode := "G01" // Default linear
	inRegion := false
	var regionVertices [][2]int

	for _, cmd := range gf.Commands {
		if cmd.Type == "APERTURE" {
			curDCode = *cmd.D
			continue
		}
		if cmd.Type == "G01" || cmd.Type == "G02" || cmd.Type == "G03" {
			interpolationMode = cmd.Type
			continue
		}
		if cmd.Type == "G36" {
			inRegion = true
			regionVertices = nil
			continue
		}
		if cmd.Type == "G37" {
			// End region: fill the collected polygon
			if len(regionVertices) >= 3 {
				drawFilledPolygon(img, regionVertices)
			}
			inRegion = false
			regionVertices = nil
			continue
		}

		prevX, prevY := curX, curY
		if cmd.X != nil {
			curX = *cmd.X
		}
		if cmd.Y != nil {
			curY = *cmd.Y
		}

		// In region mode, collect contour vertices instead of drawing
		if inRegion {
			if cmd.Type == "MOVE" {
				// D02 in region: start a new contour
				px, py := toPix(curX, curY)
				regionVertices = append(regionVertices, [2]int{px, py})
			} else if cmd.Type == "DRAW" {
				if interpolationMode == "G01" {
					// Linear segment: add endpoint
					px, py := toPix(curX, curY)
					regionVertices = append(regionVertices, [2]int{px, py})
				} else {
					// Arc segment: sample points along the arc
					iVal := 0.0
					jVal := 0.0
					if cmd.I != nil {
						iVal = *cmd.I
					}
					if cmd.J != nil {
						jVal = *cmd.J
					}
					centerX := prevX + iVal
					centerY := prevY + jVal
					radius := math.Sqrt(iVal*iVal + jVal*jVal)
					startAngle := math.Atan2(prevY-centerY, prevX-centerX)
					endAngle := math.Atan2(curY-centerY, curX-centerX)
					if interpolationMode == "G03" {
						if endAngle <= startAngle {
							endAngle += 2 * math.Pi
						}
					} else {
						if startAngle <= endAngle {
							startAngle += 2 * math.Pi
						}
					}
					arcLen := math.Abs(endAngle-startAngle) * radius
					steps := int(arcLen * scale * 2)
					if steps < 10 {
						steps = 10
					}
					for s := 1; s <= steps; s++ {
						t := float64(s) / float64(steps)
						angle := startAngle + t*(endAngle-startAngle)
						ax := centerX + radius*math.Cos(angle)
						ay := centerY + radius*math.Sin(angle)
						px, py := toPix(ax, ay)
						regionVertices = append(regionVertices, [2]int{px, py})
					}
				}
			}
			continue
		}

		if cmd.Type == "FLASH" {
			// Draw Aperture at curX, curY
			ap, ok := gf.State.Apertures[curDCode]
			if ok {
				cx, cy := toPix(curX, curY)
				gf.drawAperture(img, cx, cy, ap, scale, white)
			}
		} else if cmd.Type == "DRAW" {
			ap, ok := gf.State.Apertures[curDCode]
			if ok {
				if interpolationMode == "G01" {
					// Linear
					x1, y1 := toPix(prevX, prevY)
					x2, y2 := toPix(curX, curY)
					gf.drawLine(img, x1, y1, x2, y2, ap, scale, white)
				} else {
					// Circular Interpolation (G02/G03)
					iVal := 0.0
					jVal := 0.0
					if cmd.I != nil {
						iVal = *cmd.I
					}
					if cmd.J != nil {
						jVal = *cmd.J
					}

					centerX := prevX + iVal
					centerY := prevY + jVal

					radius := math.Sqrt(iVal*iVal + jVal*jVal)
					startAngle := math.Atan2(prevY-centerY, prevX-centerX)
					endAngle := math.Atan2(curY-centerY, curX-centerX)

					// Adjust angles for G02 (CW) vs G03 (CCW)
					if interpolationMode == "G03" { // CCW
						if endAngle <= startAngle {
							endAngle += 2 * math.Pi
						}
					} else { // G02 CW
						if startAngle <= endAngle {
							startAngle += 2 * math.Pi
						}
					}

					// Arc length approximation
					arcLen := math.Abs(endAngle-startAngle) * radius
					steps := int(arcLen * scale * 2) // 2x pixel density for smoothness
					if steps < 10 {
						steps = 10
					}

					for s := 0; s <= steps; s++ {
						t := float64(s) / float64(steps)
						angle := startAngle + t*(endAngle-startAngle)
						px := centerX + radius*math.Cos(angle)
						py := centerY + radius*math.Sin(angle)

						ix, iy := toPix(px, py)
						gf.drawAperture(img, ix, iy, ap, scale, white)
					}
				}
			}
		}
	}

	return img
}

func (gf *GerberFile) drawAperture(img *image.RGBA, x, y int, ap Aperture, scale float64, c image.Image) {
	switch ap.Type {
	case ApertureCircle: // C
		// Modifiers[0] is diameter
		if len(ap.Modifiers) > 0 {
			radius := int((ap.Modifiers[0] * scale) / 2)
			drawCircle(img, x, y, radius)
		}
		return
	case ApertureRect: // R
		// Modifiers[0] is width, [1] is height
		if len(ap.Modifiers) >= 2 {
			w := int(ap.Modifiers[0] * scale)
			h := int(ap.Modifiers[1] * scale)
			r := image.Rect(x-w/2, y-h/2, x+w/2, y+h/2)
			draw.Draw(img, r, c, image.Point{}, draw.Src)
		}
		return
	case ApertureObround: // O
		// Modifiers[0] is width, [1] is height
		if len(ap.Modifiers) >= 2 {
			w := int(ap.Modifiers[0] * scale)
			h := int(ap.Modifiers[1] * scale)
			// Draw Obround
			// If w > h: Horizontal Pill. Central Rect is (w-h) x h. Two circles of dia h.
			// If h > w: Vertical Pill. Central Rect is w x (h-w). Two circles of dia w.
			// If w == h: Circle dia w.

			drawObround := func(target *image.RGBA, x, y, w, h int, color image.Image) {
				if w == h {
					radius := w / 2
					drawCircle(target, x, y, radius)
					return
				}

				if w > h {
					// Horizontal
					rectW := w - h
					if rectW < 0 {
						rectW = 0
					} // Should be impossible if w > h

					// Center Rect
					r := image.Rect(x-rectW/2, y-h/2, x+rectW/2, y+h/2)
					draw.Draw(target, r, color, image.Point{}, draw.Src)

					// Left Circle
					drawCircle(target, x-rectW/2, y, h/2)
					// Right Circle
					drawCircle(target, x+rectW/2, y, h/2)
				} else {
					// Vertical
					rectH := h - w
					if rectH < 0 {
						rectH = 0
					}

					// Center Rect
					r := image.Rect(x-w/2, y-rectH/2, x+w/2, y+rectH/2)
					draw.Draw(target, r, color, image.Point{}, draw.Src)

					// Top Circle (Y decreases upwards in image coords usually, but here we treat y as center)
					// Note: In our coordinate system, y is center.
					drawCircle(target, x, y-rectH/2, w/2)
					// Bottom Circle
					drawCircle(target, x, y+rectH/2, w/2)
				}
			}
			drawObround(img, x, y, w, h, c)
		}
		return
	case AperturePolygon: // P
		// Modifiers: [0] diameter, [1] vertices, [2] rotation (optional)
		if len(ap.Modifiers) >= 2 {
			diameter := ap.Modifiers[0]
			numVertices := int(ap.Modifiers[1])
			rotation := 0.0
			if len(ap.Modifiers) >= 3 {
				rotation = ap.Modifiers[2]
			}

			if numVertices >= 3 {
				radius := (diameter * scale) / 2
				vertices := make([][2]int, numVertices)
				for i := 0; i < numVertices; i++ {
					angleDeg := rotation + float64(i)*360.0/float64(numVertices)
					angleRad := angleDeg * math.Pi / 180.0
					px := int(radius * math.Cos(angleRad))
					py := int(radius * math.Sin(angleRad))
					vertices[i] = [2]int{x + px, y - py}
				}
				drawFilledPolygon(img, vertices)
			}
		}
		return
	}

	// Check for Macros
	if macro, ok := gf.State.Macros[ap.Type]; ok {
		for _, prim := range macro.Primitives {
			switch prim.Code {
			case 1: // Circle
				// Mods: Exposure, Diameter, CenterX, CenterY
				if len(prim.Modifiers) >= 4 {
					exposure := evaluateMacroExpression(prim.Modifiers[0], ap.Modifiers)
					if exposure == 0 {
						break
					}
					dia := evaluateMacroExpression(prim.Modifiers[1], ap.Modifiers)
					cx := evaluateMacroExpression(prim.Modifiers[2], ap.Modifiers)
					cy := evaluateMacroExpression(prim.Modifiers[3], ap.Modifiers)

					px := int(cx * scale)
					py := int(cy * scale)

					radius := int((dia * scale) / 2)
					drawCircle(img, x+px, y-py, radius)
				}
			case 4: // Outline (Polygon)
				// Mods: Exposure, NumVertices, X1, Y1, ..., Xn, Yn, Rotation
				if len(prim.Modifiers) >= 3 {
					exposure := evaluateMacroExpression(prim.Modifiers[0], ap.Modifiers)
					if exposure == 0 {
						// Skip if exposure is off
						break
					}
					numVertices := int(evaluateMacroExpression(prim.Modifiers[1], ap.Modifiers))
					// Need at least numVertices * 2 coordinates + rotation
					if len(prim.Modifiers) >= 2+numVertices*2+1 {
						rotation := evaluateMacroExpression(prim.Modifiers[2+numVertices*2], ap.Modifiers)

						// Extract vertices
						vertices := make([][2]int, numVertices)
						for i := 0; i < numVertices; i++ {
							vx := evaluateMacroExpression(prim.Modifiers[2+i*2], ap.Modifiers)
							vy := evaluateMacroExpression(prim.Modifiers[2+i*2+1], ap.Modifiers)

							// Apply rotation
							vx, vy = rotatePoint(vx, vy, rotation)

							px := int(vx * scale)
							py := int(vy * scale)

							vertices[i] = [2]int{x + px, y - py}
						}

						// Draw filled polygon using scanline algorithm
						drawFilledPolygon(img, vertices)
					}
				}
			case 5: // Regular Polygon
				// Mods: Exposure, NumVertices, CenterX, CenterY, Diameter, Rotation
				if len(prim.Modifiers) >= 6 {
					exposure := evaluateMacroExpression(prim.Modifiers[0], ap.Modifiers)
					if exposure == 0 {
						break
					}
					numVertices := int(evaluateMacroExpression(prim.Modifiers[1], ap.Modifiers))
					if numVertices >= 3 {
						cx := evaluateMacroExpression(prim.Modifiers[2], ap.Modifiers)
						cy := evaluateMacroExpression(prim.Modifiers[3], ap.Modifiers)
						diameter := evaluateMacroExpression(prim.Modifiers[4], ap.Modifiers)
						rotation := evaluateMacroExpression(prim.Modifiers[5], ap.Modifiers)

						radius := (diameter * scale) / 2
						pxCenter := int(cx * scale)
						pyCenter := int(cy * scale)

						vertices := make([][2]int, numVertices)
						for i := 0; i < numVertices; i++ {
							angleDeg := rotation + float64(i)*360.0/float64(numVertices)
							angleRad := angleDeg * math.Pi / 180.0
							px := int(radius * math.Cos(angleRad))
							py := int(radius * math.Sin(angleRad))
							vertices[i] = [2]int{x + pxCenter + px, y - pyCenter - py}
						}
						drawFilledPolygon(img, vertices)
					}
				}
			case 20: // Vector Line
				// Mods: Exposure, Width, StartX, StartY, EndX, EndY, Rotation
				if len(prim.Modifiers) >= 7 {
					exposure := evaluateMacroExpression(prim.Modifiers[0], ap.Modifiers)
					if exposure == 0 {
						// Skip if exposure is off
						break
					}
					width := evaluateMacroExpression(prim.Modifiers[1], ap.Modifiers)
					startX := evaluateMacroExpression(prim.Modifiers[2], ap.Modifiers)
					startY := evaluateMacroExpression(prim.Modifiers[3], ap.Modifiers)
					endX := evaluateMacroExpression(prim.Modifiers[4], ap.Modifiers)
					endY := evaluateMacroExpression(prim.Modifiers[5], ap.Modifiers)
					rotation := evaluateMacroExpression(prim.Modifiers[6], ap.Modifiers)

					// Apply rotation to start and end points
					startX, startY = rotatePoint(startX, startY, rotation)
					endX, endY = rotatePoint(endX, endY, rotation)

					// Calculate the rectangle representing the line
					// The line goes from (startX, startY) to (endX, endY) with width
					dx := endX - startX
					dy := endY - startY
					length := math.Sqrt(dx*dx + dy*dy)

					if length > 0 {
						// Perpendicular vector for width
						perpX := -dy / length * width / 2
						perpY := dx / length * width / 2

						// Four corners of the rectangle
						vertices := [][2]int{
							{x + int((startX-perpX)*scale), y - int((startY-perpY)*scale)},
							{x + int((startX+perpX)*scale), y - int((startY+perpY)*scale)},
							{x + int((endX+perpX)*scale), y - int((endY+perpY)*scale)},
							{x + int((endX-perpX)*scale), y - int((endY-perpY)*scale)},
						}

						drawFilledPolygon(img, vertices)
					}
				}
			case 21: // Center Line (Rect)
				// Mods: Exposure, Width, Height, CenterX, CenterY, Rotation
				if len(prim.Modifiers) >= 6 {
					exposure := evaluateMacroExpression(prim.Modifiers[0], ap.Modifiers)
					if exposure == 0 {
						break
					}
					width := evaluateMacroExpression(prim.Modifiers[1], ap.Modifiers)
					height := evaluateMacroExpression(prim.Modifiers[2], ap.Modifiers)
					cx := evaluateMacroExpression(prim.Modifiers[3], ap.Modifiers)
					cy := evaluateMacroExpression(prim.Modifiers[4], ap.Modifiers)
					rot := evaluateMacroExpression(prim.Modifiers[5], ap.Modifiers)

					// Calculate the four corners of the rectangle (centered at origin)
					halfW := width / 2
					halfH := height / 2

					// Four corners before rotation
					corners := [][2]float64{
						{-halfW, -halfH},
						{halfW, -halfH},
						{halfW, halfH},
						{-halfW, halfH},
					}

					// Apply rotation and translation
					vertices := make([][2]int, 4)
					for i, corner := range corners {
						// Rotate around origin
						rx, ry := rotatePoint(corner[0], corner[1], rot)
						// Translate to center position
						rx += cx
						ry += cy
						// Convert to pixels
						px := int(rx * scale)
						py := int(ry * scale)
						vertices[i] = [2]int{x + px, y - py}
					}

					// Draw as polygon
					drawFilledPolygon(img, vertices)
				}
			}
		}
	}
}

func drawCircle(img *image.RGBA, x0, y0, r int) {
	// Simple Bresenham or scanline
	for y := -r; y <= r; y++ {
		for x := -r; x <= r; x++ {
			if x*x+y*y <= r*r {
				img.Set(x0+x, y0+y, color.White)
			}
		}
	}
}

func drawFilledPolygon(img *image.RGBA, vertices [][2]int) {
	if len(vertices) < 3 {
		return
	}

	// Find bounding box
	minY, maxY := vertices[0][1], vertices[0][1]
	for _, v := range vertices {
		if v[1] < minY {
			minY = v[1]
		}
		if v[1] > maxY {
			maxY = v[1]
		}
	}

	// Scanline fill algorithm
	for y := minY; y <= maxY; y++ {
		// Find intersections with polygon edges
		var intersections []int

		for i := 0; i < len(vertices); i++ {
			j := (i + 1) % len(vertices)
			y1, y2 := vertices[i][1], vertices[j][1]
			x1, x2 := vertices[i][0], vertices[j][0]

			// Check if scanline intersects this edge
			if (y1 <= y && y < y2) || (y2 <= y && y < y1) {
				// Calculate x intersection
				x := x1 + (y-y1)*(x2-x1)/(y2-y1)
				intersections = append(intersections, x)
			}
		}

		// Sort intersections
		for i := 0; i < len(intersections)-1; i++ {
			for j := i + 1; j < len(intersections); j++ {
				if intersections[i] > intersections[j] {
					intersections[i], intersections[j] = intersections[j], intersections[i]
				}
			}
		}

		// Fill between pairs of intersections
		for i := 0; i < len(intersections)-1; i += 2 {
			x1 := intersections[i]
			x2 := intersections[i+1]
			for x := x1; x <= x2; x++ {
				if x >= 0 && x < img.Bounds().Max.X && y >= 0 && y < img.Bounds().Max.Y {
					img.Set(x, y, color.White)
				}
			}
		}
	}
}

func (gf *GerberFile) drawLine(img *image.RGBA, x1, y1, x2, y2 int, ap Aperture, scale float64, c image.Image) {
	// Bresenham's line algorithm, but we need to stroke it with the aperture.
	// For simplicity, if aperture is Circle, we draw a circle at each step (inefficient but works).
	// If aperture is Rect, we draw rect at each step.

	// Optimized: Just draw a thick line if it's a circle aperture

	dx := float64(x2 - x1)
	dy := float64(y2 - y1)
	dist := math.Sqrt(dx*dx + dy*dy)
	steps := int(dist) // 1 pixel steps

	if steps == 0 {
		gf.drawAperture(img, x1, y1, ap, scale, c)
		return
	}

	for i := 0; i <= steps; i++ {
		t := float64(i) / float64(steps)
		x := int(float64(x1) + t*dx)
		y := int(float64(y1) + t*dy)
		gf.drawAperture(img, x, y, ap, scale, c)
	}
}
