using System.Globalization;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace FoodLabeling.Application.Helpers;
///
/// Print Log「Expiry Date」:从 fl_label_print_task.PrintInputJson 解析保质期展示文案。
/// 与 App 落库一致:多为整份模板快照(elements[]),到期日在 duration date 类元素的 config.text。
///
public static class ReportsPrintLogExpiryHelper
{
private static readonly string[] RootExpiryKeys =
{
"expiryDate",
"expiry",
"expirationDate",
"bestBeforeDate",
"useByDate"
};
private static readonly Regex ExpiryNamePattern = new(
@"(durationdate|duration_date|duration\s*date|expirydate|expiry_date|expiry\s*date|expirationdate|expiration_date|usebydate|use_by)",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
private static readonly Regex ExcludeNamePattern = new(
@"(currentdate|currenttime|prepped|prepdate|prepon|labelname)",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
///
/// 解析保质期展示文本;无法解析时返回「无」。
///
public static string ExtractExpiryText(string? printInputJson)
{
if (string.IsNullOrWhiteSpace(printInputJson))
{
return "无";
}
try
{
using var doc = JsonDocument.Parse(printInputJson);
if (doc.RootElement.ValueKind != JsonValueKind.Object)
{
return "无";
}
var root = doc.RootElement;
if (TryGetRootExpiryText(root, out var fromRoot))
{
return fromRoot;
}
if (TryGetProperty(root, "elements", out var elements) && elements.ValueKind == JsonValueKind.Array)
{
if (TryExtractFromElements(elements, out var fromElements))
{
return fromElements;
}
}
}
catch
{
return "无";
}
return "无";
}
private static bool TryGetRootExpiryText(JsonElement root, out string text)
{
text = "无";
foreach (var prop in root.EnumerateObject())
{
var key = prop.Name.Trim();
if (!RootExpiryKeys.Any(k => key.Equals(k, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
if (TryFormatJsonValueAsExpiryText(prop.Value, out text))
{
return true;
}
}
return false;
}
private static bool TryExtractFromElements(JsonElement elements, out string text)
{
text = "无";
var bestScore = 0;
string? bestText = null;
var bestOrder = int.MinValue;
foreach (var el in elements.EnumerateArray())
{
if (el.ValueKind != JsonValueKind.Object)
{
continue;
}
var score = ScoreExpiryElement(el);
if (score <= 0)
{
continue;
}
var display = GetElementConfigDisplayText(el);
if (string.IsNullOrWhiteSpace(display))
{
continue;
}
var order = TryGetIntProperty(el, "orderNum", "OrderNum") ?? 0;
if (score > bestScore || (score == bestScore && order > bestOrder))
{
bestScore = score;
bestOrder = order;
bestText = display.Trim();
}
}
if (string.IsNullOrWhiteSpace(bestText))
{
return false;
}
text = bestText;
return true;
}
private static int ScoreExpiryElement(JsonElement el)
{
var nameHint = $"{GetStringProperty(el, "elementName", "ElementName")} {GetStringProperty(el, "inputKey", "InputKey")}"
.Trim();
if (string.IsNullOrWhiteSpace(nameHint))
{
nameHint = GetStringProperty(el, "id", "Id");
}
if (!string.IsNullOrWhiteSpace(nameHint) && ExcludeNamePattern.IsMatch(nameHint))
{
return -1;
}
var typeAdd = GetStringProperty(el, "typeAdd", "TypeAdd")
.Trim()
.ToLowerInvariant()
.Replace(" ", " ", StringComparison.Ordinal);
if (ExpiryNamePattern.IsMatch(nameHint))
{
return 100;
}
if (typeAdd.Contains("duration date", StringComparison.Ordinal))
{
return 90;
}
if (TryGetProperty(el, "config", out var cfg) || TryGetProperty(el, "Config", out cfg))
{
var native = GetStringProperty(cfg, "nativeSourceType", "NativeSourceType")
.Trim()
.ToUpperInvariant();
if (native == "DATE" && ExpiryNamePattern.IsMatch(nameHint))
{
return 85;
}
}
var type = GetStringProperty(el, "type", "Type").Trim().ToUpperInvariant();
if (type is "DATE" or "DURATION" or "TIME")
{
if (ExpiryNamePattern.IsMatch(nameHint))
{
return 75;
}
}
return 0;
}
private static string? GetElementConfigDisplayText(JsonElement el)
{
if (!TryGetProperty(el, "config", out var cfg) && !TryGetProperty(el, "Config", out cfg))
{
return null;
}
foreach (var key in new[] { "text", "Text", "format", "Format" })
{
if (cfg.TryGetProperty(key, out var v) && v.ValueKind == JsonValueKind.String)
{
var s = v.GetString()?.Trim();
if (!string.IsNullOrWhiteSpace(s))
{
return s;
}
}
}
return null;
}
private static bool TryFormatJsonValueAsExpiryText(JsonElement v, out string text)
{
text = "无";
if (v.ValueKind == JsonValueKind.String)
{
var s = v.GetString()?.Trim();
if (string.IsNullOrWhiteSpace(s))
{
return false;
}
text = s;
return true;
}
if (v.ValueKind == JsonValueKind.Number && v.TryGetInt64(out var unix))
{
text = DateTimeOffset.FromUnixTimeSeconds(unix).LocalDateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
return true;
}
if (v.ValueKind is JsonValueKind.True or JsonValueKind.False or JsonValueKind.Number)
{
text = v.ToString();
return true;
}
return false;
}
private static bool TryGetProperty(JsonElement obj, string name, out JsonElement value)
{
if (obj.TryGetProperty(name, out value))
{
return true;
}
if (name.Length > 0)
{
var pascal = char.ToUpperInvariant(name[0]) + name[1..];
if (obj.TryGetProperty(pascal, out value))
{
return true;
}
}
value = default;
return false;
}
private static string GetStringProperty(JsonElement obj, params string[] names)
{
foreach (var name in names)
{
if (TryGetProperty(obj, name, out var v) && v.ValueKind == JsonValueKind.String)
{
return v.GetString() ?? string.Empty;
}
}
return string.Empty;
}
private static int? TryGetIntProperty(JsonElement obj, params string[] names)
{
foreach (var name in names)
{
if (!TryGetProperty(obj, name, out var v))
{
continue;
}
if (v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out var n))
{
return n;
}
}
return null;
}
}