unit Myc.Ast.Evaluator;

interface

uses
    System.SysUtils,
    System.Classes,
    System.Generics.Collections,
    Myc.Data.Scalar,
    Myc.Data.Value,
    Myc.Data.Keyword,
    Myc.Ast.Nodes,
    Myc.Ast.Scope,
    Myc.Ast;

type
    EEvaluatorException = class(EAstException);
    TEvaluatorFactory = reference to function(const AScope: IExecutionScope): IEvaluatorVisitor;

    TEvaluatorVisitor = class(TInterfacedObject, IAstVisitor, IEvaluatorVisitor)
    private
        FScope: IExecutionScope;
    protected
        function Visit(const Node: IAstNode): TDataValue; virtual;
        function CreateVisitorFactory: TEvaluatorFactory; virtual;

        function VisitConstant(const N: IConstantNode): TDataValue; virtual;
        function VisitIdentifier(const N: IIdentifierNode): TDataValue; virtual;
        function VisitKeyword(const N: IKeywordNode): TDataValue; virtual;
        function VisitParameterList(const N: IParameterList): TDataValue; virtual;
        function VisitArgumentList(const N: IArgumentList): TDataValue; virtual;
        function VisitExpressionList(const N: IExpressionList): TDataValue; virtual;
        function VisitRecordFieldList(const N: IRecordFieldList): TDataValue; virtual;
        function VisitRecordField(const N: IRecordFieldNode): TDataValue; virtual;
        function VisitIfExpression(const N: IIfExpressionNode): TDataValue; virtual;
        function VisitCondExpression(const N: ICondExpressionNode): TDataValue; virtual;
        function VisitLambdaExpression(const N: ILambdaExpressionNode): TDataValue; virtual;
        function VisitFunctionCall(const N: IFunctionCallNode): TDataValue; virtual;
        function VisitMacroExpansionNode(const N: IMacroExpansionNode): TDataValue; virtual;
        function VisitBlockExpression(const N: IBlockExpressionNode): TDataValue; virtual;
        function VisitVariableDeclaration(const N: IVariableDeclarationNode): TDataValue; virtual;
        function VisitAssignment(const N: IAssignmentNode): TDataValue; virtual;
        function VisitMacroDefinition(const N: IMacroDefinitionNode): TDataValue; virtual;
        function VisitQuasiquote(const N: IQuasiquoteNode): TDataValue; virtual;
        function VisitUnquote(const N: IUnquoteNode): TDataValue; virtual;
        function VisitUnquoteSplicing(const N: IUnquoteSplicingNode): TDataValue; virtual;
        function VisitIndexer(const N: IIndexerNode): TDataValue; virtual;
        function VisitMemberAccess(const N: IMemberAccessNode): TDataValue; virtual;
        function VisitRecordLiteral(const N: IRecordLiteralNode): TDataValue; virtual;
        function VisitCreateSeries(const N: ICreateSeriesNode): TDataValue; virtual;
        function VisitAddSeriesItem(const N: IAddSeriesItemNode): TDataValue; virtual;
        function VisitSeriesLength(const N: ISeriesLengthNode): TDataValue; virtual;
        function VisitRecurNode(const N: IRecurNode): TDataValue; virtual;
        function VisitNop(const N: INopNode): TDataValue; virtual;
        function VisitPipeInput(const N: IPipeInputNode): TDataValue; virtual;
        function VisitPipeSelectorList(const N: IPipeSelectorList): TDataValue; virtual;
        function VisitPipeInputList(const N: IPipeInputList): TDataValue; virtual;
        function VisitPipe(const N: IPipeNode): TDataValue; virtual;

        function IsTruthy(const AValue: TDataValue): Boolean; inline;
    public
        constructor Create(const AScope: IExecutionScope);
        function Execute(const RootNode: IAstNode): TDataValue;
        class procedure HandleTCO(var ResultValue: TDataValue); static;
        property Scope: IExecutionScope read FScope;
    end;

implementation

uses
    System.TypInfo,
    System.Generics.Defaults,
    Myc.Data.Decimal,
    Myc.Data.Series,
    Myc.Data.Stream,
    Myc.Data.Stream.Pipes,
    Myc.Data.Scalar.JSON,
    Myc.Ast.Types;

type
    TThunk = record
        Callee: TDataValue;
        Args: TArray<TDataValue>;
        Recur: Boolean;
        constructor Create(const ACallee: TDataValue; const AArgs: TArray<TDataValue>; ARecur: Boolean);
    end;

constructor TThunk.Create(const ACallee: TDataValue; const AArgs: TArray<TDataValue>; ARecur: Boolean);
begin
    Callee := ACallee;
    Args := AArgs;
    Recur := ARecur;
end;

constructor TEvaluatorVisitor.Create(const AScope: IExecutionScope);
begin
    inherited Create;
    FScope := AScope;
end;

function TEvaluatorVisitor.Visit(const Node: IAstNode): TDataValue;
begin
    case Node.Kind of
        akConstant: Result := VisitConstant(Node.AsConstant);
        akIdentifier: Result := VisitIdentifier(Node.AsIdentifier);
        akKeyword: Result := VisitKeyword(Node.AsKeyword);
        akIfExpression: Result := VisitIfExpression(Node.AsIfExpression);
        akCondExpression: Result := VisitCondExpression(Node.AsCondExpression);
        akLambdaExpression: Result := VisitLambdaExpression(Node.AsLambdaExpression);
        akFunctionCall: Result := VisitFunctionCall(Node.AsFunctionCall);
        akMacroExpansion: Result := VisitMacroExpansionNode(Node.AsMacroExpansion);
        akBlockExpression: Result := VisitBlockExpression(Node.AsBlockExpression);
        akVariableDeclaration: Result := VisitVariableDeclaration(Node.AsVariableDeclaration);
        akAssignment: Result := VisitAssignment(Node.AsAssignment);
        akIndexer: Result := VisitIndexer(Node.AsIndexer);
        akMemberAccess: Result := VisitMemberAccess(Node.AsMemberAccess);
        akRecordLiteral: Result := VisitRecordLiteral(Node.AsRecordLiteral);
        akCreateSeries: Result := VisitCreateSeries(Node.AsCreateSeries);
        akAddSeriesItem: Result := VisitAddSeriesItem(Node.AsAddSeriesItem);
        akSeriesLength: Result := VisitSeriesLength(Node.AsSeriesLength);
        akRecur: Result := VisitRecurNode(Node.AsRecur);
        akNop: Result := VisitNop(Node.AsNop);
        akPipe: Result := VisitPipe(Node.AsPipe);
        akExpressionList: Result := VisitExpressionList(Node.AsExpressionList);
    else
        Result := TDataValue.Void;
    end;
end;

class procedure TEvaluatorVisitor.HandleTCO(var ResultValue: TDataValue);
begin
    while ResultValue.Kind = vkGeneric do
    begin
        var thunk := ResultValue.AsGeneric<TThunk>;
        var callee := thunk.Callee.AsMethod();
        ResultValue := callee(thunk.Args);
    end;
end;

function TEvaluatorVisitor.Execute(const RootNode: IAstNode): TDataValue;
begin
    if not Assigned(RootNode) then
        exit(TDataValue.Void);
    try
        Result := Visit(RootNode);
        HandleTCO(Result);
    except
        on E: EAstException do
            raise;
        on E: Exception do
            raise EEvaluatorException.Create('Runtime: ' + E.Message);
    end;
end;

function TEvaluatorVisitor.IsTruthy(const AValue: TDataValue): Boolean;
begin
    if (AValue.Kind <> vkScalar) then
        exit(false);
    case AValue.AsScalar.Kind of
        TScalar.TKind.Ordinal, TScalar.TKind.Keyword, TScalar.TKind.Boolean: Result := AValue.AsScalar.Value.AsInt64 <> 0;
        TScalar.TKind.Float, TScalar.TKind.DateTime: Result := AValue.AsScalar.Value.AsDouble <> 0.0;
    else
        Result := false;
    end;
end;

function TEvaluatorVisitor.CreateVisitorFactory: TEvaluatorFactory;
begin
    Result := function(const AScope: IExecutionScope): IEvaluatorVisitor begin Result := TEvaluatorVisitor.Create(AScope); end;
end;

// --- Implementations ---

function TEvaluatorVisitor.VisitConstant(const N: IConstantNode): TDataValue;
begin
    Result := N.Value;
end;

function TEvaluatorVisitor.VisitKeyword(const N: IKeywordNode): TDataValue;
begin
    Result := TDataValue(TScalar.FromKeyword(N.Value));
end;

function TEvaluatorVisitor.VisitIdentifier(const N: IIdentifierNode): TDataValue;
begin
    Result := FScope[N.Address];
end;

function TEvaluatorVisitor.VisitLambdaExpression(const N: ILambdaExpressionNode): TDataValue;
var
    capturedCells: TArray<IValueCell>;
    i: Integer;
    closureScope: IExecutionScope;
    visitorFactory: TEvaluatorFactory;
begin
    if Length(N.Upvalues) > 0 then
    begin
        SetLength(capturedCells, Length(N.Upvalues));
        for i := 0 to High(N.Upvalues) do
            capturedCells[i] := FScope.Capture(N.Upvalues[i]);
    end
    else
        capturedCells := nil;

    closureScope :=
        if N.HasNestedLambdas then FScope
        else nil;
    visitorFactory := CreateVisitorFactory();

    var descriptor := N.Descriptor;
    var params := N.Parameters;

    var [unsafe] closure: TDataValue.TFunc;
    closure :=
        function(const ArgValues: TArray<TDataValue>): TDataValue
        var
            lambdaScope: IExecutionScope;
            bodyVisitor: IAstVisitor;
            k: Integer;
        begin
            if (Length(ArgValues) <> params.Count) then
                raise EEvaluatorException.Create('Arg mismatch');
            lambdaScope := TScope.CreateScope(closureScope, descriptor, capturedCells);

            // Self-reference for recursion
            lambdaScope.SetValues(TResolvedAddress.Create(akLocalOrParent, 0, 0), TDataValue(closure));

            for k := 0 to params.Count - 1 do
                lambdaScope[params[k].Address] := ArgValues[k];

            bodyVisitor := visitorFactory(lambdaScope);
            Result := bodyVisitor.Visit(N.Body);
        end;
    Result := TDataValue(closure);
end;

function TEvaluatorVisitor.VisitFunctionCall(const N: IFunctionCallNode): TDataValue;
var
    calleeValue: TDataValue;
    argValues: TArray<TDataValue>;
    i: Integer;
begin
    if Assigned(N.StaticTarget) then
    begin
        SetLength(argValues, N.Arguments.Count);
        for i := 0 to N.Arguments.Count - 1 do
            argValues[i] := Visit(N.Arguments[i]);

        Result := N.StaticTarget(argValues);
        if not N.IsTailCall then
            HandleTCO(Result); // Flushes stack if not tail
    end
    else
    begin
        calleeValue := Visit(N.Callee);
        if calleeValue.Kind <> vkMethod then
            raise EEvaluatorException.Create('Not a function');
        SetLength(argValues, N.Arguments.Count);
        for i := 0 to N.Arguments.Count - 1 do
            argValues[i] := Visit(N.Arguments[i]);

        if N.IsTailCall then
            Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(calleeValue, argValues, false))
        else
        begin
            Result := (calleeValue.AsMethod)(argValues);
            HandleTCO(Result);
        end;
    end;
end;

function TEvaluatorVisitor.VisitRecurNode(const N: IRecurNode): TDataValue;
var
    argValues: TArray<TDataValue>;
    i: Integer;
begin
    SetLength(argValues, N.Arguments.Count);
    for i := 0 to N.Arguments.Count - 1 do
        argValues[i] := Visit(N.Arguments[i]);
    // The "self" function is always at Slot 0
    var callee := FScope[TResolvedAddress.Create(akLocalOrParent, 0, 0)];
    Result := TDataValue.FromGeneric<TThunk>(TThunk.Create(callee, argValues, true));
end;

function TEvaluatorVisitor.VisitBlockExpression(const N: IBlockExpressionNode): TDataValue;
begin
    Result := Visit(N.Expressions);
end;

function TEvaluatorVisitor.VisitExpressionList(const N: IExpressionList): TDataValue;
var
    i: Integer;
begin
    Result := TDataValue.Void;
    for i := 0 to N.Count - 1 do
        Result := Visit(N[i]);
end;

function TEvaluatorVisitor.VisitIfExpression(const N: IIfExpressionNode): TDataValue;
begin
    if IsTruthy(Visit(N.Condition)) then
        Result := Visit(N.ThenBranch)
    else if Assigned(N.ElseBranch) then
        Result := Visit(N.ElseBranch)
    else
        Result := TDataValue.Void;
end;

function TEvaluatorVisitor.VisitCondExpression(const N: ICondExpressionNode): TDataValue;
var
    i: Integer;
begin
    for i := 0 to High(N.Pairs) do
        if IsTruthy(Visit(N.Pairs[i].Condition)) then
            exit(Visit(N.Pairs[i].Branch));
    Result := Visit(N.ElseBranch);
end;

function TEvaluatorVisitor.VisitVariableDeclaration(const N: IVariableDeclarationNode): TDataValue;
var
    ident: IIdentifierNode;
begin
    // 1. Initialisator auswerten
    if Assigned(N.Initializer) then
        Result := Visit(N.Initializer)
    else
        Result := TDataValue.Void;

    // 2. Das Target-Identifier-Interface extrahieren
    // N.Target ist IAstNode, wir brauchen IIdentifierNode für die Adresse
    ident := N.Target.AsIdentifier;

    // 3. Im Scope speichern
    if N.IsBoxed then
        FScope.DefineBoxed(ident.Address.SlotIndex, Result)
    else
        FScope[ident.Address] := Result;
end;

function TEvaluatorVisitor.VisitAssignment(const N: IAssignmentNode): TDataValue;
begin
    Result := Visit(N.Value);
    FScope[N.Target.AsIdentifier.Address] := Result;
end;

function TEvaluatorVisitor.VisitIndexer(const N: IIndexerNode): TDataValue;
var
    base, idx: TDataValue;
begin
    base := Visit(N.Base);
    if base.IsVoid then
        exit(TDataValue.Void);
    idx := Visit(N.Index);
    if base.Kind = vkSeries then
        Result := TDataValue(base.AsSeries.Items[Integer(idx.AsScalar.Value.AsInt64)])
    else if base.Kind = vkRecordSeries then
    begin
        var rs := base.AsRecordSeries;
        var vals: TArray<TScalar.TValue>;
        SetLength(vals, rs.Def.Count);
        var i64 := idx.AsScalar.Value.AsInt64;
        for var k := 0 to rs.Def.Count - 1 do
            vals[k] := rs.Fields[rs.Def.Items[k].Key].Items[Integer(i64)].Value;
        Result := TDataValue.FromScalarRecord(TScalarRecord.Create(rs.Def, vals));
    end
    else
        raise EEvaluatorException.Create('Indexer error');
end;

function TEvaluatorVisitor.VisitMemberAccess(const N: IMemberAccessNode): TDataValue;
var
    base: TDataValue;
begin
    base := Visit(N.Base);
    if base.IsVoid then
        exit(TDataValue.Void);
    case base.Kind of
        vkRecordSeries: Result := TDataValue.FromSeries(base.AsRecordSeries.Fields[N.Member.Value]);
        vkScalarRecord: Result := TDataValue(base.AsScalarRecord.Fields[N.Member.Value]);
        vkGenericRecord: Result := TDataValue(base.AsGenericRecord.Fields[N.Member.Value]);
        vkStream: Result := TDataValue.FromSeries(base.AsStream.Series.Fields[N.Member.Value]);
    else
        raise EEvaluatorException.Create('Member error');
    end;
end;

function TEvaluatorVisitor.VisitRecordLiteral(const N: IRecordLiteralNode): TDataValue;
var
    i: Integer;
begin
    if Assigned(N.ScalarDefinition) then
    begin
        var vals: TArray<TScalar.TValue>;
        SetLength(vals, N.Fields.Count);
        for i := 0 to N.Fields.Count - 1 do
            vals[i] := Visit(N.Fields[i].Value).AsScalar.Value;
        Result := TDataValue.FromScalarRecord(TScalarRecord.Create(N.ScalarDefinition, vals));
    end
    else
    begin
        var fields: TArray<TPair<IKeyword, TDataValue>>;
        SetLength(fields, N.Fields.Count);
        for i := 0 to N.Fields.Count - 1 do
            fields[i] := TPair<IKeyword, TDataValue>.Create(N.Fields[i].Key.Value, Visit(N.Fields[i].Value));
        Result := TDataValue.FromGenericRecord(TKeywordMapping<TDataValue>.Create(fields));
    end;
end;

function TEvaluatorVisitor.VisitCreateSeries(const N: ICreateSeriesNode): TDataValue;
begin
    if N.Definition.Trim.StartsWith('[') then
        Result := TDataValue.FromRecordSeries(TScalarRecordSeries.Create(TRttiAstHelper.JsonToRecordDefinition(N.Definition)))
    else
        Result := TDataValue.FromSeries(TScalarSeries.Create(TScalar.StringToKind(N.Definition)));
end;

function TEvaluatorVisitor.VisitAddSeriesItem(const N: IAddSeriesItemNode): TDataValue;
begin
    var lb := -1;
    if Assigned(N.Lookback) then
        lb := Visit(N.Lookback).AsScalar.Value.AsInt64;
    FScope[N.Series.Address].AsRecordSeries.Add(Visit(N.Value).AsScalarRecord, lb);
    Result := TDataValue.Void;
end;

function TEvaluatorVisitor.VisitSeriesLength(const N: ISeriesLengthNode): TDataValue;
begin
    var s := FScope[N.Series.Address];
    var len :=
        if s.Kind = vkSeries then s.AsSeries.Count
        else s.AsRecordSeries.RecordCount;
    Result := TDataValue(TScalar.FromInt64(len));
end;

function TEvaluatorVisitor.VisitPipe(const N: IPipeNode): TDataValue;
var
    i: Integer;
    inputNode: IPipeInputNode;
    sources: TArray<IStream>;
    config: TPipeConfig;
    inputVal: TDataValue;
    sourceSeries: IScalarRecordSeries;
    lambdaFunc: TDataValue.TFunc;
begin
    SetLength(sources, N.Inputs.Count);
    SetLength(config, N.Inputs.Count);
    for i := 0 to N.Inputs.Count - 1 do
    begin
        inputNode := N.Inputs[i];
        inputVal := FScope[inputNode.StreamSource.Address];
        if inputVal.Kind <> vkStream then
            raise EEvaluatorException.Create('Stream expected');
        sources[i] := inputVal.AsStream;
        sourceSeries := sources[i].Series;
        var srcDef := sourceSeries.Def;
        var selectors := inputNode.Selectors;
        SetLength(config[i], selectors.Count);
        for var k := 0 to selectors.Count - 1 do
        begin
            var key := selectors[k].Value;
            var idx := srcDef.IndexOf(key);
            config[i][k] := TScalarRecordField.Create(key, srcDef.Items[idx].Value);
        end;
    end;
    lambdaFunc := Visit(N.Transformation).AsMethod();
    var outputDef := N.StaticType.Definition;
    var pipeAdapter: TPipeStream.TPipeLambda :=
        function(const S: array of ISeries; out R: array of TScalar.TValue): Boolean
        var
            args: TArray<TDataValue>;
            res: TDataValue;
            k: Integer;
        begin
            SetLength(args, Length(S));
            for k := 0 to High(S) do
                args[k] := TDataValue(S[k].Items[0]);
            res := lambdaFunc(args);
            if res.IsVoid then
                exit(False);
            var rec := res.AsScalarRecord;
            for k := 0 to rec.Def.Count - 1 do
                R[k] := rec.Items[k].Value.Value;
            Result := True;
        end;
    Result := TDataValue.FromStream(TPipeStream.Create(config, outputDef, sources, pipeAdapter));
end;

function TEvaluatorVisitor.VisitMacroDefinition(const N: IMacroDefinitionNode): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitQuasiquote(const N: IQuasiquoteNode): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitUnquote(const N: IUnquoteNode): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitUnquoteSplicing(const N: IUnquoteSplicingNode): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitNop(const N: INopNode): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitParameterList(const N: IParameterList): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitArgumentList(const N: IArgumentList): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitRecordFieldList(const N: IRecordFieldList): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitRecordField(const N: IRecordFieldNode): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitPipeInput(const N: IPipeInputNode): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitPipeSelectorList(const N: IPipeSelectorList): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitPipeInputList(const N: IPipeInputList): TDataValue;
begin
    Result := TDataValue.Void;
end;
function TEvaluatorVisitor.VisitMacroExpansionNode(const N: IMacroExpansionNode): TDataValue;
begin
    Result := Visit(N.ExpandedBody);
end;

end.
