Move SSD box type label helpers to typed Kotlin

ober

df4de56c65386d673c048f0efa27c684df550c1e

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 757f62d..6705473 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -2244,6 +2244,7 @@
       (typed-library (com sfb ssdreview)
         (export normalizeFiringArc normalizeLabel shouldReplaceGuessLabel
                 shouldReplaceBoxTypeLabel resolvedBoxTypeLabel
+                boxTypeFallbackName boxTypeLabelMatches defaultBoxTypeLabel
                 isGenericGuessLabel isSsdPdfName
                 sourceBaseName canonicalSourceName isUsefulGuessLabel isErrorStatus
                 formatActivityDuration)
@@ -2292,6 +2293,33 @@
                 (if (string-blank? fallback)
                   (labelWithoutQuestion trimmed)
                   fallback)))))
+        (def (boxTypeFallbackName (boxType : String)) : String
+          (string-trim
+            (string-replace-regex boxType "^\\S+\\s+" "")))
+        (def (nullableBoxTypeNameMatches (label : String)
+                                         (candidate : (Nullable String))) : Bool
+          (if (nullable-null? candidate)
+              #f
+              (equal?
+                (normalizeLabel (nullable-get candidate))
+                (normalizeLabel label))))
+        (def (boxTypeLabelMatches (label : String)
+                                  (boxType : String)
+                                  (resolvedName : (Nullable String))
+                                  (resolvedDisplay : (Nullable String))) : Bool
+          (if (string-blank? boxType)
+              #f
+              (or (nullableBoxTypeNameMatches label resolvedName)
+                  (or (nullableBoxTypeNameMatches label resolvedDisplay)
+                      (equal?
+                        (normalizeLabel (boxTypeFallbackName boxType))
+                        (normalizeLabel label))))))
+        (def (defaultBoxTypeLabel (boxType : String)
+                                  (resolvedName : (Nullable String))) : String
+          (string-lowercase
+            (if (nullable-null? resolvedName)
+                (boxTypeFallbackName boxType)
+                (nullable-get resolvedName))))
         (def (isSsdPdfName (name : String)) : Bool
           (let ((lower (string-lowercase name)))
             (and (string-ends-with? lower ".pdf")
@@ -2942,20 +2970,13 @@
        "    }"
        ""
        "    private fun labelMatchesBoxType(label: String, boxType: String): Boolean {"
-       "        if (boxType.isBlank()) return false"
-       "        val normalized = normalizeLabel(label)"
        "        val previous = BoxTypes.resolve(this, boxType)"
-       "        return listOf("
-       "            previous?.name,"
-       "            previous?.let { box_type_display(it) },"
-       "            boxType.substringAfter(' ', boxType)"
-       "        ).filterNotNull().any { normalizeLabel(it) == normalized }"
+       "        return boxTypeLabelMatches(label, boxType, previous?.name, previous?.let { box_type_display(it) })"
        "    }"
        ""
        "    private fun defaultLabelForBoxType(boxType: String): String {"
        "        val resolved = BoxTypes.resolve(this, boxType)"
-       "        val raw = resolved?.name ?: boxType.substringAfter(' ', boxType).trim()"
-       "        return raw.lowercase()"
+       "        return defaultBoxTypeLabel(boxType, resolved?.name)"
        "    }"
        ""
        "    private fun finalizeGroupLabel(group: SsdGroup) {"