unit Myc.Ast.Dumper;

interface

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

type
    // Dumps a bound AST into a human-readable format for debugging purposes.
    IAstDumper = interface(IAstVisitor)
        procedure Execute(const RootNode: IAstNode);
    end;

    TAstDumper = class(TAstVisitor, IAstDumper)
    private
        FOutput: TStrings;
        FIndent: Integer;
        procedure Indent;
        procedure Unindent;
        procedure Log(const Text: string; const Node: IAstNode = nil); overload;
        procedure LogFmt(const Fmt: string; const Args: array of const; const Node: IAstNode = nil); overload;
        function FormatAddress(const Addr: TResolvedAddress): string;

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

        // List Visitors are handled implicitly by parent node iteration in Dumper,
        // but we implement them empty/default to satisfy the abstract base class if called directly.
        function VisitParameterList(const Node: IParameterList): TVoid; override;
        function VisitArgumentList(const Node: IArgumentList): TVoid; override;
        function VisitExpressionList(const Node: IExpressionList): TVoid; override;
        function VisitRecordFieldList(const Node: IRecordFieldList): TVoid; override;
        function VisitRecordField(const Node: IRecordFieldNode): TVoid; override;

    public
        constructor Create(const AOutput: TStrings);
        class procedure Dump(const RootNode: IAstNode; const Output: TStrings);
        procedure Execute(const RootNode: IAstNode);
    end;

implementation

uses
    Myc.Data.Keyword,
    Myc.Ast.Types;

{ TAstDumper }

class procedure TAstDumper.Dump(const RootNode: IAstNode; const Output: TStrings);
var
    dumper: TAstDumper;
begin
    if (not Assigned(Output)) or (not Assigned(RootNode)) then
        exit;

    Output.Clear;
    dumper := TAstDumper.Create(Output);
    try
        dumper.Execute(RootNode);
    finally
        dumper.Free;
    end;
end;

constructor TAstDumper.Create(const AOutput: TStrings);
begin
    inherited Create;
    FOutput := AOutput;
    FIndent := 0;
end;

procedure TAstDumper.Execute(const RootNode: IAstNode);
begin
    if Assigned(RootNode) then
        RootNode.Accept(Self);
end;

procedure TAstDumper.Indent;
begin
    inc(FIndent, 2);
end;

procedure TAstDumper.Unindent;
begin
    dec(FIndent, 2);
end;

procedure TAstDumper.Log(const Text: string; const Node: IAstNode = nil);
var
    typeStr: string;
    typedNode: IAstTypedNode;
    staticType: IStaticType;
begin
    typeStr := '';
    if Assigned(Node) and Node.IsTyped then
    begin
        typedNode := Node.AsTypedNode;
        staticType := typedNode.StaticType;
        if Assigned(staticType) then
            typeStr := Format(' <Type: %s>', [staticType.ToString])
        else
            typeStr := ' <Type: nil>';
    end;
    FOutput.Add(StringOfChar(' ', FIndent) + Text + typeStr);
end;

procedure TAstDumper.LogFmt(const Fmt: string; const Args: array of const; const Node: IAstNode = nil);
begin
    Log(Format(Fmt, Args), Node);
end;

function TAstDumper.FormatAddress(const Addr: TResolvedAddress): string;
begin
    case Addr.Kind of
        akUnresolved: Result := '!! UNRESOLVED !!';
        akLocalOrParent: Result := Format('LocalOrParent (Depth: %d, Slot: %d)', [Addr.ScopeDepth, Addr.SlotIndex]);
        akUpvalue: Result := Format('Upvalue (Index: %d)', [Addr.SlotIndex]);
    else
        Result := 'Unknown Address Kind';
    end;
end;

function TAstDumper.VisitConstant(const Node: IConstantNode): TVoid;
begin
    LogFmt('Constant: %s', [Node.Value.ToString], Node);
end;

function TAstDumper.VisitIdentifier(const Node: IIdentifierNode): TVoid;
var
    adr: TResolvedAddress;
begin
    adr := Node.Address;
    if adr.Kind <> akUnresolved then
        LogFmt('Identifier: %s -> %s', [Node.Name, FormatAddress(adr)], Node)
    else
        LogFmt('Identifier: %s (unbound)', [Node.Name], Node);
end;

function TAstDumper.VisitKeyword(const Node: IKeywordNode): TVoid;
begin
    LogFmt('Keyword: :%s', [Node.Value.Name], Node);
end;

function TAstDumper.VisitIfExpression(const Node: IIfExpressionNode): TVoid;
begin
    Log('IfExpression', Node);
    Indent;
    Log('Condition:');
    Node.Condition.Accept(Self);
    Log('Then:');
    Node.ThenBranch.Accept(Self);
    if Assigned(Node.ElseBranch) then
    begin
        Log('Else:');
        Node.ElseBranch.Accept(Self);
    end;
    Unindent;
end;

function TAstDumper.VisitCondExpression(const Node: ICondExpressionNode): TVoid;
var
    i: Integer;
begin
    LogFmt('CondExpression (%d pairs)', [Length(Node.Pairs)], Node);
    Indent;
    for i := 0 to High(Node.Pairs) do
    begin
        LogFmt('Pair %d:', [i]);
        Indent;
        Log('Condition:');
        Node.Pairs[i].Condition.Accept(Self);
        Log('Branch:');
        Node.Pairs[i].Branch.Accept(Self);
        Unindent;
    end;
    Log('Else:');
    Node.ElseBranch.Accept(Self);
    Unindent;
end;

function TAstDumper.VisitLambdaExpression(const Node: ILambdaExpressionNode): TVoid;
var
    upvalueAddr: TResolvedAddress;
    symbols: TArray<string>;
    layout: IScopeLayout;
    slot: Integer;
    typ: IStaticType;
begin
    LogFmt(
        'LambdaExpression (HasNested: %s, IsPure: %s)',
        [Node.HasNestedLambdas.ToString(TUseBoolStrs.True), BoolToStr(Node.IsPure, True)],
        Node
    );
    Indent;

    // 1. Layout & Symbols
    if Assigned(Node.Layout) then
    begin
        LogFmt('Scope: Layout Slots=%d', [Node.Layout.SlotCount]);
        if Assigned(Node.Descriptor) then
        begin
            Log('Symbol Table:');
            Indent;
            layout := Node.Layout;
            symbols := layout.GetSymbols;
            TArray.Sort<string>(symbols);

            for var name in symbols do
            begin
                slot := layout.FindSlot(name);
                typ := Node.Descriptor.GetSymbolType(slot);
                LogFmt('"%s" -> Slot %d (Type: %s)', [name, slot, typ.ToString]);
            end;
            Unindent;
        end;
    end
    else
        Log('Scope: No Layout (Raw)');

    // 3. Parameters
    Log('Parameters:');
    Indent;
    // Iterate over IParameterList
    for var param in Node.Parameters do
        param.Accept(Self);
    Unindent;

    // 4. Upvalues
    if Length(Node.Upvalues) > 0 then
    begin
        LogFmt('Captured Upvalues (%d):', [Length(Node.Upvalues)]);
        Indent;
        for upvalueAddr in Node.Upvalues do
            Log(FormatAddress(upvalueAddr));
        Unindent;
    end;

    // 5. Body
    Log('Body:');
    Node.Body.Accept(Self);

    Unindent;
end;

function TAstDumper.VisitFunctionCall(const Node: IFunctionCallNode): TVoid;
var
    arg: IAstNode;
    staticStatus: string;
    sigStr: string;
    argTypes: TArray<string>;
    i: Integer;
begin
    sigStr := '';
    // Note: Node.Arguments is IArgumentList now.
    if Assigned(Node.StaticTarget) then
    begin
        staticStatus := 'Assigned';
        SetLength(argTypes, Node.Arguments.Count);
        for i := 0 to Node.Arguments.Count - 1 do
        begin
            if Node.Arguments[i].IsTyped then
                argTypes[i] := Node.Arguments[i].AsTypedNode.StaticType.ToString
            else
                argTypes[i] := 'Untyped';
        end;
        sigStr := Format(' <ResolvedSig: Method(%s): %s>', [string.Join(', ', argTypes), Node.StaticType.ToString]);
    end
    else
        staticStatus := 'nil';

    LogFmt(
        'FunctionCall (IsTailCall: %s, StaticTarget: %s%s, IsTargetPure: %s)',
        [Node.IsTailCall.ToString(TUseBoolStrs.True), staticStatus, sigStr, BoolToStr(Node.IsTargetPure, True)],
        Node
    );

    Indent;
    Log('Callee:');
    Node.Callee.Accept(Self);
    LogFmt('Arguments (%d):', [Node.Arguments.Count]);
    Indent;
    // Iterate over IArgumentList
    for arg in Node.Arguments do
        arg.Accept(Self);
    Unindent;
    Unindent;
end;

function TAstDumper.VisitMacroExpansionNode(const Node: IMacroExpansionNode): TVoid;
var
    arg: IAstNode;
begin
    Log('MacroExpansion', Node);
    Indent;

    Log('Original Call:');
    Indent;
    Log('Callee:');
    Node.CallNode.Callee.Accept(Self);
    LogFmt('Arguments (%d):', [Node.CallNode.Arguments.Count]);
    Indent;
    for arg in Node.CallNode.Arguments do
        arg.Accept(Self);
    Unindent;
    Unindent;

    Log('Expanded Body:');
    Indent;
    Node.ExpandedBody.Accept(Self);
    Unindent;

    Unindent;
end;

function TAstDumper.VisitRecurNode(const Node: IRecurNode): TVoid;
var
    arg: IAstNode;
begin
    Log('Recur', Node);
    Indent;
    LogFmt('Arguments (%d):', [Node.Arguments.Count]);
    Indent;
    for arg in Node.Arguments do
        arg.Accept(Self);
    Unindent;
    Unindent;
end;

function TAstDumper.VisitBlockExpression(const Node: IBlockExpressionNode): TVoid;
var
    expr: IAstNode;
begin
    Log('BlockExpression', Node);
    Indent;
    for expr in Node.Expressions do
        expr.Accept(Self);
    Unindent;
end;

function TAstDumper.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TVoid;
begin
    LogFmt('VariableDeclaration (IsBoxed: %s)', [Node.IsBoxed.ToString(TUseBoolStrs.True)], Node);
    Indent;
    Node.Target.Accept(Self);
    if Assigned(Node.Initializer) then
    begin
        Log('Initializer:');
        Node.Initializer.Accept(Self);
    end;
    Unindent;
end;

function TAstDumper.VisitAssignment(const Node: IAssignmentNode): TVoid;
begin
    Log('Assignment', Node);
    Indent;
    Node.Target.Accept(Self);
    Log('Value:');
    Node.Value.Accept(Self);
    Unindent;
end;

function TAstDumper.VisitMacroDefinition(const Node: IMacroDefinitionNode): TVoid;
begin
    Log('MacroDefinition', Node);
    Indent;

    Log('Name:');
    Indent;
    Node.Name.Accept(Self);
    Unindent;

    Log('Parameters:');
    Indent;
    for var param in Node.Parameters do
        param.Accept(Self);
    Unindent;

    Log('Body:');
    Indent;
    Node.Body.Accept(Self);
    Unindent;

    Unindent;
end;

function TAstDumper.VisitQuasiquote(const Node: IQuasiquoteNode): TVoid;
begin
    Log('Quasiquote', Node);
    Indent;
    Node.Expression.Accept(Self);
    Unindent;
end;

function TAstDumper.VisitUnquote(const Node: IUnquoteNode): TVoid;
begin
    Log('Unquote', Node);
    Indent;
    Node.Expression.Accept(Self);
    Unindent;
end;

function TAstDumper.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TVoid;
begin
    Log('UnquoteSplicing', Node);
    Indent;
    Node.Expression.Accept(Self);
    Unindent;
end;

function TAstDumper.VisitIndexer(const Node: IIndexerNode): TVoid;
begin
    Log('Indexer', Node);
    Indent;
    Log('Base:');
    Node.Base.Accept(Self);
    Log('Index:');
    Node.Index.Accept(Self);
    Unindent;
end;

function TAstDumper.VisitMemberAccess(const Node: IMemberAccessNode): TVoid;
begin
    Log('MemberAccess', Node);
    Indent;
    Log('Base:');
    Node.Base.Accept(Self);
    Log('Member:');
    Node.Member.Accept(Self);
    Unindent;
end;

function TAstDumper.VisitRecordLiteral(const Node: IRecordLiteralNode): TVoid;
var
    field: IRecordFieldNode;
begin
    LogFmt('RecordLiteral (%d fields)', [Node.Fields.Count], Node);
    Indent;
    // Iterate over IRecordFieldList
    for field in Node.Fields do
    begin
        LogFmt('Field :%s', [field.Key.Value.Name]);
        Indent;
        field.Value.Accept(Self);
        Unindent;
    end;
    Unindent;
end;

function TAstDumper.VisitCreateSeries(const Node: ICreateSeriesNode): TVoid;
begin
    LogFmt('CreateSeries: %s', [Node.Definition], Node);
end;

function TAstDumper.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TVoid;
begin
    Log('AddSeriesItem', Node);
    Indent;
    Log('Series:');
    Node.Series.Accept(Self);
    Log('Value:');
    Node.Value.Accept(Self);
    if Assigned(Node.Lookback) then
    begin
        Log('Lookback:');
        Node.Lookback.Accept(Self);
    end;
    Unindent;
end;

function TAstDumper.VisitSeriesLength(const Node: ISeriesLengthNode): TVoid;
begin
    Log('SeriesLength', Node);
    Indent;
    Log('Series:');
    Node.Series.Accept(Self);
    Unindent;
end;

function TAstDumper.VisitNop(const Node: INopNode): TVoid;
begin
    Log('Nop', Node);
end;

// --- List Visitors Implementation ---

function TAstDumper.VisitParameterList(const Node: IParameterList): TVoid;
begin
    // Usually handled by Parent Node iteration (Lambda),
    // but if visited directly:
    for var item in Node do
        item.Accept(Self);
end;

function TAstDumper.VisitArgumentList(const Node: IArgumentList): TVoid;
begin
    for var item in Node do
        item.Accept(Self);
end;

function TAstDumper.VisitExpressionList(const Node: IExpressionList): TVoid;
begin
    for var item in Node do
        item.Accept(Self);
end;

function TAstDumper.VisitRecordFieldList(const Node: IRecordFieldList): TVoid;
begin
    for var item in Node do
        item.Accept(Self);
end;

function TAstDumper.VisitRecordField(const Node: IRecordFieldNode): TVoid;
begin
    // If visited directly (e.g. inside a list)
    LogFmt('Field :%s', [Node.Key.Value.Name]);
    Indent;
    Node.Value.Accept(Self);
    Unindent;
end;

end.
