using Strata.SqlTools.SqlBreakdown.Interfaces.Core; using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; namespace Strata.SqlTools.SqlBreakdown.Expressions; /// /// Represents a reference to a property from an input data source, typically used for /// dynamic data binding in query construction. Used when building queries that reference /// external data sources by GUID identifiers. /// /// /// This expression type is used in scenarios where data comes from registered data /// sources (like data tables or dimensions) that are identified by GUIDs. The /// expression can reference a primary data source and optionally a secondary data source /// for hierarchical or relational lookups. /// /// /// /// // Reference a simple property from a data source /// var patientName = new InputPropertyExpression( /// dataSourceGuid: Guid.Parse("41639c8f-fecf-4449-b6e6-53f796c0c3e4"), /// dataKeyLookup: "PatientName" /// ); /// /// // Reference a property with a secondary data source (dimension lookup) /// var patientType = new InputPropertyExpression( /// dataSourceGuid: Guid.Parse("41639c8f-fecf-4449-b6e6-53f796c0c3e4"), /// secondaryDataSource: Guid.Parse("6ef6b1f7-a50c-4198-8866-140bb82e2dda"), /// dataKeyLookup: "PatientTypeRollupName" /// ); /// /// public class InputPropertyExpression : Expression { /// /// Gets the GUID identifier of the primary data source. /// /// /// The data source GUID, typically representing a data table or primary data entity. /// /// /// 41639c8f-fecf-4449-b6e6-53f796c0c3e4 - the data table id of PES /// public Guid DataSourceGuid { get; } /// /// Gets the optional GUID identifier of a secondary data source. /// /// /// The secondary data source GUID, typically representing a dimension or lookup table. /// Null if no secondary source is required. /// /// /// 6ef6b1f7-a50c-4198-8866-140bb82e2dda - the dimension id of the Patient Type /// Rollup dimension /// public Guid? SecondaryDataSource { get; } /// /// Gets the key name used to look up the data value. /// /// /// The property or column name within the data source. /// /// "PatientTypeRollupName" public string DataKeyLookup { get; } /// /// Initializes a new instance of the class /// with a primary data source. /// /// The GUID of the primary data source. /// The key name for data lookup. /// /// Example: /// /// var property = new InputPropertyExpression( /// Guid.Parse("41639c8f-fecf-4449-b6e6-53f796c0c3e4"), /// "PatientName"); /// /// public InputPropertyExpression(Guid dataSourceGuid, string dataKeyLookup) : this(dataSourceGuid, null, dataKeyLookup) { } /// /// Initializes a new instance of the class /// with primary and optional secondary data sources. /// /// The GUID of the primary data source. /// /// The optional GUID of the secondary data source. /// /// The key name for data lookup. /// /// Example with secondary data source: /// /// var property = new InputPropertyExpression( /// Guid.Parse("41639c8f-fecf-4449-b6e6-53f796c0c3e4"), /// Guid.Parse("6ef6b1f7-a50c-4198-8866-140bb82e2dda"), /// "PatientTypeRollupName"); /// /// public InputPropertyExpression(Guid dataSourceGuid, Guid? secondaryDataSource, string dataKeyLookup) { DataSourceGuid = dataSourceGuid; SecondaryDataSource = secondaryDataSource; DataKeyLookup = dataKeyLookup; } public override T Accept(IVisitor visitor) { return visitor.VisitInputPropertyExpression(this); } } public class CollectionInputPropertyExpression : InputPropertyExpression { public CollectionInputPropertyExpression(Guid dataSourceGuid, Guid secondaryDataSource, string dataKeyLookup) : base(dataSourceGuid, secondaryDataSource, dataKeyLookup) { } public InputPropertyExpression? ItemProperty { get; } public override T Accept(IVisitor visitor) { throw new NotImplementedException(); } } public interface IFlatData : IDictionary { string RowIdKey { get; } long RowId { get; } } public class MyFlatData : Dictionary, IFlatData { private readonly Lazy _rowId; public MyFlatData() : this(new Dictionary()) { } public MyFlatData(IDictionary rawData) : this(rawData, "RowID") { } public MyFlatData(IDictionary rawData, string rowIdKey) : base(rawData) { RowIdKey = rowIdKey; _rowId = new Lazy(() => this.GetValue(RowIdKey), false); } public string RowIdKey { get; } public long RowId => _rowId.Value; } public class FlatData : IFlatData { private readonly IDictionary _data; private readonly Lazy _rowId; public FlatData() : this(new Dictionary(), "RowID") { } public FlatData(IDictionary rawData, string rowIdKey) { _data = rawData; RowIdKey = rowIdKey; _rowId = new Lazy(() => this.GetValue(RowIdKey), false); } public string RowIdKey { get; } public long RowId => _rowId.Value; public IEnumerator> GetEnumerator() { return _data.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_data).GetEnumerator(); } public void Add(KeyValuePair item) { _data.Add(item); } public void Clear() { _data.Clear(); } public bool Contains(KeyValuePair item) { return _data.Contains(item); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { _data.CopyTo(array, arrayIndex); } public bool Remove(KeyValuePair item) { return _data.Remove(item); } public int Count => _data.Count; public bool IsReadOnly { get; } public void Add(string key, object value) { _data.Add(key, value); } public bool ContainsKey(string key) { return _data.ContainsKey(key); } public bool Remove(string key) { throw new NotImplementedException(); } public bool TryGetValue(string key, out object value) { return _data.TryGetValue(key, out value!); } public IEnumerable Keys => _data.Keys; ICollection IDictionary.Values => _data.Values; ICollection IDictionary.Keys => _data.Keys; public IEnumerable Values => _data.Values; public object this[string key] { get => _data[key]; set => _data[key] = value; } } public static class FlatDataUtils { private static readonly IFormatProvider _culture = new CultureInfo("en-US"); public static object GetValue(this IFlatData data, string key) { ArgumentNullException.ThrowIfNull(data); if (!data.TryGetValue(key, out object? rawValue)) { throw new ArgumentException( $"The specified key is not available. Requested key: [{key}] Available keys: [{string.Join(", ", data.Keys)}]", nameof(key)); } return rawValue; } public static TValue GetValue(this IFlatData data, string key) { var rawValue = data.GetValue(key); var convertedValue = (TValue)Convert.ChangeType(rawValue, typeof(TValue), _culture); return convertedValue; } public static IEnumerable GetRowIds(this IEnumerable data) { return data.Select(x => x.RowId); } } public interface IHierarchicalData { /// /// The DataSource for this level of the Hierarchy /// Guid DataSourceGuid { get; } /// /// The Data at this level of the Hierarchy /// IFlatData Data { get; set; } /// /// Returns all Child Data across all DataSources, or an empty collection if there is no Child Data /// IEnumerable AllChildData { get; set; } /// /// Checks if this record has Child Data for a particular DataSource /// /// The DataSource to check /// True if any Child Data exists for the DataSource bool HasChildData(Guid dataSourceGuid); /// /// Returns the Child Data for the given DataSource /// /// DataSource identifier for the child data /// The Child Data or an empty collection if no child data is set for the DataSource IEnumerable GetChildData(Guid dataSourceGuid); /// /// Attempts to add Child Data. Will check if child data exists before adding. /// /// DataSource identifier for the child data /// The Child Data to Add to the Record /// False if child data exists and was not overriden bool TryAddChildData(Guid datasourceGuid, IEnumerable data); /// /// Add Child Data, will override any existing Child Data for the DataSource /// /// DataSource identifier for the child data /// The Child Data to Add to the Record void SetChildData(Guid datasourceGuid, IEnumerable data); } public class HierarchicalData : IHierarchicalData { private Dictionary> _childDataMap; [JsonConstructor] public HierarchicalData() : this(Guid.Empty, default!, new List()) { } public HierarchicalData(Guid dataSourceGuid, IFlatData rootData) : this(dataSourceGuid, rootData, new List()) { } public HierarchicalData(Guid dataSourceGuid, IFlatData rootData, IEnumerable childData) { DataSourceGuid = dataSourceGuid; Data = rootData; _childDataMap = childData.GroupBy(x => x.DataSourceGuid).ToDictionary(x => x.Key, x => x.ToList()); } public Guid DataSourceGuid { get; set; } public IFlatData Data { get; set; } [SuppressMessage("Major Code Smell", "S2365:Properties should not make collection or array copies", Justification = "AllChildData is part of the IHierarchicalData interface contract and is JSON-serialized (see Data.json). The flatten-on-get / group-on-set transformation is the deliberate purpose of the property — _childDataMap is the storage form, the property is the wire form.")] public IEnumerable AllChildData { get => _childDataMap.SelectMany(x => x.Value).ToList(); set => _childDataMap = value.GroupBy(x => x.DataSourceGuid).ToDictionary(x => x.Key, x => x.ToList()); } public bool HasChildData(Guid dataSourceGuid) { return _childDataMap.ContainsKey(dataSourceGuid); } public IEnumerable GetChildData(Guid dataSourceGuid) { if (HasChildData(dataSourceGuid)) { return _childDataMap[dataSourceGuid]; } return new List(); } public bool TryAddChildData(Guid datasourceGuid, IEnumerable data) { if (_childDataMap.ContainsKey(datasourceGuid)) { return false; } _childDataMap[datasourceGuid] = data.ToList(); return true; } public void SetChildData(Guid datasourceGuid, IEnumerable data) { _childDataMap[datasourceGuid] = data.ToList(); } public override string? ToString() { if (Data?.ContainsKey("DimPatientEnEncounterID") ?? false) { return Data["DimPatientEnEncounterID"].ToString(); } return base.ToString(); } } #region Json Converters public class HierarchicalDataConverter : JsonConverter { public override IHierarchicalData? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return JsonSerializer.Deserialize(ref reader, options); } public override void Write(Utf8JsonWriter writer, IHierarchicalData value, JsonSerializerOptions options) { throw new NotImplementedException(); } } public class FlatDataConverter : JsonConverter { public override IFlatData? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return JsonSerializer.Deserialize(ref reader, options); } public override void Write(Utf8JsonWriter writer, IFlatData value, JsonSerializerOptions options) { throw new NotImplementedException(); } } public class ObjectToInferredTypesConverter : JsonConverter { public override object Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.TokenType switch { JsonTokenType.True => true, JsonTokenType.False => false, JsonTokenType.Number when reader.TryGetInt64(out long l) => l, JsonTokenType.Number => reader.GetDouble(), JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => datetime, JsonTokenType.String => reader.GetString()!, _ => JsonDocument.ParseValue(ref reader).RootElement.Clone() }; public override void Write( Utf8JsonWriter writer, object objectToWrite, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, objectToWrite, objectToWrite.GetType(), options); } #endregion