// jsonrpc.js - Implementation of JSON-RPC 2.0 protocol for Moonraker communication
import { loadConfig } from "../config.js";
import log4js from "log4js";

// Load configuration
const config = loadConfig();

const logger = log4js.getLogger("JSON RPC");
logger.level = config.logLevel;

export class JsonRPC {
  constructor() {
    this.idCounter = 0;
    this.methods = {};
    this.pendingRequests = {};
  }

  // Generate a unique ID for RPC requests
  generateId() {
    return this.idCounter++;
  }

  // Register a method to handle incoming notifications/responses
  registerMethod(methodName, callback) {
    this.methods[methodName] = callback;
  }

  // Process incoming messages
  processMessage(message) {
    if (message.method && this.methods[message.method]) {
      // Handle method call or notification
      this.methods[message.method](message.params);
      logger.trace(`JSON-RPC notification: ${message.method}`);
    } else if (message.id !== undefined) {
      // Handle response to a previous request
      const rpcPromise = this.pendingRequests[message.id];
      if (rpcPromise) {
        if (message.error) {
          logger.error(`Error in JSON-RPC response: ${message.error}`);
          rpcPromise.reject(message.error);
        } else {
          logger.debug(`JSON-RPC response: OK`);
          logger.trace("Result:", message.result);

          rpcPromise.resolve(message.result);
        }
        delete this.pendingRequests[message.id];
      }
    }
    // If it's a notification without a registered method, ignore it
  }

  // Call a method without parameters
  callMethod(method) {
    return this.callMethodWithKwargs(method, {});
  }

  // Call a method with parameters
  callMethodWithKwargs(method, params) {
    logger.debug(`Calling method: ${method}`);
    logger.trace("Params:", params);
    const id = this.generateId();
    const request = {
      jsonrpc: "2.0",
      method: method,
      params: params,
      id: id,
    };

    return new Promise((resolve, reject) => {
      this.pendingRequests[id] = { resolve, reject };
      // The actual sending of the message is done by the WebSocket connection
      // This just prepares the message and returns a promise
      if (this.socket) {
        this.socket.send(JSON.stringify(request));
      } else {
        // If socket is not directly attached to this instance, the caller
        // is responsible for sending the serialized request
        this.lastRequest = JSON.stringify(request);
      }
    });
  }

  // For external socket handling
  getLastRequest() {
    const req = this.lastRequest;
    this.lastRequest = null;
    return req;
  }

  // Associate a WebSocket with this RPC instance for direct communication
  setSocket(socket) {
    this.socket = socket;
  }
}
