import {
  SimpleLocalClient,
  SimpleLocalMapper,
  type IIntegrationEntity,
  type IServiceCallRequest,
  type IServiceCallResult,
  type ISimpleLocalEntitySnapshot,
  type ISimpleLocalSnapshot,
} from '../../core/index.js';
import type { IIndevoltConfig } from './indevolt.types.js';
import { indevoltProfile } from './indevolt.types.js';

const indevoltDefaultPort = 8080;
const indevoltDefaultTimeoutMs = 10000;

const setRealtimeActionKey = '47015';

const realtimeActions = {
  stop: 0,
  charge: 1,
  discharge: 2,
} as const;

const energyModes = {
  outdoorPortable: 0,
  selfConsumedPrioritized: 1,
  realtimeControl: 4,
  chargeDischargeSchedule: 5,
} as const;

const deviceLimits: Record<number, { maxDischargePower: number; maxChargePower: number; minimumSoc: number }> = {
  1: { maxDischargePower: 800, maxChargePower: 1200, minimumSoc: 5 },
  2: { maxDischargePower: 2400, maxChargePower: 2400, minimumSoc: 5 },
};

const keys = {
  writeEnergyMode: '47005',
  writeDischargeLimit: '1142',
  writeMaxAcOutputPower: '1147',
  writeInverterInputLimit: '1138',
  writeFeedinPowerLimit: '1146',
  writeGridCharging: '1143',
  writeLight: '7265',
  writeBypass: '7266',
  readEnergyMode: '7101',
  readDischargeLimit: '6105',
  readMaxAcOutputPower: '11011',
  readInverterInputLimit: '11009',
  readFeedinPowerLimit: '11010',
  readGridCharging: '2618',
  readLight: '7171',
  readBypass: '680',
  operatingMode: '606',
  inputPower: '2101',
  outputPower: '2108',
  bypassPower: '667',
  totalInputEnergy: '2107',
  totalOutputEnergy: '2104',
  offGridOutputEnergy: '2105',
  bypassInputEnergy: '11034',
  heatingState: '7121',
  meterPowerGen1: '21028',
  meterPowerGen2: '11016',
  voltage: '2600',
  frequency: '2612',
  meterConnected: '7120',
  batteryPower: '6000',
  batteryState: '6001',
  batterySoc: '6002',
  dailyChargingEnergy: '6004',
  dailyDischargingEnergy: '6005',
  totalChargingEnergy: '6006',
  totalDischargingEnergy: '6007',
  ratedCapacity: '142',
  inverterTemperatureGen1: '7600',
  pack1TemperatureGen1: '7605',
  pack2TemperatureGen1: '7620',
  pack3TemperatureGen1: '7621',
  mainSerialNumber: '9008',
  pack1SerialNumber: '9032',
  pack2SerialNumber: '9051',
  pack3SerialNumber: '9070',
  pack4SerialNumber: '9165',
  pack5SerialNumber: '9218',
  mainSoc: '9000',
  pack1Soc: '9016',
  pack2Soc: '9035',
  pack3Soc: '9054',
  pack4Soc: '9149',
  pack5Soc: '9202',
  mainTemperature: '9012',
  pack1Temperature: '9030',
  pack2Temperature: '9049',
  pack3Temperature: '9068',
  pack4Temperature: '9163',
  pack5Temperature: '9216',
  mainVoltage: '9004',
  pack1Voltage: '9020',
  pack2Voltage: '9039',
  pack3Voltage: '9058',
  pack4Voltage: '9153',
  pack5Voltage: '9206',
  mainCurrent: '9013',
  pack1Current: '19173',
  pack2Current: '19174',
  pack3Current: '19175',
  pack4Current: '19176',
  pack5Current: '19177',
  mainHeatingState: '9079',
  pack1HeatingState: '9096',
  pack2HeatingState: '9112',
  pack3HeatingState: '9128',
  pack4HeatingState: '9144',
  pack5HeatingState: '9279',
  dcOutputPower: '1501',
  dailyProduction: '1502',
  cumulativeProduction: '1505',
  dcInputVoltage1: '1600',
  dcInputVoltage2: '1601',
  dcInputVoltage3: '1602',
  dcInputVoltage4: '1603',
  dcInputCurrent1: '1632',
  dcInputCurrent2: '1633',
  dcInputCurrent3: '1634',
  dcInputCurrent4: '1635',
  dcInputPower1: '1664',
  dcInputPower2: '1665',
  dcInputPower3: '1666',
  dcInputPower4: '1667',
} as const;

const sensorKeysByGeneration: Record<number, string[]> = {
  1: [
    keys.operatingMode,
    keys.readEnergyMode,
    keys.inputPower,
    keys.outputPower,
    keys.totalInputEnergy,
    keys.batteryPower,
    keys.batteryState,
    keys.batterySoc,
    keys.dcOutputPower,
    keys.dailyProduction,
    keys.dcInputPower1,
    keys.dcInputPower2,
    keys.dcInputPower3,
    keys.dcInputPower4,
    keys.readDischargeLimit,
    keys.meterPowerGen1,
    keys.meterConnected,
    keys.cumulativeProduction,
    keys.heatingState,
    keys.inverterTemperatureGen1,
    keys.pack1TemperatureGen1,
    keys.pack2TemperatureGen1,
    keys.pack3TemperatureGen1,
  ],
  2: [
    keys.operatingMode,
    keys.readEnergyMode,
    keys.inputPower,
    keys.outputPower,
    keys.totalInputEnergy,
    keys.batteryPower,
    keys.batteryState,
    keys.batterySoc,
    keys.dcOutputPower,
    keys.dailyProduction,
    keys.dcInputPower1,
    keys.dcInputPower2,
    keys.dcInputPower3,
    keys.dcInputPower4,
    keys.ratedCapacity,
    keys.bypassPower,
    keys.totalOutputEnergy,
    keys.offGridOutputEnergy,
    keys.bypassInputEnergy,
    keys.dailyChargingEnergy,
    keys.dailyDischargingEnergy,
    keys.totalChargingEnergy,
    keys.totalDischargingEnergy,
    keys.meterPowerGen2,
    keys.voltage,
    keys.frequency,
    keys.dcInputCurrent1,
    keys.dcInputVoltage1,
    keys.dcInputCurrent2,
    keys.dcInputVoltage2,
    keys.dcInputCurrent3,
    keys.dcInputVoltage3,
    keys.dcInputCurrent4,
    keys.dcInputVoltage4,
    keys.mainSerialNumber,
    keys.pack1SerialNumber,
    keys.pack2SerialNumber,
    keys.pack3SerialNumber,
    keys.pack4SerialNumber,
    keys.pack5SerialNumber,
    keys.mainSoc,
    keys.pack1Soc,
    keys.pack2Soc,
    keys.pack3Soc,
    keys.pack4Soc,
    keys.pack5Soc,
    keys.mainTemperature,
    keys.pack1Temperature,
    keys.pack2Temperature,
    keys.pack3Temperature,
    keys.pack4Temperature,
    keys.pack5Temperature,
    keys.mainVoltage,
    keys.pack1Voltage,
    keys.pack2Voltage,
    keys.pack3Voltage,
    keys.pack4Voltage,
    keys.pack5Voltage,
    keys.mainCurrent,
    keys.pack1Current,
    keys.pack2Current,
    keys.pack3Current,
    keys.pack4Current,
    keys.pack5Current,
    keys.readBypass,
    keys.readGridCharging,
    keys.readLight,
    keys.readMaxAcOutputPower,
    keys.readInverterInputLimit,
    keys.readFeedinPowerLimit,
    keys.readDischargeLimit,
    keys.mainHeatingState,
    keys.pack1HeatingState,
    keys.pack2HeatingState,
    keys.pack3HeatingState,
    keys.pack4HeatingState,
    keys.pack5HeatingState,
    keys.meterConnected,
    keys.cumulativeProduction,
  ],
};

type TIndevoltControlKind = 'number' | 'select' | 'switch';

interface IIndevoltKeyDescription {
  name: string;
  platform?: ISimpleLocalEntitySnapshot['platform'];
  unit?: string;
  deviceClass?: string;
  stateClass?: string;
  writable?: boolean;
  controlKind?: TIndevoltControlKind;
  writeKey?: string;
  onValue?: number;
  offValue?: number;
  writeOnValue?: number;
  writeOffValue?: number;
  options?: Record<string, number>;
  stateMapping?: Record<string, string>;
}

const keyDescriptions: Record<string, IIndevoltKeyDescription> = {
  [keys.operatingMode]: { name: 'Operating mode', deviceClass: 'enum', stateMapping: { '1000': 'main', '1001': 'sub', '1002': 'standalone' } },
  [keys.readEnergyMode]: { name: 'Energy mode', platform: 'select', writable: true, controlKind: 'select', writeKey: keys.writeEnergyMode, options: { self_consumed_prioritized: energyModes.selfConsumedPrioritized, real_time_control: energyModes.realtimeControl, charge_discharge_schedule: energyModes.chargeDischargeSchedule }, stateMapping: { '0': 'outdoor_portable', '1': 'self_consumed_prioritized', '4': 'real_time_control', '5': 'charge_discharge_schedule' } },
  [keys.inputPower]: { name: 'AC input power', unit: 'W', deviceClass: 'power', stateClass: 'measurement' },
  [keys.outputPower]: { name: 'AC output power', unit: 'W', deviceClass: 'power', stateClass: 'measurement' },
  [keys.bypassPower]: { name: 'Bypass power', unit: 'W', deviceClass: 'power', stateClass: 'measurement' },
  [keys.totalInputEnergy]: { name: 'Total AC input energy', unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' },
  [keys.totalOutputEnergy]: { name: 'Total AC output energy', unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' },
  [keys.offGridOutputEnergy]: { name: 'Off-grid output energy', unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' },
  [keys.bypassInputEnergy]: { name: 'Bypass input energy', unit: 'Wh', deviceClass: 'energy', stateClass: 'total_increasing' },
  [keys.batteryPower]: { name: 'Battery power', unit: 'W', deviceClass: 'power', stateClass: 'measurement' },
  [keys.batteryState]: { name: 'Battery charge/discharge state', deviceClass: 'enum', stateMapping: { '1000': 'static', '1001': 'charging', '1002': 'discharging' } },
  [keys.batterySoc]: { name: 'Battery SOC', unit: '%', deviceClass: 'battery', stateClass: 'measurement' },
  [keys.ratedCapacity]: { name: 'Rated capacity', unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' },
  [keys.dcOutputPower]: { name: 'DC output power', unit: 'W', deviceClass: 'power', stateClass: 'measurement' },
  [keys.dailyProduction]: { name: 'Daily production', unit: 'kWh', deviceClass: 'energy', stateClass: 'total_increasing' },
  [keys.cumulativeProduction]: { name: 'Cumulative production', unit: 'Wh', deviceClass: 'energy', stateClass: 'total_increasing' },
  [keys.meterPowerGen1]: { name: 'Meter power', unit: 'W', deviceClass: 'power', stateClass: 'measurement' },
  [keys.meterPowerGen2]: { name: 'Meter power', unit: 'W', deviceClass: 'power', stateClass: 'measurement' },
  [keys.voltage]: { name: 'Grid voltage', unit: 'V', deviceClass: 'voltage', stateClass: 'measurement' },
  [keys.frequency]: { name: 'Grid frequency', unit: 'Hz', deviceClass: 'frequency', stateClass: 'measurement' },
  [keys.meterConnected]: { name: 'Meter connected', platform: 'binary_sensor', deviceClass: 'connectivity', onValue: 1000, offValue: 1001 },
  [keys.heatingState]: { name: 'Electric heating state', platform: 'binary_sensor' },
  [keys.mainHeatingState]: { name: 'Main electric heating state', platform: 'binary_sensor' },
  [keys.pack1HeatingState]: { name: 'Battery pack 1 electric heating state', platform: 'binary_sensor' },
  [keys.pack2HeatingState]: { name: 'Battery pack 2 electric heating state', platform: 'binary_sensor' },
  [keys.pack3HeatingState]: { name: 'Battery pack 3 electric heating state', platform: 'binary_sensor' },
  [keys.pack4HeatingState]: { name: 'Battery pack 4 electric heating state', platform: 'binary_sensor' },
  [keys.pack5HeatingState]: { name: 'Battery pack 5 electric heating state', platform: 'binary_sensor' },
  [keys.readDischargeLimit]: { name: 'Discharge limit', platform: 'number', unit: '%', writable: true, controlKind: 'number', writeKey: keys.writeDischargeLimit },
  [keys.readMaxAcOutputPower]: { name: 'Max AC output power', platform: 'number', unit: 'W', deviceClass: 'power', writable: true, controlKind: 'number', writeKey: keys.writeMaxAcOutputPower },
  [keys.readInverterInputLimit]: { name: 'Inverter input limit', platform: 'number', unit: 'W', deviceClass: 'power', writable: true, controlKind: 'number', writeKey: keys.writeInverterInputLimit },
  [keys.readFeedinPowerLimit]: { name: 'Feed-in power limit', platform: 'number', unit: 'W', deviceClass: 'power', writable: true, controlKind: 'number', writeKey: keys.writeFeedinPowerLimit },
  [keys.readGridCharging]: { name: 'Grid charging', platform: 'switch', writable: true, controlKind: 'switch', writeKey: keys.writeGridCharging, onValue: 1001, offValue: 1000, writeOnValue: 1, writeOffValue: 0 },
  [keys.readLight]: { name: 'Light', platform: 'switch', writable: true, controlKind: 'switch', writeKey: keys.writeLight, writeOnValue: 1, writeOffValue: 0 },
  [keys.readBypass]: { name: 'Bypass', platform: 'switch', writable: true, controlKind: 'switch', writeKey: keys.writeBypass, writeOnValue: 1, writeOffValue: 0 },
};

const generatedNames: Record<string, string> = Object.fromEntries(Object.entries(keys).map(([nameArg, keyArg]) => [keyArg, titleCase(nameArg)]));

interface IIndevoltRpcResponse {
  result?: boolean;
  [key: string]: unknown;
}

export class IndevoltClient {
  private readonly fallbackClient: SimpleLocalClient<IIndevoltConfig>;
  private currentSnapshot?: ISimpleLocalSnapshot;

  constructor(configArg: IIndevoltConfig) {
    this.config = configArg;
    this.fallbackClient = new SimpleLocalClient(indevoltProfile, configArg);
  }

  private readonly config: IIndevoltConfig;

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

    if (this.shouldUseFallbackBeforeHttp(forceRefreshArg)) {
      this.currentSnapshot = await this.fallbackClient.getSnapshot(forceRefreshArg);
      return SimpleLocalMapper.clone(this.currentSnapshot);
    }

    if (this.config.host || this.config.url) {
      try {
        this.currentSnapshot = await this.fetchSnapshot();
        return SimpleLocalMapper.clone(this.currentSnapshot);
      } catch (errorArg) {
        this.currentSnapshot = this.offlineSnapshot(this.errorMessage(errorArg));
        return SimpleLocalMapper.clone(this.currentSnapshot);
      }
    }

    this.currentSnapshot = await this.fallbackClient.getSnapshot(forceRefreshArg);
    return SimpleLocalMapper.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> {
    if (!(this.config.host || this.config.url)) {
      return this.fallbackClient.execute(requestArg);
    }

    try {
      if (requestArg.domain === 'indevolt' && (requestArg.service === 'charge' || requestArg.service === 'discharge')) {
        return this.realtimeAction(requestArg.service, requestArg);
      }
      if ((requestArg.domain === 'button' && requestArg.service === 'press') || (requestArg.domain === 'indevolt' && (requestArg.service === 'press' || requestArg.service === 'stop'))) {
        return this.stopRealtimeAction();
      }
      if (requestArg.domain === 'number' && requestArg.service === 'set_value') {
        return this.setNumberValue(requestArg);
      }
      if (requestArg.domain === 'select' && requestArg.service === 'select_option') {
        return this.selectOption(requestArg);
      }
      if (requestArg.domain === 'switch' && ['turn_on', 'turn_off', 'toggle'].includes(requestArg.service)) {
        return this.setSwitchValue(requestArg);
      }
      return this.fallbackClient.execute(requestArg);
    } catch (errorArg) {
      return { success: false, error: this.errorMessage(errorArg) };
    }
  }

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

  private shouldUseFallbackBeforeHttp(forceRefreshArg: boolean): boolean {
    if (!forceRefreshArg && this.config.snapshot) {
      return true;
    }
    return Boolean(this.config.client || this.config.snapshotProvider);
  }

  private async fetchSnapshot(): Promise<ISimpleLocalSnapshot> {
    const config = await this.getConfig();
    const device = recordValue(config.device) || {};
    const generation = this.deviceGeneration(device);
    const data = await this.fetchData(this.sensorKeys(generation));
    const serialNumber = stringValue(device.sn) || stringValue(device.serial_number) || stringValue(data[keys.mainSerialNumber]) || stringValue(this.config.serialNumber) || this.hostId();
    const model = stringValue(device.type) || stringValue(this.config.model) || 'Indevolt device';

    return {
      device: {
        id: this.config.uniqueId || serialNumber,
        name: this.config.name || (model ? `INDEVOLT ${model}` : indevoltProfile.defaultName),
        manufacturer: 'Indevolt',
        model,
        serialNumber,
        host: this.host(),
        port: this.port(),
        protocol: 'http',
        configurationUrl: this.baseUrl(),
        attributes: {
          generation,
          firmwareVersion: stringValue(device.fw),
        },
      },
      entities: this.entitiesFromData(data, generation),
      online: true,
      updatedAt: new Date().toISOString(),
      source: 'http',
      rawData: {
        config,
        data,
        generation,
      },
      capabilities: {
        localRead: true,
        localControl: true,
        protocol: 'indevolt-rpc-http',
      },
    };
  }

  private entitiesFromData(dataArg: Record<string, unknown>, generationArg: number): ISimpleLocalEntitySnapshot[] {
    const entities: ISimpleLocalEntitySnapshot[] = [];
    for (const [keyArg, rawValueArg] of Object.entries(dataArg)) {
      const description = keyDescriptions[keyArg] || { name: generatedNames[keyArg] || `Register ${keyArg}` };
      const state = this.mappedState(rawValueArg, description);
      entities.push({
        id: slug(description.name),
        name: description.name,
        platform: description.platform || 'sensor',
        state,
        available: rawValueArg !== undefined && rawValueArg !== null,
        writable: description.writable ?? false,
        unit: description.unit,
        deviceClass: description.deviceClass,
        stateClass: description.stateClass,
        attributes: this.cleanAttributes({
          register: keyArg,
          generation: generationArg,
          rawValue: rawValueArg,
          writeKey: description.writeKey,
          controlKind: description.controlKind,
          options: description.options ? Object.keys(description.options) : undefined,
          onValue: description.onValue,
          offValue: description.offValue,
          writeOnValue: description.writeOnValue,
          writeOffValue: description.writeOffValue,
        }),
      });
    }

    entities.push({
      id: 'stop',
      name: 'Stop',
      platform: 'button',
      state: 'idle',
      available: true,
      writable: true,
      attributes: { action: 'stop_realtime' },
    });

    return entities;
  }

  private mappedState(valueArg: unknown, descriptionArg: IIndevoltKeyDescription): unknown {
    const numeric = this.numberValue(valueArg);
    if (descriptionArg.platform === 'binary_sensor' || descriptionArg.platform === 'switch') {
      if (numeric === undefined) {
        return null;
      }
      if (descriptionArg.onValue !== undefined && numeric === descriptionArg.onValue) {
        return true;
      }
      if (descriptionArg.offValue !== undefined && numeric === descriptionArg.offValue) {
        return false;
      }
      return numeric === 1;
    }
    const mapped = descriptionArg.stateMapping?.[String(valueArg)];
    return mapped ?? valueArg;
  }

  private async realtimeAction(actionArg: 'charge' | 'discharge', requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const power = this.numberValue(requestArg.data?.power);
    const targetSoc = this.numberValue(requestArg.data?.target_soc ?? requestArg.data?.targetSoc);
    if (power === undefined || targetSoc === undefined) {
      return { success: false, error: 'Indevolt charge/discharge requires data.power and data.target_soc.' };
    }
    const snapshot = await this.getSnapshot();
    const generation = this.snapshotGeneration(snapshot);
    const limits = deviceLimits[generation] || deviceLimits[2];
    const maxPower = actionArg === 'charge' ? limits.maxChargePower : limits.maxDischargePower;
    if (power > maxPower) {
      return { success: false, error: `Indevolt ${actionArg} power ${power}W exceeds generation ${generation} maximum ${maxPower}W.` };
    }
    if (targetSoc < limits.minimumSoc) {
      return { success: false, error: `Indevolt target_soc ${targetSoc}% is below minimum ${limits.minimumSoc}%.` };
    }

    await this.ensureRealtimeMode(snapshot);
    const success = await this.setData(setRealtimeActionKey, [realtimeActions[actionArg], power, targetSoc]);
    if (success) {
      this.currentSnapshot = undefined;
    }
    return { success, error: success ? undefined : `Indevolt ${actionArg} command was rejected by the device.`, data: { action: actionArg, power, targetSoc } };
  }

  private async stopRealtimeAction(): Promise<IServiceCallResult> {
    const success = await this.setData(setRealtimeActionKey, [realtimeActions.stop, 0, 0]);
    if (success) {
      this.currentSnapshot = undefined;
    }
    return { success, error: success ? undefined : 'Indevolt stop command was rejected by the device.', data: { action: 'stop' } };
  }

  private async setNumberValue(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const target = await this.targetControl(requestArg, 'number');
    const value = this.numberValue(requestArg.data?.value ?? requestArg.data?.native_value);
    if (!target || !target.writeKey) {
      return { success: false, error: 'Indevolt number.set_value requires a writable Indevolt number entity target.' };
    }
    if (value === undefined) {
      return { success: false, error: 'Indevolt number.set_value requires data.value.' };
    }
    const success = await this.setData(target.writeKey, value);
    if (success) {
      this.currentSnapshot = undefined;
    }
    return { success, error: success ? undefined : `Indevolt write to ${target.writeKey} was rejected by the device.`, data: { writeKey: target.writeKey, value } };
  }

  private async selectOption(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const target = await this.targetControl(requestArg, 'select');
    const option = stringValue(requestArg.data?.option);
    if (!target || !target.writeKey || !target.options) {
      return { success: false, error: 'Indevolt select.select_option requires a writable Indevolt select entity target.' };
    }
    if (!option || target.options[option] === undefined) {
      return { success: false, error: `Indevolt select option must be one of: ${Object.keys(target.options).join(', ')}.` };
    }
    const success = await this.setData(target.writeKey, target.options[option]);
    if (success) {
      this.currentSnapshot = undefined;
    }
    return { success, error: success ? undefined : `Indevolt write to ${target.writeKey} was rejected by the device.`, data: { writeKey: target.writeKey, option, value: target.options[option] } };
  }

  private async setSwitchValue(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    const target = await this.targetControl(requestArg, 'switch');
    if (!target || !target.writeKey) {
      return { success: false, error: 'Indevolt switch service requires a writable Indevolt switch entity target.' };
    }
    const enabled = requestArg.service === 'toggle' ? !Boolean(target.currentState) : requestArg.service === 'turn_on';
    const value = enabled ? target.writeOnValue ?? 1 : target.writeOffValue ?? 0;
    const success = await this.setData(target.writeKey, value);
    if (success) {
      this.currentSnapshot = undefined;
    }
    return { success, error: success ? undefined : `Indevolt write to ${target.writeKey} was rejected by the device.`, data: { writeKey: target.writeKey, enabled, value } };
  }

  private async ensureRealtimeMode(snapshotArg: ISimpleLocalSnapshot): Promise<void> {
    const rawData = recordValue(snapshotArg.rawData);
    const data = recordValue(rawData?.data) || {};
    const currentMode = this.numberValue(data[keys.readEnergyMode]);
    if (currentMode === energyModes.outdoorPortable) {
      throw new Error('Indevolt realtime control is unavailable while the device is in outdoor_portable energy mode.');
    }
    if (currentMode !== energyModes.realtimeControl) {
      const switched = await this.setData(keys.writeEnergyMode, energyModes.realtimeControl);
      if (!switched) {
        throw new Error('Indevolt failed to switch to real_time_control energy mode.');
      }
    }
  }

  private async targetControl(requestArg: IServiceCallRequest, kindArg: TIndevoltControlKind): Promise<{
    entity?: IIntegrationEntity;
    writeKey?: string;
    currentState?: unknown;
    options?: Record<string, number>;
    writeOnValue?: number;
    writeOffValue?: number;
  } | undefined> {
    const snapshot = await this.getSnapshot();
    const entities = SimpleLocalMapper.toEntities(indevoltProfile, snapshot);
    const entity = requestArg.target.entityId ? entities.find((entityArg) => entityArg.id === requestArg.target.entityId) : undefined;
    if (!entity) {
      const dataKey = stringValue(requestArg.data?.key ?? requestArg.data?.register);
      const description = dataKey ? keyDescriptions[dataKey] : undefined;
      if (description?.controlKind === kindArg) {
        return { writeKey: description.writeKey, options: description.options, writeOnValue: description.writeOnValue, writeOffValue: description.writeOffValue };
      }
      return undefined;
    }
    if (entity.attributes?.controlKind !== kindArg) {
      return undefined;
    }
    return {
      entity,
      writeKey: stringValue(entity.attributes.writeKey),
      currentState: entity.state,
      options: optionsValue(entity.attributes.options, entity.attributes.register),
      writeOnValue: this.numberValue(entity.attributes.writeOnValue),
      writeOffValue: this.numberValue(entity.attributes.writeOffValue),
    };
  }

  private async getConfig(): Promise<Record<string, unknown>> {
    const response = await globalThis.fetch(`${this.baseUrl()}/Sys.GetConfig`, {
      headers: { accept: 'application/json' },
      signal: AbortSignal.timeout(this.config.timeoutMs || indevoltDefaultTimeoutMs),
    });
    const data = await this.responseJson<Record<string, unknown>>(response, 'Sys.GetConfig');
    const device = recordValue(data.device);
    if (device && device.generation === undefined && typeof device.type === 'string') {
      device.generation = this.generationFromType(device.type);
    }
    return data;
  }

  private async fetchData(fetchKeysArg: string[]): Promise<Record<string, unknown>> {
    return this.rpcRequest('Indevolt.GetData', { t: fetchKeysArg.map((keyArg) => Number(keyArg)) });
  }

  private async setData(keyArg: string | number, valueArg: number | number[]): Promise<boolean> {
    const values = Array.isArray(valueArg) ? valueArg : [valueArg];
    const response = await this.rpcRequest<IIndevoltRpcResponse>('Indevolt.SetData', { f: 16, t: Number(keyArg), v: values.map((value) => Number(value)) });
    return Boolean(response.result);
  }

  private async rpcRequest<TResponse extends Record<string, unknown> = Record<string, unknown>>(endpointArg: string, configArg: Record<string, unknown>): Promise<TResponse> {
    const configParam = JSON.stringify(configArg).replace(/\s+/g, '');
    const response = await globalThis.fetch(`${this.baseUrl()}/${endpointArg}?config=${configParam}`, {
      method: 'POST',
      headers: { accept: 'application/json' },
      signal: AbortSignal.timeout(this.config.timeoutMs || indevoltDefaultTimeoutMs),
    });
    return this.responseJson<TResponse>(response, endpointArg);
  }

  private async responseJson<TResponse>(responseArg: Response, labelArg: string): Promise<TResponse> {
    const text = await responseArg.text();
    if (!responseArg.ok) {
      throw new Error(`Indevolt ${labelArg} request failed with HTTP ${responseArg.status}: ${text.slice(0, 160)}`);
    }
    return (text ? JSON.parse(text) : {}) as TResponse;
  }

  private baseUrl(): string {
    const url = this.config.url ? new URL(this.config.url) : undefined;
    const protocol = this.config.useTls || url?.protocol === 'https:' ? 'https' : 'http';
    const host = url?.hostname || this.config.host;
    if (!host) {
      throw new Error('Indevolt HTTP RPC access requires config.host or config.url.');
    }
    const port = this.port(url);
    const defaultPort = protocol === 'https' ? 443 : indevoltDefaultPort;
    const portPart = port && port !== defaultPort ? `:${port}` : '';
    const basePath = url?.pathname && url.pathname !== '/' ? url.pathname.replace(/\/+$/g, '') : '';
    const rpcBasePath = basePath.endsWith('/rpc') ? basePath : `${basePath}/rpc`;
    return `${protocol}://${host}${portPart}${rpcBasePath}`;
  }

  private sensorKeys(generationArg: number): string[] {
    const configured = this.config.sensorKeys;
    if (Array.isArray(configured) && configured.every((valueArg) => typeof valueArg === 'string' || typeof valueArg === 'number')) {
      return configured.map((valueArg) => String(valueArg));
    }
    return sensorKeysByGeneration[generationArg] || sensorKeysByGeneration[1];
  }

  private deviceGeneration(deviceArg: Record<string, unknown>): number {
    return this.numberValue(this.config.generation) || this.numberValue(deviceArg.generation) || (typeof deviceArg.type === 'string' ? this.generationFromType(deviceArg.type) : 1);
  }

  private generationFromType(typeArg: string): number {
    return ['CMS-SP2000', 'CMS-SF2000', 'CMS-SP3000', 'CMS-SF3000', 'CMS-SF1200'].includes(typeArg) ? 2 : 1;
  }

  private snapshotGeneration(snapshotArg: ISimpleLocalSnapshot): number {
    return this.numberValue(snapshotArg.device.attributes?.generation) || this.numberValue(this.config.generation) || 1;
  }

  private host(): string | undefined {
    return this.config.host || (this.config.url ? new URL(this.config.url).hostname : undefined);
  }

  private port(urlArg?: URL): number | undefined {
    const url = urlArg || (this.config.url ? new URL(this.config.url) : undefined);
    return this.config.port || (url?.port ? Number(url.port) : undefined) || (this.host() ? indevoltDefaultPort : undefined);
  }

  private hostId(): string | undefined {
    const host = this.host();
    return host ? `${host}:${this.port() || indevoltDefaultPort}` : undefined;
  }

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

  private offlineSnapshot(errorArg: string): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({ profile: indevoltProfile, config: this.config, online: false, source: 'runtime', error: errorArg });
  }

  private cleanAttributes(attributesArg: Record<string, unknown>): Record<string, unknown> {
    return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  }

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

const recordValue = (valueArg: unknown): Record<string, unknown> | undefined => valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : undefined;

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

const optionsValue = (valueArg: unknown, registerArg: unknown): Record<string, number> | undefined => {
  if (Array.isArray(valueArg) && typeof registerArg === 'string') {
    const description = keyDescriptions[registerArg];
    return description?.options;
  }
  return recordValue(valueArg) as Record<string, number> | undefined;
};

function titleCase(valueArg: string): string {
  return valueArg.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ').replace(/^\w/, (charArg) => charArg.toUpperCase());
}

function slug(valueArg: string): string {
  return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'register';
}
