using System.Text.Json; namespace GB5Shared.DTO.Framework.Criteria { /// /// Dynamically parses any CriteriaDTO into a typed dictionary keyed by FieldName (case-insensitive). /// Type conversion is driven entirely by CriteriaAttributeType — no field-name switch required. /// /// CriteriaAttributeType byte values (from DB config): /// 0 = PICKLIST → List<int> (single or comma-separated IDs) /// 3 = LIST → List<string> /// 4 = DATE → DateTime UTC (epoch seconds, 100% DB-accurate via AddSeconds) /// 5 = NUMERICENTRY → decimal /// * = default → string /// public static class CriteriaParser { private static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private const byte TYPE_PICKLIST = 0; private const byte TYPE_LIST = 3; private const byte TYPE_DATE = 4; private const byte TYPE_NUMERICENTRY = 5; /// /// Flattens all sections/attributes of a CriteriaDTO into one dictionary. /// Each entry is typed based on CriteriaAttributeType — no caller switch needed. /// public static Dictionary Parse(CriteriaDTO criteriaDTO) { var result = new Dictionary(StringComparer.OrdinalIgnoreCase); if (criteriaDTO?.SectionCriteriaList == null) return result; foreach (var section in criteriaDTO.SectionCriteriaList) { if (section?.AttributesCriteriaList == null) continue; foreach (var attr in section.AttributesCriteriaList) { if (attr.FieldValue == null || string.IsNullOrEmpty(attr.FieldName)) continue; string raw = RawString(attr.FieldValue); if (string.IsNullOrWhiteSpace(raw)) continue; object parsed = attr.CriteriaAttributeType switch { TYPE_DATE => EpochSecondsToDateTime(attr.FieldValue), TYPE_PICKLIST => ToIntList(raw), TYPE_NUMERICENTRY => decimal.TryParse(raw, out decimal d) ? d : 0m, TYPE_LIST => raw.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()) .Where(s => s.Length > 0) .ToList(), _ => raw }; // Last writer wins if the same FieldName appears in multiple sections result[attr.FieldName] = parsed; } } return result; } // ── Typed accessors ─────────────────────────────────────────────────── public static DateTime GetDate(Dictionary c, string field, DateTime fallback = default) => c.TryGetValue(field, out var v) && v is DateTime dt ? dt : fallback; public static int GetInt(Dictionary c, string field, int fallback = 0) { if (!c.TryGetValue(field, out var v)) return fallback; if (v is List list) return list.Count > 0 ? list[0] : fallback; if (v is string s && int.TryParse(s, out int i)) return i; return fallback; } public static List GetIntList(Dictionary c, string field) => c.TryGetValue(field, out var v) && v is List list ? list : new List(); public static decimal GetDecimal(Dictionary c, string field, decimal fallback = 0m) => c.TryGetValue(field, out var v) && v is decimal d ? d : fallback; public static List GetStringList(Dictionary c, string field) => c.TryGetValue(field, out var v) && v is List list ? list : new List(); public static string GetString(Dictionary c, string field, string fallback = "") { if (!c.TryGetValue(field, out var v) || v == null) return fallback; if (v is List intList) return intList.Count > 0 ? intList[0].ToString() : fallback; if (v is List strList) return strList.Count > 0 ? strList[0] : fallback; return v.ToString() ?? fallback; } // ── Private helpers ─────────────────────────────────────────────────── /// /// Converts epoch seconds (as stored by the DB) to UTC DateTime. /// Uses AddSeconds — identical to GB5CommonFunction.FromEpoch — for 100% DB accuracy. /// private static DateTime EpochSecondsToDateTime(object fieldValue) { long seconds; if (fieldValue is JsonElement je) seconds = je.ValueKind == JsonValueKind.Number ? je.GetInt64() : long.Parse(je.GetString()!); else seconds = Convert.ToInt64(fieldValue); return Epoch.AddSeconds(seconds); } private static List ToIntList(string raw) => raw.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(s => int.TryParse(s.Trim(), out int v) ? v : 0) .Where(v => v != 0) .ToList(); private static string RawString(object value) => value is JsonElement je ? je.ToString() : value?.ToString() ?? string.Empty; } }