import * as plugins from './smartsocket.plugins.js';
import * as interfaces from './interfaces/index.js';

// import interfaces
import {
  SocketFunction,
  type ISocketFunctionCallDataRequest,
  type ISocketFunctionCallDataResponse,
} from './smartsocket.classes.socketfunction.js';

// import classes
import { SocketConnection } from './smartsocket.classes.socketconnection.js';
import { logger } from './smartsocket.logging.js';
import { Smartsocket } from './smartsocket.classes.smartsocket.js';
import { SmartsocketClient } from './smartsocket.classes.smartsocketclient.js';

// export interfaces
export type TSocketRequestStatus = 'new' | 'pending' | 'finished';
export type TSocketRequestSide = 'requesting' | 'responding';

/**
 * interface of constructor of class SocketRequest
 */
export interface ISocketRequestConstructorOptions<
  T extends plugins.typedrequestInterfaces.ITypedRequest
> {
  side: TSocketRequestSide;
  originSocketConnection: SocketConnection;
  shortId: string;
  funcCallData: ISocketFunctionCallDataRequest<T>;
}

/**
 * request object that is sent initially and may or may not receive a response
 */
export interface ISocketRequestDataObject<T extends plugins.typedrequestInterfaces.ITypedRequest> {
  funcCallData: ISocketFunctionCallDataRequest<T> | ISocketFunctionCallDataResponse<T>;
  shortId: string;
  responseTimeout?: number;
}

// export classes
export class SocketRequest<T extends plugins.typedrequestInterfaces.ITypedRequest> {
  // STATIC
  public static getSocketRequestById(
    smartsocketRef: Smartsocket | SmartsocketClient,
    shortIdArg: string
  ): SocketRequest<any> {
    return smartsocketRef.socketRequests.findSync((socketRequestArg) => {
      return socketRequestArg.shortid === shortIdArg;
    });
  }

  // INSTANCE
  public status: TSocketRequestStatus = 'new';
  public side: TSocketRequestSide;
  public shortid: string;
  public originSocketConnection: SocketConnection;
  public funcCallData: ISocketFunctionCallDataRequest<T>;
  public done = plugins.smartpromise.defer<ISocketFunctionCallDataResponse<T>>();

  public smartsocketRef: Smartsocket | SmartsocketClient;

  constructor(
    smartsocketRefArg: Smartsocket | SmartsocketClient,
    optionsArg: ISocketRequestConstructorOptions<T>
  ) {
    this.smartsocketRef = smartsocketRefArg;
    this.side = optionsArg.side;
    this.shortid = optionsArg.shortId;
    this.funcCallData = optionsArg.funcCallData;
    this.originSocketConnection = optionsArg.originSocketConnection;
    this.smartsocketRef.socketRequests.add(this);
  }

  // requesting --------------------------

  /**
   * dispatches a socketrequest from the requesting to the receiving side
   */
  public dispatch(): Promise<ISocketFunctionCallDataResponse<T>> {
    const message: interfaces.ISocketMessage<interfaces.IFunctionCallPayload> = {
      type: 'function',
      id: this.shortid,
      payload: {
        funcName: this.funcCallData.funcName,
        funcData: this.funcCallData.funcDataArg,
      },
    };
    this.originSocketConnection.sendMessage(message);
    return this.done.promise;
  }

  /**
   * handles the response that is received by the requesting side
   */
  public async handleResponse(responseDataArg: ISocketRequestDataObject<T>) {
    this.done.resolve(responseDataArg.funcCallData);
    this.smartsocketRef.socketRequests.remove(this);
  }

  // responding --------------------------

  /**
   * creates the response on the responding side
   */
  public async createResponse(): Promise<void> {
    const targetSocketFunction: SocketFunction<T> = SocketFunction.getSocketFunctionByName(
      this.smartsocketRef,
      this.funcCallData.funcName
    );

    if (!targetSocketFunction) {
      logger.log('error', `There is no SocketFunction defined for ${this.funcCallData.funcName}`);
      return;
    }

    targetSocketFunction
      .invoke(this.funcCallData, this.originSocketConnection)
      .then((resultData) => {
        const message: interfaces.ISocketMessage<interfaces.IFunctionCallPayload> = {
          type: 'functionResponse',
          id: this.shortid,
          payload: {
            funcName: resultData.funcName,
            funcData: resultData.funcDataArg,
          },
        };
        this.originSocketConnection.sendMessage(message);
        this.smartsocketRef.socketRequests.remove(this);
      })
      .catch((error) => {
        logger.log('error', `Function invocation failed for ${this.funcCallData.funcName}: ${error instanceof Error ? error.message : String(error)}`);
        this.smartsocketRef.socketRequests.remove(this);
      });
  }
}
