unit Test.Myc.Ast.Integration;

interface

uses
    System.SysUtils,
    System.Classes,
    System.IOUtils,
    System.TypInfo,
    System.Generics.Collections,
    DUnitX.TestFramework,
    // Core
    Myc.Data.Value,
    Myc.Ast.Environment,
    Myc.Ast.Script,
    Myc.Ast.Nodes; // For Exception handling

type
    [TestFixture]
    TTestIntegration = class
    private
        const
            TEST_DIR = 'T:\Myc\Test\Scripts';

        procedure RunSingleScript(const FileName: string);
        function ParseKind(const KindStr: string): TDataValueKind;
    public
        [Setup]
        procedure Setup;

        [Test]
        [IgnoreMemoryLeaks]
        procedure RunAllFileBasedTests;
    end;

implementation

{ TTestIntegration }

procedure TTestIntegration.Setup;
begin
    // Ensure the directory exists to avoid immediate crash
    if not TDirectory.Exists(TEST_DIR) then
        ForceDirectories(TEST_DIR);
end;

function TTestIntegration.ParseKind(const KindStr: string): TDataValueKind;
var
    I: Integer;
begin
    // Uses RTTI/TypInfo to convert string 'vkScalar' to enum TDataValueKind.vkScalar
    I := GetEnumValue(TypeInfo(TDataValueKind), KindStr);
    if I < 0 then
        raise Exception.CreateFmt('Unknown TDataValueKind in test header: %s', [KindStr]);
    Result := TDataValueKind(I);
end;

procedure TTestIntegration.RunSingleScript(const FileName: string);
var
    Lines: TArray<string>;
    ScriptSource: TStringBuilder;
    ExpectedOutput, ExpectedSignature: string; // <-- Neu
    ExpectedTypeStr: string;
    ExpectedType: TDataValueKind;
    HasExpectation: Boolean;
    Line: string;

    Env: TAstEnvironment;
    ResultValue: TDataValue;

    // AST Variablen für die Analyse
    RootNode, BoundNode, SpecializedNode: IAstNode;
    ActualSignature: string;
begin
    Lines := TFile.ReadAllLines(FileName);
    ScriptSource := TStringBuilder.Create;
    try
        ExpectedOutput := '';
        ExpectedSignature := ''; // <-- Init
        ExpectedTypeStr := '';
        ExpectedType := TDataValueKind.vkVoid;
        HasExpectation := False;

        // 1. Parsing (angepasst für SIGNATURE)
        for Line in Lines do
        begin
            if Line.Trim.StartsWith(';; EXPECT:') then
            begin
                ExpectedOutput := Line.Substring(10).Trim;
                HasExpectation := True;
            end
            else if Line.Trim.StartsWith(';; TYPE:') then
            begin
                ExpectedTypeStr := Line.Substring(8).Trim;
                ExpectedType := ParseKind(ExpectedTypeStr);
            end
            else if Line.Trim.StartsWith(';; SIGNATURE:') then // <-- Neu
            begin
                ExpectedSignature := Line.Substring(13).Trim;
            end
            else
            begin
                ScriptSource.AppendLine(Line);
            end;
        end;

        if not HasExpectation then
            Assert.Fail('Test file missing ";; EXPECT:" tag: ' + FileName);

        // 2. Environment Setup
        Env := TAstEnvironment.Construct(nil);
        Env.SetStandardMode;

        // 3. Parse AST
        RootNode := TAstScript.Parse(ScriptSource.ToString);

        // --- SIGNATURE CHECK START ---
        if ExpectedSignature <> '' then
        begin
            try
                // Wir simulieren die Pipeline bis zum Type-Check
                // Hinweis: Env.Bind führt intern oft schon Namensauflösung und TypeCheck durch.
                // Env.Specialize löst generische Aufrufe auf und finalisiert Typen.

                // A. Bind (Namensauflösung & TypeCheck)
                // Wir übergeben einen Dummy-Log, oder nil wenn erlaubt
                BoundNode := Env.Bind(RootNode, [], nil);

                // B. Specialize (Optimierung & finale Typisierung)
                SpecializedNode := Env.Specialize(BoundNode);

                // C. Typ extrahieren
                if SpecializedNode.IsTyped then
                begin
                    ActualSignature := SpecializedNode.AsTypedNode.StaticType.ToString;

                    Assert
                        .AreEqual(ExpectedSignature, ActualSignature, Format('File: %s - Signature Mismatch', [ExtractFileName(FileName)]));
                end
                else
                begin
                    Assert.Fail(Format('File: %s - Root node is not typed, cannot check signature.', [ExtractFileName(FileName)]));
                end;

            except
                on E: Exception do
                begin
                    Assert.Fail(Format('File: %s - Compilation/TypeCheck failed: %s', [ExtractFileName(FileName), E.Message]));
                end;
            end;
        end;
        // --- SIGNATURE CHECK END ---

        try
            // 4. Run Execution (Value Check)
            // Wir nutzen hier wieder RootNode (oder SpecializedNode, falls Env.Run damit umgehen kann),
            // um sicherzustellen, dass die Ausführung sauber durchläuft.
            ResultValue := Env.Run(RootNode);

            // 5. Verify Type
            if ExpectedTypeStr <> '' then
            begin
                Assert.AreEqual(ExpectedType, ResultValue.Kind, Format('File: %s - Result Type Mismatch', [ExtractFileName(FileName)]));
            end;

            // 6. Verify Output Value
            Assert.AreEqual(ExpectedOutput, ResultValue.ToString, Format('File: %s - Result Value Mismatch', [ExtractFileName(FileName)]));

        except
            on E: EAssertionFailed do
                raise;
            on E: Exception do
                Assert.Fail(Format('File: %s - Exception during execution: %s', [ExtractFileName(FileName), E.Message]));
        end;

    finally
        ScriptSource.Free;
    end;
end;

procedure TTestIntegration.RunAllFileBasedTests;
var
    Files: TArray<string>;
    FileName: string;
    Failures: TStringBuilder;
begin
    Files := TDirectory.GetFiles(TEST_DIR, '*.txt');

    if Length(Files) = 0 then
        Exit; // Silent exit if no files found (success, effectively)

    Failures := TStringBuilder.Create;
    try
        for FileName in Files do
        begin
            try
                RunSingleScript(FileName);
            except
                on E: Exception do
                begin
                    Failures.AppendLine(Format('[FAIL] %s: %s', [ExtractFileName(FileName), E.Message]));
                end;
            end;
        end;

        // Only fail if errors were collected
        if Failures.Length > 0 then
            Assert.Fail('One or more integration scripts failed:' + SLineBreak + Failures.ToString);

    finally
        Failures.Free;
    end;
end;

end.
