/**
 * Mock WebSocket
 * Behaves like a real WebSocket but sends fake data,
 * keeping the code path identical to live mode.
 */

function MockWebSocket(url) {
    var self = this;

    this.url = url;
    this.readyState = WebSocket.CONNECTING;
    this.onopen = null;
    this.onmessage = null;
    this.onclose = null;
    this.onerror = null;
    this._interval = null;

    // Simulate connection delay
    setTimeout(function() {
        self.readyState = WebSocket.OPEN;
        if (self.onopen) {
            self.onopen({ type: 'open' });
        }
        self._startSending();
    }, 100);
}

// WebSocket States
MockWebSocket.CONNECTING = 0;
MockWebSocket.OPEN = 1;
MockWebSocket.CLOSING = 2;
MockWebSocket.CLOSED = 3;

MockWebSocket._createJoint = function(id, name, position, refState) {
    return {
        id: id,
        name: name,
        actualPosition: position,
        targetPosition: position,
        referencingState: refState,
        temperatureBoard: 35 + Math.random() * 5,
        temperatureMotor: 40 + Math.random() * 8,
        current: 100 + Math.random() * 50
    };
};

MockWebSocket._createMotionProgram = function(runState, program, cmdIndex, cmdCount) {
    return {
        runState: runState,
        replayMode: "single",
        mainProgram: program,
        currentProgram: program,
        currentProgramIndex: 0,
        programCount: 1,
        currentCommandIndex: cmdIndex,
        commandCount: cmdCount
    };
};

// Mutable mock state (modified by actions)
MockWebSocket.mockState = {
    robot: {
        hardwareState: "NoError",
        kinematicState: "normal",
        referencingState: "referenced",
        velocityOverride: 40,
        cartesianVelocity: 0.0,
        isEnabled: false,
        temperatureCPU: 45,
        supplyVoltage: 24000,
        currentAll: 500,
        tcp: { x: 217.4, y: 180.0, z: 350.2, a: 0, b: 0, c: 0 },
        joints: [
            MockWebSocket._createJoint(0, "A1", 0.0, "referenced"),
            MockWebSocket._createJoint(1, "A2", -20.0, "referenced"),
            MockWebSocket._createJoint(2, "A3", 110.0, "referenced"),
            MockWebSocket._createJoint(3, "A4", 0.0, "referenced"),
            MockWebSocket._createJoint(4, "A5", 90.0, "referenced"),
            MockWebSocket._createJoint(5, "A6", 0.0, "referenced"),
            MockWebSocket._createJoint(6, "E1", 0.0, "n/a"),
            MockWebSocket._createJoint(7, "E2", 0.0, "n/a"),
            MockWebSocket._createJoint(8, "E3", 0.0, "n/a")
        ]
    },
    motion: {
        motionProgram: MockWebSocket._createMotionProgram("not running", "", 0, 0),
        logicProgram: MockWebSocket._createMotionProgram("not running", "", 0, 0)
    },
    system: {
        robotType: "REBEL-6DOF-03",
        version: "15.0.0.0",
        projectFile: "DemoProject.project",
        projectTitle: "Demo Project",
        systemType: "Simulation",
        isSimulation: true,
        robotAxisCount: 6,
        externalAxisCount: 0
    },
    // Pre-formatted strings, mirroring info_formatting.build_info_variables in Python.
    variables: {
        programStartsTotal:  "87",
        programStarts:       "12",
        lastProgramDuration: "45 s",
        partsGood:           "142 (97.93%)",
        partsBad:            "3 (2.07%)",
        operatingTime:        "35 min",
        operatingTimeTotal:   "21 h 0 min",
        operatingTimeEnabled: "16 h 20 min",
        operatingTimeMotion:  "6 h 50 min",
        robotAxisDirectionChanges:    ["12345", "8732", "4012", "2987", "1654", "921"],
        externalAxisDirectionChanges: []
    },
    license: {
        valid: true,
        licensed: true,
        trialRemainingSeconds: 0
    }
};

// ?nolicense simulates a missing license (combine with mock mode: ?mock&nolicense).
// At runtime the console works too: MockWebSocket.mockState.license.valid = false
if (window.location.search.indexOf('nolicense') !== -1) {
    MockWebSocket.mockState.license.valid = false;
    MockWebSocket.mockState.license.licensed = false;
}

// Referencing timer (for simulation)
MockWebSocket._referencingTimer = null;

// Active instance (for actions)
MockWebSocket._instance = null;

// Error-code lookup table, fetched once from the backend so mock mode
// resolves hardwareState the same way the Python serializer does.
MockWebSocket._errorCodes = {};
fetch('/api/error-codes')
    .then(function (r) { return r.ok ? r.json() : {}; })
    .then(function (table) { MockWebSocket._errorCodes = table || {}; })
    .catch(function () { /* leave empty — frontend falls back to raw codes */ });

MockWebSocket._resolveError = function(state) {
    var code = state.robot && state.robot.hardwareState;
    if (!code || code === 'NoError') {
        state.robot.errorTitle = '';
        state.robot.errorDescription = '';
        return;
    }
    var entry = MockWebSocket._errorCodes[code];
    if (entry) {
        state.robot.errorTitle = entry.title;
        state.robot.errorDescription = entry.description;
    } else {
        state.robot.errorTitle = code;
        state.robot.errorDescription = '';
    }
};

MockWebSocket.prototype._startSending = function() {
    var self = this;
    MockWebSocket._instance = this;

    // Send state every 200ms (like real backend)
    this._interval = setInterval(function() {
        if (self.readyState !== WebSocket.OPEN) return;

        // Deep copy of current state
        var state = JSON.parse(JSON.stringify(MockWebSocket.mockState));

        // Simulate motion when running
        if (state.motion.motionProgram.runState === 'running') {
            // Increment command index (in original state)
            var mp = MockWebSocket.mockState.motion.motionProgram;
            mp.currentCommandIndex = (mp.currentCommandIndex + 1) % mp.commandCount;
            state.motion.motionProgram.currentCommandIndex = mp.currentCommandIndex;

            // Velocity based on override
            state.robot.cartesianVelocity = (state.robot.velocityOverride / 100) * 150 + Math.random() * 20;

            // Simulate joint movement
            for (var i = 0; i < 6; i++) {
                state.robot.joints[i].actualPosition += (Math.random() - 0.5) * 2;
                state.robot.joints[i].current = 200 + Math.random() * 100;
            }
        } else {
            state.robot.cartesianVelocity = 0;
        }

        // Small temperature fluctuations
        state.robot.temperatureCPU = 45 + Math.random() * 3;
        for (var j = 0; j < state.robot.joints.length; j++) {
            if (state.robot.joints[j].referencingState !== 'n/a') {
                state.robot.joints[j].temperatureMotor = 40 + Math.random() * 5;
            }
        }

        // Resolve error code → title + description so the wire shape
        // matches what the Python backend emits.
        MockWebSocket._resolveError(state);

        if (self.onmessage) {
            self.onmessage({
                type: 'message',
                data: JSON.stringify(state)
            });
        }
    }, 200);
};

MockWebSocket.prototype.send = function(data) {
    // Mock ignores incoming messages
    console.log('[MockWebSocket] Received:', data);
};

MockWebSocket.prototype.close = function() {
    this.readyState = WebSocket.CLOSED;
    if (this._interval) {
        clearInterval(this._interval);
    }
    if (this.onclose) {
        this.onclose({ type: 'close' });
    }
};

// ============================================
// Mock Actions (simulates API responses)
// ============================================

MockWebSocket.action = function(actionName) {
    var state = MockWebSocket.mockState;
    console.log('[MockWebSocket] Action:', actionName);

    switch (actionName) {
        case 'enable':
            // Mirror the backend /api/enable, which resets errors before
            // enabling the motors (so e.g. an MNE error after Reset is cleared
            // by pressing Enable).
            state.robot.hardwareState = 'NoError';
            state.robot.kinematicState = 'normal';
            state.robot.isEnabled = true;
            break;

        case 'disable':
            state.robot.isEnabled = false;
            // Stop program when disabling
            state.motion.motionProgram.runState = 'not running';
            break;

        case 'reset':
            if (state.motion.motionProgram.runState === 'running' ||
                state.motion.motionProgram.runState === 'paused') {
                // Reset mid-run mirrors the real controller: existing errors
                // are cleared but the motors end up disabled (MNE), while the
                // interpolator still reports the program as running/paused for
                // a moment. This is the case where Start/Pause must be disabled.
                state.robot.hardwareState = 'MNE';
                state.robot.kinematicState = 'normal';
                state.robot.isEnabled = false;
            } else {
                // Normal reset: clear errors and stop the program
                state.robot.hardwareState = 'NoError';
                state.robot.kinematicState = 'normal';
                state.robot.isEnabled = false;
                state.motion.motionProgram.runState = 'not running';
            }
            break;

        case 'reference':
            // Start referencing process
            if (state.robot.referencingState !== 'referencing...') {
                state.robot.referencingState = 'referencing...';
                for (var i = 0; i < 6; i++) {
                    state.robot.joints[i].referencingState = 'referencing...';
                }
                // Simulate referencing completion after 2 seconds
                if (MockWebSocket._referencingTimer) {
                    clearTimeout(MockWebSocket._referencingTimer);
                }
                MockWebSocket._referencingTimer = setTimeout(function() {
                    state.robot.referencingState = 'referenced';
                    for (var j = 0; j < 6; j++) {
                        state.robot.joints[j].referencingState = 'referenced';
                    }
                }, 2000);
            }
            break;

        case 'program/start':
            // Can only start if enabled, referenced, and program loaded
            if (state.robot.isEnabled &&
                state.robot.referencingState === 'referenced' &&
                state.motion.motionProgram.currentProgram) {
                state.motion.motionProgram.runState = 'running';
                state.motion.motionProgram.currentCommandIndex = 0;
            }
            break;

        case 'program/pause':
            if (state.motion.motionProgram.runState === 'running') {
                state.motion.motionProgram.runState = 'paused';
            } else if (state.motion.motionProgram.runState === 'paused') {
                // Resume on second pause
                state.motion.motionProgram.runState = 'running';
            }
            break;

        case 'program/stop':
            state.motion.motionProgram.runState = 'not running';
            state.motion.motionProgram.currentCommandIndex = 0;
            break;
    }
};

MockWebSocket.setVelocity = function(value) {
    MockWebSocket.mockState.robot.velocityOverride = Math.max(0, Math.min(100, value));
};

MockWebSocket.loadProgram = function(programName) {
    var state = MockWebSocket.mockState;
    state.motion.motionProgram.currentProgram = programName;
    state.motion.motionProgram.mainProgram = programName;
    state.motion.motionProgram.commandCount = 5 + Math.floor(Math.random() * 10);
    state.motion.motionProgram.currentCommandIndex = 0;
    state.motion.motionProgram.runState = 'not running';
    console.log('[MockWebSocket] Program loaded:', programName);
};

// ============================================
// Mock Jogging
// ============================================

MockWebSocket.jog = function(action, payload) {
    var state = MockWebSocket.mockState;
    console.log('[MockWebSocket] Jog:', action, payload);

    if (action === 'jog/stop') {
        return;
    }

    if (action === 'jog/joint-relative') {
        var idx = payload.axis;
        if (idx >= 0 && idx < state.robot.joints.length) {
            state.robot.joints[idx].targetPosition += payload.delta;
            state.robot.joints[idx].actualPosition = state.robot.joints[idx].targetPosition;
        }
    }

    if (action === 'jog/cart-relative') {
        var axisMap = { x: 'x', y: 'y', z: 'z', a: 'a', b: 'b', c: 'c' };
        var key = axisMap[payload.axis];
        if (key && state.robot.tcp) {
            state.robot.tcp[key] += payload.delta;
            // Simulate small joint changes when moving cartesian
            for (var i = 0; i < 6; i++) {
                state.robot.joints[i].targetPosition += (Math.random() - 0.5) * payload.delta * 0.1;
                state.robot.joints[i].actualPosition = state.robot.joints[i].targetPosition;
            }
        }
    }
};

MockWebSocket.setScenario = function(name) {
    var state = MockWebSocket.mockState;
    console.log('[MockWebSocket] Scenario:', name);

    switch (name) {
        case 'normal':
            state.robot.hardwareState = 'NoError';
            state.robot.kinematicState = 'normal';
            state.robot.referencingState = 'referenced';
            state.robot.isEnabled = false;
            state.motion.motionProgram.runState = 'not running';
            for (var i = 0; i < 6; i++) {
                state.robot.joints[i].referencingState = 'referenced';
            }
            break;

        case 'error':
            state.robot.hardwareState = 'ESTOP / LOWV';
            state.robot.kinematicState = 'KINEMATIC_ERROR_MOTION_NOT_ALLOWED';
            state.robot.referencingState = 'not referenced';
            state.robot.isEnabled = false;
            state.motion.motionProgram.runState = 'not running';
            for (var j = 0; j < 6; j++) {
                state.robot.joints[j].referencingState = 'not referenced';
            }
            break;

        case 'running':
            state.robot.hardwareState = 'NoError';
            state.robot.kinematicState = 'normal';
            state.robot.referencingState = 'referenced';
            state.robot.isEnabled = true;
            state.motion.motionProgram.runState = 'running';
            state.motion.motionProgram.currentProgram = 'ReBel_MoveToZero.xml';
            state.motion.motionProgram.commandCount = 8;
            for (var k = 0; k < 6; k++) {
                state.robot.joints[k].referencingState = 'referenced';
            }
            break;
    }
};
