/**
 * rbxl-lua-mvp.test.js — headless smoke-тест Roblox Lua API shim.
 *
 * НЕ запускает Worker (это требует браузерного Worker API). Вместо этого
 * напрямую импортирует roblox-shim.js и инициализирует Lua в текущем потоке.
 *
 * Запуск: node --experimental-vm-modules tests/rbxl-lua-mvp.test.js
 */
import { LuaFactory } from 'wasmoon';
import { registerRobloxApi } from '../src/engine/roblox-shim.js';

const FAKE_SCENE_SNAP = {
    primitives: {
        1: { id: 1, type: 'cube', name: 'Floor', x: 0, y: 0, z: 0, sx: 10, sy: 1, sz: 10,
             color: '#888888', material: 'glossy', anchored: true, canCollide: true, opacity: 1 },
        2: { id: 2, type: 'cube', name: 'KillBrick', x: 5, y: 1, z: 0, sx: 2, sy: 1, sz: 2,
             color: '#ff0000', material: 'neon', anchored: true, canCollide: true, opacity: 1 },
    },
};

const SNIPPETS = [
    {
        name: 'print hello',
        lua: `print("Hello from Lua!")`,
        expectLogs: [{ level: 'info', text: 'Hello from Lua!' }],
    },
    {
        name: 'Vector3 math',
        lua: `
            local v = Vector3.new(3, 4, 0)
            print("magnitude:", v.Magnitude)
            local u = v.Unit
            print("unit:", u.X, u.Y, u.Z)
        `,
        expectLogs: [
            { level: 'info', text: 'magnitude:\t5' },
        ],
    },
    {
        name: 'workspace iteration',
        lua: `
            local children = workspace:GetChildren()
            print("count:", #children)
            for i, c in ipairs(children) do
                print("child:", c.Name, "class:", c.ClassName)
            end
        `,
        expectLogs: [
            { level: 'info', text: 'count:\t2' },
        ],
    },
    {
        name: 'FindFirstChild',
        lua: `
            local kb = workspace:FindFirstChild("KillBrick")
            if kb then print("found:", kb.Name)
            else print("not found") end
        `,
        expectLogs: [{ level: 'info', text: 'found:\tKillBrick' }],
    },
    {
        name: 'Part.Position get',
        lua: `
            local kb = workspace:FindFirstChild("KillBrick")
            print("position:", kb.Position.X, kb.Position.Y, kb.Position.Z)
        `,
        expectLogs: [{ level: 'info', text: 'position:\t5\t1\t0' }],
    },
    {
        name: 'Part.Color set',
        lua: `
            local kb = workspace:FindFirstChild("KillBrick")
            kb.Color = Color3.new(0, 1, 0)
            print("new color hex (via Position):", kb.Color.R, kb.Color.G, kb.Color.B)
        `,
        expectPartSet: { primId: 2, prop: 'color' },
    },
    {
        name: 'CFrame.Angles',
        lua: `
            local cf = CFrame.Angles(0, math.pi/2, 0)
            print("lookvector:", cf.LookVector.X, cf.LookVector.Y, cf.LookVector.Z)
        `,
        expectLogs: [],
    },
    {
        name: 'Instance.new + Parent',
        lua: `
            local f = Instance.new("Folder", workspace)
            f.Name = "MyFolder"
            print("folder name:", f.Name, "parent:", f.Parent.Name)
        `,
        expectLogs: [{ level: 'info', text: 'folder name:\tMyFolder\tparent:\tWorkspace' }],
    },
    {
        name: 'IsA hierarchy',
        lua: `
            local kb = workspace:FindFirstChild("KillBrick")
            print("isa Part:", kb:IsA("Part"))
            print("isa BasePart:", kb:IsA("BasePart"))
            print("isa Instance:", kb:IsA("Instance"))
            print("isa Sound:", kb:IsA("Sound"))
        `,
        expectLogs: [
            { level: 'info', text: 'isa Part:\ttrue' },
            { level: 'info', text: 'isa BasePart:\ttrue' },
            { level: 'info', text: 'isa Instance:\ttrue' },
            { level: 'info', text: 'isa Sound:\tfalse' },
        ],
    },
];

async function runSnippet(snippet) {
    const factory = new LuaFactory();
    const lua = await factory.createEngine();

    const logs = [];
    const sent = [];
    const send = (cmd, payload) => sent.push({ cmd, payload });

    registerRobloxApi(lua, {
        getSceneSnap: () => FAKE_SCENE_SNAP,
        targetPrimitiveId: 2,    // как будто скрипт прикреплён к KillBrick
        send,
    });

    // Перехват print через send('log', ...)
    let errMsg = null;
    try {
        await lua.doString(snippet.lua);
    } catch (e) {
        errMsg = e && e.message ? e.message : String(e);
    }
    lua.global.close();

    const captured = sent.filter(s => s.cmd === 'log');
    return { logs: captured.map(s => s.payload), partSets: sent.filter(s => s.cmd === 'partSet'), error: errMsg };
}

(async () => {
    let passed = 0;
    let failed = 0;
    for (const s of SNIPPETS) {
        const result = await runSnippet(s);
        const ok = checkExpectations(s, result);
        if (ok.success) {
            console.log(`✓ ${s.name}`);
            passed++;
        } else {
            console.log(`✗ ${s.name}`);
            console.log(`    error: ${result.error || 'none'}`);
            console.log(`    logs received:`);
            for (const l of result.logs) console.log(`      [${l.level}] ${JSON.stringify(l.text)}`);
            if (result.partSets.length) {
                console.log(`    partSets:`, JSON.stringify(result.partSets));
            }
            console.log(`    reason: ${ok.reason}`);
            failed++;
        }
    }
    console.log(`\n${passed} passed, ${failed} failed`);
    process.exit(failed > 0 ? 1 : 0);
})();

function checkExpectations(snippet, result) {
    if (result.error) {
        return { success: false, reason: `lua error: ${result.error}` };
    }
    if (snippet.expectLogs) {
        for (const exp of snippet.expectLogs) {
            const found = result.logs.find(l => l.level === exp.level && l.text === exp.text);
            if (!found) {
                return { success: false, reason: `missing log: [${exp.level}] ${JSON.stringify(exp.text)}` };
            }
        }
    }
    if (snippet.expectPartSet) {
        const found = result.partSets.find(s =>
            s.payload.primId === snippet.expectPartSet.primId &&
            s.payload.prop === snippet.expectPartSet.prop
        );
        if (!found) {
            return { success: false, reason: `missing partSet ${JSON.stringify(snippet.expectPartSet)}` };
        }
    }
    return { success: true };
}
