'use strict';

var EventBus = require('../core/EventBus.js');

/* ────────────────────────────────────────────────────────────────
   Visual Scripting — Node Graph Framework
   
   A no-code programming system using node-based visual graphs.
   Nodes have typed ports, connections transport data between them.
──────────────────────────────────────────────────────────────── */
/* ── Node Library ─────────────────────────────────────────────── */
class NodeLibrary {
    constructor() {
        this.defs = new Map();
    }
    register(def) {
        this.defs.set(def.type, def);
    }
    get(type) {
        return this.defs.get(type);
    }
    getAll() {
        return Array.from(this.defs.values());
    }
    getByCategory(category) {
        return this.getAll().filter(d => d.category === category);
    }
    search(query) {
        const q = query.toLowerCase();
        return this.getAll().filter(d => d.name.toLowerCase().includes(q) ||
            d.type.toLowerCase().includes(q) ||
            d.description.toLowerCase().includes(q));
    }
}
/* ── Graph Executor ───────────────────────────────────────────── */
class GraphExecutor {
    constructor(library) {
        this.events = new EventBus.EventBus();
        this.library = library;
    }
    /** Execute a graph starting from an entry node (e.g. "on_start", "on_update"). */
    execute(graph, entryType, context) {
        // Find entry nodes
        const entries = graph.nodes.filter(n => n.type === entryType);
        for (const entry of entries) {
            this.executeNode(graph, entry, context);
        }
    }
    executeNode(graph, node, context) {
        const def = this.library.get(node.type);
        if (!def)
            return;
        // Gather inputs
        const inputs = { ...node.inputValues };
        for (const conn of graph.connections) {
            if (conn.toNode === node.id) {
                const fromNode = graph.nodes.find(n => n.id === conn.fromNode);
                if (fromNode) {
                    inputs[conn.toPort] = fromNode.outputValues[conn.fromPort];
                }
            }
        }
        // Setup flow trigger
        const flowTargets = [];
        const origTrigger = context.triggerFlow;
        context.triggerFlow = (portId) => {
            flowTargets.push(portId);
        };
        // Execute
        const outputs = def.execute(inputs, context);
        if (outputs) {
            node.outputValues = outputs;
        }
        // Follow flow connections
        for (const flowPort of flowTargets) {
            const outConns = graph.connections.filter(c => c.fromNode === node.id && c.fromPort === flowPort);
            for (const conn of outConns) {
                const nextNode = graph.nodes.find(n => n.id === conn.toNode);
                if (nextNode) {
                    this.executeNode(graph, nextNode, context);
                }
            }
        }
        context.triggerFlow = origTrigger;
    }
}
/* ── Built-in Nodes ───────────────────────────────────────────── */
function registerBuiltInNodes(library) {
    // ── Flow Control ───────────────────────────────
    library.register({
        type: 'event_start',
        name: 'On Start',
        category: 'flow',
        description: 'Fires once when the scene starts.',
        color: '#e64553',
        ports: [
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
        ],
        execute: (_inputs, ctx) => { ctx.triggerFlow('out'); },
    });
    library.register({
        type: 'event_update',
        name: 'On Update',
        category: 'flow',
        description: 'Fires every frame.',
        color: '#e64553',
        ports: [
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
            { id: 'dt', name: 'Delta Time', direction: 'output', dataType: 'number' },
        ],
        execute: (_inputs, ctx) => {
            ctx.triggerFlow('out');
            return { dt: ctx.dt };
        },
    });
    library.register({
        type: 'if_else',
        name: 'If / Else',
        category: 'flow',
        description: 'Branch based on condition.',
        color: '#fab387',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'cond', name: 'Condition', direction: 'input', dataType: 'boolean' },
            { id: 'true', name: 'True', direction: 'output', dataType: 'flow' },
            { id: 'false', name: 'False', direction: 'output', dataType: 'flow' },
        ],
        execute: (inputs, ctx) => {
            ctx.triggerFlow(inputs.cond ? 'true' : 'false');
        },
    });
    library.register({
        type: 'delay',
        name: 'Delay',
        category: 'flow',
        description: 'Wait for a duration then continue.',
        color: '#fab387',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'seconds', name: 'Seconds', direction: 'input', dataType: 'number', defaultValue: 1 },
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
        ],
        execute: (inputs, ctx) => {
            // In a real implementation this would be async/coroutine-based
            ctx.triggerFlow('out');
        },
    });
    library.register({
        type: 'sequence',
        name: 'Sequence',
        category: 'flow',
        description: 'Execute outputs in order.',
        color: '#fab387',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'out1', name: '1', direction: 'output', dataType: 'flow' },
            { id: 'out2', name: '2', direction: 'output', dataType: 'flow' },
            { id: 'out3', name: '3', direction: 'output', dataType: 'flow' },
        ],
        execute: (_inputs, ctx) => {
            ctx.triggerFlow('out1');
            ctx.triggerFlow('out2');
            ctx.triggerFlow('out3');
        },
    });
    // ── Math ───────────────────────────────────────
    library.register({
        type: 'math_add',
        name: 'Add',
        category: 'math',
        description: 'A + B',
        color: '#89b4fa',
        ports: [
            { id: 'a', name: 'A', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'b', name: 'B', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'number' },
        ],
        execute: (inputs) => ({ result: inputs.a + inputs.b }),
    });
    library.register({
        type: 'math_sub',
        name: 'Subtract',
        category: 'math',
        description: 'A - B',
        color: '#89b4fa',
        ports: [
            { id: 'a', name: 'A', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'b', name: 'B', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'number' },
        ],
        execute: (inputs) => ({ result: inputs.a - inputs.b }),
    });
    library.register({
        type: 'math_mul',
        name: 'Multiply',
        category: 'math',
        description: 'A × B',
        color: '#89b4fa',
        ports: [
            { id: 'a', name: 'A', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'b', name: 'B', direction: 'input', dataType: 'number', defaultValue: 1 },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'number' },
        ],
        execute: (inputs) => ({ result: inputs.a * inputs.b }),
    });
    library.register({
        type: 'math_div',
        name: 'Divide',
        category: 'math',
        description: 'A ÷ B',
        color: '#89b4fa',
        ports: [
            { id: 'a', name: 'A', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'b', name: 'B', direction: 'input', dataType: 'number', defaultValue: 1 },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'number' },
        ],
        execute: (inputs) => {
            const b = inputs.b;
            return { result: b !== 0 ? inputs.a / b : 0 };
        },
    });
    library.register({
        type: 'math_random',
        name: 'Random',
        category: 'math',
        description: 'Random number between min and max.',
        color: '#89b4fa',
        ports: [
            { id: 'min', name: 'Min', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'max', name: 'Max', direction: 'input', dataType: 'number', defaultValue: 1 },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'number' },
        ],
        execute: (inputs) => {
            const min = inputs.min;
            const max = inputs.max;
            return { result: min + Math.random() * (max - min) };
        },
    });
    // ── Comparison ─────────────────────────────────
    library.register({
        type: 'compare_eq',
        name: 'Equals',
        category: 'logic',
        description: 'A == B',
        color: '#a6e3a1',
        ports: [
            { id: 'a', name: 'A', direction: 'input', dataType: 'any' },
            { id: 'b', name: 'B', direction: 'input', dataType: 'any' },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'boolean' },
        ],
        execute: (inputs) => ({ result: inputs.a === inputs.b }),
    });
    library.register({
        type: 'compare_gt',
        name: 'Greater Than',
        category: 'logic',
        description: 'A > B',
        color: '#a6e3a1',
        ports: [
            { id: 'a', name: 'A', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'b', name: 'B', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'boolean' },
        ],
        execute: (inputs) => ({ result: inputs.a > inputs.b }),
    });
    library.register({
        type: 'compare_lt',
        name: 'Less Than',
        category: 'logic',
        description: 'A < B',
        color: '#a6e3a1',
        ports: [
            { id: 'a', name: 'A', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'b', name: 'B', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'boolean' },
        ],
        execute: (inputs) => ({ result: inputs.a < inputs.b }),
    });
    library.register({
        type: 'logic_and',
        name: 'And',
        category: 'logic',
        description: 'A AND B',
        color: '#a6e3a1',
        ports: [
            { id: 'a', name: 'A', direction: 'input', dataType: 'boolean' },
            { id: 'b', name: 'B', direction: 'input', dataType: 'boolean' },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'boolean' },
        ],
        execute: (inputs) => ({ result: !!(inputs.a && inputs.b) }),
    });
    library.register({
        type: 'logic_or',
        name: 'Or',
        category: 'logic',
        description: 'A OR B',
        color: '#a6e3a1',
        ports: [
            { id: 'a', name: 'A', direction: 'input', dataType: 'boolean' },
            { id: 'b', name: 'B', direction: 'input', dataType: 'boolean' },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'boolean' },
        ],
        execute: (inputs) => ({ result: !!(inputs.a || inputs.b) }),
    });
    library.register({
        type: 'logic_not',
        name: 'Not',
        category: 'logic',
        description: 'NOT A',
        color: '#a6e3a1',
        ports: [
            { id: 'a', name: 'A', direction: 'input', dataType: 'boolean' },
            { id: 'result', name: 'Result', direction: 'output', dataType: 'boolean' },
        ],
        execute: (inputs) => ({ result: !inputs.a }),
    });
    // ── Game / Input ───────────────────────────────
    library.register({
        type: 'input_action',
        name: 'Input Action',
        category: 'input',
        description: 'Read an input action value.',
        color: '#f9e2af',
        ports: [
            { id: 'action', name: 'Action', direction: 'input', dataType: 'string', defaultValue: 'moveX' },
            { id: 'pressed', name: 'Pressed', direction: 'output', dataType: 'boolean' },
            { id: 'value', name: 'Value', direction: 'output', dataType: 'number' },
        ],
        execute: (_inputs, ctx) => {
            var _a;
            const actionName = (_a = _inputs.action) !== null && _a !== void 0 ? _a : 'moveX';
            const input = ctx.services['input'];
            if (input === null || input === void 0 ? void 0 : input.getAction) {
                const state = input.getAction(actionName);
                return { pressed: state.pressed, value: state.value };
            }
            return { pressed: false, value: 0 };
        },
    });
    library.register({
        type: 'play_sound',
        name: 'Play Sound',
        category: 'audio',
        description: 'Play a sound effect.',
        color: '#cba6f7',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'soundId', name: 'Sound', direction: 'input', dataType: 'string' },
            { id: 'volume', name: 'Volume', direction: 'input', dataType: 'number', defaultValue: 1 },
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
        ],
        execute: (_inputs, ctx) => {
            var _a;
            const audio = ctx.services['audio'];
            if (audio === null || audio === void 0 ? void 0 : audio.play) {
                audio.play(_inputs.soundId, { volume: (_a = _inputs.volume) !== null && _a !== void 0 ? _a : 1 });
            }
            ctx.triggerFlow('out');
        },
    });
    // ── Entity ─────────────────────────────────────
    library.register({
        type: 'get_position',
        name: 'Get Position',
        category: 'entity',
        description: 'Get entity position.',
        color: '#74c7ec',
        ports: [
            { id: 'entity', name: 'Entity', direction: 'input', dataType: 'entity' },
            { id: 'x', name: 'X', direction: 'output', dataType: 'number' },
            { id: 'y', name: 'Y', direction: 'output', dataType: 'number' },
        ],
        execute: (_inputs, ctx) => {
            var _a;
            const world = ctx.world;
            const eid = ((_a = _inputs.entity) !== null && _a !== void 0 ? _a : ctx.entityId);
            if ((world === null || world === void 0 ? void 0 : world.getComponent) && eid != null) {
                const t = world.getComponent(eid, 'Transform');
                if (t === null || t === void 0 ? void 0 : t.position)
                    return { x: t.position.x, y: t.position.y };
            }
            return { x: 0, y: 0 };
        },
    });
    library.register({
        type: 'set_position',
        name: 'Set Position',
        category: 'entity',
        description: 'Set entity position.',
        color: '#74c7ec',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'entity', name: 'Entity', direction: 'input', dataType: 'entity' },
            { id: 'x', name: 'X', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'y', name: 'Y', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
        ],
        execute: (_inputs, ctx) => {
            var _a, _b, _c;
            const world = ctx.world;
            const eid = ((_a = _inputs.entity) !== null && _a !== void 0 ? _a : ctx.entityId);
            if ((world === null || world === void 0 ? void 0 : world.getComponent) && eid != null) {
                const t = world.getComponent(eid, 'Transform');
                if (t === null || t === void 0 ? void 0 : t.position) {
                    t.position.x = (_b = _inputs.x) !== null && _b !== void 0 ? _b : 0;
                    t.position.y = (_c = _inputs.y) !== null && _c !== void 0 ? _c : 0;
                }
            }
            ctx.triggerFlow('out');
        },
    });
    library.register({
        type: 'spawn_entity',
        name: 'Spawn Entity',
        category: 'entity',
        description: 'Create a new entity from a prefab.',
        color: '#74c7ec',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'prefab', name: 'Prefab', direction: 'input', dataType: 'string' },
            { id: 'x', name: 'X', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'y', name: 'Y', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
            { id: 'entity', name: 'Entity', direction: 'output', dataType: 'entity' },
        ],
        execute: (_inputs, ctx) => {
            ctx.triggerFlow('out');
            return { entity: null };
        },
    });
    library.register({
        type: 'destroy_entity',
        name: 'Destroy Entity',
        category: 'entity',
        description: 'Remove an entity from the world.',
        color: '#74c7ec',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'entity', name: 'Entity', direction: 'input', dataType: 'entity' },
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
        ],
        execute: (_inputs, ctx) => {
            ctx.triggerFlow('out');
        },
    });
    // ── Variables ──────────────────────────────────
    library.register({
        type: 'get_variable',
        name: 'Get Variable',
        category: 'variable',
        description: 'Read a variable value.',
        color: '#94e2d5',
        ports: [
            { id: 'name', name: 'Name', direction: 'input', dataType: 'string', defaultValue: '' },
            { id: 'value', name: 'Value', direction: 'output', dataType: 'any' },
        ],
        execute: (inputs, ctx) => { var _a; return ({ value: ctx.variables[(_a = inputs.name) !== null && _a !== void 0 ? _a : ''] }); },
    });
    library.register({
        type: 'set_variable',
        name: 'Set Variable',
        category: 'variable',
        description: 'Write a variable value.',
        color: '#94e2d5',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'name', name: 'Name', direction: 'input', dataType: 'string', defaultValue: '' },
            { id: 'value', name: 'Value', direction: 'input', dataType: 'any' },
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
        ],
        execute: (inputs, ctx) => {
            const name = inputs.name;
            if (name)
                ctx.variables[name] = inputs.value;
            ctx.triggerFlow('out');
        },
    });
    // ── Physics ────────────────────────────────────
    library.register({
        type: 'apply_force',
        name: 'Apply Force',
        category: 'physics',
        description: 'Apply a force to an entity.',
        color: '#f38ba8',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'entity', name: 'Entity', direction: 'input', dataType: 'entity' },
            { id: 'x', name: 'X', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'y', name: 'Y', direction: 'input', dataType: 'number', defaultValue: 0 },
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
        ],
        execute: (_inputs, ctx) => {
            ctx.triggerFlow('out');
        },
    });
    library.register({
        type: 'on_collision',
        name: 'On Collision',
        category: 'physics',
        description: 'Fires when entity collides with another.',
        color: '#f38ba8',
        ports: [
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
            { id: 'other', name: 'Other', direction: 'output', dataType: 'entity' },
            { id: 'normal_x', name: 'Normal X', direction: 'output', dataType: 'number' },
            { id: 'normal_y', name: 'Normal Y', direction: 'output', dataType: 'number' },
        ],
        execute: (_inputs, ctx) => {
            ctx.triggerFlow('out');
            return { other: null, normal_x: 0, normal_y: 0 };
        },
    });
    // ── Game Control ───────────────────────────────
    library.register({
        type: 'load_scene',
        name: 'Load Scene',
        category: 'game',
        description: 'Load a scene by ID.',
        color: '#eba0ac',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'sceneId', name: 'Scene ID', direction: 'input', dataType: 'string' },
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
        ],
        execute: (_inputs, ctx) => {
            ctx.triggerFlow('out');
        },
    });
    library.register({
        type: 'camera_shake',
        name: 'Camera Shake',
        category: 'game',
        description: 'Shake the camera.',
        color: '#eba0ac',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'intensity', name: 'Intensity', direction: 'input', dataType: 'number', defaultValue: 5 },
            { id: 'duration', name: 'Duration', direction: 'input', dataType: 'number', defaultValue: 0.3 },
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
        ],
        execute: (_inputs, ctx) => {
            ctx.triggerFlow('out');
        },
    });
    library.register({
        type: 'print',
        name: 'Print',
        category: 'flow',
        description: 'Log a value to the console.',
        color: '#fab387',
        ports: [
            { id: 'in', name: '', direction: 'input', dataType: 'flow' },
            { id: 'message', name: 'Message', direction: 'input', dataType: 'any' },
            { id: 'out', name: '', direction: 'output', dataType: 'flow' },
        ],
        execute: (inputs, ctx) => {
            ctx.events.emit('script:log', inputs.message);
            ctx.triggerFlow('out');
        },
    });
}
/* ── Helper: Create a default library with all built-in nodes ── */
function createDefaultNodeLibrary() {
    const lib = new NodeLibrary();
    registerBuiltInNodes(lib);
    return lib;
}

exports.GraphExecutor = GraphExecutor;
exports.NodeLibrary = NodeLibrary;
exports.createDefaultNodeLibrary = createDefaultNodeLibrary;
exports.registerBuiltInNodes = registerBuiltInNodes;
//# sourceMappingURL=NodeGraph.js.map
