unit Myc.Data.Scalar.JSON;

interface

uses
    System.SysUtils,
    System.Rtti,
    System.TypInfo,
    System.JSON,
    Myc.Data.Scalar;

type
    TRttiAstHelper = class
    private
        // Maps a Delphi RTTI type to its TScalar.TKind.
        class function TypeToScalarKind(AType: TRttiType): TScalar.TKind; static;
    public
        // Creates a JSON definition string from a record type info.
        class function RecordDefinitionToJson<T: record>: string; static;
        // Creates a record definition from a JSON string.
        class function JsonToRecordDefinition(const AJson: string): IScalarRecordDefinition; static;
    end;

implementation

uses
    System.Generics.Collections,
    Myc.Data.Decimal,
    Myc.Data.Keyword;

{ TRttiAstHelper }

class function TRttiAstHelper.JsonToRecordDefinition(const AJson: string): IScalarRecordDefinition;
var
    jsonValue: TJSONValue;
    fieldsArray: TJSONArray;
    i: Integer;
    fieldObj: TJSONObject;
    kindStr, fieldName: string;
    fieldKey: IKeyword;
    fields: TArray<TScalarRecordField>;
begin
    jsonValue := TJSONObject.ParseJSONValue(AJson);
    if not Assigned(jsonValue) then
        exit;

    try
        if not (jsonValue is TJSONArray) then
            exit;

        fieldsArray := jsonValue as TJSONArray;

        SetLength(fields, fieldsArray.Count);
        for i := 0 to fieldsArray.Count - 1 do
        begin
            fieldObj := fieldsArray.Items[i] as TJSONObject;
            if not Assigned(fieldObj) then
                continue;

            fieldName := fieldObj.GetValue<string>('name');
            fieldKey := TKeywordRegistry.Intern(fieldName);
            kindStr := fieldObj.GetValue<string>('kind');

            fields[i] := TScalarRecordField.Create(fieldKey, TScalar.TKind(GetEnumValue(TypeInfo(TScalar.TKind), kindStr)));
        end;
        Result := TScalarRecord.TRegistry.Intern(fields);
    finally
        jsonValue.Free;
    end;
end;

class function TRttiAstHelper.RecordDefinitionToJson<T>: string;
var
    ctx: TRttiContext;
    rttiType: TRttiType;
    rttiRecordType: TRttiRecordType;
    field: TRttiField;
    fieldsArray: TJSONArray;
    fieldObj: TJSONObject;
begin
    ctx := TRttiContext.Create;
    rttiType := ctx.GetType(TypeInfo(T));

    if not (rttiType is TRttiRecordType) then
        raise EArgumentException.Create('PTypeInfo provided is not a record type.');

    rttiRecordType := rttiType as TRttiRecordType;

    fieldsArray := TJSONArray.Create;
    try
        for field in rttiRecordType.GetFields do
        begin
            fieldObj := TJSONObject.Create;
            fieldObj.AddPair('name', TJSONString.Create(field.Name));
            fieldObj.AddPair('kind', TJSONString.Create(GetEnumName(TypeInfo(TScalar.TKind), Ord(TypeToScalarKind(field.FieldType)))));
            fieldsArray.Add(fieldObj);
        end;

        Result := fieldsArray.ToJSON;
    finally
        fieldsArray.Free;
    end;
end;

class function TRttiAstHelper.TypeToScalarKind(AType: TRttiType): TScalar.TKind;
var
    typeHandle: PTypeInfo;
begin
    typeHandle := AType.Handle;

    if typeHandle = TypeInfo(Int64) then
        Result := TScalar.TKind.Ordinal
    else if typeHandle = TypeInfo(Integer) then
        Result := TScalar.TKind.Ordinal
    else if typeHandle = TypeInfo(UInt64) then
        Result := TScalar.TKind.Ordinal
    else if typeHandle = TypeInfo(Boolean) then
        Result := TScalar.TKind.Ordinal
    else if typeHandle = TypeInfo(Double) then
        Result := TScalar.TKind.Float
    else if typeHandle = TypeInfo(Single) then
        Result := TScalar.TKind.Float
    else if typeHandle = TypeInfo(TDateTime) then
        Result := TScalar.TKind.Float // Correctly handle TDateTime as a Float
    else
        raise EArgumentException.CreateFmt('Unsupported record field type: %s', [AType.Name]);
end;

end.
