unit Myc.Ast.Script.Print;

interface

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

type
    TPrettyPrintVisitor = class(TAstVisitor)
    strict private
        FBuilder: TStringBuilder;
        FIndentLevel: Integer;
        procedure Indent;
        procedure Unindent;
        procedure Append(const S: string);
        procedure NewLine;

        // Handlers now strictly accept IAstNode
        function VisitConstant(const N: IAstNode): TVoid;
        function VisitIdentifier(const N: IAstNode): TVoid;
        function VisitKeyword(const N: IAstNode): TVoid;

        function VisitParameterList(const N: IAstNode): TVoid;
        function VisitArgumentList(const N: IAstNode): TVoid;
        function VisitExpressionList(const N: IAstNode): TVoid;
        function VisitRecordFieldList(const N: IAstNode): TVoid;
        function VisitRecordField(const N: IAstNode): TVoid;

        function VisitIfExpression(const N: IAstNode): TVoid;
        function VisitCondExpression(const N: IAstNode): TVoid;
        function VisitLambdaExpression(const N: IAstNode): TVoid;
        function VisitMacroDefinition(const N: IAstNode): TVoid;
        function VisitQuasiquote(const N: IAstNode): TVoid;
        function VisitUnquote(const N: IAstNode): TVoid;
        function VisitUnquoteSplicing(const N: IAstNode): TVoid;
        function VisitFunctionCall(const N: IAstNode): TVoid;
        function VisitMacroExpansionNode(const N: IAstNode): TVoid;
        function VisitBlockExpression(const N: IAstNode): TVoid;
        function VisitVariableDeclaration(const N: IAstNode): TVoid;
        function VisitAssignment(const N: IAstNode): TVoid;
        function VisitIndexer(const N: IAstNode): TVoid;
        function VisitMemberAccess(const N: IAstNode): TVoid;
        function VisitRecordLiteral(const N: IAstNode): TVoid;
        function VisitCreateSeries(const N: IAstNode): TVoid;
        function VisitAddSeriesItem(const N: IAstNode): TVoid;
        function VisitSeriesLength(const N: IAstNode): TVoid;
        function VisitRecurNode(const N: IAstNode): TVoid;
        function VisitNop(const N: IAstNode): TVoid;

        function VisitPipeInput(const N: IAstNode): TVoid;
        function VisitPipeSelectorList(const N: IAstNode): TVoid;
        function VisitPipeInputList(const N: IAstNode): TVoid;
        function VisitPipe(const N: IAstNode): TVoid;

    protected
        procedure SetupHandlers; override;

    public
        constructor Create;
        destructor Destroy; override;
        function GetResult: string;
        procedure Execute(const RootNode: IAstNode);
    end;

implementation

{ TPrettyPrintVisitor }

constructor TPrettyPrintVisitor.Create;
begin
    inherited Create;
    FBuilder := TStringBuilder.Create;
    FIndentLevel := 0;
end;

destructor TPrettyPrintVisitor.Destroy;
begin
    FBuilder.Free;
    inherited;
end;

procedure TPrettyPrintVisitor.SetupHandlers;
begin
    Register(akConstant, VisitConstant);
    Register(akIdentifier, VisitIdentifier);
    Register(akKeyword, VisitKeyword);

    Register(akParameterList, VisitParameterList);
    Register(akArgumentList, VisitArgumentList);
    Register(akExpressionList, VisitExpressionList);
    Register(akRecordFieldList, VisitRecordFieldList);
    Register(akRecordField, VisitRecordField);

    Register(akIfExpression, VisitIfExpression);
    Register(akCondExpression, VisitCondExpression);
    Register(akLambdaExpression, VisitLambdaExpression);
    Register(akFunctionCall, VisitFunctionCall);
    Register(akMacroExpansion, VisitMacroExpansionNode);
    Register(akBlockExpression, VisitBlockExpression);
    Register(akVariableDeclaration, VisitVariableDeclaration);
    Register(akAssignment, VisitAssignment);
    Register(akMacroDefinition, VisitMacroDefinition);
    Register(akQuasiquote, VisitQuasiquote);
    Register(akUnquote, VisitUnquote);
    Register(akUnquoteSplicing, VisitUnquoteSplicing);
    Register(akIndexer, VisitIndexer);
    Register(akMemberAccess, VisitMemberAccess);
    Register(akRecordLiteral, VisitRecordLiteral);
    Register(akCreateSeries, VisitCreateSeries);
    Register(akAddSeriesItem, VisitAddSeriesItem);
    Register(akSeriesLength, VisitSeriesLength);
    Register(akRecur, VisitRecurNode);
    Register(akNop, VisitNop);

    Register(akPipeInput, VisitPipeInput);
    Register(akPipeSelectorList, VisitPipeSelectorList);
    Register(akPipeInputList, VisitPipeInputList);
    Register(akPipe, VisitPipe);
end;

function TPrettyPrintVisitor.GetResult: string;
begin
    Result := FBuilder.ToString;
end;

procedure TPrettyPrintVisitor.Execute(const RootNode: IAstNode);
begin
    if Assigned(RootNode) then
        Visit(RootNode);
end;

procedure TPrettyPrintVisitor.Indent;
begin
    Inc(FIndentLevel, 2);
end;

procedure TPrettyPrintVisitor.Unindent;
begin
    Dec(FIndentLevel, 2);
end;

procedure TPrettyPrintVisitor.Append(const S: string);
begin
    FBuilder.Append(S);
end;

procedure TPrettyPrintVisitor.NewLine;
begin
    FBuilder.AppendLine;
    FBuilder.Append(''.PadLeft(FIndentLevel));
end;

// --- Implementations (Clean & Typed) ---

function TPrettyPrintVisitor.VisitPipeInput(const N: IAstNode): TVoid;
var
    P: IPipeInputNode;
begin
    P := N.AsPipeInput;
    Visit(P.StreamSource);
    Append(' ');
    Visit(P.Selectors);
end;

function TPrettyPrintVisitor.VisitPipeSelectorList(const N: IAstNode): TVoid;
var
    L: IPipeSelectorList;
    i: Integer;
begin
    L := N.AsPipeSelectorList;
    Append('[');
    for i := 0 to L.Count - 1 do
    begin
        if i > 0 then
            Append(' ');
        Visit(L[i]);
    end;
    Append(']');
end;

function TPrettyPrintVisitor.VisitPipeInputList(const N: IAstNode): TVoid;
var
    L: IPipeInputList;
    i: Integer;
begin
    L := N.AsPipeInputList;
    Append('[');
    for i := 0 to L.Count - 1 do
    begin
        if i > 0 then
            Append(' ');
        Visit(L[i]);
    end;
    Append(']');
end;

function TPrettyPrintVisitor.VisitPipe(const N: IAstNode): TVoid;
var
    P: IPipeNode;
begin
    P := N.AsPipe;
    Append('(pipe ');
    Visit(P.Inputs);
    Indent;
    NewLine;
    Visit(P.Transformation);
    Unindent;
    NewLine;
    Append(')');
end;

function TPrettyPrintVisitor.VisitConstant(const N: IAstNode): TVoid;
var
    C: IConstantNode;
begin
    C := N.AsConstant;
    if C.Value.Kind = vkText then
        Append('"' + C.Value.AsText + '"')
    else
        Append(C.Value.ToString);
end;

function TPrettyPrintVisitor.VisitIdentifier(const N: IAstNode): TVoid;
begin
    Append(N.AsIdentifier.Name);
end;

function TPrettyPrintVisitor.VisitKeyword(const N: IAstNode): TVoid;
begin
    Append(':' + N.AsKeyword.Value.Name);
end;

function TPrettyPrintVisitor.VisitNop(const N: IAstNode): TVoid;
begin
    Append('...');
end;

function TPrettyPrintVisitor.VisitParameterList(const N: IAstNode): TVoid;
var
    L: IParameterList;
    i: Integer;
begin
    L := N.AsParameterList;
    Append('[');
    for i := 0 to L.Count - 1 do
    begin
        if i > 0 then
            Append(' ');
        Visit(L[i]);
    end;
    Append(']');
end;

function TPrettyPrintVisitor.VisitArgumentList(const N: IAstNode): TVoid;
var
    L: IArgumentList;
    i: Integer;
begin
    L := N.AsArgumentList;
    for i := 0 to L.Count - 1 do
    begin
        Append(' ');
        Visit(L[i]);
    end;
end;

function TPrettyPrintVisitor.VisitExpressionList(const N: IAstNode): TVoid;
var
    L: IExpressionList;
    item: IAstNode;
begin
    L := N.AsExpressionList;
    Indent;
    for item in L do
    begin
        NewLine;
        Visit(item);
    end;
    Unindent;
    NewLine;
end;

function TPrettyPrintVisitor.VisitRecordFieldList(const N: IAstNode): TVoid;
var
    L: IRecordFieldList;
    item: IRecordFieldNode;
begin
    L := N.AsRecordFieldList;
    Indent;
    for item in L do
    begin
        NewLine;
        Visit(item);
    end;
    Unindent;
    NewLine;
end;

function TPrettyPrintVisitor.VisitRecordField(const N: IAstNode): TVoid;
var
    F: IRecordFieldNode;
begin
    F := N.AsRecordField;
    Visit(F.Key);
    Append(' ');
    Visit(F.Value);
end;

function TPrettyPrintVisitor.VisitIfExpression(const N: IAstNode): TVoid;
var
    E: IIfExpressionNode;
begin
    E := N.AsIfExpression;
    Append('(if ');
    Visit(E.Condition);
    Indent;
    NewLine;
    Visit(E.ThenBranch);
    if Assigned(E.ElseBranch) then
    begin
        NewLine;
        Visit(E.ElseBranch);
    end;
    Unindent;
    NewLine;
    Append(')');
end;

function TPrettyPrintVisitor.VisitCondExpression(const N: IAstNode): TVoid;
var
    E: ICondExpressionNode;
begin
    E := N.AsCondExpression;
    Append('(?');
    Indent;
    for var pair in E.Pairs do
    begin
        NewLine;
        Visit(pair.Condition);
        Append(' ');
        Visit(pair.Branch);
    end;
    NewLine;
    Visit(E.ElseBranch);
    Unindent;
    NewLine;
    Append(')');
end;

function TPrettyPrintVisitor.VisitLambdaExpression(const N: IAstNode): TVoid;
var
    E: ILambdaExpressionNode;
begin
    E := N.AsLambdaExpression;
    Append('(fn ');
    Visit(E.Parameters);
    Indent;
    NewLine;
    Visit(E.Body);
    Unindent;
    NewLine;
    Append(')');
end;

function TPrettyPrintVisitor.VisitMacroDefinition(const N: IAstNode): TVoid;
var
    M: IMacroDefinitionNode;
begin
    M := N.AsMacroDefinition;
    Append('(defmacro ');
    Visit(M.Name);
    Append(' ');
    Visit(M.Parameters);
    Indent;
    NewLine;
    Visit(M.Body);
    Unindent;
    NewLine;
    Append(')');
end;

function TPrettyPrintVisitor.VisitQuasiquote(const N: IAstNode): TVoid;
begin
    Append('`');
    Visit(N.AsQuasiquote.Expression);
end;

function TPrettyPrintVisitor.VisitUnquote(const N: IAstNode): TVoid;
begin
    Append('~');
    Visit(N.AsUnquote.Expression);
end;

function TPrettyPrintVisitor.VisitUnquoteSplicing(const N: IAstNode): TVoid;
begin
    Append('~@');
    Visit(N.AsUnquoteSplicing.Expression);
end;

function TPrettyPrintVisitor.VisitFunctionCall(const N: IAstNode): TVoid;
var
    C: IFunctionCallNode;
begin
    C := N.AsFunctionCall;
    if (C.Callee.Kind = akIdentifier) and (C.Callee.AsIdentifier.Name = 'quote') and (C.Arguments.Count = 1) then
    begin
        Append('''');
        Visit(C.Arguments[0]);
        exit;
    end;
    Append('(');
    Visit(C.Callee);
    Visit(C.Arguments);
    Append(')');
end;

function TPrettyPrintVisitor.VisitMacroExpansionNode(const N: IAstNode): TVoid;
begin
    Visit(N.AsMacroExpansion.CallNode);
end;

function TPrettyPrintVisitor.VisitRecurNode(const N: IAstNode): TVoid;
begin
    Append('(recur');
    Visit(N.AsRecur.Arguments);
    Append(')');
end;

function TPrettyPrintVisitor.VisitBlockExpression(const N: IAstNode): TVoid;
begin
    Append('(do');
    Visit(N.AsBlockExpression.Expressions);
    Append(')');
end;

function TPrettyPrintVisitor.VisitVariableDeclaration(const N: IAstNode): TVoid;
var
    V: IVariableDeclarationNode;
begin
    V := N.AsVariableDeclaration;
    Append('(def ');
    Visit(V.Target);
    if Assigned(V.Initializer) then
    begin
        Append(' ');
        Visit(V.Initializer);
    end;
    Append(')');
end;

function TPrettyPrintVisitor.VisitAssignment(const N: IAstNode): TVoid;
var
    A: IAssignmentNode;
begin
    A := N.AsAssignment;
    Append('(assign ');
    Visit(A.Target);
    Append(' ');
    Visit(A.Value);
    Append(')');
end;

function TPrettyPrintVisitor.VisitIndexer(const N: IAstNode): TVoid;
var
    I: IIndexerNode;
begin
    I := N.AsIndexer;
    Append('(get ');
    Visit(I.Base);
    Append(' ');
    Visit(I.Index);
    Append(')');
end;

function TPrettyPrintVisitor.VisitMemberAccess(const N: IAstNode): TVoid;
var
    M: IMemberAccessNode;
begin
    M := N.AsMemberAccess;
    Append('(.');
    Visit(M.Member);
    Append(' ');
    Visit(M.Base);
    Append(')');
end;

function TPrettyPrintVisitor.VisitRecordLiteral(const N: IAstNode): TVoid;
var
    R: IRecordLiteralNode;
begin
    R := N.AsRecordLiteral;
    if R.Fields.Count = 0 then
    begin
        Append('{}');
        exit;
    end;
    Append('{');
    Visit(R.Fields);
    Append('}');
end;

function TPrettyPrintVisitor.VisitCreateSeries(const N: IAstNode): TVoid;
begin
    Append(Format('(new-series "%s")', [N.AsCreateSeries.Definition]));
end;

function TPrettyPrintVisitor.VisitAddSeriesItem(const N: IAstNode): TVoid;
var
    A: IAddSeriesItemNode;
begin
    A := N.AsAddSeriesItem;
    Append('(add-item ');
    Visit(A.Series);
    Append(' ');
    Visit(A.Value);
    if Assigned(A.Lookback) then
    begin
        Append(' ');
        Visit(A.Lookback);
    end;
    Append(')');
end;

function TPrettyPrintVisitor.VisitSeriesLength(const N: IAstNode): TVoid;
begin
    Append('(count ');
    Visit(N.AsSeriesLength.Series);
    Append(')');
end;

end.
