unit Myc.Ast.Debugger;

interface

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

type
    TDebugEvaluatorVisitor = class(TEvaluatorVisitor)
    private
        FLog: TStrings;
        FIndentLevel: Integer;
        FShowScope: Boolean;
        procedure Indent;
        procedure Unindent;
        procedure AppendLine(const S: string);
        procedure ShowScope;

    protected
        // Overridden to provide the debug-specific visitor factory for nested calls (Lambdas).
        function CreateVisitorFactory: TEvaluatorFactory; override;

    public
        constructor Create(const AScope: IExecutionScope; ALog: TStrings; AShowScope: Boolean; AInitialIndent: Integer = 0);

        // The logging overrides for Visit... methods
        function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
        function VisitConstant(const Node: IConstantNode): TDataValue; override;
        function VisitIdentifier(const Node: IIdentifierNode): TDataValue; override;
        function VisitKeyword(const Node: IKeywordNode): TDataValue; override;
        function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override;
        function VisitCondExpression(const Node: ICondExpressionNode): TDataValue; override; // Replaces Ternary
        function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override;
        function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override;
        function VisitAssignment(const Node: IAssignmentNode): TDataValue; override;
        function VisitIndexer(const Node: IIndexerNode): TDataValue; override;
        function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; override;
        function VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; override;
        function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; override;
        function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; override;
        function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; override;
        function VisitNop(const Node: INopNode): TDataValue; override;
    end;

implementation

uses
    System.TypInfo,
    Myc.Data.Keyword;

{ TDebugEvaluatorVisitor }

constructor TDebugEvaluatorVisitor.Create(const AScope: IExecutionScope; ALog: TStrings; AShowScope: Boolean; AInitialIndent: Integer = 0);
begin
    inherited Create(AScope);
    Assert(Assigned(ALog));
    FLog := ALog;
    FIndentLevel := AInitialIndent;
    FShowScope := AShowScope;
    ShowScope;
end;

function TDebugEvaluatorVisitor.CreateVisitorFactory: TEvaluatorFactory;
begin
    // Return a closure that creates a new Debug visitor, ensuring the debug context (Log, Indent) is passed down.
    // This is used by TEvaluatorVisitor.VisitLambdaExpression when executing the closure body.

    // Capture current state
    var currentLog := FLog;
    var currentShowScope := FShowScope;
    var currentIndent := FIndentLevel; // Pass current indent as base for new frame

    Result :=
        function(const AScope: IExecutionScope): IEvaluatorVisitor
        begin
            Result := TDebugEvaluatorVisitor.Create(AScope, currentLog, currentShowScope, currentIndent);
        end;
end;

procedure TDebugEvaluatorVisitor.Indent;
begin
    inc(FIndentLevel);
end;

procedure TDebugEvaluatorVisitor.Unindent;
begin
    dec(FIndentLevel);
end;

procedure TDebugEvaluatorVisitor.AppendLine(const S: string);
var
    pad: string;
    i: Integer;
begin
    pad := '';
    for i := 0 to FIndentLevel - 1 do
        pad := pad + ':' + ''.PadLeft(3);
    FLog.Add(pad + S);
end;

procedure TDebugEvaluatorVisitor.ShowScope;
var
    scopeDump: TArray<string>;
    line: string;
begin
    if FShowScope then
    begin
        AppendLine('-- Scope --');
        // Scope.Dump logic has been updated in Myc.Ast.Scope to handle new architecture
        scopeDump := Scope.Dump.Split([sLineBreak]);
        for line in scopeDump do
        begin
            AppendLine(line);
        end;
        AppendLine('-----------');
    end;
end;

// --- Visit Overrides ---

function TDebugEvaluatorVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
var
    mode: string;
begin
    if Assigned(Node.StaticTarget) then
        mode := 'STATIC'
    else
        mode := 'DYNAMIC';

    AppendLine(Format('FunctionCall (%s, Tail=%s) {', [mode, Node.IsTailCall.ToString(TUseBoolStrs.True)]));
    Indent;
    try
        // ShowScope is called in Constructor of new Visitor (via CreateVisitorFactory) for the body,
        // but for arguments evaluation here in the current scope, we might want to see it.
        ShowScope;
        Result := inherited VisitFunctionCall(Node);
    finally
        Unindent;
    end;
    AppendLine(Format('} -> %s', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
begin
    AppendLine('AddSeriesItem {');
    Indent;
    try
        Result := inherited VisitAddSeriesItem(Node);
    finally
        Unindent;
    end;
    AppendLine('} -> (void)');
end;

function TDebugEvaluatorVisitor.VisitAssignment(const Node: IAssignmentNode): TDataValue;
begin
    AppendLine(Format('Assignment to "%s" {', [Node.Target.AsIdentifier.Name]));
    Indent;
    try
        Result := inherited VisitAssignment(Node);
    finally
        Unindent;
    end;
    AppendLine(Format('} -> %s', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitConstant(const Node: IConstantNode): TDataValue;
begin
    Result := inherited VisitConstant(Node);
    AppendLine(Format('Constant (%s)', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitKeyword(const Node: IKeywordNode): TDataValue;
begin
    Result := inherited VisitKeyword(Node);
    AppendLine(Format('Keyword (%s)', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
begin
    AppendLine('CreateSeries {');
    Indent;
    try
        Result := inherited VisitCreateSeries(Node);
    finally
        Unindent;
    end;
    AppendLine(Format('} -> %s', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
begin
    Result := inherited VisitIdentifier(Node);
    AppendLine(Format('Identifier "%s" -> %s', [Node.Name, Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitIfExpression(const Node: IIfExpressionNode): TDataValue;
begin
    AppendLine('IfExpr {');
    Indent;
    try
        Result := inherited VisitIfExpression(Node);
    finally
        Unindent;
    end;
    AppendLine(Format('} -> %s', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitIndexer(const Node: IIndexerNode): TDataValue;
begin
    AppendLine('Indexer {');
    Indent;
    try
        Result := inherited VisitIndexer(Node);
    finally
        Unindent;
    end;
    AppendLine(Format('} -> %s', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitMemberAccess(const Node: IMemberAccessNode): TDataValue;
begin
    AppendLine(Format('MemberAccess (Member: %s) {', [Node.Member.Value.Name]));
    Indent;
    try
        Result := inherited VisitMemberAccess(Node);
    finally
        Unindent;
    end;
    AppendLine(Format('} -> %s', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
begin
    AppendLine('RecordLiteral {');
    Indent;
    try
        Result := inherited VisitRecordLiteral(Node);
    finally
        Unindent;
    end;
    AppendLine(Format('} -> %s', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitCondExpression(const Node: ICondExpressionNode): TDataValue;
begin
    AppendLine(Format('CondExpr (%d pairs) {', [Length(Node.Pairs)]));
    Indent;
    try
        // We just call inherited, which does the logic.
        // We don't log every branch check here to avoid spamming output,
        // but the recursive evaluation of conditions will show up in the log naturally.
        Result := inherited VisitCondExpression(Node);
    finally
        Unindent;
    end;
    AppendLine(Format('} -> %s', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
begin
    AppendLine('Block {');
    Indent;
    try
        // Delegates to VisitExpressionList via inherited
        Result := inherited VisitBlockExpression(Node);
        // Scope might have changed after block execution if vars were defined
        ShowScope;
    finally
        Unindent;
    end;
    AppendLine(Format('} -> %s', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
begin
    AppendLine(Format('VarDecl %s :=', [Node.Target.AsIdentifier.Name]));
    Indent;
    try
        Result := inherited VisitVariableDeclaration(Node);
    finally
        Unindent;
    end;
end;

function TDebugEvaluatorVisitor.VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
begin
    AppendLine('SeriesLength {');
    Indent;
    try
        Result := inherited VisitSeriesLength(Node);
    finally
        Unindent;
    end;
    AppendLine(Format('} -> %s', [Result.ToString]));
end;

function TDebugEvaluatorVisitor.VisitNop(const Node: INopNode): TDataValue;
begin
    AppendLine('Nop (void)');
    Result := TDataValue.Void;
end;

end.
