/**
 * rbxl-lua-integration.test.js — реалистичные Roblox-сниппеты из obby/simulator карт.
 */
import { LuaFactory } from 'wasmoon';
import { registerRobloxApi } from '../src/engine/roblox-shim.js';
import { RobloxScheduler } from '../src/engine/roblox-scheduler.js';
import { installRobloxServices } from '../src/engine/roblox-services.js';
import { RobloxTweenManager } from '../src/engine/roblox-tween.js';
import { RobloxPhysicsManager } from '../src/engine/roblox-physics.js';

function makeScene() {
    return {
        primitives: {
            10: { id: 10, type: 'cube', name: 'KillPart', x: 5, y: 1, z: 0, sx: 4, sy: 1, sz: 4,
                  color: '#ff0000', material: 'neon', anchored: true, canCollide: true, opacity: 1 },
            11: { id: 11, type: 'cube', name: 'WinPart', x: 30, y: 1, z: 0, sx: 4, sy: 1, sz: 4,
                  color: '#00ff00', material: 'neon', anchored: true, canCollide: true, opacity: 1 },
            12: { id: 12, type: 'cube', name: 'Conveyor', x: 15, y: 1, z: 0, sx: 8, sy: 0.5, sz: 4,
                  color: '#888888', material: 'metal', anchored: true, canCollide: true, opacity: 1 },
            13: { id: 13, type: 'cube', name: 'Door', x: 20, y: 3, z: 0, sx: 2, sy: 6, sz: 4,
                  color: '#a0522d', material: 'matte', anchored: true, canCollide: true, opacity: 1 },
        },
    };
}

const STORE = new Map();

async function run(luaSource, targetPrimId = 10, ticks = []) {
    const factory = new LuaFactory();
    const lua = await factory.createEngine();
    const sent = [];
    const send = (cmd, payload) => sent.push({ cmd, payload });
    let playerState = { x: 0, y: 5, z: 0, hp: 100 };
    registerRobloxApi(lua, { getSceneSnap: makeScene, targetPrimitiveId: targetPrimId, send });
    const sched = new RobloxScheduler(lua);
    sched.install();
    installRobloxServices(lua, {
        send,
        getPlayerState: () => playerState,
        loadSave: (k) => STORE.get(k),
        saveSave: (k, v) => STORE.set(k, v),
        removeSave: (k) => STORE.delete(k),
    });
    const tween = new RobloxTweenManager();
    tween.install(lua);
    const phys = new RobloxPhysicsManager(send);
    phys.install(lua);

    await sched.spawnMain(luaSource);
    for (const dt of ticks) {
        await sched.tick(dt);
        tween.tick(dt);
        phys.tick(dt);
    }
    lua.global.close();
    return {
        logs: sent.filter(s => s.cmd === 'log').map(s => s.payload),
        partSets: sent.filter(s => s.cmd === 'partSet').map(s => s.payload),
        partVels: sent.filter(s => s.cmd === 'partVel').map(s => s.payload),
        playerCmds: sent.filter(s => s.cmd === 'playerCmd').map(s => s.payload),
    };
}

const TESTS = [
    {
        name: 'KillBrick (Touched → Humanoid.Health = 0)',
        lua: `
            local part = script.Parent
            part.Touched:Connect(function(hit)
                local hum = hit.Parent and hit.Parent:FindFirstChild("Humanoid")
                if hum then hum.Health = 0 end
            end)
            print("kill brick armed")
        `,
        ticks: [],
        check: (r) => r.logs.some(l => l.text === 'kill brick armed'),
    },
    {
        name: 'WalkSpeed boost через trigger',
        lua: `
            local h = game:GetService("Players").LocalPlayer.Character.Humanoid
            h.WalkSpeed = 32
            print("speed boosted to", h.WalkSpeed)
        `,
        check: (r) => r.playerCmds.some(c => c.method === 'setWalkSpeed' && c.args[0] === 32)
            && r.logs.some(l => l.text.includes('speed boosted')),
    },
    {
        name: 'Door open: TweenService двигает дверь вверх',
        lua: `
            local door = workspace:FindFirstChild("Door")
            local TS = game:GetService("TweenService")
            local goal = { Position = Vector3.new(door.Position.X, door.Position.Y + 10, door.Position.Z) }
            local info = TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
            local tw = TS:Create(door, info, goal)
            tw:Play()
            print("door opening")
        `,
        ticks: [0.5, 0.5, 0.1],
        check: (r) => r.partSets.some(p => p.primId === 13 && p.prop === 'position'),
    },
    {
        name: 'Конвейер: BodyVelocity толкает игрока',
        lua: `
            local conv = workspace:FindFirstChild("Conveyor")
            local bv = Instance.new("BodyVelocity", conv)
            bv.Velocity = Vector3.new(20, 0, 0)
            bv.MaxForce = Vector3.new(4000, 0, 4000)
            print("conveyor started")
        `,
        ticks: [0.1],
        check: (r) => r.partVels.some(v => v.primId === 12 && v.vx === 20),
    },
    {
        name: 'leaderstats (как в tycoon)',
        lua: `
            local Players = game:GetService("Players")
            local plr = Players.LocalPlayer
            local money = Instance.new("IntValue", plr.leaderstats)
            money.Name = "Money"
            money.Value = 100
            print("money:", money.Value)
        `,
        check: (r) => r.logs.some(l => l.text === 'money:\t100'),
    },
    {
        name: 'Checkpoint сохраняется в DataStore',
        lua: `
            local DSS = game:GetService("DataStoreService")
            local store = DSS:GetDataStore("checkpoints")
            store:SetAsync("player1", 5)
            local cp = store:GetAsync("player1")
            print("checkpoint:", cp)
        `,
        check: (r) => r.logs.some(l => l.text === 'checkpoint:\t5'),
    },
    {
        name: 'Цикл с wait — подсчёт',
        lua: `
            for i = 1, 3 do
                print("count:", i)
                wait(0.3)
            end
            print("done")
        `,
        ticks: [0.3, 0.3, 0.3, 0.3],
        check: (r) => {
            const texts = r.logs.map(l => l.text);
            return texts.includes('count:\t1') && texts.includes('count:\t2')
                && texts.includes('count:\t3') && texts.includes('done');
        },
    },
    {
        name: 'task.spawn — параллельные функции',
        lua: `
            task.spawn(function() print("parallel A") end)
            task.spawn(function() print("parallel B") end)
            print("main")
        `,
        check: (r) => {
            const texts = r.logs.map(l => l.text);
            return texts.includes('parallel A') && texts.includes('parallel B') && texts.includes('main');
        },
    },
    {
        name: 'Color3 + Material смена при Touched',
        lua: `
            local part = workspace:FindFirstChild("KillPart")
            part.Touched:Connect(function()
                part.Color = Color3.fromRGB(0, 0, 255)
                part.Material = "Neon"
            end)
            -- симулируем touch
            part.Touched:Fire(workspace)
        `,
        check: (r) => r.partSets.some(p => p.primId === 10 && p.prop === 'color')
            && r.partSets.some(p => p.primId === 10 && p.prop === 'material'),
    },
    {
        name: 'RemoteEvent: client→server message',
        lua: `
            local re = Instance.new("RemoteEvent", workspace)
            re.Name = "Coins"
            re.OnServerEvent:Connect(function(player, amount)
                print("server received:", amount)
            end)
            re:FireServer(50)
        `,
        check: (r) => r.logs.some(l => l.text === 'server received:\t50'),
    },
    {
        name: 'Heartbeat: счётчик через RunService',
        lua: `
            local RS = game:GetService("RunService")
            local count = 0
            RS.Heartbeat:Connect(function(dt)
                count = count + 1
                if count == 3 then print("tick3") end
            end)
        `,
        ticks: [0.1, 0.1, 0.1],
        check: (r) => r.logs.some(l => l.text === 'tick3'),
    },
    {
        name: 'Math: Vector3 arithmetic',
        lua: `
            local a = Vector3.new(1, 2, 3)
            local b = Vector3.new(4, 5, 6)
            local sum = a:add(b)
            print("sum:", sum.X, sum.Y, sum.Z)
            local d = a:Dot(b)
            print("dot:", d)
        `,
        check: (r) => {
            const texts = r.logs.map(l => l.text);
            return texts.some(t => t === 'sum:\t5\t7\t9') && texts.some(t => t === 'dot:\t32');
        },
    },
];

(async () => {
    let passed = 0, failed = 0;
    for (const t of TESTS) {
        try {
            const r = await run(t.lua, t.targetPrimId, t.ticks || []);
            const ok = t.check(r);
            if (ok) { console.log(`✓ ${t.name}`); passed++; }
            else {
                console.log(`✗ ${t.name}`);
                console.log(`    logs: ${JSON.stringify(r.logs.map(l => l.text))}`);
                if (r.partSets.length) console.log(`    partSets: ${JSON.stringify(r.partSets)}`);
                if (r.partVels.length) console.log(`    partVels: ${JSON.stringify(r.partVels)}`);
                if (r.playerCmds.length) console.log(`    playerCmds: ${JSON.stringify(r.playerCmds)}`);
                failed++;
            }
        } catch (e) {
            console.log(`✗ ${t.name} — exception: ${e.message || e}`);
            failed++;
        }
    }
    console.log(`\n${passed} passed, ${failed} failed`);
    process.exit(failed > 0 ? 1 : 0);
})();
