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;
}
}