package processing import ( "fmt" "strings" ) // StandardFieldDef is the subset of standard_fields used while filling gaps. type StandardFieldDef struct { Key string DefaultValue string Unit string MappingHints []string } // FillMissingStandardFields copies values from mapped/raw (via key + mapping_hints) // or default_value into mapped when an enabled field is empty. func FillMissingStandardFields(mapped, raw map[string]any, fields []StandardFieldDef) map[string]any { out := mapped if out == nil { out = map[string]any{} } else { // Shallow copy so callers can keep the original. cp := make(map[string]any, len(out)+len(fields)) for k, v := range out { cp[k] = v } out = cp } for _, f := range fields { key := strings.TrimSpace(f.Key) if key == "" { continue } if valuePresent(out[key]) { continue } candidates := make([]string, 0, 1+len(f.MappingHints)) candidates = append(candidates, key) for _, h := range f.MappingHints { h = strings.TrimSpace(h) if h != "" { candidates = append(candidates, h) } } if v := lookupAny(mapped, candidates...); valuePresent(v) { out[key] = normalizeFilled(v, f.Unit) continue } if v := lookupAny(raw, candidates...); valuePresent(v) { out[key] = normalizeFilled(v, f.Unit) continue } if strings.TrimSpace(f.DefaultValue) != "" { out[key] = normalizeFilled(f.DefaultValue, f.Unit) } } return out } func valuePresent(v any) bool { if v == nil { return false } switch t := v.(type) { case string: return strings.TrimSpace(t) != "" case []any: return len(t) > 0 case map[string]any: return len(t) > 0 default: s := strings.TrimSpace(fmt.Sprint(t)) return s != "" && s != "" } } func lookupAny(m map[string]any, keys ...string) any { if m == nil { return nil } lower := map[string]any{} for k, v := range m { lower[strings.ToLower(strings.TrimSpace(k))] = v } for _, k := range keys { lk := strings.ToLower(strings.TrimSpace(k)) if v, ok := lower[lk]; ok && valuePresent(v) { return v } } return nil } func normalizeFilled(v any, unit string) any { switch t := v.(type) { case string: s := SanitizeText(t) if unit != "" && !strings.Contains(strings.ToLower(s), strings.ToLower(unit)) { // Keep numeric values plain; unit stays on the field definition. return s } return s default: return v } } func parseHints(v any) []string { switch t := v.(type) { case []string: return t case []any: out := make([]string, 0, len(t)) for _, item := range t { if s, ok := item.(string); ok && strings.TrimSpace(s) != "" { out = append(out, strings.TrimSpace(s)) } } return out case string: s := strings.TrimSpace(t) if s == "" { return nil } parts := strings.Split(s, ",") out := make([]string, 0, len(parts)) for _, p := range parts { p = strings.TrimSpace(p) if p != "" { out = append(out, p) } } return out default: return nil } }