package main

import (
	"bufio"
	"math"
	"os"
	"regexp"
	"strconv"
	"strings"
)

// DrillHoleType classifies a drill hole by function
type DrillHoleType int

const (
	DrillTypeUnknown   DrillHoleType = iota
	DrillTypeVia                     // ViaDrill — ignore for enclosure
	DrillTypeComponent               // ComponentDrill — component leads
	DrillTypeMounting                // Mounting holes (from NPTH)
)

// DrillHole represents a single drill hole with position, diameter, and type
type DrillHole struct {
	X, Y     float64       // Position in mm
	Diameter float64       // Diameter in mm
	Type     DrillHoleType // Classified by TA.AperFunction
	ToolNum  int           // Tool number (T1, T2, etc.)
}

// ParseDrill parses an Excellon drill file and returns hole positions
func ParseDrill(filename string) ([]DrillHole, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var holes []DrillHole
	type toolInfo struct {
		diameter float64
		holeType DrillHoleType
	}
	tools := make(map[int]toolInfo)
	currentTool := 0
	inHeader := true
	units := "MM"
	isNPTH := false

	// Format spec
	formatDec := 0

	// Pending aperture function for the next tool definition
	pendingType := DrillTypeUnknown

	scanner := bufio.NewScanner(file)

	reToolDef := regexp.MustCompile(`^T(\d+)C([\d.]+)`)
	reToolSelect := regexp.MustCompile(`^T(\d+)$`)
	reCoord := regexp.MustCompile(`^X([+-]?[\d.]+)Y([+-]?[\d.]+)`)
	reAperFunc := regexp.MustCompile(`TA\.AperFunction,(\w+),(\w+),(\w+)`)

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

		// Check file function for NPTH
		if strings.Contains(line, "TF.FileFunction") {
			if strings.Contains(line, "NonPlated") || strings.Contains(line, "NPTH") {
				isNPTH = true
			}
		}

		// Parse TA.AperFunction comments (appears before tool definition)
		if strings.HasPrefix(line, ";") || strings.HasPrefix(line, "G04") {
			m := reAperFunc.FindStringSubmatch(line)
			if len(m) >= 4 {
				funcType := m[3]
				switch funcType {
				case "ViaDrill":
					pendingType = DrillTypeVia
				case "ComponentDrill":
					pendingType = DrillTypeComponent
				default:
					pendingType = DrillTypeUnknown
				}
			}
			// Also check for format spec
			if strings.HasPrefix(line, ";FORMAT=") {
				re := regexp.MustCompile(`\{(\d+):(\d+)\}`)
				fm := re.FindStringSubmatch(line)
				if len(fm) == 3 {
					formatDec, _ = strconv.Atoi(fm[2])
				}
			}
			continue
		}

		// Detect header end
		if line == "%" || line == "M95" {
			inHeader = false
			continue
		}

		// Units
		if strings.Contains(line, "METRIC") || line == "M71" {
			units = "MM"
			continue
		}
		if strings.Contains(line, "INCH") || line == "M72" {
			units = "IN"
			continue
		}

		// Tool definitions (in header): T01C0.300
		if inHeader {
			m := reToolDef.FindStringSubmatch(line)
			if len(m) == 3 {
				toolNum, _ := strconv.Atoi(m[1])
				dia, _ := strconv.ParseFloat(m[2], 64)

				ht := pendingType
				// If this is an NPTH file and type is unknown, classify as mounting
				if isNPTH && ht == DrillTypeUnknown {
					ht = DrillTypeMounting
				}

				tools[toolNum] = toolInfo{diameter: dia, holeType: ht}
				pendingType = DrillTypeUnknown // Reset
				continue
			}
		}

		// Tool selection: T01
		m := reToolSelect.FindStringSubmatch(line)
		if len(m) == 2 {
			toolNum, _ := strconv.Atoi(m[1])
			currentTool = toolNum
			continue
		}

		// End of file
		if line == "M30" || line == "M00" {
			break
		}

		// Coordinate: X123456Y789012
		mc := reCoord.FindStringSubmatch(line)
		if len(mc) == 3 && currentTool != 0 {
			x := parseExcellonCoord(mc[1], formatDec)
			y := parseExcellonCoord(mc[2], formatDec)

			ti := tools[currentTool]
			dia := ti.diameter

			// Convert inches to mm if needed
			if units == "IN" {
				x *= 25.4
				y *= 25.4
				if dia < 1.0 {
					dia *= 25.4
				}
			}

			holes = append(holes, DrillHole{
				X:        x,
				Y:        y,
				Diameter: dia,
				Type:     ti.holeType,
				ToolNum:  currentTool,
			})
		}
	}

	return holes, nil
}

func parseExcellonCoord(s string, fmtDec int) float64 {
	if strings.Contains(s, ".") {
		val, _ := strconv.ParseFloat(s, 64)
		return val
	}
	val, _ := strconv.ParseFloat(s, 64)
	if fmtDec > 0 {
		return val / math.Pow(10, float64(fmtDec))
	}
	return val / 1000.0
}
