import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IIntegrationEvent, IServiceCallRequest } from '../../core/types.js';
import {
  homematicAttributeSupport,
  homematicBatteryDatapoints,
  homematicBinaryDeviceClassByType,
  homematicBinarySensorDeviceTypes,
  homematicBooleanControlDatapoints,
  homematicClimateCurrentTemperatureDatapoints,
  homematicClimateDeviceTypes,
  homematicClimateHumidityDatapoints,
  homematicClimateTemperatureDatapoints,
  homematicControlModeDatapoints,
  homematicCoverDeviceTypes,
  homematicDomain,
  homematicIgnoredSensorDatapointExceptions,
  homematicIgnoredSensorDatapoints,
  homematicLevelControlDatapoints,
  homematicLightDeviceTypes,
  homematicLockDeviceTypes,
  homematicPressEvents,
  homematicSensorDeviceTypes,
  homematicSensorMetadata,
  homematicStateCasts,
  homematicSwitchDeviceTypes,
} from './homematic.constants.js';
import type {
  IHomematicChannel,
  IHomematicCommand,
  IHomematicDatapoint,
  IHomematicDevice,
  IHomematicEvent,
  IHomematicSnapshot,
  IHomematicValue,
} from './homematic.types.js';

type THomematicEntityPlatform = IIntegrationEntity['platform'] | 'lock';

export class HomematicMapper {
  public static toDevices(snapshotArg: IHomematicSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [{
      id: this.ccuDeviceId(snapshotArg),
      integrationDomain: homematicDomain,
      name: snapshotArg.ccu.name || 'Homematic CCU',
      protocol: 'http',
      manufacturer: snapshotArg.ccu.manufacturer || 'eQ-3',
      model: snapshotArg.ccu.model || 'Homematic CCU',
      online: snapshotArg.connected && snapshotArg.ccu.online !== false,
      features: [
        { id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
        { id: 'device_count', capability: 'sensor', name: 'Device count', readable: true, writable: false },
        { id: 'service_messages', capability: 'sensor', name: 'Service messages', readable: true, writable: false },
      ],
      state: [
        { featureId: 'connectivity', value: snapshotArg.connected ? 'online' : 'offline', updatedAt },
        { featureId: 'device_count', value: snapshotArg.devices.length, updatedAt },
        { featureId: 'service_messages', value: snapshotArg.ccu.serviceMessages?.length ?? 0, updatedAt },
      ],
      metadata: this.cleanAttributes({
        host: snapshotArg.ccu.host,
        port: snapshotArg.ccu.port,
        serialNumber: snapshotArg.ccu.serialNumber,
        firmwareVersion: snapshotArg.ccu.firmwareVersion,
        source: snapshotArg.source,
        eventServer: 'unsupported',
      }),
    }];

    for (const device of snapshotArg.devices) {
      const features: plugins.shxInterfaces.data.IDeviceFeature[] = [];
      const state: plugins.shxInterfaces.data.IDeviceState[] = [];
      for (const channel of device.channels) {
        for (const datapoint of channel.datapoints) {
          const id = `${channel.index}_${this.slug(datapoint.name)}`;
          const metadata = this.sensorMetadata(datapoint.name);
          features.push({
            id,
            capability: this.capabilityForDatapoint(device, channel, datapoint),
            name: this.datapointName(channel, datapoint),
            readable: datapoint.readable !== false,
            writable: datapoint.writable === true || datapoint.kind === 'write' || datapoint.kind === 'action',
            unit: datapoint.unit || metadata.unit,
          });
          state.push({ featureId: id, value: this.deviceStateValue(datapoint.value), updatedAt: datapoint.updatedAt || updatedAt });
        }
      }
      devices.push({
        id: this.deviceId(snapshotArg, device),
        integrationDomain: homematicDomain,
        name: device.name || device.address,
        protocol: 'http',
        manufacturer: device.manufacturer || 'eQ-3',
        model: device.model || device.type,
        online: snapshotArg.connected && device.available !== false,
        features,
        state,
        metadata: this.cleanAttributes({
          address: device.address,
          interfaceName: device.interfaceName,
          type: device.type,
          firmwareVersion: device.firmwareVersion,
          viaDevice: this.ccuDeviceId(snapshotArg),
          ...device.attributes,
        }),
      });
    }

    return devices;
  }

  public static toEntities(snapshotArg: IHomematicSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    const usedIds = new Map<string, number>();
    const ccuId = this.ccuDeviceId(snapshotArg);
    const uniqueBase = this.uniqueBase(snapshotArg);

    entities.push(this.entity('sensor', 'Homematic service messages', ccuId, `homematic_${uniqueBase}_service_messages`, snapshotArg.ccu.serviceMessages?.length ?? 0, usedIds, {
      host: snapshotArg.ccu.host,
      source: snapshotArg.source,
      eventServer: 'unsupported',
    }, snapshotArg.connected && snapshotArg.ccu.online !== false));

    for (const device of snapshotArg.devices) {
      this.addClimateEntities(snapshotArg, device, usedIds, entities);
      this.addCoverEntities(snapshotArg, device, usedIds, entities);
      this.addLightEntities(snapshotArg, device, usedIds, entities);
      this.addSwitchEntities(snapshotArg, device, usedIds, entities);
      this.addLockEntities(snapshotArg, device, usedIds, entities);
      this.addBinarySensorEntities(snapshotArg, device, usedIds, entities);
      this.addSensorEntities(snapshotArg, device, usedIds, entities);
    }

    return entities;
  }

  public static commandForService(snapshotArg: IHomematicSnapshot, requestArg: IServiceCallRequest): IHomematicCommand | undefined {
    if (requestArg.domain === homematicDomain && ['refresh', 'reload', 'snapshot'].includes(requestArg.service)) {
      return { type: 'refresh' };
    }

    if (requestArg.domain === homematicDomain && ['raw_xmlrpc', 'xmlrpc'].includes(requestArg.service)) {
      const method = this.stringData(requestArg, 'method');
      return method ? { type: 'raw_xmlrpc', method, params: this.arrayData(requestArg, 'params') || [] } : undefined;
    }

    if (requestArg.domain === homematicDomain && ['set_value', 'set_device_value'].includes(requestArg.service)) {
      const address = this.stringData(requestArg, 'address');
      const datapoint = this.stringData(requestArg, 'datapoint') || this.stringData(requestArg, 'parameter') || this.stringData(requestArg, 'param');
      const value = requestArg.data?.value as IHomematicValue | undefined;
      const channel = this.numberData(requestArg, 'channel');
      if (address && datapoint && value !== undefined) {
        return { type: 'set_value', address, channel, datapoint: datapoint.toUpperCase(), value, interfaceName: this.stringData(requestArg, 'interface') || this.stringData(requestArg, 'interfaceName'), entityId: requestArg.target.entityId, deviceId: requestArg.target.deviceId };
      }
    }

    if (requestArg.domain === homematicDomain && ['press', 'virtualkey'].includes(requestArg.service)) {
      const address = this.stringData(requestArg, 'address');
      const channel = this.numberData(requestArg, 'channel');
      const datapoint = (this.stringData(requestArg, 'datapoint') || this.stringData(requestArg, 'parameter') || this.stringData(requestArg, 'param') || 'PRESS_SHORT').toUpperCase();
      if (address && channel !== undefined) {
        return { type: 'press', address, channel, datapoint, value: requestArg.data?.value as IHomematicValue | undefined, interfaceName: this.stringData(requestArg, 'interface') || this.stringData(requestArg, 'interfaceName'), entityId: requestArg.target.entityId, deviceId: requestArg.target.deviceId };
      }
    }

    if (['turn_on', 'turn_off', 'open', 'close', 'stop'].includes(requestArg.service)) {
      return this.commandForTargetService(snapshotArg, requestArg);
    }

    if (requestArg.domain === 'climate' && requestArg.service === 'set_temperature') {
      const entity = this.findEntity(snapshotArg, requestArg);
      const temperature = this.numberData(requestArg, 'temperature') ?? this.numberData(requestArg, 'value');
      if (entity && temperature !== undefined) {
        return {
          type: 'set_temperature',
          address: String(entity.attributes?.address || ''),
          channel: Number(entity.attributes?.channel || 0),
          datapoint: String(entity.attributes?.targetTemperatureDatapoint || 'SET_TEMPERATURE'),
          value: temperature,
          interfaceName: this.stringAttribute(entity, 'interfaceName'),
          entityId: requestArg.target.entityId,
          deviceId: requestArg.target.deviceId,
        };
      }
    }

    return undefined;
  }

  public static toIntegrationEvent(eventArg: IHomematicEvent): IIntegrationEvent {
    return {
      type: eventArg.type === 'error' || eventArg.type === 'command_failed' ? 'error' : 'state_changed',
      integrationDomain: homematicDomain,
      deviceId: eventArg.deviceId,
      entityId: eventArg.entityId,
      data: eventArg.data || eventArg.command || eventArg,
      timestamp: eventArg.timestamp,
    };
  }

  public static ccuDeviceId(snapshotArg: IHomematicSnapshot): string {
    return `homematic.ccu.${this.uniqueBase(snapshotArg)}`;
  }

  public static deviceId(snapshotArg: IHomematicSnapshot, deviceArg: IHomematicDevice): string {
    return `homematic.device.${this.uniqueBase(snapshotArg)}.${this.slug(deviceArg.address)}`;
  }

  private static addClimateEntities(snapshotArg: IHomematicSnapshot, deviceArg: IHomematicDevice, usedIdsArg: Map<string, number>, entitiesArg: IIntegrationEntity[]): void {
    if (!this.isClimateDevice(deviceArg)) {
      return;
    }
    for (const channel of deviceArg.channels) {
      const target = this.firstDatapoint(channel, homematicClimateTemperatureDatapoints);
      if (!target) {
        continue;
      }
      const currentTemperature = this.firstDatapointValue(channel, homematicClimateCurrentTemperatureDatapoints);
      const currentHumidity = this.firstDatapointValue(channel, homematicClimateHumidityDatapoints);
      const controlMode = this.firstDatapointValue(channel, homematicControlModeDatapoints);
      entitiesArg.push(this.entity('climate', this.entityName(deviceArg, channel), this.deviceId(snapshotArg, deviceArg), `homematic_${this.uniqueBase(snapshotArg)}_${this.slug(deviceArg.address)}_${channel.index}_climate`, target.value ?? null, usedIdsArg, {
        address: deviceArg.address,
        channel: channel.index,
        channelAddress: channel.address,
        interfaceName: deviceArg.interfaceName,
        targetTemperatureDatapoint: target.name,
        currentTemperature,
        currentHumidity,
        controlMode,
        hvacModes: ['auto', 'heat', 'off'],
        presetModes: ['boost', 'comfort', 'eco'],
        minTemp: target.min ?? 4.5,
        maxTemp: target.max ?? 30.5,
        step: 0.5,
      }, this.available(snapshotArg, deviceArg)));
    }
  }

  private static addCoverEntities(snapshotArg: IHomematicSnapshot, deviceArg: IHomematicDevice, usedIdsArg: Map<string, number>, entitiesArg: IIntegrationEntity[]): void {
    if (!this.isCoverDevice(deviceArg)) {
      return;
    }
    for (const channel of deviceArg.channels) {
      const level = this.firstDatapoint(channel, ['LEVEL']);
      const doorState = this.firstDatapoint(channel, ['DOOR_STATE']);
      if (!level && !doorState) {
        continue;
      }
      const position = level && typeof level.value === 'number' ? Math.round(level.value * 100) : undefined;
      const state = doorState ? this.castState(deviceArg, doorState) : position === 0 ? 'closed' : 'open';
      const isGarage = deviceArg.type === 'IPGarage' || Boolean(doorState);
      entitiesArg.push(this.entity('cover', this.entityName(deviceArg, channel), this.deviceId(snapshotArg, deviceArg), `homematic_${this.uniqueBase(snapshotArg)}_${this.slug(deviceArg.address)}_${channel.index}_cover`, state, usedIdsArg, {
        address: deviceArg.address,
        channel: channel.index,
        channelAddress: channel.address,
        interfaceName: deviceArg.interfaceName,
        position,
        tiltPosition: this.levelPercent(this.firstDatapointValue(channel, ['LEVEL_2'])),
        datapoint: level?.name || doorState?.name,
        coverCommandDatapoint: isGarage ? 'DOOR_COMMAND' : 'LEVEL',
        deviceClass: isGarage ? 'garage' : undefined,
      }, this.available(snapshotArg, deviceArg)));
    }
  }

  private static addLightEntities(snapshotArg: IHomematicSnapshot, deviceArg: IHomematicDevice, usedIdsArg: Map<string, number>, entitiesArg: IIntegrationEntity[]): void {
    if (!this.isLightDevice(deviceArg) || this.isCoverDevice(deviceArg) || this.isClimateDevice(deviceArg)) {
      return;
    }
    for (const channel of deviceArg.channels) {
      const level = this.firstDatapoint(channel, homematicLevelControlDatapoints);
      if (!level) {
        continue;
      }
      entitiesArg.push(this.entity('light', this.entityName(deviceArg, channel), this.deviceId(snapshotArg, deviceArg), `homematic_${this.uniqueBase(snapshotArg)}_${this.slug(deviceArg.address)}_${channel.index}_light`, this.numericValue(level.value) > 0 ? 'on' : 'off', usedIdsArg, {
        address: deviceArg.address,
        channel: channel.index,
        channelAddress: channel.address,
        interfaceName: deviceArg.interfaceName,
        datapoint: level.name,
        brightness: Math.round(this.numericValue(level.value) * 255),
        level: level.value,
        supportsTransition: true,
        supportsColor: Boolean(this.firstDatapoint(channel, ['COLOR'])),
        supportsColorTemperature: deviceArg.type === 'ColdWarmDimmer',
        effect: this.firstDatapointValue(channel, ['PROGRAM']),
      }, this.available(snapshotArg, deviceArg)));
    }
  }

  private static addSwitchEntities(snapshotArg: IHomematicSnapshot, deviceArg: IHomematicDevice, usedIdsArg: Map<string, number>, entitiesArg: IIntegrationEntity[]): void {
    if (!this.isSwitchDevice(deviceArg) || this.isLightDevice(deviceArg) || this.isCoverDevice(deviceArg) || this.isClimateDevice(deviceArg)) {
      return;
    }
    for (const channel of deviceArg.channels) {
      const state = this.firstDatapoint(channel, homematicBooleanControlDatapoints);
      if (!state) {
        continue;
      }
      entitiesArg.push(this.entity('switch', this.primaryEntityName(deviceArg, channel, ['STATE']), this.deviceId(snapshotArg, deviceArg), `homematic_${this.uniqueBase(snapshotArg)}_${this.slug(deviceArg.address)}_${channel.index}_switch`, this.onOffState(state.value), usedIdsArg, {
        address: deviceArg.address,
        channel: channel.index,
        channelAddress: channel.address,
        interfaceName: deviceArg.interfaceName,
        datapoint: state.name,
      }, this.available(snapshotArg, deviceArg)));
    }
  }

  private static addLockEntities(snapshotArg: IHomematicSnapshot, deviceArg: IHomematicDevice, usedIdsArg: Map<string, number>, entitiesArg: IIntegrationEntity[]): void {
    if (!this.isLockDevice(deviceArg)) {
      return;
    }
    for (const channel of deviceArg.channels) {
      const state = this.firstDatapoint(channel, ['STATE', 'LOCK_STATE']);
      if (!state) {
        continue;
      }
      entitiesArg.push(this.entity('lock', this.entityName(deviceArg, channel), this.deviceId(snapshotArg, deviceArg), `homematic_${this.uniqueBase(snapshotArg)}_${this.slug(deviceArg.address)}_${channel.index}_lock`, this.lockState(deviceArg, state), usedIdsArg, {
        address: deviceArg.address,
        channel: channel.index,
        channelAddress: channel.address,
        interfaceName: deviceArg.interfaceName,
        datapoint: state.name,
        supportsOpen: true,
        openDatapoint: 'OPEN',
      }, this.available(snapshotArg, deviceArg)));
    }
  }

  private static addBinarySensorEntities(snapshotArg: IHomematicSnapshot, deviceArg: IHomematicDevice, usedIdsArg: Map<string, number>, entitiesArg: IIntegrationEntity[]): void {
    for (const channel of deviceArg.channels) {
      for (const datapoint of channel.datapoints) {
        if (!this.shouldCreateBinarySensor(deviceArg, channel, datapoint)) {
          continue;
        }
        entitiesArg.push(this.entity('binary_sensor', this.datapointEntityName(deviceArg, channel, datapoint), this.deviceId(snapshotArg, deviceArg), `homematic_${this.uniqueBase(snapshotArg)}_${this.slug(deviceArg.address)}_${channel.index}_${this.slug(datapoint.name)}`, this.onOffState(datapoint.value), usedIdsArg, {
          address: deviceArg.address,
          channel: channel.index,
          channelAddress: channel.address,
          interfaceName: deviceArg.interfaceName,
          datapoint: datapoint.name,
          deviceClass: this.binaryDeviceClass(deviceArg, datapoint),
        }, this.available(snapshotArg, deviceArg)));
      }
    }
  }

  private static addSensorEntities(snapshotArg: IHomematicSnapshot, deviceArg: IHomematicDevice, usedIdsArg: Map<string, number>, entitiesArg: IIntegrationEntity[]): void {
    for (const channel of deviceArg.channels) {
      for (const datapoint of channel.datapoints) {
        if (!this.shouldCreateSensor(deviceArg, channel, datapoint)) {
          continue;
        }
        const metadata = this.sensorMetadata(datapoint.name);
        entitiesArg.push(this.entity('sensor', this.datapointEntityName(deviceArg, channel, datapoint), this.deviceId(snapshotArg, deviceArg), `homematic_${this.uniqueBase(snapshotArg)}_${this.slug(deviceArg.address)}_${channel.index}_${this.slug(datapoint.name)}`, this.sensorState(deviceArg, datapoint), usedIdsArg, {
          address: deviceArg.address,
          channel: channel.index,
          channelAddress: channel.address,
          interfaceName: deviceArg.interfaceName,
          datapoint: datapoint.name,
          unit: datapoint.unit || metadata.unit,
          deviceClass: datapoint.deviceClass || metadata.deviceClass,
          stateClass: datapoint.stateClass || metadata.stateClass,
          icon: metadata.icon,
          min: datapoint.min,
          max: datapoint.max,
          valueList: datapoint.valueList,
        }, this.available(snapshotArg, deviceArg)));
      }
    }
  }

  private static commandForTargetService(snapshotArg: IHomematicSnapshot, requestArg: IServiceCallRequest): IHomematicCommand | undefined {
    const entity = this.findEntity(snapshotArg, requestArg);
    if (!entity) {
      return undefined;
    }
    const address = this.stringAttribute(entity, 'address');
    const channel = this.numberAttribute(entity, 'channel');
    const interfaceName = this.stringAttribute(entity, 'interfaceName');
    if (!address || channel === undefined) {
      return undefined;
    }
    if (requestArg.service === 'turn_on' || requestArg.service === 'turn_off') {
      const on = requestArg.service === 'turn_on';
      const datapoint = this.stringAttribute(entity, 'datapoint') || (entity.platform === 'light' ? 'LEVEL' : 'STATE');
      const brightness = this.numberData(requestArg, 'brightness');
      const value = datapoint === 'LEVEL' ? (on ? brightness !== undefined ? Math.max(0, Math.min(1, brightness > 1 ? brightness / 255 : brightness)) : 1 : 0) : on;
      return { type: on ? 'turn_on' : 'turn_off', address, channel, datapoint, value, interfaceName, entityId: requestArg.target.entityId, deviceId: requestArg.target.deviceId };
    }
    if (requestArg.service === 'open' || requestArg.service === 'close' || requestArg.service === 'stop') {
      const datapoint = this.stringAttribute(entity, 'coverCommandDatapoint') || (this.stringAttribute(entity, 'openDatapoint') ? 'OPEN' : 'LEVEL');
      const value = datapoint === 'DOOR_COMMAND' ? requestArg.service === 'open' ? 1 : requestArg.service === 'close' ? 3 : 2 : datapoint === 'OPEN' ? true : requestArg.service === 'open' ? 1 : requestArg.service === 'close' ? 0 : true;
      return { type: requestArg.service, address, channel, datapoint, value, interfaceName, entityId: requestArg.target.entityId, deviceId: requestArg.target.deviceId };
    }
    return undefined;
  }

  private static findEntity(snapshotArg: IHomematicSnapshot, requestArg: IServiceCallRequest): IIntegrationEntity | undefined {
    const target = requestArg.target.entityId || requestArg.target.deviceId;
    const entities = this.toEntities(snapshotArg);
    if (!target) {
      const address = this.stringData(requestArg, 'address');
      const channel = this.numberData(requestArg, 'channel');
      return address ? entities.find((entityArg) => entityArg.attributes?.address === address && (channel === undefined || entityArg.attributes?.channel === channel)) : undefined;
    }
    return entities.find((entityArg) => entityArg.id === target || entityArg.uniqueId === target || entityArg.deviceId === target);
  }

  private static entity(platformArg: THomematicEntityPlatform, nameArg: string, deviceIdArg: string, uniqueIdArg: string, stateArg: unknown, usedIdsArg: Map<string, number>, attributesArg: Record<string, unknown>, availableArg: boolean): IIntegrationEntity {
    const baseId = `${platformArg}.${this.slug(nameArg) || this.slug(uniqueIdArg)}`;
    const seen = usedIdsArg.get(baseId) || 0;
    usedIdsArg.set(baseId, seen + 1);
    return {
      id: seen ? `${baseId}_${seen + 1}` : baseId,
      uniqueId: uniqueIdArg,
      integrationDomain: homematicDomain,
      deviceId: deviceIdArg,
      platform: platformArg as IIntegrationEntity['platform'],
      name: nameArg,
      state: stateArg,
      attributes: this.cleanAttributes(attributesArg),
      available: availableArg,
    };
  }

  private static capabilityForDatapoint(deviceArg: IHomematicDevice, channelArg: IHomematicChannel, datapointArg: IHomematicDatapoint): plugins.shxInterfaces.data.TDeviceCapability {
    void channelArg;
    if (this.isLightDevice(deviceArg) && datapointArg.name === 'LEVEL') {
      return 'light';
    }
    if (this.isSwitchDevice(deviceArg) && datapointArg.name === 'STATE') {
      return 'switch';
    }
    if (this.isCoverDevice(deviceArg) && ['LEVEL', 'LEVEL_2', 'DOOR_STATE'].includes(datapointArg.name)) {
      return 'cover';
    }
    if (this.isClimateDevice(deviceArg) && [...homematicClimateTemperatureDatapoints, ...homematicClimateCurrentTemperatureDatapoints].includes(datapointArg.name)) {
      return 'climate';
    }
    if (this.isLockDevice(deviceArg)) {
      return 'lock';
    }
    return 'sensor';
  }

  private static shouldCreateBinarySensor(deviceArg: IHomematicDevice, channelArg: IHomematicChannel, datapointArg: IHomematicDatapoint): boolean {
    if (this.isPrimaryControlDatapoint(deviceArg, datapointArg)) {
      return false;
    }
    if (homematicBatteryDatapoints.includes(datapointArg.name)) {
      return true;
    }
    if (datapointArg.kind === 'binary') {
      return true;
    }
    if (homematicBinarySensorDeviceTypes.includes(deviceArg.type || '') && typeof datapointArg.value === 'boolean') {
      return true;
    }
    void channelArg;
    return false;
  }

  private static shouldCreateSensor(deviceArg: IHomematicDevice, channelArg: IHomematicChannel, datapointArg: IHomematicDatapoint): boolean {
    if (this.shouldCreateBinarySensor(deviceArg, channelArg, datapointArg) || this.isPrimaryControlDatapoint(deviceArg, datapointArg) || homematicPressEvents.includes(datapointArg.name as typeof homematicPressEvents[number])) {
      return false;
    }
    if (homematicIgnoredSensorDatapoints.includes(datapointArg.name) && !homematicIgnoredSensorDatapointExceptions[datapointArg.name]?.includes(deviceArg.type || '')) {
      return false;
    }
    return datapointArg.kind === 'sensor' || datapointArg.kind === 'attribute' || homematicSensorDeviceTypes.includes(deviceArg.type || '') || datapointArg.readable !== false;
  }

  private static isPrimaryControlDatapoint(deviceArg: IHomematicDevice, datapointArg: IHomematicDatapoint): boolean {
    if (this.isClimateDevice(deviceArg) && homematicClimateTemperatureDatapoints.includes(datapointArg.name)) {
      return true;
    }
    if (this.isCoverDevice(deviceArg) && ['LEVEL', 'LEVEL_2', 'DOOR_STATE'].includes(datapointArg.name)) {
      return true;
    }
    if (this.isLightDevice(deviceArg) && ['LEVEL', 'COLOR', 'PROGRAM'].includes(datapointArg.name)) {
      return true;
    }
    if (this.isSwitchDevice(deviceArg) && !this.isLightDevice(deviceArg) && datapointArg.name === 'STATE') {
      return true;
    }
    if (this.isLockDevice(deviceArg) && ['STATE', 'OPEN'].includes(datapointArg.name)) {
      return true;
    }
    return false;
  }

  private static isClimateDevice(deviceArg: IHomematicDevice): boolean {
    return homematicClimateDeviceTypes.includes(deviceArg.type || '') || deviceArg.channels.some((channelArg) => Boolean(this.firstDatapoint(channelArg, homematicClimateTemperatureDatapoints)));
  }

  private static isCoverDevice(deviceArg: IHomematicDevice): boolean {
    return homematicCoverDeviceTypes.includes(deviceArg.type || '') || deviceArg.channels.some((channelArg) => Boolean(this.firstDatapoint(channelArg, ['DOOR_STATE'])));
  }

  private static isLightDevice(deviceArg: IHomematicDevice): boolean {
    return homematicLightDeviceTypes.includes(deviceArg.type || '') || deviceArg.channels.some((channelArg) => Boolean(this.firstDatapoint(channelArg, ['LEVEL'])?.writable) && !this.isCoverDevice(deviceArg));
  }

  private static isSwitchDevice(deviceArg: IHomematicDevice): boolean {
    return homematicSwitchDeviceTypes.includes(deviceArg.type || '') || deviceArg.channels.some((channelArg) => Boolean(this.firstDatapoint(channelArg, ['STATE'])));
  }

  private static isLockDevice(deviceArg: IHomematicDevice): boolean {
    return homematicLockDeviceTypes.includes(deviceArg.type || '');
  }

  private static firstDatapoint(channelArg: IHomematicChannel, namesArg: string[]): IHomematicDatapoint | undefined {
    return channelArg.datapoints.find((datapointArg) => namesArg.includes(datapointArg.name));
  }

  private static firstDatapointValue(channelArg: IHomematicChannel, namesArg: string[]): IHomematicValue | undefined {
    return this.firstDatapoint(channelArg, namesArg)?.value;
  }

  private static binaryDeviceClass(deviceArg: IHomematicDevice, datapointArg: IHomematicDatapoint): string | undefined {
    if (homematicBatteryDatapoints.includes(datapointArg.name)) {
      return 'battery';
    }
    if (datapointArg.name === 'MOTION') {
      return 'motion';
    }
    if (datapointArg.name === 'RAINING' || datapointArg.name === 'MOISTURE_DETECTED') {
      return 'moisture';
    }
    return homematicBinaryDeviceClassByType[deviceArg.type || ''];
  }

  private static sensorMetadata(nameArg: string): { unit?: string; deviceClass?: string; stateClass?: string; icon?: string } {
    return homematicSensorMetadata[nameArg] || {};
  }

  private static sensorState(deviceArg: IHomematicDevice, datapointArg: IHomematicDatapoint): unknown {
    const cast = this.castState(deviceArg, datapointArg);
    if (cast !== undefined) {
      return cast;
    }
    const attr = homematicAttributeSupport[datapointArg.name];
    if (attr?.values && datapointArg.value !== undefined && datapointArg.value !== null) {
      return attr.values[String(datapointArg.value)] ?? datapointArg.value;
    }
    if (datapointArg.name === 'LEVEL' && typeof datapointArg.value === 'number') {
      return Math.round(datapointArg.value * 100);
    }
    return datapointArg.value ?? null;
  }

  private static castState(deviceArg: IHomematicDevice, datapointArg: IHomematicDatapoint): string | null | undefined {
    const cast = homematicStateCasts[deviceArg.type || ''];
    if (!cast || datapointArg.value === undefined || datapointArg.value === null) {
      return undefined;
    }
    return cast[String(datapointArg.value)];
  }

  private static lockState(deviceArg: IHomematicDevice, datapointArg: IHomematicDatapoint): string {
    if (deviceArg.type === 'KeyMatic') {
      return datapointArg.value ? 'unlocked' : 'locked';
    }
    const cast = this.castState(deviceArg, datapointArg);
    return typeof cast === 'string' ? cast : datapointArg.value ? 'unlocked' : 'locked';
  }

  private static entityName(deviceArg: IHomematicDevice, channelArg: IHomematicChannel): string {
    if (deviceArg.channels.length <= 1) {
      return deviceArg.name || channelArg.name || deviceArg.address;
    }
    return channelArg.name || `${deviceArg.name || deviceArg.address} ${channelArg.index}`;
  }

  private static primaryEntityName(deviceArg: IHomematicDevice, channelArg: IHomematicChannel, datapointsArg: string[]): string {
    const matchingChannels = deviceArg.channels.filter((channelItemArg) => datapointsArg.some((datapointArg) => Boolean(this.firstDatapoint(channelItemArg, [datapointArg]))));
    return matchingChannels.length === 1 ? deviceArg.name || channelArg.name || deviceArg.address : this.entityName(deviceArg, channelArg);
  }

  private static datapointEntityName(deviceArg: IHomematicDevice, channelArg: IHomematicChannel, datapointArg: IHomematicDatapoint): string {
    const base = this.entityName(deviceArg, channelArg);
    return `${base} ${this.humanName(datapointArg.name)}`;
  }

  private static datapointName(channelArg: IHomematicChannel, datapointArg: IHomematicDatapoint): string {
    return `${channelArg.index} ${this.humanName(datapointArg.name)}`;
  }

  private static humanName(valueArg: string): string {
    return valueArg.toLowerCase().split('_').map((partArg) => partArg.charAt(0).toUpperCase() + partArg.slice(1)).join(' ');
  }

  private static available(snapshotArg: IHomematicSnapshot, deviceArg: IHomematicDevice): boolean {
    return snapshotArg.connected && deviceArg.available !== false;
  }

  private static uniqueBase(snapshotArg: IHomematicSnapshot): string {
    return this.slug(snapshotArg.ccu.serialNumber || snapshotArg.ccu.id || snapshotArg.ccu.host || snapshotArg.ccu.name || 'homematic');
  }

  private static onOffState(valueArg: unknown): 'on' | 'off' {
    return this.numericValue(valueArg) > 0 || valueArg === true ? 'on' : 'off';
  }

  private static levelPercent(valueArg: unknown): number | undefined {
    return typeof valueArg === 'number' ? Math.round(valueArg * 100) : undefined;
  }

  private static numericValue(valueArg: unknown): number {
    return typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : typeof valueArg === 'boolean' ? valueArg ? 1 : 0 : typeof valueArg === 'string' && Number.isFinite(Number(valueArg)) ? Number(valueArg) : 0;
  }

  private static numberData(requestArg: IServiceCallRequest, keyArg: string): number | undefined {
    const value = requestArg.data?.[keyArg];
    return this.numberValue(value);
  }

  private static stringData(requestArg: IServiceCallRequest, keyArg: string): string | undefined {
    const value = requestArg.data?.[keyArg];
    return typeof value === 'string' && value.trim() ? value.trim() : undefined;
  }

  private static arrayData(requestArg: IServiceCallRequest, keyArg: string): unknown[] | undefined {
    const value = requestArg.data?.[keyArg];
    return Array.isArray(value) ? value : undefined;
  }

  private static stringAttribute(entityArg: IIntegrationEntity, keyArg: string): string | undefined {
    const value = entityArg.attributes?.[keyArg];
    return typeof value === 'string' && value ? value : undefined;
  }

  private static numberAttribute(entityArg: IIntegrationEntity, keyArg: string): number | undefined {
    return this.numberValue(entityArg.attributes?.[keyArg]);
  }

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

  private static deviceStateValue(valueArg: unknown): plugins.shxInterfaces.data.TDeviceStateValue {
    if (valueArg === undefined) {
      return null;
    }
    if (valueArg === null || typeof valueArg === 'string' || typeof valueArg === 'number' || typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'object' && !Array.isArray(valueArg)) {
      return valueArg as Record<string, unknown>;
    }
    return { value: valueArg };
  }

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

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