import * as plugins from '../../plugins.js';
import { SimpleLocalMapper, type ISimpleLocalClientLike, type ISimpleLocalEntitySnapshot, type IServiceCallRequest, type IServiceCallResult } from '../../core/index.js';
import type { INutConfig, INutSnapshot, INutStatusRecord, INutUpsInfo, TNutSnapshotSource } from './nut.types.js';
import { nutDefaultName, nutDefaultPort, nutDefaultTimeoutMs, nutProfile } from './nut.types.js';

const nutStateTypes: Record<string, string> = {
  OL: 'Online',
  OB: 'On Battery',
  LB: 'Low Battery',
  HB: 'High Battery',
  RB: 'Battery Needs Replacement',
  CHRG: 'Battery Charging',
  DISCHRG: 'Battery Discharging',
  BYPASS: 'Bypass Active',
  CAL: 'Runtime Calibration',
  OFF: 'Offline',
  OVER: 'Overloaded',
  TRIM: 'Trimming Voltage',
  BOOST: 'Boosting Voltage',
  FSD: 'Forced Shutdown',
  ALARM: 'Alarm',
  HE: 'ECO Mode',
  TEST: 'Battery Testing',
};

const fakeSerials = new Set(['unknown', 'blank']);

export class NutClient {
  private currentSnapshot?: INutSnapshot;

  constructor(private readonly config: INutConfig) {}

  public async getSnapshot(forceRefreshArg = false): Promise<INutSnapshot> {
    if (!forceRefreshArg && this.currentSnapshot) {
      return this.clone(this.currentSnapshot);
    }

    this.currentSnapshot = await this.readSnapshot(forceRefreshArg);
    return this.clone(this.currentSnapshot);
  }

  public async refresh(): Promise<IServiceCallResult> {
    this.currentSnapshot = undefined;
    const snapshot = await this.getSnapshot(true);
    const success = snapshot.online && !snapshot.error;
    return { success, error: success ? undefined : snapshot.error, data: snapshot };
  }

  public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const commandName = this.commandNameFromRequest(requestArg);
    const endpoint = this.endpoint();
    if (!endpoint || !commandName || !this.config.username || this.config.password === undefined) {
      return this.executeInjectedOrUnsupported(requestArg, commandName);
    }

    try {
      const alias = await this.resolveAlias();
      await this.runCommand(alias, commandName, this.stringValue(requestArg.data?.commandParam));
      this.currentSnapshot = undefined;
      return { success: true, data: { alias, command: commandName } };
    } catch (errorArg) {
      return { success: false, error: this.errorMessage(errorArg) };
    }
  }

  public async ping(): Promise<boolean> {
    const snapshot = await this.getSnapshot(true);
    return snapshot.online && !snapshot.error;
  }

  public async destroy(): Promise<void> {
    await this.config.client?.destroy?.();
  }

  public static parseLine(lineArg: string): string[] {
    const tokens: string[] = [];
    let token = '';
    let inQuote = false;
    let escaping = false;
    let hasToken = false;

    for (const char of lineArg.trim()) {
      if (escaping) {
        token += char;
        hasToken = true;
        escaping = false;
        continue;
      }
      if (inQuote) {
        if (char === '\\') {
          escaping = true;
          continue;
        }
        if (char === '"') {
          inQuote = false;
          hasToken = true;
          continue;
        }
        token += char;
        hasToken = true;
        continue;
      }
      if (char === '"') {
        inQuote = true;
        hasToken = true;
        continue;
      }
      if (/\s/.test(char)) {
        if (hasToken) {
          tokens.push(token);
          token = '';
          hasToken = false;
        }
        continue;
      }
      token += char;
      hasToken = true;
    }

    if (hasToken) {
      tokens.push(token);
    }
    return tokens;
  }

  public static statusDisplay(statusArg: string | undefined): string | undefined {
    if (!statusArg) {
      return undefined;
    }
    return statusArg.split(/\s+/).filter(Boolean).map((stateArg) => nutStateTypes[stateArg] || stateArg).join(', ');
  }

  private async readSnapshot(forceRefreshArg: boolean): Promise<INutSnapshot> {
    if (!forceRefreshArg && this.config.snapshot) {
      return this.snapshotFromSimpleLocal('snapshot');
    }

    if (this.config.client) {
      try {
        return await this.snapshotFromClient(this.config.client);
      } catch (errorArg) {
        return this.offlineSnapshot(this.errorMessage(errorArg));
      }
    }

    if (this.config.snapshotProvider) {
      try {
        const provided = await this.config.snapshotProvider();
        if (provided !== undefined) {
          return SimpleLocalMapper.toSnapshot({ profile: nutProfile, config: this.config, rawData: provided, online: true, source: 'provider' }) as INutSnapshot;
        }
      } catch (errorArg) {
        return this.offlineSnapshot(this.errorMessage(errorArg));
      }
    }

    if (this.config.rawData !== undefined || this.config.rawText !== undefined || this.config.entities || this.config.snapshot) {
      return this.snapshotFromSimpleLocal(this.config.snapshot ? 'snapshot' : 'manual');
    }

    if (this.endpoint()) {
      try {
        return await this.fetchSnapshot();
      } catch (errorArg) {
        return this.offlineSnapshot(this.errorMessage(errorArg));
      }
    }

    return this.offlineSnapshot('Network UPS Tools (NUT) snapshots require config.host/config.url for the NUT TCP server, snapshot/manual data, snapshotProvider, or an injected client.');
  }

  private async fetchSnapshot(): Promise<INutSnapshot> {
    if (this.config.useTls) {
      throw new Error('NUT STARTTLS/TLS upgrade is not implemented by this native client; use plain local TCP or an injected native client.');
    }
    const upsList = await this.listUps();
    const alias = this.config.alias || this.config.upsName || Object.keys(upsList)[0];
    if (!alias) {
      throw new Error('NUT server returned no UPS entries from LIST UPS.');
    }

    const vars = await this.listVars(alias);
    let commands: string[] = [];
    try {
      commands = await this.listCommands(alias);
    } catch {
      commands = [];
    }
    return this.snapshotFromStatus(alias, upsList, vars, commands, true, 'tcp');
  }

  private async snapshotFromClient(clientArg: ISimpleLocalClientLike): Promise<INutSnapshot> {
    const value = clientArg.getSnapshot ? await clientArg.getSnapshot() : clientArg.getState ? await clientArg.getState() : clientArg.getRawData ? await clientArg.getRawData() : undefined;
    if (value === undefined) {
      throw new Error('NUT injected client must expose getSnapshot(), getState(), or getRawData().');
    }
    return SimpleLocalMapper.toSnapshot({ profile: nutProfile, config: this.config, rawData: value, online: true, source: 'client' }) as INutSnapshot;
  }

  private snapshotFromSimpleLocal(sourceArg: TNutSnapshotSource): INutSnapshot {
    return SimpleLocalMapper.toSnapshot({
      profile: nutProfile,
      config: this.config,
      rawData: this.config.rawData ?? this.config.rawText,
      online: this.config.online ?? this.config.snapshot?.online ?? true,
      source: sourceArg,
    }) as INutSnapshot;
  }

  private snapshotFromStatus(aliasArg: string, upsListArg: Record<string, string>, varsArg: INutStatusRecord, commandsArg: string[], onlineArg: boolean, sourceArg: TNutSnapshotSource): INutSnapshot {
    const endpoint = this.endpoint();
    const ups = this.upsInfo(aliasArg, upsListArg[aliasArg], varsArg);
    const device = {
      id: this.config.uniqueId || ups.id,
      name: this.config.name || ups.name || nutDefaultName,
      manufacturer: ups.manufacturer || nutProfile.manufacturer,
      model: ups.model || nutProfile.model,
      serialNumber: ups.serialNumber,
      host: endpoint?.host,
      port: endpoint?.port,
      protocol: 'tcp' as const,
      configurationUrl: endpoint ? `nut://${endpoint.host}:${endpoint.port}` : undefined,
      attributes: SimpleLocalMapper.clone({
        alias: ups.alias,
        description: ups.description,
        firmware: ups.firmware,
        macAddress: ups.macAddress,
      }),
    };

    return {
      device,
      entities: this.entitiesFromVars(varsArg, commandsArg, onlineArg),
      online: onlineArg,
      updatedAt: new Date().toISOString(),
      source: sourceArg,
      rawData: { alias: aliasArg, upsList: upsListArg, vars: varsArg, commands: commandsArg },
      capabilities: {
        localRead: true,
        localControl: commandsArg.length > 0 && Boolean(this.config.username && this.config.password !== undefined),
        commands: commandsArg,
      },
      ups,
      vars: varsArg,
      upsList: upsListArg,
      commands: commandsArg,
    };
  }

  private upsInfo(aliasArg: string, descriptionArg: string | undefined, varsArg: INutStatusRecord): INutUpsInfo {
    const endpoint = this.endpoint();
    const serialNumber = this.serialFromStatus(varsArg);
    const manufacturer = varsArg['device.mfr'] || varsArg['ups.mfr'] || varsArg['ups.vendorid'] || varsArg['driver.version.data'];
    const model = varsArg['device.model'] || varsArg['ups.model'] || varsArg['ups.productid'];
    const status = varsArg['ups.status'];
    return {
      alias: aliasArg,
      description: descriptionArg,
      id: this.config.uniqueId || serialNumber || (endpoint ? `${endpoint.host}:${endpoint.port}:${aliasArg}` : undefined) || aliasArg,
      name: this.config.name || descriptionArg || aliasArg,
      manufacturer,
      model,
      serialNumber,
      firmware: varsArg['ups.firmware'] || varsArg['ups.firmware.aux'],
      macAddress: varsArg['device.macaddr'],
      host: endpoint?.host,
      port: endpoint?.port,
      status,
      statusDisplay: NutClient.statusDisplay(status),
    };
  }

  private entitiesFromVars(varsArg: INutStatusRecord, commandsArg: string[], onlineArg: boolean): ISimpleLocalEntitySnapshot[] {
    const entities: ISimpleLocalEntitySnapshot[] = [];
    const statusDisplay = NutClient.statusDisplay(varsArg['ups.status']);
    if (statusDisplay) {
      entities.push({
        id: 'ups_status_display',
        name: 'Status',
        platform: 'sensor',
        state: statusDisplay,
        available: onlineArg,
        attributes: { nutVariable: 'ups.status', rawStatus: varsArg['ups.status'] },
      });
    }

    for (const key of Object.keys(varsArg).sort()) {
      entities.push({
        id: SimpleLocalMapper.slug(key),
        name: this.titleFromKey(key),
        platform: 'sensor',
        state: this.valueFromNutString(varsArg[key]),
        available: onlineArg,
        unit: this.unitForKey(key),
        deviceClass: this.deviceClassForKey(key),
        stateClass: this.numericValue(varsArg[key]) !== undefined ? 'measurement' : undefined,
        attributes: { nutVariable: key },
      });
    }

    const outletCount = this.numericValue(varsArg['outlet.count']);
    if (outletCount) {
      for (let outlet = 1; outlet <= outletCount; outlet++) {
        const prefix = `outlet.${outlet}`;
        const outletName = varsArg[`${prefix}.name`] || String(outlet);
        const status = varsArg[`${prefix}.status`];
        if (varsArg[`${prefix}.switchable`] === 'yes' && commandsArg.includes(`${prefix}.load.on`) && commandsArg.includes(`${prefix}.load.off`)) {
          entities.push({
            id: `outlet_${outlet}_power`,
            name: `Outlet ${outletName} Power`,
            platform: 'switch',
            state: status === 'on',
            available: onlineArg,
            writable: true,
            attributes: { outlet, status, onCommand: `${prefix}.load.on`, offCommand: `${prefix}.load.off` },
          });
        }
        if (commandsArg.includes(`${prefix}.load.cycle`)) {
          entities.push({
            id: `outlet_${outlet}_cycle`,
            name: `Power cycle outlet ${outletName}`,
            platform: 'button',
            state: false,
            available: onlineArg,
            writable: true,
            attributes: { outlet, command: `${prefix}.load.cycle` },
          });
        }
      }
    }

    return entities;
  }

  private async listUps(): Promise<Record<string, string>> {
    const lines = await this.requestLines('LIST UPS');
    const upsList: Record<string, string> = {};
    for (const line of lines) {
      const parts = NutClient.parseLine(line);
      if (parts[0] === 'UPS' && parts[1]) {
        upsList[parts[1]] = parts[2] || parts[1];
      }
    }
    return upsList;
  }

  private async listVars(aliasArg: string): Promise<INutStatusRecord> {
    const lines = await this.requestLines(`LIST VAR ${aliasArg}`);
    const vars: INutStatusRecord = {};
    for (const line of lines) {
      const parts = NutClient.parseLine(line);
      if (parts[0] === 'VAR' && parts[1] === aliasArg && parts[2]) {
        vars[parts[2]] = parts.slice(3).join(' ');
      }
    }
    return vars;
  }

  private async listCommands(aliasArg: string): Promise<string[]> {
    const lines = await this.requestLines(`LIST CMD ${aliasArg}`, Boolean(this.config.username && this.config.password !== undefined));
    const commands: string[] = [];
    for (const line of lines) {
      const parts = NutClient.parseLine(line);
      if (parts[0] === 'CMD' && parts[1] === aliasArg && parts[2]) {
        commands.push(parts[2]);
      }
    }
    return commands;
  }

  private async resolveAlias(): Promise<string> {
    const alias = this.config.alias || this.config.upsName;
    if (alias) {
      return alias;
    }
    const upsList = await this.listUps();
    const firstAlias = Object.keys(upsList)[0];
    if (!firstAlias) {
      throw new Error('NUT server returned no UPS entries from LIST UPS.');
    }
    return firstAlias;
  }

  private async runCommand(aliasArg: string, commandNameArg: string, commandParamArg?: string): Promise<void> {
    if (/\s/.test(commandNameArg)) {
      throw new Error(`Invalid NUT command name: ${commandNameArg}`);
    }
    const command = `INSTCMD ${aliasArg} ${commandNameArg}${commandParamArg ? ` ${quoteNutValue(commandParamArg)}` : ''}`;
    await this.requestLines(command, true);
  }

  private async requestLines(commandArg: string, authenticateArg = false): Promise<string[]> {
    const endpoint = this.endpoint();
    if (!endpoint) {
      throw new Error('NUT TCP request requires config.host/config.url.');
    }
    const connection = await NutProtocolConnection.connect(endpoint.host, endpoint.port, this.config.timeoutMs || nutDefaultTimeoutMs);
    try {
      if (authenticateArg) {
        if (!this.config.username || this.config.password === undefined) {
          throw new Error('NUT command execution requires username and password credentials.');
        }
        await connection.command(`USERNAME ${this.config.username}`);
        await connection.command(`PASSWORD ${this.config.password}`);
      }
      return await connection.command(commandArg);
    } finally {
      connection.close();
    }
  }

  private commandNameFromRequest(requestArg: IServiceCallRequest): string | undefined {
    const data = requestArg.data || {};
    const direct = this.stringValue(data.commandName) || this.stringValue(data.command) || this.stringValue(data.instcmd);
    if (direct) {
      return direct;
    }
    if (requestArg.service.includes('.')) {
      return requestArg.service;
    }
    const outlet = this.numberValue(data.outlet ?? data.outletNumber) ?? this.outletFromEntityId(requestArg.target.entityId);
    if (outlet !== undefined) {
      if (requestArg.service === 'turn_on') {
        return `outlet.${outlet}.load.on`;
      }
      if (requestArg.service === 'turn_off') {
        return `outlet.${outlet}.load.off`;
      }
      if (requestArg.service === 'toggle' || requestArg.service === 'press') {
        return `outlet.${outlet}.load.cycle`;
      }
    }
    return undefined;
  }

  private async executeInjectedOrUnsupported(requestArg: IServiceCallRequest, commandNameArg: string | undefined): Promise<IServiceCallResult> {
    if (this.config.client?.execute) {
      return normalizeServiceResult(await this.config.client.execute(requestArg));
    }
    if (this.config.commandExecutor) {
      return normalizeServiceResult(await this.config.commandExecutor.execute(requestArg));
    }
    const missing = [
      this.endpoint() ? undefined : 'NUT TCP host',
      commandNameArg ? undefined : 'command name',
      this.config.username ? undefined : 'username',
      this.config.password !== undefined ? undefined : 'password',
    ].filter(Boolean).join(', ');
    return {
      success: false,
      error: `Network UPS Tools (NUT) service ${requestArg.service} requires an injected client.execute() or commandExecutor when native command execution is unavailable (${missing || 'missing native command prerequisites'}); no live control is reported without a real executor or NUT INSTCMD request.`,
    };
  }

  private offlineSnapshot(errorArg: string): INutSnapshot {
    const endpoint = this.endpoint();
    return {
      device: {
        id: this.config.uniqueId || (endpoint ? `${endpoint.host}:${endpoint.port}` : undefined) || this.config.name || nutProfile.domain,
        name: this.config.name || nutDefaultName,
        manufacturer: nutProfile.manufacturer,
        model: nutProfile.model,
        host: endpoint?.host,
        port: endpoint?.port,
        protocol: endpoint ? 'tcp' : 'unknown',
      },
      entities: [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: false, available: true, attributes: { source: 'runtime' } }],
      online: false,
      updatedAt: new Date().toISOString(),
      source: 'runtime',
      error: errorArg,
      rawData: undefined,
      capabilities: { localRead: true, localControl: false },
      ups: { host: endpoint?.host, port: endpoint?.port, name: this.config.name || nutDefaultName },
      vars: {},
      upsList: {},
      commands: [],
    };
  }

  private endpoint(): { host: string; port: number } | undefined {
    if (this.config.host) {
      return { host: this.config.host, port: this.config.port || nutDefaultPort };
    }
    if (!this.config.url) {
      return undefined;
    }
    try {
      const url = new URL(this.config.url);
      return { host: url.hostname, port: Number(url.port) || this.config.port || nutDefaultPort };
    } catch {
      return undefined;
    }
  }

  private serialFromStatus(statusArg: INutStatusRecord): string | undefined {
    const serial = statusArg['device.serial'] || statusArg['ups.serial'];
    if (!serial) {
      return undefined;
    }
    const normalized = serial.trim().toLowerCase();
    if (fakeSerials.has(normalized) || /^0+$/.test(normalized)) {
      return undefined;
    }
    return serial;
  }

  private valueFromNutString(valueArg: string): string | number | boolean {
    const numeric = this.numericValue(valueArg);
    if (numeric !== undefined) {
      return numeric;
    }
    if (valueArg === 'yes') {
      return true;
    }
    if (valueArg === 'no') {
      return false;
    }
    return valueArg;
  }

  private numericValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg !== 'string' || !/^-?\d+(?:\.\d+)?$/.test(valueArg.trim())) {
      return undefined;
    }
    const value = Number(valueArg);
    return Number.isFinite(value) ? value : undefined;
  }

  private numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg === 'string') {
      const value = Number(valueArg);
      return Number.isFinite(value) ? value : undefined;
    }
    return undefined;
  }

  private stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.length ? valueArg : undefined;
  }

  private outletFromEntityId(entityIdArg: string | undefined): number | undefined {
    const match = entityIdArg ? /outlet[_\.](\d+)/.exec(entityIdArg) : undefined;
    return match ? Number(match[1]) : undefined;
  }

  private titleFromKey(keyArg: string): string {
    return keyArg.split('.').map((partArg) => partArg ? `${partArg[0].toUpperCase()}${partArg.slice(1)}` : partArg).join(' ');
  }

  private unitForKey(keyArg: string): string | undefined {
    if (keyArg.includes('charge') || keyArg.includes('load') || keyArg.includes('humidity') || keyArg.includes('percent')) {
      return '%';
    }
    if (keyArg.includes('voltage')) {
      return 'V';
    }
    if (keyArg.includes('current')) {
      return 'A';
    }
    if (keyArg.includes('frequency')) {
      return 'Hz';
    }
    if (keyArg.includes('temperature')) {
      return '°C';
    }
    if (keyArg.includes('runtime') || keyArg.includes('timer') || keyArg.includes('delay')) {
      return 's';
    }
    return undefined;
  }

  private deviceClassForKey(keyArg: string): string | undefined {
    if (keyArg.includes('charge')) {
      return 'battery';
    }
    if (keyArg.includes('voltage')) {
      return 'voltage';
    }
    if (keyArg.includes('current')) {
      return 'current';
    }
    if (keyArg.includes('frequency')) {
      return 'frequency';
    }
    if (keyArg.includes('temperature')) {
      return 'temperature';
    }
    if (keyArg.includes('runtime') || keyArg.includes('timer') || keyArg.includes('delay')) {
      return 'duration';
    }
    return undefined;
  }

  private errorMessage(errorArg: unknown): string {
    return errorArg instanceof Error ? errorArg.message : String(errorArg);
  }

  private clone<TValue>(valueArg: TValue): TValue {
    return JSON.parse(JSON.stringify(valueArg)) as TValue;
  }
}

interface INutPendingCommand {
  command: string;
  lines: string[];
  timer: NodeJS.Timeout;
  resolve: (valueArg: string[]) => void;
  reject: (errorArg: Error) => void;
}

class NutProtocolConnection {
  private buffer = '';
  private pending?: INutPendingCommand;

  private constructor(private readonly socket: plugins.net.Socket, private readonly timeoutMs: number) {
    this.socket.on('data', (chunkArg) => this.handleData(Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg)));
    this.socket.on('error', (errorArg) => this.finishPending(errorArg));
    this.socket.on('close', () => this.finishPending(new Error('NUT TCP connection closed before response completed.')));
  }

  public static async connect(hostArg: string, portArg: number, timeoutMsArg: number): Promise<NutProtocolConnection> {
    return new Promise<NutProtocolConnection>((resolve, reject) => {
      const socket = plugins.net.createConnection({ host: hostArg, port: portArg });
      let settled = false;
      let timer: NodeJS.Timeout;
      function onConnect() {
        finish();
      }
      function onError(errorArg: Error) {
        finish(errorArg);
      }
      function finish(errorArg?: Error) {
        if (settled) {
          return;
        }
        settled = true;
        clearTimeout(timer);
        socket.removeListener('connect', onConnect);
        socket.removeListener('error', onError);
        if (errorArg) {
          socket.destroy();
          reject(errorArg);
          return;
        }
        resolve(new NutProtocolConnection(socket, timeoutMsArg));
      }
      timer = setTimeout(() => finish(new Error(`NUT TCP connection to ${hostArg}:${portArg} timed out after ${timeoutMsArg}ms.`)), timeoutMsArg);
      socket.once('connect', onConnect);
      socket.once('error', onError);
    });
  }

  public async command(commandArg: string): Promise<string[]> {
    if (this.pending) {
      throw new Error('NUT TCP client cannot run concurrent commands on one connection.');
    }
    return new Promise<string[]>((resolve, reject) => {
      this.pending = {
        command: commandArg.trim(),
        lines: [],
        resolve,
        reject,
        timer: setTimeout(() => this.finishPending(new Error(`NUT command ${commandArg} timed out after ${this.timeoutMs}ms.`)), this.timeoutMs),
      };
      this.socket.write(`${commandArg.trim()}\n`);
    });
  }

  public close(): void {
    this.socket.removeAllListeners();
    this.socket.destroy();
  }

  private handleData(chunkArg: Buffer): void {
    this.buffer += chunkArg.toString('utf8');
    let newlineIndex = this.buffer.indexOf('\n');
    while (newlineIndex >= 0) {
      const line = this.buffer.slice(0, newlineIndex).replace(/\r$/, '');
      this.buffer = this.buffer.slice(newlineIndex + 1);
      this.handleLine(line);
      newlineIndex = this.buffer.indexOf('\n');
    }
  }

  private handleLine(lineArg: string): void {
    const pending = this.pending;
    if (!pending) {
      return;
    }
    if (!lineArg) {
      return;
    }
    pending.lines.push(lineArg);
    if (this.isComplete(pending.command, lineArg)) {
      this.finishPending();
    }
  }

  private isComplete(commandArg: string, lineArg: string): boolean {
    if (lineArg.startsWith('ERR ')) {
      return true;
    }
    if (commandArg.startsWith('LIST ')) {
      return lineArg === `END ${commandArg}`;
    }
    return true;
  }

  private finishPending(errorArg?: Error): void {
    const pending = this.pending;
    if (!pending) {
      return;
    }
    this.pending = undefined;
    clearTimeout(pending.timer);
    if (errorArg) {
      pending.reject(errorArg);
      return;
    }
    const firstError = pending.lines.find((lineArg) => lineArg.startsWith('ERR '));
    if (firstError) {
      pending.reject(new Error(`NUT command ${pending.command} failed: ${firstError}`));
      return;
    }
    pending.resolve(pending.lines);
  }
}

const quoteNutValue = (valueArg: string): string => `"${valueArg.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;

const normalizeServiceResult = (valueArg: IServiceCallResult | unknown): IServiceCallResult => {
  if (valueArg && typeof valueArg === 'object' && 'success' in valueArg) {
    return valueArg as IServiceCallResult;
  }
  return { success: true, data: valueArg };
};
