<%
'=======================================================================================================================
' Jurisdiction Number Validation + Lookup
'=======================================================================================================================
' Validates a submitted jurisdiction number against the JCode field of the jurisdictions
' list served by JurisdictionApiUrl (see public/web.config), and looks up the matching
' municipality Name (used to pre-fill the order form's Municipality field). That list is
' essentially static reference data (townships/municipalities), so it's cached in
' Application scope for JurisdictionCacheMinutes (default 60) instead of being fetched on
' every /request-order submission. If a refresh attempt fails, stale cached data is used
' rather than blocking order requests; only when there is no cached data at all (e.g.
' immediately after an app pool restart, with the API also unreachable) does validation
' fail open (allow) rather than block every order request site-wide.
'
' The cache is stored as a single delimited STRING ("|code1<TAB>name1|code2<TAB>name2|...|"),
' not a Scripting.Dictionary or array of objects - Scripting.Dictionary is an
' apartment-threaded COM object, and IIS raises "ASP 0197: Disallowed object use" if you try
' to store one in the Application intrinsic (which is shared across all requests/threads). A
' plain string is just script data, so it's safe to cache this way.
'=======================================================================================================================

Function IsValidJurisdictionNumber(jurisdictionNumber)
    Dim codesText
    codesText = GetJurisdictionCodesText()

    jurisdictionNumber = Trim(jurisdictionNumber)

    If Len(codesText) = 0 Then
        ' No jurisdiction list available at all (first request after startup, API also
        ' down) - fail open rather than block every order request site-wide.
        IsValidJurisdictionNumber = True
    Else
        IsValidJurisdictionNumber = (InStr(codesText, "|" & jurisdictionNumber & Chr(9)) > 0)
    End If
End Function

' Returns the municipality Name matching a jurisdiction number, or "" if the cache is empty
' or has no matching entry (callers should treat "" as "couldn't look this up" and fall back
' to letting the user enter it manually, not as a hard error).
Function GetJurisdictionName(jurisdictionNumber)
    Dim codesText, marker, startPos, nameStart, endPos

    GetJurisdictionName = ""

    codesText = GetJurisdictionCodesText()
    If Len(codesText) = 0 Then Exit Function

    jurisdictionNumber = Trim(jurisdictionNumber)
    marker = "|" & jurisdictionNumber & Chr(9)
    startPos = InStr(codesText, marker)
    If startPos = 0 Then Exit Function

    nameStart = startPos + Len(marker)
    endPos = InStr(nameStart, codesText, "|")
    If endPos = 0 Then Exit Function

    GetJurisdictionName = Mid(codesText, nameStart, endPos - nameStart)
End Function

' Returns the cached "|code1<TAB>name1|code2<TAB>name2|...|" string, refreshing it from
' JurisdictionApiUrl when missing or older than JurisdictionCacheMinutes. Falls back to the
' existing (stale) cached string if a refresh attempt fails.
Private Function GetJurisdictionCodesText()
    Dim cacheMinutes, cachedAt, isStale

    cacheMinutes = GetAppSetting("JurisdictionCacheMinutes")
    If Not IsNumeric(cacheMinutes) Then cacheMinutes = 60

    cachedAt = Application("JurisdictionCodesFetchedAt")
    isStale = IsEmpty(cachedAt)
    If Not isStale Then isStale = (DateDiff("n", cachedAt, Now()) >= CInt(cacheMinutes))

    If isStale Then
        Dim fresh
        fresh = FetchJurisdictionCodesText()
        If Len(fresh) > 0 Then
            Application.Lock
            Application("JurisdictionCodesText") = fresh
            Application("JurisdictionCodesFetchedAt") = Now()
            Application.Unlock
        End If
    End If

    GetJurisdictionCodesText = Application("JurisdictionCodesText")
End Function

' Fetches the jurisdictions list and extracts each JCode/Name pair. Returns a
' "|code1<TAB>name1|code2<TAB>name2|...|" string, or "" on any failure (network, HTTP
' status, or no matches).
'
' Uses a targeted regex over the raw response text rather than this codebase's generic
' aspJSON parser (core/lib.json.asp) - that parser walks the input character-by-character in
' plain VBScript, which measured at over 40 seconds for this API's ~450KB/1500-record
' response. RegExp.Execute is implemented natively and handles the same payload
' near-instantly. The regex assumes JCode is immediately followed by Name in the source
' JSON (verified true for all 1525 current records) - if the API ever reorders those fields
' this will need revisiting, but a full generic parse still isn't necessary since only these
' two fields are used.
Private Function FetchJurisdictionCodesText()
    Dim apiUrl : apiUrl = GetAppSetting("JurisdictionApiUrl")
    Dim result : result = ""

    On Error Resume Next

    Dim http : Set http = Server.CreateObject("Msxml2.ServerXMLHTTP")
    http.setTimeouts 5000, 5000, 5000, 5000
    http.Open "GET", apiUrl, False
    http.Send ""

    If Err.Number = 0 And http.Status = 200 Then
        Dim re, matches, m, codes, code, name
        Set re = New RegExp
        re.Pattern = """JCode""\s*:\s*""([^""]*)""\s*,\s*""Name""\s*:\s*""([^""]*)"""
        re.Global = True
        re.IgnoreCase = True

        Set matches = re.Execute(http.responseText)
        If Err.Number = 0 And matches.Count > 0 Then
            codes = "|"
            For Each m In matches
                code = Trim(m.SubMatches(0))
                name = Trim(m.SubMatches(1))
                If Len(code) > 0 Then codes = codes & code & Chr(9) & name & "|"
            Next
            If Len(codes) > 1 Then result = codes
        End If
    End If

    Err.Clear
    On Error GoTo 0

    FetchJurisdictionCodesText = result
End Function
%>
