// Package metricparse extracts a numeric value from the free-text engine/metric strings
// used across FM reports (e.g. "GPC/N1 63.2"). The same parser MUST be used both when
// aggregating totals and when deriving a single report's "today" contribution, otherwise
// prev = total - today would not reconcile.
package metricparse

import (
	"regexp"
	"strconv"
	"strings"
	"unicode"
)

// numberToken matches a signed decimal number (integer or fractional).
var numberToken = regexp.MustCompile(`[-+]?\d*\.?\d+`)

// Parse returns ParseString(*v), or 0 when v is nil.
func Parse(v *string) float64 {
	if v == nil {
		return 0
	}
	return ParseString(*v)
}

// ParseString pulls the numeric VALUE out of a metric label string:
//
//	"GPC/N1 63.2" -> 63.2   "PTC/N2 61.8" -> 61.8   "63.2" -> 63.2
//	"63,2" -> 63.2          "  -5 "       -> -5      "N1" -> 0 (label only)
//
// It ignores digits fused to a label letter (the "1" in "N1", the "2" in "N2") by skipping
// any number immediately preceded by a letter, and prefers a token that carries a decimal
// point when several stand-alone numbers are present.
func ParseString(s string) float64 {
	s = strings.TrimSpace(s)
	if s == "" {
		return 0
	}
	// Normalize a decimal comma ("63,2") to a dot when it is the sole separator.
	if !strings.Contains(s, ".") {
		if i := strings.IndexByte(s, ','); i > 0 && i+1 < len(s) &&
			isASCIIDigit(s[i-1]) && isASCIIDigit(s[i+1]) {
			s = s[:i] + "." + s[i+1:]
		}
	}

	locs := numberToken.FindAllStringIndex(s, -1)
	candidates := make([]string, 0, len(locs))
	for _, loc := range locs {
		// A value is never glued to a preceding letter; that only happens for labels
		// like N1/N2. Real values start the string or follow a space/slash/colon/etc.
		if loc[0] > 0 && unicode.IsLetter(rune(s[loc[0]-1])) {
			continue
		}
		candidates = append(candidates, s[loc[0]:loc[1]])
	}
	if len(candidates) == 0 {
		return 0
	}

	chosen := candidates[0]
	for _, c := range candidates {
		if strings.Contains(c, ".") {
			chosen = c
			break
		}
	}
	n, err := strconv.ParseFloat(chosen, 64)
	if err != nil {
		return 0
	}
	return n
}

func isASCIIDigit(b byte) bool { return b >= '0' && b <= '9' }
