import * as plugins from '../../plugins.js';
import type {
  IOpenthermGwCommandRequest,
  IOpenthermGwCommandResponse,
  IOpenthermGwConfig,
  IOpenthermGwEvent,
  IOpenthermGwGatewayInfo,
  IOpenthermGwGatewayStatus,
  IOpenthermGwSnapshot,
  IOpenthermGwStatus,
  TOpenthermGwCommandCode,
  TOpenthermGwCommandValue,
  TOpenthermGwStatusValue,
} from './opentherm_gw.types.js';
import { openthermGwDefaultTcpPort, openthermGwDefaultTimeoutMs } from './opentherm_gw.types.js';

const commandErrorCodes = new Set(['NG', 'SE', 'BV', 'OR', 'NS', 'NF', 'OE', 'MPC']);
const reportCodes = ['A', 'B', 'C', 'W', 'G', 'I', 'L', 'M', 'Q', 'S', 'T', 'V', 'P', 'R'];

export class OpenthermGwCommandError extends Error {
  constructor(public readonly code: string, messageArg: string) {
    super(`OpenTherm Gateway command ${code} failed: ${messageArg}`);
    this.name = 'OpenthermGwCommandError';
  }
}

export class OpenthermGwClient {
  private currentSnapshot?: IOpenthermGwSnapshot;
  private commandHistory: IOpenthermGwCommandResponse[] = [];
  private handlers = new Set<(eventArg: IOpenthermGwEvent) => void>();

  constructor(private readonly config: IOpenthermGwConfig) {
    if (config.snapshot) {
      this.currentSnapshot = this.normalizeSnapshot(this.cloneSnapshot(config.snapshot), config.snapshot.source || 'snapshot');
    } else if (config.status) {
      this.currentSnapshot = this.snapshotFromStatus(config.status, false, 'manual');
    }
  }

  public async getSnapshot(): Promise<IOpenthermGwSnapshot> {
    if (this.isSnapshotMode()) {
      return this.normalizeSnapshot(this.cloneSnapshot(this.currentSnapshot || this.emptySnapshot()), this.currentSnapshot?.source || 'manual');
    }

    const status = defaultStatus();
    await this.populateReports(status.gateway);
    const summaryStatus = await this.getSummaryStatus();
    status.boiler = { ...status.boiler, ...summaryStatus.boiler };
    status.thermostat = { ...status.thermostat, ...summaryStatus.thermostat };
    this.currentSnapshot = this.snapshotFromStatus(status, true, 'tcp');
    return this.cloneSnapshot(this.currentSnapshot);
  }

  public async setRoomSetpoint(temperatureArg: number, temporaryArg = this.config.temporaryOverrideMode ?? true): Promise<IOpenthermGwCommandResponse> {
    return this.command(temporaryArg ? 'TT' : 'TC', roundCommandNumber(temperatureArg, 1));
  }

  public async setControlSetpoint(temperatureArg: number): Promise<IOpenthermGwCommandResponse> {
    return this.command('CS', roundCommandNumber(temperatureArg, 1));
  }

  public async setHotWater(valueArg: boolean | string | number): Promise<IOpenthermGwCommandResponse> {
    if (typeof valueArg === 'boolean') {
      return this.command('HW', valueArg ? 1 : 0);
    }
    return this.command('HW', valueArg);
  }

  public async setHotWaterSetpoint(temperatureArg: number): Promise<IOpenthermGwCommandResponse> {
    return this.command('SW', roundCommandNumber(temperatureArg, 1));
  }

  public async setOutsideTemperature(temperatureArg: number): Promise<IOpenthermGwCommandResponse> {
    return this.command('OT', roundCommandNumber(temperatureArg, 1));
  }

  public async setCentralHeatingOverride(circuitArg: 1 | 2, enabledArg: boolean): Promise<IOpenthermGwCommandResponse> {
    return this.command(circuitArg === 2 ? 'H2' : 'CH', enabledArg ? 1 : 0);
  }

  public async reset(): Promise<IOpenthermGwCommandResponse> {
    return this.command('GW', 'R');
  }

  public async command(codeArg: TOpenthermGwCommandCode, valueArg: TOpenthermGwCommandValue): Promise<IOpenthermGwCommandResponse> {
    const request: IOpenthermGwCommandRequest = { code: codeArg, value: valueArg };
    const response = this.isSnapshotMode() ? this.snapshotCommand(request) : await this.requestTcp(request);
    this.commandHistory.push(response);
    this.commandHistory = this.commandHistory.slice(-50);
    this.applyCommandToSnapshot(response);
    this.emit({ type: 'command', gatewayId: this.gatewayId(), command: response, status: this.currentSnapshot?.status, timestamp: Date.now() });
    return response;
  }

  public subscribe(handlerArg: (eventArg: IOpenthermGwEvent) => void): () => void {
    this.handlers.add(handlerArg);
    return () => this.handlers.delete(handlerArg);
  }

  public async destroy(): Promise<void> {
    this.handlers.clear();
  }

  private async populateReports(statusArg: IOpenthermGwGatewayStatus): Promise<void> {
    for (const reportCode of reportCodes) {
      try {
        const response = await this.requestTcp({ code: 'PR', value: reportCode });
        applyReport(statusArg, response.value);
      } catch {
        // Report support varies by firmware; keep the snapshot usable with partial data.
      }
    }
  }

  private async getSummaryStatus(): Promise<Pick<IOpenthermGwStatus, 'boiler' | 'thermostat'>> {
    const response = await this.requestTcp({ code: 'PS', value: 1 });
    this.requestTcp({ code: 'PS', value: 0 }).catch(() => undefined);
    return parseSummaryLine(response.summaryLine || response.rawLines[response.rawLines.length - 1] || '');
  }

  private requestTcp(requestArg: IOpenthermGwCommandRequest): Promise<IOpenthermGwCommandResponse> {
    const host = this.config.host;
    if (!host) {
      throw new Error('OpenTherm Gateway TCP command requires config.host.');
    }
    const port = this.config.port || openthermGwDefaultTcpPort;
    const timeoutMs = requestArg.timeoutMs || this.config.timeoutMs || openthermGwDefaultTimeoutMs;
    const code = requestArg.code.toUpperCase();
    const value = formatCommandValue(requestArg.value);
    const commandLine = `${code}=${value}`;

    return new Promise<IOpenthermGwCommandResponse>((resolve, reject) => {
      let buffer = '';
      let settled = false;
      let waitingForSummary = false;
      const rawLines: string[] = [];
      const socket = plugins.net.createConnection({ host, port });
      const timeout = setTimeout(() => finish(new OpenthermGwCommandError(code, `Timed out after ${timeoutMs}ms.`)), timeoutMs);

      const finish = (errorArg?: Error, responseArg?: IOpenthermGwCommandResponse) => {
        if (settled) {
          return;
        }
        settled = true;
        clearTimeout(timeout);
        socket.removeAllListeners();
        socket.destroy();
        if (errorArg) {
          reject(errorArg);
          return;
        }
        resolve(responseArg!);
      };

      const response = (responseValueArg: string, summaryLineArg?: string): IOpenthermGwCommandResponse => ({
        code,
        value: responseValueArg,
        accepted: true,
        commandLine,
        rawLines: [...rawLines],
        summaryLine: summaryLineArg,
        updatedAt: new Date().toISOString(),
      });

      const processLine = (lineArg: string) => {
        const line = cleanLine(lineArg);
        if (!line) {
          return;
        }
        rawLines.push(line);

        if (isOpenThermTrafficLine(line)) {
          return;
        }
        if (commandErrorCodes.has(line)) {
          finish(new OpenthermGwCommandError(code, line));
          return;
        }
        if (waitingForSummary) {
          finish(undefined, response('1', line));
          return;
        }
        if (code === 'GW' && value === 'R' && /^OpenTherm Gateway\s+/i.test(line)) {
          finish(undefined, response(line));
          return;
        }

        const prefixed = line.match(new RegExp(`^${escapeRegExp(code)}:\\s*(.+)$`, 'i'));
        const compatibleBare = (code === 'C2' || code === 'H2') ? line.match(/^(0|1|[0-9]+\.[0-9]{1,2}|[A-Z]{2})$/i) : undefined;
        const valueMatch = prefixed?.[1] || compatibleBare?.[1];
        if (!valueMatch) {
          return;
        }
        if (commandErrorCodes.has(valueMatch)) {
          finish(new OpenthermGwCommandError(code, valueMatch));
          return;
        }
        if (code === 'PS' && valueMatch.trim() === '1') {
          waitingForSummary = true;
          return;
        }
        finish(undefined, response(valueMatch.trim()));
      };

      socket.on('connect', () => {
        socket.write(`${commandLine}\r\n`, 'ascii');
      });
      socket.on('data', (dataArg) => {
        buffer += dataArg.toString('ascii');
        const lines = buffer.split(/\r?\n/);
        buffer = lines.pop() || '';
        for (const line of lines) {
          processLine(line);
        }
      });
      socket.on('error', (errorArg) => finish(errorArg));
      socket.on('close', () => {
        if (buffer.trim()) {
          processLine(buffer);
        }
        if (!settled) {
          finish(new OpenthermGwCommandError(code, 'Connection closed before a command response was received.'));
        }
      });
    });
  }

  private snapshotCommand(requestArg: IOpenthermGwCommandRequest): IOpenthermGwCommandResponse {
    const code = requestArg.code.toUpperCase();
    const value = formatCommandValue(requestArg.value);
    return {
      code,
      value,
      accepted: true,
      commandLine: `${code}=${value}`,
      rawLines: [`${code}: ${value}`],
      updatedAt: new Date().toISOString(),
    };
  }

  private applyCommandToSnapshot(responseArg: IOpenthermGwCommandResponse): void {
    const snapshot = this.normalizeSnapshot(this.currentSnapshot || this.emptySnapshot(), this.currentSnapshot?.source || 'manual');
    const numericValue = numberValue(responseArg.value);
    const value = responseArg.value;

    if (responseArg.code === 'TT' || responseArg.code === 'TC') {
      snapshot.status.gateway.otgw_setpoint_ovrd_mode = numericValue === 0 ? 'N' : responseArg.code === 'TT' ? 'T' : 'C';
      snapshot.status.thermostat.room_setpoint_ovrd = numericValue === 0 ? null : numericValue;
    } else if (responseArg.code === 'CS') {
      snapshot.status.boiler.control_setpoint = numericValue;
    } else if (responseArg.code === 'C2') {
      snapshot.status.boiler.control_setpoint_2 = numericValue;
    } else if (responseArg.code === 'SW') {
      snapshot.status.boiler.dhw_setpoint = numericValue;
    } else if (responseArg.code === 'HW') {
      snapshot.status.gateway.otgw_dhw_ovrd = value === 'A' ? null : value;
    } else if (responseArg.code === 'OT') {
      snapshot.status.thermostat.outside_temp = value === '-' ? null : numericValue;
    } else if (responseArg.code === 'CH') {
      snapshot.status.gateway.central_heating_1_override = boolNumber(value);
      snapshot.status.boiler.master_ch_enabled = boolNumber(value);
    } else if (responseArg.code === 'H2') {
      snapshot.status.gateway.central_heating_2_override = boolNumber(value);
      snapshot.status.boiler.master_ch2_enabled = boolNumber(value);
    } else if (responseArg.code === 'GW' && value === 'R') {
      snapshot.status.gateway.otgw_mode = 'R';
    }

    snapshot.commands = [...(snapshot.commands || []), responseArg].slice(-50);
    snapshot.online = snapshot.online || Boolean(this.config.host);
    snapshot.updatedAt = responseArg.updatedAt;
    snapshot.source = snapshot.source === 'tcp' ? 'tcp' : 'runtime';
    this.currentSnapshot = this.normalizeSnapshot(snapshot, snapshot.source);
  }

  private normalizeSnapshot(snapshotArg: IOpenthermGwSnapshot, sourceArg: IOpenthermGwSnapshot['source']): IOpenthermGwSnapshot {
    const status = {
      gateway: { ...(snapshotArg.status?.gateway || {}) },
      boiler: { ...(snapshotArg.status?.boiler || {}) },
      thermostat: { ...(snapshotArg.status?.thermostat || {}) },
    };
    const configuredGateway = this.gatewayInfo();
    const gateway = {
      ...configuredGateway,
      ...(snapshotArg.gateway || {}),
      firmwareVersion: stringValue(snapshotArg.gateway?.firmwareVersion) || stringValue(status.gateway.otgw_about) || configuredGateway.firmwareVersion,
    };
    return {
      gateway,
      status,
      climate: snapshotArg.climate,
      waterHeater: snapshotArg.waterHeater,
      commands: snapshotArg.commands || this.commandHistory,
      events: snapshotArg.events || [],
      online: Boolean(snapshotArg.online),
      source: sourceArg,
      updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
    };
  }

  private snapshotFromStatus(statusArg: IOpenthermGwStatus, onlineArg: boolean, sourceArg: IOpenthermGwSnapshot['source']): IOpenthermGwSnapshot {
    return this.normalizeSnapshot({
      gateway: this.gatewayInfo(),
      status: statusArg,
      commands: this.commandHistory,
      online: onlineArg,
      source: sourceArg,
      updatedAt: new Date().toISOString(),
    }, sourceArg);
  }

  private emptySnapshot(): IOpenthermGwSnapshot {
    return this.snapshotFromStatus(defaultStatus(), Boolean(this.config.host), this.config.host ? 'tcp' : 'manual');
  }

  private gatewayInfo(): IOpenthermGwGatewayInfo {
    const port = this.config.host ? this.config.port || openthermGwDefaultTcpPort : this.config.port;
    return {
      id: this.gatewayId(),
      name: this.config.name || 'OpenTherm Gateway',
      host: this.config.host,
      port,
      device: this.config.device,
    };
  }

  private gatewayId(): string {
    const port = this.config.host ? this.config.port || openthermGwDefaultTcpPort : this.config.port;
    return this.config.id || (this.config.host ? `${this.config.host}:${port}` : this.config.device) || 'opentherm_gateway';
  }

  private isSnapshotMode(): boolean {
    return Boolean(this.config.snapshot || this.config.status || !this.config.host);
  }

  private emit(eventArg: IOpenthermGwEvent): void {
    for (const handler of this.handlers) {
      handler(eventArg);
    }
  }

  private cloneSnapshot(snapshotArg: IOpenthermGwSnapshot): IOpenthermGwSnapshot {
    return cloneValue(snapshotArg);
  }
}

export const defaultStatus = (): IOpenthermGwStatus => ({
  gateway: {},
  boiler: {},
  thermostat: {},
});

export const parseSummaryLine = (lineArg: string): Pick<IOpenthermGwStatus, 'boiler' | 'thermostat'> => {
  const fields = lineArg.split(',').map((fieldArg) => fieldArg.trim());
  if (fields.length >= 34) {
    return parseStatusFieldsV5(fields);
  }
  if (fields.length >= 25) {
    return parseStatusFieldsV4(fields);
  }
  return { boiler: {}, thermostat: {} };
};

const parseStatusFieldsV4 = (fieldsArg: string[]): Pick<IOpenthermGwStatus, 'boiler' | 'thermostat'> => {
  const deviceStatus = splitField(fieldsArg[0], '/');
  const remoteParams = splitField(fieldsArg[2], '/');
  const capModLimits = splitField(fieldsArg[4], '/');
  const dhwSetpointBounds = splitField(fieldsArg[13], '/');
  const chSetpointBounds = splitField(fieldsArg[14], '/');
  const masterStatus = deviceStatus[0] || '';
  const slaveStatus = deviceStatus[1] || '';
  return {
    thermostat: stripUndefined({
      master_ch_enabled: bit(masterStatus, 7),
      master_dhw_enabled: bit(masterStatus, 6),
      master_cooling_enabled: bit(masterStatus, 5),
      master_otc_enabled: bit(masterStatus, 4),
      master_ch2_enabled: bit(masterStatus, 3),
      control_setpoint: numberValue(fieldsArg[1]),
      room_setpoint: numberValue(fieldsArg[5]),
      room_temp: numberValue(fieldsArg[8]),
    }),
    boiler: stripUndefined({
      slave_fault_indication: bit(slaveStatus, 7),
      slave_ch_active: bit(slaveStatus, 6),
      slave_dhw_active: bit(slaveStatus, 5),
      slave_flame_on: bit(slaveStatus, 4),
      slave_cooling_active: bit(slaveStatus, 3),
      slave_ch2_active: bit(slaveStatus, 2),
      slave_diagnostic_indication: bit(slaveStatus, 1),
      remote_transfer_dhw: bit(remoteParams[0] || '', 7),
      remote_transfer_max_ch: bit(remoteParams[0] || '', 6),
      remote_rw_dhw: bit(remoteParams[1] || '', 7),
      remote_rw_max_ch: bit(remoteParams[1] || '', 6),
      slave_max_relative_modulation: numberValue(fieldsArg[3]),
      slave_max_capacity: numberValue(capModLimits[0]),
      slave_min_mod_level: numberValue(capModLimits[1]),
      relative_mod_level: numberValue(fieldsArg[6]),
      ch_water_pressure: numberValue(fieldsArg[7]),
      ch_water_temp: numberValue(fieldsArg[9]),
      dhw_temp: numberValue(fieldsArg[10]),
      outside_temp: numberValue(fieldsArg[11]),
      return_water_temp: numberValue(fieldsArg[12]),
      slave_dhw_max_setp: numberValue(dhwSetpointBounds[0]),
      slave_dhw_min_setp: numberValue(dhwSetpointBounds[1]),
      slave_ch_max_setp: numberValue(chSetpointBounds[0]),
      slave_ch_min_setp: numberValue(chSetpointBounds[1]),
      dhw_setpoint: numberValue(fieldsArg[15]),
      max_ch_setpoint: numberValue(fieldsArg[16]),
      burner_starts: numberValue(fieldsArg[17]),
      ch_pump_starts: numberValue(fieldsArg[18]),
      dhw_pump_starts: numberValue(fieldsArg[19]),
      dhw_burner_starts: numberValue(fieldsArg[20]),
      burner_hours: numberValue(fieldsArg[21]),
      ch_pump_hours: numberValue(fieldsArg[22]),
      dhw_pump_hours: numberValue(fieldsArg[23]),
      dhw_burner_hours: numberValue(fieldsArg[24]),
    }),
  };
};

const parseStatusFieldsV5 = (fieldsArg: string[]): Pick<IOpenthermGwStatus, 'boiler' | 'thermostat'> => {
  const deviceStatus = splitField(fieldsArg[0], '/');
  const remoteParams = splitField(fieldsArg[2], '/');
  const capModLimits = splitField(fieldsArg[6], '/');
  const dhwSetpointBounds = splitField(fieldsArg[19], '/');
  const chSetpointBounds = splitField(fieldsArg[20], '/');
  const vhDeviceStatus = splitField(fieldsArg[23], '/');
  const masterStatus = deviceStatus[0] || '';
  const slaveStatus = deviceStatus[1] || '';
  const vhMasterStatus = vhDeviceStatus[0] || '';
  const vhSlaveStatus = vhDeviceStatus[1] || '';
  return {
    thermostat: stripUndefined({
      master_ch_enabled: bit(masterStatus, 7),
      master_dhw_enabled: bit(masterStatus, 6),
      master_cooling_enabled: bit(masterStatus, 5),
      master_otc_enabled: bit(masterStatus, 4),
      master_ch2_enabled: bit(masterStatus, 3),
      control_setpoint: numberValue(fieldsArg[1]),
      cooling_control: numberValue(fieldsArg[3]),
      control_setpoint_2: numberValue(fieldsArg[4]),
      room_setpoint: numberValue(fieldsArg[7]),
      room_setpoint_2: numberValue(fieldsArg[11]),
      room_temp: numberValue(fieldsArg[12]),
      vh_master_vent_enabled: bit(vhMasterStatus, 7),
      vh_master_bypass_pos: bit(vhMasterStatus, 6),
      vh_master_bypass_mode: bit(vhMasterStatus, 5),
      vh_master_free_vent_mode: bit(vhMasterStatus, 4),
      vh_control_setpoint: numberValue(fieldsArg[24]),
    }),
    boiler: stripUndefined({
      slave_fault_indication: bit(slaveStatus, 7),
      slave_ch_active: bit(slaveStatus, 6),
      slave_dhw_active: bit(slaveStatus, 5),
      slave_flame_on: bit(slaveStatus, 4),
      slave_cooling_active: bit(slaveStatus, 3),
      slave_ch2_active: bit(slaveStatus, 2),
      slave_diagnostic_indication: bit(slaveStatus, 1),
      remote_transfer_dhw: bit(remoteParams[0] || '', 7),
      remote_transfer_max_ch: bit(remoteParams[0] || '', 6),
      remote_rw_dhw: bit(remoteParams[1] || '', 7),
      remote_rw_max_ch: bit(remoteParams[1] || '', 6),
      slave_max_relative_modulation: numberValue(fieldsArg[5]),
      slave_max_capacity: numberValue(capModLimits[0]),
      slave_min_mod_level: numberValue(capModLimits[1]),
      relative_mod_level: numberValue(fieldsArg[8]),
      ch_water_pressure: numberValue(fieldsArg[9]),
      dhw_flow_rate: numberValue(fieldsArg[10]),
      ch_water_temp: numberValue(fieldsArg[13]),
      dhw_temp: numberValue(fieldsArg[14]),
      outside_temp: numberValue(fieldsArg[15]),
      return_water_temp: numberValue(fieldsArg[16]),
      ch_water_temp_2: numberValue(fieldsArg[17]),
      exhaust_temp: numberValue(fieldsArg[18]),
      slave_dhw_max_setp: numberValue(dhwSetpointBounds[0]),
      slave_dhw_min_setp: numberValue(dhwSetpointBounds[1]),
      slave_ch_max_setp: numberValue(chSetpointBounds[0]),
      slave_ch_min_setp: numberValue(chSetpointBounds[1]),
      dhw_setpoint: numberValue(fieldsArg[21]),
      max_ch_setpoint: numberValue(fieldsArg[22]),
      vh_slave_fault_indicate: bit(vhSlaveStatus, 7),
      vh_slave_vent_mode: bit(vhSlaveStatus, 6),
      vh_slave_bypass_status: bit(vhSlaveStatus, 5),
      vh_slave_bypass_auto_status: bit(vhSlaveStatus, 4),
      vh_slave_free_vent_status: bit(vhSlaveStatus, 3),
      vh_slave_diag_indicate: bit(vhSlaveStatus, 1),
      vh_relative_vent: numberValue(fieldsArg[25]),
      burner_starts: numberValue(fieldsArg[26]),
      ch_pump_starts: numberValue(fieldsArg[27]),
      dhw_pump_starts: numberValue(fieldsArg[28]),
      dhw_burner_starts: numberValue(fieldsArg[29]),
      burner_hours: numberValue(fieldsArg[30]),
      ch_pump_hours: numberValue(fieldsArg[31]),
      dhw_pump_hours: numberValue(fieldsArg[32]),
      dhw_burner_hours: numberValue(fieldsArg[33]),
    }),
  };
};

const applyReport = (statusArg: IOpenthermGwGatewayStatus, reportArg: string): void => {
  const [reportCode, reportValue = ''] = splitField(reportArg, '=');
  if (reportCode === 'A') {
    statusArg.otgw_about = reportValue;
  } else if (reportCode === 'B') {
    statusArg.otgw_build = reportValue;
  } else if (reportCode === 'C') {
    statusArg.otgw_clockmhz = reportValue;
  } else if (reportCode === 'W') {
    statusArg.otgw_dhw_ovrd = reportValue;
  } else if (reportCode === 'M') {
    statusArg.otgw_mode = reportValue;
  } else if (reportCode === 'Q') {
    statusArg.otgw_reset_cause = reportValue;
  } else if (reportCode === 'S') {
    statusArg.otgw_setback_temp = numberValue(reportValue);
  } else if (reportCode === 'P') {
    statusArg.otgw_smart_pwr = reportValue;
  } else if (reportCode === 'R') {
    statusArg.otgw_thermostat_detect = reportValue;
  } else if (reportCode === 'V') {
    statusArg.otgw_vref = numberValue(reportValue);
  } else if (reportCode === 'G') {
    statusArg.otgw_gpio_a = numberValue(reportValue[0]);
    statusArg.otgw_gpio_b = numberValue(reportValue[1]);
  } else if (reportCode === 'I') {
    statusArg.otgw_gpio_a_state = numberValue(reportValue[0]);
    statusArg.otgw_gpio_b_state = numberValue(reportValue[1]);
  } else if (reportCode === 'L') {
    statusArg.otgw_led_a = reportValue[0];
    statusArg.otgw_led_b = reportValue[1];
    statusArg.otgw_led_c = reportValue[2];
    statusArg.otgw_led_d = reportValue[3];
    statusArg.otgw_led_e = reportValue[4];
    statusArg.otgw_led_f = reportValue[5];
  } else if (reportCode === 'T') {
    statusArg.otgw_ignore_transitions = numberValue(reportValue[0]);
    statusArg.otgw_ovrd_high_byte = numberValue(reportValue[1]);
  }
};

const splitField = (valueArg: string | undefined, separatorArg: string): string[] => (valueArg || '').split(separatorArg);

const bit = (valueArg: string, indexArg: number): number | undefined => valueArg.length > indexArg ? (valueArg[indexArg] === '1' ? 1 : 0) : undefined;

const boolNumber = (valueArg: unknown): number | undefined => {
  if (valueArg === true || valueArg === 1 || valueArg === '1') {
    return 1;
  }
  if (valueArg === false || valueArg === 0 || valueArg === '0') {
    return 0;
  }
  return undefined;
};

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

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const stripUndefined = <TValue extends Record<string, TOpenthermGwStatusValue>>(valueArg: TValue): TValue => {
  for (const key of Object.keys(valueArg)) {
    if (valueArg[key] === undefined) {
      delete valueArg[key];
    }
  }
  return valueArg;
};

const roundCommandNumber = (valueArg: number, digitsArg: number): number => Number(valueArg.toFixed(digitsArg));

const formatCommandValue = (valueArg: TOpenthermGwCommandValue): string => {
  if (typeof valueArg === 'boolean') {
    return valueArg ? '1' : '0';
  }
  return String(valueArg);
};

const cleanLine = (lineArg: string): string => {
  const eotIndex = lineArg.lastIndexOf('\x04');
  return (eotIndex >= 0 ? lineArg.slice(eotIndex + 1) : lineArg).trim();
};

const isOpenThermTrafficLine = (lineArg: string): boolean => /^(T|B|R|A|E)[0-9A-F]{8}$/i.test(lineArg) || /^Error 0[1-4]/.test(lineArg);

const escapeRegExp = (valueArg: string): string => valueArg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

const cloneValue = <TValue>(valueArg: TValue): TValue => JSON.parse(JSON.stringify(valueArg)) as TValue;
