import AmbientConfig from "./config/AmbientConfig";
import { socketConfig } from "./config/socketConfig";
import { AmbientRestAPI } from "./api/AmbientRestAPI";
import { Logger } from "./utils/Logger";
import { Guard } from "./utils/Guard";
import { AugnitoRecorder } from "augnitorecorder";
import { AugnitoSocketResponse } from "./support/AugnitoSocketResponse";
import { AmbientPACEAPI } from "./api/AmbientPACEAPI";
import { SettingsConfigType, UserConfigMap } from "./config/SettingsConfig";
import { NoteTypeConfig } from "./config/NoteTypeConfig";
import { HttpCodedError, NoteOutputStyleConfig, NoteVerbosityConfig, PreferenceConfig } from "./config";
import { ErrorCodes } from "./config/ErrorCodes";
import { ErrorMessages } from "./utils/Constants";
import ErrorInfo from "./config/ErrorInfo";
import NetworkMonitor from "./support/NetworkMonitor";

/**
 * Augnito Ambient Manager
 * @description Handles the connection with the Ambient API Server.
 */
export class AugnitoAmbient {
  private _logTag = "Augnito-Ambient";
  private _ambientRestAPI: AmbientRestAPI;
  private _ambientPaceAPI: AmbientPACEAPI;
  private config: socketConfig | null | undefined;
  private enableLog: boolean | undefined;
  private shouldReadAudioIntensity: boolean | undefined;
  private recorderIns: AugnitoRecorder | undefined;
  private forceStopAudio: boolean = false;
  private _isRecording: boolean = false;
  private echoCancellation: boolean | { exact: boolean } | undefined;
  private noiseSuppression: boolean | { exact: boolean } | undefined;
  private autoGainControl: boolean | { exact: boolean } | undefined;
  constructor(config: AmbientConfig) {
    this._ambientRestAPI = new AmbientRestAPI(this.validateConfig(config));
    this._ambientPaceAPI = new AmbientPACEAPI(config);
    this.config = this.createSocketConfig(config);
    this.enableLog = config.enableLogs;
    this.shouldReadAudioIntensity = config.shouldReadAudioIntensity;
    this.echoCancellation = config.echoCancellation;
    this.noiseSuppression = config.noiseSuppression;
    this.autoGainControl = config.autoGainControl;
  }

  /**
   * @description Callback to receive the JOb Id
   */
  public onJobCreated?: (text: string) => void;

  /**
   * @description Callback for status changed
   */
  public onStateChanged?: (isRecording: boolean) => void;

  /**
   * Callback triggered when an error occurs within the SDK
   */
  public onError?: (error: ErrorInfo) => void;
  public onOtherResult?: (text: string) => void;
  public onIntensityValue?: (intensity: number) => void;
  public onIdleMic?: () => void;
  public onSoapNoteGenerated?: (soapNote: string) => void;
  public onTranscriptGenerated?: (transcript: string) => void;
  public onCodesGenerated?: (codes: string) => void;
  public onJobStatus?: (statusResponse: string) => void;
  private initRecorder(url: string) {
    // console.log(_filetype, _noteparams);
    this.recorderIns = new AugnitoRecorder({
      // "{\"Region\": 801, \"Specialty\": 200, \"NoteType\": 40, \"Gender\": 0}"
      serverURL: url,
      enableLogs: this.enableLog ?? false,
      isDebug: this.enableLog ?? false,
      bufferInterval: 15,
      EOS_Message: `{"JobAction": "EOS","Status": 0,"Type": "meta"}`,
      socketTimeoutInterval: 20000,
      shouldSendAudioDataSequence: true,
      shouldReadIntensity: this.shouldReadAudioIntensity,
      echoCancellation: this.echoCancellation,
      noiseSuppression: this.noiseSuppression,
      autoGainControl: this.autoGainControl
    });
    this.recorderIns.echoCancellation = this.echoCancellation ?? false;
    this.recorderIns.noiseSuppression = this.noiseSuppression ?? false;
    this.recorderIns.autoGainControl = this.autoGainControl ?? false;
    this.recorderIns.onError = this.onErrorCallback.bind(this);
    this.recorderIns.onStateChanged = this.onStateChangeCallback.bind(this);
    this.recorderIns.onSessionEvent = this.onSessionEventCallback.bind(this);
    this.recorderIns.onOtherResults = this.onOtherResultsCallback.bind(this);
    this.recorderIns.onIntensity = this.onIntensityCallback.bind(this);
    this.recorderIns.onSpeechResponse = this.onSpeechResponseCallback.bind(this);
    this.recorderIns.onJobStatus = this.onJobStatusEventCallback.bind(this);
  }

  // #region Public Methods

  /**
   * Returns the Note parameters which need be use while doing toggle listeing
   * @returns JSON object of Note parameters
   */
  public async getNoteParams(): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var responseJson = await this._ambientPaceAPI.getNoteParams();
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param PageSize number of notes to fetch
   * @param PageID is optional parameter, send start page id to fetch
   * @returns list of notes for the user
   */
  public async getAllNotes(PageSize: number, PageID?: string): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var responseJson = await this._ambientRestAPI.ListJobs(PageSize, PageID);
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param JobId to retrieve output for a specific audio file
   * @returns JSON object contains both Transcript and Note on sucess else fail resonse
   */
  public async getSummarizedNote(JobId: string): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var responseJson = await this._ambientRestAPI.FetchJob(JobId);
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param JobId needs to pass to store the final Note for that audio
   * @param NoteDate This key will store the final edited Note that user wants to send.
   * @returns suceess response on successful submit of note else fail response
   */
  public async sendSummarizedNote(
    JobId: string,
    NoteDate: string
  ): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      return await this._ambientRestAPI.SendFinalNote(JobId, NoteDate);
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param filetype Type of file being uploaded, wav, mp3, etc. Ex: “filetype=wav“
   * @param noteparams Qualifiers to determine the type of clinical note to be generated for that audio file.
   * @returns Callback triggers to reurn the Job id on meta message
   */
  public toggleListening(
    _filetype: string,
    _noteparams: string,
    jobName?: string,
    jobId?: string,
    recordedDuration?: number
  ): void {
    // console.log("toggleListening:", _filetype, _noteparams);
    var serverUrl =
      this.config?.prepareWSSURL(_filetype, _noteparams, jobName, jobId) || "";
    if (!this.recorderIns) {
      this.initRecorder(serverUrl);
    }
    this.recorderIns?.toggleStartStopAudioStream(recordedDuration, serverUrl);
  }

  /**
   * Method called to pause or resume audio recording accordingly
   * @param filetype Type of file being uploaded, wav, mp3, etc. Ex: “filetype=wav“
   * @param noteparams Qualifiers to determine the type of clinical note to be generated for that audio file.
   * @returns Callback triggers to reurn the Job id on meta message
   */
  public togglePauseResumeListening(
    _filetype: string,
    _noteparams: string,
    jobName?: string,
    jobId?: string,
    recordedDuration?: number
  ): void {
    // console.log("togglePauseResumeListening:", _filetype, _noteparams);
    var serverUrl =
      this.config?.prepareWSSURL(_filetype, _noteparams, jobName, jobId) || "";
    if (!this.recorderIns) {
      this.initRecorder(serverUrl);
    }
    this.recorderIns?.togglePauseResumeAudioStream(recordedDuration, serverUrl);
  }

  /**
   * Method called to start audio recording
   */
  public startListening(): void {
    this.recorderIns?.startAudio();
  }

  /**
   * Method called to stop audio recording and clean up resources
   */
  public stopListening(): void {
    this.recorderIns?.stopAudio(true, true);
  }

  /**
   * @param JobIds to delete one or more notes
   * @returns success response on successful delete of notes else fail response
   */
  public async deleteNotes(JobIds: string[]): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      // if (!await this.isInternetAvailable()) {
      //   this.onErrorCallback(ErrorMessages.noInternetConnection);
      //   return;
      // }
      var responseJson = await this._ambientRestAPI.DeleteNotes(JobIds);
      if (responseJson) {
        if (responseJson.Status === 200) {
          this.recorderIns?.stopAudio(false, true);
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param JobId to rename note title
   * @param NewJobName new title for the note
   * @returns success response on successful rename of note else fail response
   */
  public async renameNoteTitle(
    JobId: string,
    NewJobName: string
  ): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      // if (!await this.isInternetAvailable()) {
      //   this.onErrorCallback(ErrorMessages.noInternetConnection);
      //   return;
      // }
      var responseJson = await this._ambientRestAPI.RenameNoteTitle(
        JobId,
        NewJobName
      );
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param JobId to retrieve pdf for a specific note
   * @param NoteType takes a NoteTypeConfig value 
   * @param SpecialityId a numberic value which identifies the department to populate in the PDF
   * @param Language, optional parameter, to specify the language in which to generate PDF, default value being English
   * @param PersonnelInfo, optional parameter, to give details to fill into header of PDF. Default json string is '{"PatientName":"","PatientID":"","Age":"","Gender":"","ReceivingDoctor":"","DoctorName":"","DoctorDesignation":"","DoctorEmail":""}'
   * @param RegenerateNote, optional parameter, to indicate if PDF has to be re-generated especially after an update is made to the note
   * @returns json response with base64 string of the pdf output to be read from Data.PDF of the response 
   */
  public async getNotePDF(JobId: string, NoteType: NoteTypeConfig, SpecialityId: number, Language?: string, PersonnelInfo?: string, RegenerateNote?: boolean): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      // if (!await this.isInternetAvailable()) {
      //   this.onErrorCallback(ErrorMessages.noInternetConnection);
      //   return;
      // }
      var responseJson = await this._ambientRestAPI.DownloadNotePDF(JobId, NoteType, SpecialityId, Language, PersonnelInfo, RegenerateNote);
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param JobId to end note forcefully for paused job
   * @returns success response on end of job else fail response
   */
  public async endPausedJob(JobId: string): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var responseJson = await this._ambientRestAPI.EndJob(JobId);
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param JobId needs to be passed to generate output
   * @param Regenerate a bool to be set to true if CDI suggestions should be re-generated after an update is made to the note else set to false
   * @param SoapNote is the structered soap note used to generate suggestions
   * @param Codes structured medical codes used to generate suggestions
   * @param PatientDetails json string which contains details such as age, gender and history of patient
   * @returns JSON object contains suggestion Items on success else fail response 
   */
  public async generateCDISuggestions(
    JobId: string,
    Regenerate: boolean,
    SoapNote?: string,
    Codes?: string,
    PatientDetails?: string,
  ): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var cdiVersion = 1;
      var versionRes = await this._ambientPaceAPI.getOrgConfigMapping(SettingsConfigType.CDIVersion);
      if (versionRes?.Items && versionRes.Items.length > 0 && versionRes.Items[0].Settings) {
        var setting = versionRes.Items[0].Settings.find((x: { ID: number; }) => x.ID === 209);
        if (setting)
          cdiVersion = Number.parseInt(setting.ConfigValue);
      }
      var responseJson = await this._ambientRestAPI.GenerateSuggestions(
        JobId,
        Regenerate,
        SoapNote,
        PatientDetails,
        Codes,
        cdiVersion
      );
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param JobId for which patient context needs to be updated
   * @param Context a text string which gives context/history of patient useful to generate note
   * @param Replace to be set to true if an update of context needs to be saved
   * @returns success response on update of context else fail response
   */
  public async updatePatientContext(JobId: string, Context: string,
    Replace: boolean) {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      // if (!await this.isInternetAvailable()) {
      //   this.onErrorCallback(ErrorMessages.noInternetConnection);
      //   return;
      // }
      var responseJson = await this._ambientRestAPI.UpdatePatientContext(
        JobId,
        Context,
        Replace
      );
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param JobId for which note such as patient/referral/CDI suggestions needs to be updated
   * @param NoteType takes a NoteTypeConfig value
   * @param NewNote to update note json with specific values modified such as to submit feedback for CDI suggestions
   * @returns success response on update of new note else fail response
   */
  public async modifyGeneratedNote(JobId: string, NoteType: NoteTypeConfig, NewNote?: string): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var responseJson = await this._ambientRestAPI.ModifyGeneratedNote(
        JobId,
        NoteType,
        NewNote
      );
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param JobId to retrieve transcript for a specific audio file
   * @returns JSON object contains Time in seconds, Text spoken at that time and also array of MedicalData to highlight on success else fail response. 
   */
  public async fetchTranscriptionForNote(JobId: string) {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var preference = await this._ambientPaceAPI.getUserPreferences();
      if (preference.Items[0].AutomaticTranscriptionEnabledForOrg) {
        var responseJson = await this._ambientRestAPI.FetchTranscription(
          JobId
        );
        if (responseJson) {
          if (responseJson.Status === 200) {
            return responseJson;
          } else {
            this.onErrorCallback(responseJson.ServerMessage);
          }
        } else {
          this.onErrorCallback("Unknown Error!");
        }
      } else {
        this.onErrorCallback(ErrorMessages.transcriptionNotEnabled)
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }

  /**
   * @param JobId the job identifier for which feedback is being submitted
   * @param Ratings a numeric rating from 1 to 5, value representing user satisfaction with the generated note
   * @param Comment optional text comment providing additional feedback details
   * @param Type optional string to specify the feedback category, defaults to 'SOAPFeedBack'
   * @returns success response on successful submission of feedback else fail response
   */
  public async sendFeedback(JobId: string, Ratings: number, Comment?: string, Type?: string): Promise<any> {
    try {
      if (!this._ambientRestAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var preference = await this._ambientPaceAPI.getUserPreferences();
      if (preference.Items[0]?.FeedbackEnabledForOrg && preference.Items[0]?.FeedbackEnabled) {
        var responseJson = await this._ambientRestAPI.SendFeedback(
          JobId,
          Ratings,
          Comment ?? '',
          Type ?? 'SOAPFeedBack'
        );

        if (responseJson) {
          if (responseJson.Status === 200) {
            return responseJson;
          } else {
            this.onErrorCallback(responseJson.ServerMessage);
          }
        } else {
          this.onErrorCallback("Unknown Error!");
        }
      } else {
        this.onErrorCallback(ErrorMessages.feedbackNotEnabled)
      }
    } catch (e: any) {
      this.handleException(e)
    }
  }


  // /**
  //  * @param ConfigId to retrieve master config list for speciality type, visit type etc
  //  * @returns a list of all available type
  //  */
  // public async getMasterConfiguration(
  //   ConfigId: SettingsConfigType
  // ): Promise<any> {
  //   try {
  //     if (!this._ambientPaceAPI) {
  //       Logger.error("SDK not initialized", this._logTag);
  //       return;
  //     }
  //     var responseJson = await this._ambientPaceAPI.getSettingsConfigMaster(
  //       ConfigId
  //     );
  //     if (responseJson) {
  //       if (responseJson.Status === 200) {
  //         return responseJson;
  //       } else {
  //         this.onErrorCallback(responseJson.ErrorMessage);
  //       }
  //     } else {
  //       this.onErrorCallback("Unknown Error!");
  //     }
  //   } catch (e: any) {
  //     if (e instanceof Error) {
  //       this.onErrorCallback(e.message);
  //     }
  //   }
  // }

  /**
   * @param ConfigId to retrieve user config list for speciality type, visit type etc
   * @returns a list of user config for a particular type
   */
  public async getUserConfiguration(
    ConfigId: SettingsConfigType
  ): Promise<any> {
    try {
      if (!this._ambientPaceAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var responseJson = await this._ambientPaceAPI.getUserConfigSettings(
        ConfigId
      );
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e);
    }
  }

  /**
   * @param ConfigMap a list of userconfig settings to update selected/unselected speciality type, visit type etc
   * @returns success response on successful update else fail response
   */
  public async updateUserConfiguration(
    ConfigMap: UserConfigMap[]
  ): Promise<any> {
    try {
      if (!this._ambientPaceAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var responseJson = await this._ambientPaceAPI.updateUserConfig(ConfigMap);
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e);
    }
  }

  /**
   * @returns a list of user preference 
   */
  public async getUserPreferenceSettings(): Promise<any> {
    try {
      if (!this._ambientPaceAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var responseJson = await this._ambientPaceAPI.getUserPreferences();
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e);
    }
  }

  /**
   * @param NewUserPreference a list of preference of type PreferenceConfig to be updated
   * @returns success response on successful update else fail response
   */
  public async updateUserPreferenceSettings(
    NewUserPreference: PreferenceConfig
  ): Promise<any> {
    try {
      if (!this._ambientPaceAPI) {
        Logger.error("SDK not initialized", this._logTag);
        return;
      }
      var responseJson = await this._ambientPaceAPI.updateUserPreference(NewUserPreference.AutomaticallyGenerateTranscript ?? false, NewUserPreference.PreferredTranscriptionLanguage ?? 'English', NewUserPreference.HighlightMedicalTerms ?? false, NewUserPreference.DisplayPatientContext ?? false, NewUserPreference.NoteOutputStyle ?? NoteOutputStyleConfig.DashFormat, NewUserPreference.NoteOutputVerbosity ?? NoteVerbosityConfig.Detailed);
      if (responseJson) {
        if (responseJson.Status === 200) {
          return responseJson;
        } else {
          this.onErrorCallback(responseJson.ServerMessage);
        }
      } else {
        this.onErrorCallback("Unknown Error!");
      }
    } catch (e: any) {
      this.handleException(e);
    }
  }

  /**
   * @param options an object which is optional and takes custom url as string and timeout in milliseconds
   * @returns true if connected else false and onError callback triggered with SDK12
   */
  public async isInternetAvailable(options?: { url: null; timeoutMs?: null; } | undefined) {
    const online = await NetworkMonitor.isOnline(options);
    if (!online) {
      this.onErrorCallback(ErrorMessages.noInternetConnection);
    }
    return online;
  }

  // #endregion

  // #region client callbacks

  private onEventCallback(data: string): void {
    if (this.onJobCreated) {
      this.onJobCreated(data);
    }
  }

  private onStateChangeCallback(isRecording: boolean): void {
    if (this.forceStopAudio) {
      this.recorderIns?.stopAudio(false, true);
      this.forceStopAudio = false;
      return;
    }
    this._isRecording = isRecording;
    if (this.onStateChanged) {
      this.onStateChanged(isRecording);
    }
  }

  private handleException(e: Error) {
    if (e instanceof TypeError) {
      this.onErrorCallback(e.message)
    }
    else if (e instanceof HttpCodedError) {
      throw e;
    } else {
      Logger.error(e.message, this._logTag);
    }
  }

  private onErrorCallback(errorMessage: string, errorCode?: string): void {
    var errorInfo: ErrorInfo = {
      ErrorCode: errorCode ?? ErrorCodes.ERRUNKNOWN,
      ErrorMessage: errorMessage
    }
    try {
      var error = JSON.parse(errorMessage);
      errorInfo.ErrorMessage = error.Data;
      if (error.Data === ErrorMessages.timeoutExceeded) {
        this.stopListening();
        Logger.error("Recording time limit exceeded", this._logTag);
        errorInfo.ErrorCode = ErrorCodes.WSJOB05
      } else if (error.Data === ErrorMessages.invalidUserDetails) {
        if (this._isRecording) {
          this.recorderIns?.stopAudio(false, true);
        } else {
          this.forceStopAudio = true; //required to stop mic if there is delay in starting microphone
        }
        errorInfo.ErrorCode = ErrorCodes.WSAUTH03
      } else if (error.Data.includes(ErrorMessages.unableTofetchAccount)) {
        errorInfo.ErrorCode = ErrorCodes.WSAUTH04
      } else if (error.Data.includes(ErrorMessages.unableToFetchJob)) {
        errorInfo.ErrorCode = ErrorCodes.WSJOB01
      } else if (error.Data.includes(ErrorMessages.jobNotConnectable)) {
        errorInfo.ErrorCode = ErrorCodes.WSJOB02
      } else if (error.Data.includes(ErrorMessages.rateLimitExceeded)) {
        errorInfo.ErrorCode = ErrorCodes.WSACC01
      } else if (error.Data.includes(ErrorMessages.weakAudioSignal)) {
        errorInfo.ErrorCode = ErrorCodes.WSAUD01
      }

    } catch (ex) { }
    if (errorInfo.ErrorMessage?.includes(ErrorMessages.jobIdEmpty)) {
      errorInfo.ErrorCode = ErrorCodes.SDK01
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.pageSizeEmpty)) {
      errorInfo.ErrorCode = ErrorCodes.SDK02
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.noteDataEmpty)) {
      errorInfo.ErrorCode = ErrorCodes.SDK03
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.jobNameEmpty)) {
      errorInfo.ErrorCode = ErrorCodes.SDK04
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.patientContextEmpty)) {
      errorInfo.ErrorCode = ErrorCodes.SDK05
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.micPermissionDenied)) {
      errorInfo.ErrorCode = ErrorCodes.SDK06
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.micMuted)) {
      errorInfo.ErrorCode = ErrorCodes.SDK07
      this.recorderIns?.stopAudio(false, true);
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.audioUnplugged)) {
      errorInfo.ErrorCode = ErrorCodes.SDK08
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.invalidSubscription) || errorInfo.ErrorMessage?.includes(ErrorMessages.invalidRequest)) {
      errorInfo.ErrorCode = ErrorCodes.AUTH01
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.activeSeatNotAvailable)) {
      errorInfo.ErrorCode = ErrorCodes.AUTH02
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.noInternetConnection)) {
      errorInfo.ErrorCode = ErrorCodes.SDK12
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.transcriptionNotEnabled)) {
      errorInfo.ErrorCode = ErrorCodes.SDK13
    } else if (errorInfo.ErrorMessage?.includes(ErrorMessages.feedbackNotEnabled)) {
      errorInfo.ErrorCode = ErrorCodes.SDK14
    }
    if (this.onError) {
      this.onError(errorInfo);
    }
  }

  private onOtherResultsCallback(data: string): void {
    if (this.onOtherResult) {
      this.onOtherResult(data);
    }
  }

  private onIntensityCallback(intensity: number): void {
    if (this.onIntensityValue) {
      this.onIntensityValue(intensity);
    }
  }

  private onIdleMicCallback(): void {
    if (this.onIdleMic) {
      this.onIdleMic();
    }
  }

  private onSpeechResponseCallback(data: string): void {
    var json = JSON.parse(data);
    if (json.Type.toLowerCase() === "note" && this.onSoapNoteGenerated) {
      this.onSoapNoteGenerated(json.Data);
    }
    else if (json.Type.toLowerCase() === "transcript" && this.onTranscriptGenerated) {
      this.onTranscriptGenerated(json.Data);
    } else if (json.Type.toLowerCase() === "codes" && this.onCodesGenerated) {
      this.onCodesGenerated(json.Data);
    }
  }

  private onJobStatusEventCallback(status: string): void {
    // can add callbacks for job status events such as job completion, job in progress etc here
    if (this.onJobStatus) {
      this.onJobStatus(status);
    }
  }

  private onSessionEventCallback(data: string | AugnitoSocketResponse): void {
    if (!data) {
      return;
    }

    let json: any;
    try {
      json = typeof data === "string" ? JSON.parse(data) : data;
    } catch (error) {
      Logger.error(`Error parsing session event data: ${error}`, this._logTag);
      return;
    }

    if (!json || !("Type" in json)) {
      return;
    }

    if (json.Type.toLowerCase() === "meta" && this.config?.onMetaEvent) {
      if (!json.JobID) {
        Logger.error(`JobID is missing in meta event`, this._logTag);
        return;
      }
      this.config.onMetaEvent(json.JobID);
    } else if (json.Type.toLowerCase() === "error" && json.Data) {
      this.onErrorCallback(json.Data);
    }

    if (typeof json.Event !== "object" || json.Event === null) {
      return;
    }

    const { Type: eventType, Value: eventValue } = json.Event;

    if (!eventType) {
      return;
    }

    if (eventType === "SESSION_CREATED" && eventValue) {
      Logger.log(`session Token ${eventValue}`, this._logTag);
    } else if (eventType === "SERVICE_DOWN") {
      Logger.error(eventType, this._logTag);
    } else if (eventType === "NO_DICTATION_STOP_MIC") {
      Logger.log("NO_DICTATION_STOP_MIC", this._logTag);
      this.onIdleMicCallback();
    } else if (eventType === "INVALID_AUTH_CREDENTIALS") {
      Logger.error("INVALID_AUTH_CREDENTIALS", this._logTag);
    } else if (eventType === "LOW_BANDWIDTH") {
      Logger.log("LOW_BANDWIDTH: Check internet connection", this._logTag);
    }
  }
  // #endregion

  /**
   * Validates the Ambient config has all the mandatory fields
   * @param config The config sent by the client application
   */
  validateConfig(config: AmbientConfig): AmbientConfig {
    Guard.Against.NullOrEmpty(config.server, "Server");
    Guard.Against.NullOrEmpty(config.subscriptionCode, "SubscriptionCode");
    Guard.Against.NullOrEmpty(config.accessKey, "AccessKey");
    Guard.Against.NullOrEmpty(config.userTag, "UserTag");
    return config;
  }

  private createSocketConfig(config: AmbientConfig): socketConfig {
    const _socketConfig = new socketConfig(config);
    // _socketConfig.onStartOfRecording =
    //     this.onStateChangeCallback.bind(this);
    // _socketConfig.onStopOfRecording = this.onStateChangeCallback.bind(this);
    _socketConfig.onError = this.onErrorCallback.bind(this);
    _socketConfig.onMetaEvent = this.onEventCallback.bind(this);
    return _socketConfig;
  }
}
