unit Myc.Ast.Json;

interface

uses
    System.SysUtils,
    System.Generics.Collections,
    System.JSON,
    Myc.Data.Scalar,
    Myc.Data.Value,
    Myc.Ast,
    Myc.Ast.Visitor,
    Myc.Ast.Nodes;

type
    IJsonAstConverter = interface
        function Serialize(const RootNode: IAstNode): TJSONObject;
        function Deserialize(const AJson: TJSONObject): IAstNode;
    end;

    // TJsonAstConverter implements the visitor pattern for serialization
    // and uses factory functions for deserialization.
    // Inherits from the new generic visitor base class
    TJsonAstConverter = class(TAstVisitor<TJSONObject>, IJsonAstConverter)
    private
        procedure DataValueToJson(const AValue: TDataValue; const AParent: TJSONObject; const AName: string);
        function JsonToNode(const AJson: TJSONValue; const ExpectedType: String = ''): IAstNode;
        function JsonToDataValue(const AObj: TJSONObject; const AName: string): TDataValue;

        function JsonToConstantNode(const AObj: TJSONObject): IConstantNode;
        function JsonToIdentifierNode(const AObj: TJSONObject): IIdentifierNode;
        function JsonToKeywordNode(const AObj: TJSONObject): IKeywordNode;
        function JsonToIfExprNode(const AObj: TJSONObject): IIfExpressionNode;
        function JsonToCondExprNode(const AObj: TJSONObject): ICondExpressionNode;
        function JsonToTernaryExprNode(const AObj: TJSONObject): IAstNode; // Backward Compatibility
        function JsonToLambdaExprNode(const AObj: TJSONObject): ILambdaExpressionNode;
        function JsonToMacroDefNode(const AObj: TJSONObject): IMacroDefinitionNode;
        function JsonToQuasiquoteNode(const AObj: TJSONObject): IQuasiquoteNode;
        function JsonToUnquoteNode(const AObj: TJSONObject): IUnquoteNode;
        function JsonToUnquoteSplicingNode(const AObj: TJSONObject): IUnquoteSplicingNode;
        function JsonToFunctionCallNode(const AObj: TJSONObject): IFunctionCallNode;
        function JsonToMacroExpansionNode(const AObj: TJSONObject): IMacroExpansionNode;
        function JsonToRecurNode(const AObj: TJSONObject): IRecurNode;
        function JsonToBlockNode(const AObj: TJSONObject): IBlockExpressionNode;
        function JsonToVarDeclNode(const AObj: TJSONObject): IVariableDeclarationNode;
        function JsonToAssignmentNode(const AObj: TJSONObject): IAssignmentNode;
        function JsonToIndexerNode(const AObj: TJSONObject): IIndexerNode;
        function JsonToMemberAccessNode(const AObj: TJSONObject): IMemberAccessNode;
        function JsonToRecordLiteralNode(const AObj: TJSONObject): IRecordLiteralNode;
        function JsonToCreateSeriesNode(const AObj: TJSONObject): ICreateSeriesNode;
        function JsonToAddSeriesItemNode(const AObj: TJSONObject): IAddSeriesItemNode;
        function JsonToSeriesLengthNode(const AObj: TJSONObject): ISeriesLengthNode;
        function JsonToNopNode(const AObj: TJSONObject): INopNode;

    protected
        // IAstVisitor implementation for serialization (T = TJSONObject)
        function VisitConstant(const Node: IConstantNode): TJSONObject; override;
        function VisitIdentifier(const Node: IIdentifierNode): TJSONObject; override;
        function VisitKeyword(const Node: IKeywordNode): TJSONObject; override;

        // List Visitors
        function VisitParameterList(const Node: IParameterList): TJSONObject; override;
        function VisitArgumentList(const Node: IArgumentList): TJSONObject; override;
        function VisitExpressionList(const Node: IExpressionList): TJSONObject; override;
        function VisitRecordFieldList(const Node: IRecordFieldList): TJSONObject; override;
        function VisitRecordField(const Node: IRecordFieldNode): TJSONObject; override;

        function VisitIfExpression(const Node: IIfExpressionNode): TJSONObject; override;
        function VisitCondExpression(const Node: ICondExpressionNode): TJSONObject; override;
        function VisitLambdaExpression(const Node: ILambdaExpressionNode): TJSONObject; override;
        function VisitMacroDefinition(const Node: IMacroDefinitionNode): TJSONObject; override;
        function VisitQuasiquote(const Node: IQuasiquoteNode): TJSONObject; override;
        function VisitUnquote(const Node: IUnquoteNode): TJSONObject; override;
        function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TJSONObject; override;
        function VisitFunctionCall(const Node: IFunctionCallNode): TJSONObject; override;
        function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TJSONObject; override;
        function VisitRecurNode(const Node: IRecurNode): TJSONObject; override;
        function VisitBlockExpression(const Node: IBlockExpressionNode): TJSONObject; override;
        function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TJSONObject; override;
        function VisitAssignment(const Node: IAssignmentNode): TJSONObject; override;
        function VisitIndexer(const Node: IIndexerNode): TJSONObject; override;
        function VisitMemberAccess(const Node: IMemberAccessNode): TJSONObject; override;
        function VisitRecordLiteral(const Node: IRecordLiteralNode): TJSONObject; override;
        function VisitCreateSeries(const Node: ICreateSeriesNode): TJSONObject; override;
        function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TJSONObject; override;
        function VisitSeriesLength(const Node: ISeriesLengthNode): TJSONObject; override;
        function VisitNop(const Node: INopNode): TJSONObject; override;

        function Serialize(const RootNode: IAstNode): TJSONObject;
        function Deserialize(const AJson: TJSONObject): IAstNode;
    public
        constructor Create;
    end;

implementation

uses
    Myc.Data.Keyword;

{ TJsonAstConverter }

constructor TJsonAstConverter.Create;
begin
    inherited;
end;

function TJsonAstConverter.Deserialize(const AJson: TJSONObject): IAstNode;
begin
    Result := JsonToNode(AJson);
end;

{ TJsonAstConverter - IAstVisitor for Serialization }

procedure TJsonAstConverter.DataValueToJson(const AValue: TDataValue; const AParent: TJSONObject; const AName: string);
var
    valObj, scalarObj: TJSONObject;
    kwName: string;
begin
    valObj := TJSONObject.Create;
    valObj.AddPair('Kind', TJSONString.Create(AValue.Kind.ToString));

    case AValue.Kind of
        vkScalar:
        begin
            scalarObj := TJSONObject.Create;
            scalarObj.AddPair('Kind', TJSONString.Create(AValue.AsScalar.Kind.ToString));
            case AValue.AsScalar.Kind of
                TScalar.TKind.Ordinal: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsInt64));
                TScalar.TKind.Float: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsDouble));
                TScalar.TKind.Boolean: scalarObj.AddPair('Value', TJSONBool.Create(AValue.AsScalar.Value.AsInt64 <> 0));
                TScalar.TKind.DateTime: scalarObj.AddPair('Value', TJSONNumber.Create(AValue.AsScalar.Value.AsDouble));
                TScalar.TKind.Keyword:
                begin
                    // Use reverse map to get name from index (AsInt64)
                    kwName := TKeywordRegistry.GetName(AValue.AsScalar.Value.AsInt64);
                    scalarObj.AddPair('Value', TJSONString.Create(kwName));
                end;
            end;
            valObj.AddPair('Value', scalarObj);
        end;
        vkText: valObj.AddPair('Value', TJSONString.Create(AValue.AsText));
        vkVoid:; // No value to add
    else
        raise ENotSupportedException.Create('Unsupported TDataValue kind for constant serialization.');
    end;

    AParent.AddPair(AName, valObj);
end;

function TJsonAstConverter.VisitConstant(const Node: IConstantNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Constant'));
    DataValueToJson(Node.Value, Result, 'Value');
end;

function TJsonAstConverter.VisitIdentifier(const Node: IIdentifierNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Identifier'));
    Result.AddPair('Name', TJSONString.Create(Node.Name));
end;

function TJsonAstConverter.VisitKeyword(const Node: IKeywordNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Keyword'));
    Result.AddPair('Name', TJSONString.Create(Node.Value.Name));
end;

function TJsonAstConverter.VisitIfExpression(const Node: IIfExpressionNode): TJSONObject;
var
    condObj, thenObj, elseObj: TJSONObject;
begin
    condObj := Accept(Node.Condition);
    thenObj := Accept(Node.ThenBranch);
    elseObj := Accept(Node.ElseBranch); // Accept handles nil

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('IfExpr'));
    Result.AddPair('Condition', condObj);
    Result.AddPair('ThenBranch', thenObj);

    if Assigned(elseObj) then
        Result.AddPair('ElseBranch', elseObj)
    else
        Result.AddPair('ElseBranch', TJSONNull.Create);
end;

function TJsonAstConverter.VisitCondExpression(const Node: ICondExpressionNode): TJSONObject;
var
    pairsArray: TJSONArray;
    pairObj: TJSONObject;
    elseObj: TJSONObject;
    i: Integer;
begin
    pairsArray := TJSONArray.Create;
    for i := 0 to High(Node.Pairs) do
    begin
        pairObj := TJSONObject.Create;
        pairObj.AddPair('Condition', Accept(Node.Pairs[i].Condition));
        pairObj.AddPair('Branch', Accept(Node.Pairs[i].Branch));
        pairsArray.Add(pairObj);
    end;

    elseObj := Accept(Node.ElseBranch); // Accept handles nil

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('CondExpr'));
    Result.AddPair('Pairs', pairsArray);

    if Assigned(elseObj) then
        Result.AddPair('ElseBranch', elseObj)
    else
        Result.AddPair('ElseBranch', TJSONNull.Create);
end;

function TJsonAstConverter.VisitLambdaExpression(const Node: ILambdaExpressionNode): TJSONObject;
var
    bodyObj: TJSONObject;
    paramsArray: TJSONArray;
    i: Integer;
begin
    bodyObj := Accept(Node.Body);

    paramsArray := TJSONArray.Create;
    for i := 0 to Node.Parameters.Count - 1 do
        paramsArray.Add(Accept(Node.Parameters[i]));

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('LambdaExpr'));
    Result.AddPair('Parameters', paramsArray);
    Result.AddPair('Body', bodyObj);
end;

function TJsonAstConverter.VisitMacroDefinition(const Node: IMacroDefinitionNode): TJSONObject;
var
    nameObj, bodyObj: TJSONObject;
    paramsArray: TJSONArray;
    i: Integer;
begin
    nameObj := Accept(Node.Name);
    bodyObj := Accept(Node.Body);

    paramsArray := TJSONArray.Create;
    for i := 0 to Node.Parameters.Count - 1 do
        paramsArray.Add(Accept(Node.Parameters[i]));

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('MacroDef'));
    Result.AddPair('Name', nameObj);
    Result.AddPair('Parameters', paramsArray);
    Result.AddPair('Body', bodyObj);
end;

function TJsonAstConverter.VisitQuasiquote(const Node: IQuasiquoteNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Quasiquote'));
    Result.AddPair('Expression', Accept(Node.Expression));
end;

function TJsonAstConverter.VisitUnquote(const Node: IUnquoteNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Unquote'));
    Result.AddPair('Expression', Accept(Node.Expression));
end;

function TJsonAstConverter.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('UnquoteSplicing'));
    Result.AddPair('Expression', Accept(Node.Expression));
end;

function TJsonAstConverter.VisitFunctionCall(const Node: IFunctionCallNode): TJSONObject;
var
    calleeObj: TJSONObject;
    argsArray: TJSONArray;
    i: Integer;
begin
    calleeObj := Accept(Node.Callee);

    argsArray := TJSONArray.Create;
    for i := 0 to Node.Arguments.Count - 1 do
        argsArray.Add(Accept(Node.Arguments[i]));

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('FunctionCall'));
    Result.AddPair('Callee', calleeObj);
    Result.AddPair('Arguments', argsArray);
end;

function TJsonAstConverter.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TJSONObject;
var
    calleeObj, bodyObj: TJSONObject;
    argsArray: TJSONArray;
    i: Integer;
begin
    calleeObj := Accept(Node.CallNode.Callee);
    bodyObj := Accept(Node.ExpandedBody);

    argsArray := TJSONArray.Create;
    for i := 0 to Node.CallNode.Arguments.Count - 1 do
        argsArray.Add(Accept(Node.CallNode.Arguments[i]));

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('MacroExpansion'));
    Result.AddPair('Callee', calleeObj);
    Result.AddPair('Arguments', argsArray);
    Result.AddPair('ExpandedBody', bodyObj);
end;

function TJsonAstConverter.VisitRecurNode(const Node: IRecurNode): TJSONObject;
var
    argsArray: TJSONArray;
    i: Integer;
begin
    argsArray := TJSONArray.Create;
    for i := 0 to Node.Arguments.Count - 1 do
        argsArray.Add(Accept(Node.Arguments[i]));

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Recur'));
    Result.AddPair('Arguments', argsArray);
end;

function TJsonAstConverter.VisitBlockExpression(const Node: IBlockExpressionNode): TJSONObject;
var
    exprsArray: TJSONArray;
    i: Integer;
begin
    exprsArray := TJSONArray.Create;
    for i := 0 to Node.Expressions.Count - 1 do
        exprsArray.Add(Accept(Node.Expressions[i]));

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Block'));
    Result.AddPair('Expressions', exprsArray);
end;

function TJsonAstConverter.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TJSONObject;
var
    identObj, initObj: TJSONObject;
begin
    identObj := Accept(Node.Target);
    initObj := Accept(Node.Initializer); // Accept handles nil

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('VarDecl'));
    Result.AddPair('Identifier', identObj);

    if Assigned(initObj) then
        Result.AddPair('Initializer', initObj)
    else
        Result.AddPair('Initializer', TJSONNull.Create);
end;

function TJsonAstConverter.VisitAssignment(const Node: IAssignmentNode): TJSONObject;
var
    identObj, valueObj: TJSONObject;
begin
    identObj := Accept(Node.Target);
    valueObj := Accept(Node.Value);

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Assignment'));
    Result.AddPair('Identifier', identObj);
    Result.AddPair('Value', valueObj);
end;

function TJsonAstConverter.VisitIndexer(const Node: IIndexerNode): TJSONObject;
var
    baseObj, indexObj: TJSONObject;
begin
    baseObj := Accept(Node.Base);
    indexObj := Accept(Node.Index);

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Indexer'));
    Result.AddPair('Base', baseObj);
    Result.AddPair('Index', indexObj);
end;

function TJsonAstConverter.VisitMemberAccess(const Node: IMemberAccessNode): TJSONObject;
var
    baseObj, memberObj: TJSONObject;
begin
    baseObj := Accept(Node.Base);
    memberObj := Accept(Node.Member);

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('MemberAccess'));
    Result.AddPair('Base', baseObj);
    Result.AddPair('Member', memberObj);
end;

function TJsonAstConverter.VisitRecordLiteral(const Node: IRecordLiteralNode): TJSONObject;
var
    fieldsArray: TJSONArray;
    i: Integer;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('RecordLiteral'));

    fieldsArray := TJSONArray.Create;
    for i := 0 to Node.Fields.Count - 1 do
    begin
        // We visit the RecordFieldNode, which returns a JSON Object
        fieldsArray.Add(Accept(Node.Fields[i]));
    end;

    Result.AddPair('Fields', fieldsArray);
end;

function TJsonAstConverter.VisitRecordField(const Node: IRecordFieldNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('Name', TJSONString.Create(Node.Key.Value.Name));
    Result.AddPair('Value', Accept(Node.Value));
end;

function TJsonAstConverter.VisitCreateSeries(const Node: ICreateSeriesNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('CreateSeries'));
    Result.AddPair('Definition', TJSONString.Create(Node.Definition));
end;

function TJsonAstConverter.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TJSONObject;
var
    seriesObj, valueObj, lookbackObj: TJSONObject;
begin
    seriesObj := Accept(Node.Series);
    valueObj := Accept(Node.Value);
    lookbackObj := Accept(Node.Lookback); // Accept handles nil

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('AddSeriesItem'));
    Result.AddPair('Series', seriesObj);
    Result.AddPair('Value', valueObj);

    if Assigned(lookbackObj) then
        Result.AddPair('Lookback', lookbackObj)
    else
        Result.AddPair('Lookback', TJSONNull.Create);
end;

function TJsonAstConverter.VisitSeriesLength(const Node: ISeriesLengthNode): TJSONObject;
var
    seriesObj: TJSONObject;
begin
    seriesObj := Accept(Node.Series);

    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('SeriesLength'));
    Result.AddPair('Series', seriesObj);
end;

function TJsonAstConverter.VisitNop(const Node: INopNode): TJSONObject;
begin
    Result := TJSONObject.Create;
    Result.AddPair('NodeType', TJSONString.Create('Nop'));
end;

// --- List Visitors (Placeholder return) ---

function TJsonAstConverter.VisitParameterList(const Node: IParameterList): TJSONObject;
begin
    Result := nil;
end;

function TJsonAstConverter.VisitArgumentList(const Node: IArgumentList): TJSONObject;
begin
    Result := nil;
end;

function TJsonAstConverter.VisitExpressionList(const Node: IExpressionList): TJSONObject;
begin
    Result := nil;
end;

function TJsonAstConverter.VisitRecordFieldList(const Node: IRecordFieldList): TJSONObject;
begin
    Result := nil;
end;

// ------------------------------------------------------------------------------------------------
// Deserialization
// ------------------------------------------------------------------------------------------------

function TJsonAstConverter.JsonToDataValue(const AObj: TJSONObject; const AName: string): TDataValue;
var
    valObj, scalarObj: TJSONObject;
    kindStr: string;
    scalarKind: TScalar.TKind;
begin
    valObj := AObj.GetValue<TJSONObject>(AName);
    kindStr := valObj.GetValue<string>('Kind');

    if SameText(kindStr, 'Scalar') then
    begin
        scalarObj := valObj.GetValue<TJSONObject>('Value');
        kindStr := scalarObj.GetValue<string>('Kind');
        scalarKind := TScalar.StringToKind(kindStr);
        case scalarKind of
            TScalar.TKind.Ordinal: Result := TScalar.FromInt64(scalarObj.GetValue<TJSONNumber>('Value').AsInt64);
            TScalar.TKind.Float: Result := TScalar.FromDouble(scalarObj.GetValue<TJSONNumber>('Value').AsDouble);
            TScalar.TKind.Boolean: Result := TScalar.FromBoolean(scalarObj.GetValue<TJSONBool>('Value').AsBoolean);
            TScalar.TKind.DateTime: Result := TScalar.FromDateTime(scalarObj.GetValue<TJSONNumber>('Value').AsDouble);
            TScalar.TKind.Keyword:
                // Keywords are serialized by name, intern them back
                Result := TScalar.FromKeyword(TKeywordRegistry.Intern(scalarObj.GetValue<string>('Value')));
        end;
    end
    else if SameText(kindStr, 'Text') then
    begin
        Result := valObj.GetValue<string>('Value');
    end
    else if SameText(kindStr, 'Void') then
    begin
        Result := TDataValue.Void;
    end
    else
        raise ENotSupportedException.Create('Unsupported TDataValue kind for constant deserialization.');
end;

function TJsonAstConverter.JsonToConstantNode(const AObj: TJSONObject): IConstantNode;
begin
    Result := TAst.Constant(JsonToDataValue(AObj, 'Value'));
end;

function TJsonAstConverter.JsonToIdentifierNode(const AObj: TJSONObject): IIdentifierNode;
begin
    Result := TAst.Identifier(AObj.GetValue<string>('Name'));
end;

function TJsonAstConverter.JsonToKeywordNode(const AObj: TJSONObject): IKeywordNode;
begin
    Result := TAst.Keyword(AObj.GetValue<string>('Name'));
end;

function TJsonAstConverter.JsonToIfExprNode(const AObj: TJSONObject): IIfExpressionNode;
var
    condNode, thenNode, elseNode: IAstNode;
begin
    condNode := JsonToNode(AObj.GetValue('Condition'));
    thenNode := JsonToNode(AObj.GetValue('ThenBranch'));
    if not (AObj.GetValue('ElseBranch') is TJSONNull) then
        elseNode := JsonToNode(AObj.GetValue('ElseBranch'))
    else
        elseNode := nil;
    Result := TAst.IfExpr(condNode, thenNode, elseNode);
end;

function TJsonAstConverter.JsonToCondExprNode(const AObj: TJSONObject): ICondExpressionNode;
var
    pairsArray: TJSONArray;
    pairs: TArray<TCondPair>;
    elseNode: IAstNode;
    i: Integer;
    pairObj: TJSONObject;
begin
    pairsArray := AObj.GetValue<TJSONArray>('Pairs');
    SetLength(pairs, pairsArray.Count);
    for i := 0 to pairsArray.Count - 1 do
    begin
        pairObj := pairsArray.Items[i] as TJSONObject;
        pairs[i] := TCondPair.Create(JsonToNode(pairObj.GetValue('Condition')), JsonToNode(pairObj.GetValue('Branch')));
    end;

    if not (AObj.GetValue('ElseBranch') is TJSONNull) then
        elseNode := JsonToNode(AObj.GetValue('ElseBranch'))
    else
        elseNode := nil;

    Result := TAst.CondExpr(pairs, elseNode);
end;

function TJsonAstConverter.JsonToTernaryExprNode(const AObj: TJSONObject): IAstNode;
var
    condNode, thenNode, elseNode: IAstNode;
begin
    condNode := JsonToNode(AObj.GetValue('Condition'));
    thenNode := JsonToNode(AObj.GetValue('ThenBranch'));
    elseNode := JsonToNode(AObj.GetValue('ElseBranch'));

    // Map to CondExpr using the helper in TAst
    Result := TAst.TernaryExpr(condNode, thenNode, elseNode);
end;

function TJsonAstConverter.JsonToLambdaExprNode(const AObj: TJSONObject): ILambdaExpressionNode;
var
    params: TArray<IIdentifierNode>;
    body: IAstNode;
    paramArray: TJSONArray;
    i: Integer;
begin
    paramArray := AObj.GetValue<TJSONArray>('Parameters');
    SetLength(params, paramArray.Count);
    for i := 0 to paramArray.Count - 1 do
        params[i] := JsonToIdentifierNode(paramArray.Items[i] as TJSONObject);

    body := JsonToNode(AObj.GetValue('Body'));
    Result := TAst.LambdaExpr(params, body);
end;

function TJsonAstConverter.JsonToMacroDefNode(const AObj: TJSONObject): IMacroDefinitionNode;
var
    name: IIdentifierNode;
    params: TArray<IIdentifierNode>;
    body: IAstNode;
    paramArray: TJSONArray;
    i: Integer;
begin
    name := JsonToIdentifierNode(AObj.GetValue('Name') as TJSONObject);

    paramArray := AObj.GetValue<TJSONArray>('Parameters');
    SetLength(params, paramArray.Count);
    for i := 0 to paramArray.Count - 1 do
        params[i] := JsonToIdentifierNode(paramArray.Items[i] as TJSONObject);

    body := JsonToNode(AObj.GetValue('Body'), 'Quasiquote');
    Result := TAst.MacroDef(name, params, body);
end;

function TJsonAstConverter.JsonToMacroExpansionNode(const AObj: TJSONObject): IMacroExpansionNode;
var
    callee, expandedBody: IAstNode;
    args: TArray<IAstNode>;
    argsArray: TJSONArray;
    i: Integer;
    tempCallNode: IFunctionCallNode;
begin
    callee := JsonToNode(AObj.GetValue('Callee'));
    expandedBody := JsonToNode(AObj.GetValue('ExpandedBody'));
    argsArray := AObj.GetValue<TJSONArray>('Arguments');
    SetLength(args, argsArray.Count);
    for i := 0 to argsArray.Count - 1 do
        args[i] := JsonToNode(argsArray.Items[i]);

    tempCallNode := TAst.FunctionCall(callee, args);
    Result := TAst.MacroExpansionNode(tempCallNode, expandedBody);
end;

function TJsonAstConverter.JsonToQuasiquoteNode(const AObj: TJSONObject): IQuasiquoteNode;
begin
    Result := TAst.Quasiquote(JsonToNode(AObj.GetValue('Expression')));
end;

function TJsonAstConverter.JsonToUnquoteNode(const AObj: TJSONObject): IUnquoteNode;
begin
    Result := TAst.Unquote(JsonToNode(AObj.GetValue('Expression')));
end;

function TJsonAstConverter.JsonToUnquoteSplicingNode(const AObj: TJSONObject): IUnquoteSplicingNode;
begin
    Result := TAst.UnquoteSplicing(JsonToNode(AObj.GetValue('Expression')).AsQuasiquote);
end;

function TJsonAstConverter.JsonToFunctionCallNode(const AObj: TJSONObject): IFunctionCallNode;
var
    callee: IAstNode;
    args: TArray<IAstNode>;
    argsArray: TJSONArray;
    i: Integer;
begin
    callee := JsonToNode(AObj.GetValue('Callee'));
    argsArray := AObj.GetValue<TJSONArray>('Arguments');
    SetLength(args, argsArray.Count);
    for i := 0 to argsArray.Count - 1 do
        args[i] := JsonToNode(argsArray.Items[i]);

    Result := TAst.FunctionCall(callee, args);
end;

function TJsonAstConverter.JsonToRecurNode(const AObj: TJSONObject): IRecurNode;
var
    args: TArray<IAstNode>;
    argsArray: TJSONArray;
    i: Integer;
begin
    argsArray := AObj.GetValue<TJSONArray>('Arguments');
    SetLength(args, argsArray.Count);
    for i := 0 to argsArray.Count - 1 do
        args[i] := JsonToNode(argsArray.Items[i]);

    Result := TAst.Recur(args);
end;

function TJsonAstConverter.JsonToBlockNode(const AObj: TJSONObject): IBlockExpressionNode;
var
    expressions: TArray<IAstNode>;
    exprsArray: TJSONArray;
    i: Integer;
begin
    exprsArray := AObj.GetValue<TJSONArray>('Expressions');
    SetLength(expressions, exprsArray.Count);
    for i := 0 to exprsArray.Count - 1 do
        expressions[i] := JsonToNode(exprsArray.Items[i]);

    Result := TAst.Block(expressions);
end;

function TJsonAstConverter.JsonToVarDeclNode(const AObj: TJSONObject): IVariableDeclarationNode;
var
    ident: IIdentifierNode;
    initializer: IAstNode;
begin
    ident := JsonToIdentifierNode(AObj.GetValue('Identifier') as TJSONObject);
    if not (AObj.GetValue('Initializer') is TJSONNull) then
        initializer := JsonToNode(AObj.GetValue('Initializer'))
    else
        initializer := nil;

    Result := TAst.VarDecl(ident, initializer);
end;

function TJsonAstConverter.JsonToAssignmentNode(const AObj: TJSONObject): IAssignmentNode;
var
    ident: IIdentifierNode;
    value: IAstNode;
begin
    ident := JsonToIdentifierNode(AObj.GetValue('Identifier') as TJSONObject);
    value := JsonToNode(AObj.GetValue('Value'));
    Result := TAst.Assign(ident, value);
end;

function TJsonAstConverter.JsonToIndexerNode(const AObj: TJSONObject): IIndexerNode;
begin
    Result := TAst.Indexer(JsonToNode(AObj.GetValue('Base')), JsonToNode(AObj.GetValue('Index')));
end;

function TJsonAstConverter.JsonToMemberAccessNode(const AObj: TJSONObject): IMemberAccessNode;
begin
    Result := TAst.MemberAccess(JsonToNode(AObj.GetValue('Base')), JsonToKeywordNode(AObj.GetValue('Member') as TJSONObject));
end;

function TJsonAstConverter.JsonToRecordLiteralNode(const AObj: TJSONObject): IRecordLiteralNode;
var
    fields: TArray<IRecordFieldNode>;
    fieldsArray: TJSONArray;
    fieldObj: TJSONObject;
    i: Integer;
    keyNode: IKeywordNode;
    valueNode: IAstNode;
begin
    fieldsArray := AObj.GetValue<TJSONArray>('Fields');
    SetLength(fields, fieldsArray.Count);

    for i := 0 to fieldsArray.Count - 1 do
    begin
        fieldObj := fieldsArray.Items[i] as TJSONObject;
        keyNode := TAst.Keyword(fieldObj.GetValue<string>('Name'));
        valueNode := JsonToNode(fieldObj.GetValue('Value'));

        fields[i] := TAst.RecordField(keyNode, valueNode);
    end;

    Result := TAst.RecordLiteral(fields);
end;

function TJsonAstConverter.JsonToCreateSeriesNode(const AObj: TJSONObject): ICreateSeriesNode;
begin
    Result := TAst.CreateSeries(AObj.GetValue<string>('Definition'));
end;

function TJsonAstConverter.JsonToAddSeriesItemNode(const AObj: TJSONObject): IAddSeriesItemNode;
var
    lookbackNode: IAstNode;
begin
    if not (AObj.GetValue('Lookback') is TJSONNull) then
        lookbackNode := JsonToNode(AObj.GetValue('Lookback'))
    else
        lookbackNode := nil;

    Result :=
        TAst.AddSeriesItem(JsonToIdentifierNode(AObj.GetValue('Series') as TJSONObject), JsonToNode(AObj.GetValue('Value')), lookbackNode);
end;

function TJsonAstConverter.JsonToSeriesLengthNode(const AObj: TJSONObject): ISeriesLengthNode;
begin
    Result := TAst.SeriesLength(JsonToIdentifierNode(AObj.GetValue('Series') as TJSONObject));
end;

function TJsonAstConverter.JsonToNopNode(const AObj: TJSONObject): INopNode;
begin
    Result := TAst.Nop.AsNop;
end;

function TJsonAstConverter.JsonToNode(const AJson: TJSONValue; const ExpectedType: String = ''): IAstNode;
var
    obj: TJSONObject;
    nodeType: string;
begin
    if not (AJson is TJSONObject) then
        raise EInvalidCast.Create('Expected a JSON object for node deserialization.');

    obj := AJson as TJSONObject;
    nodeType := obj.GetValue<string>('NodeType');

    if (ExpectedType <> '') and (nodeType <> ExpectedType) then
        raise EInvalidCast.CreateFmt('Expected a JSON object of type %s for deserialization, but got a %s.', [ExpectedType, nodeType]);

    if nodeType = 'Constant' then
        Result := JsonToConstantNode(obj)
    else if nodeType = 'Identifier' then
        Result := JsonToIdentifierNode(obj)
    else if nodeType = 'Keyword' then
        Result := JsonToKeywordNode(obj)
    else if nodeType = 'IfExpr' then
        Result := JsonToIfExprNode(obj)
    else if nodeType = 'CondExpr' then
        Result := JsonToCondExprNode(obj)
    else if nodeType = 'TernaryExpr' then
        Result := JsonToTernaryExprNode(obj) // Backward Compatibility
    else if nodeType = 'LambdaExpr' then
        Result := JsonToLambdaExprNode(obj)
    else if nodeType = 'MacroDef' then
        Result := JsonToMacroDefNode(obj)
    else if nodeType = 'MacroExpansion' then
        Result := JsonToMacroExpansionNode(obj)
    else if nodeType = 'Quasiquote' then
        Result := JsonToQuasiquoteNode(obj)
    else if nodeType = 'Unquote' then
        Result := JsonToUnquoteNode(obj)
    else if nodeType = 'UnquoteSplicing' then
        Result := JsonToUnquoteSplicingNode(obj)
    else if nodeType = 'FunctionCall' then
        Result := JsonToFunctionCallNode(obj)
    else if nodeType = 'Recur' then
        Result := JsonToRecurNode(obj)
    else if nodeType = 'Block' then
        Result := JsonToBlockNode(obj)
    else if nodeType = 'VarDecl' then
        Result := JsonToVarDeclNode(obj)
    else if nodeType = 'Assignment' then
        Result := JsonToAssignmentNode(obj)
    else if nodeType = 'Indexer' then
        Result := JsonToIndexerNode(obj)
    else if nodeType = 'MemberAccess' then
        Result := JsonToMemberAccessNode(obj)
    else if nodeType = 'RecordLiteral' then
        Result := JsonToRecordLiteralNode(obj)
    else if nodeType = 'CreateSeries' then
        Result := JsonToCreateSeriesNode(obj)
    else if nodeType = 'AddSeriesItem' then
        Result := JsonToAddSeriesItemNode(obj)
    else if nodeType = 'SeriesLength' then
        Result := JsonToSeriesLengthNode(obj)
    else if nodeType = 'Nop' then
        Result := JsonToNopNode(obj)
    else
        raise ENotSupportedException.CreateFmt('Unsupported NodeType "%s" for JSON deserialization.', [nodeType]);
end;

function TJsonAstConverter.Serialize(const RootNode: IAstNode): TJSONObject;
begin
    if not Assigned(RootNode) then
        exit(nil);

    // Call Accept, which returns TDataValue(vkGeneric(TJSONObject))
    // and unwrap it.
    Result := RootNode.Accept(Self).AsGeneric<TJSONObject>;
end;

end.
