import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IIntegrationEvent, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type {
  IFritzActionDescriptor,
  IFritzCallDeflection,
  IFritzClientDevice,
  IFritzCommand,
  IFritzConfig,
  IFritzConnectionInfo,
  IFritzEvent,
  IFritzInterfaceStats,
  IFritzManualEntry,
  IFritzPortForward,
  IFritzRouterInfo,
  IFritzSensorMap,
  IFritzSnapshot,
  IFritzUpdateInfo,
  IFritzWifiNetwork,
  TFritzAction,
  TFritzClientAction,
  TFritzMeshRole,
  TFritzProtocol,
  TFritzRouterAction,
  TFritzServiceAction,
  TFritzSwitchAction,
} from './fritz.types.js';
import { fritzDefaultHttpPort, fritzDefaultHttpsPort, fritzDefaultSsl, fritzDomain } from './fritz.types.js';

type TSensorDescriptor = {
  key: keyof IFritzSensorMap & string;
  name: string;
  unit?: string;
  deviceClass?: string;
  stateClass?: string;
  entityCategory?: string;
  enabledByDefault?: boolean;
  transform?: (valueArg: unknown) => unknown;
};

const manufacturer = 'FRITZ!';

const connectionSensorDescriptors: TSensorDescriptor[] = [
  { key: 'external_ip', name: 'External IP', deviceClass: 'ip' },
  { key: 'external_ipv6', name: 'External IPv6', deviceClass: 'ip' },
  { key: 'connection_uptime', name: 'Connection uptime', deviceClass: 'timestamp', entityCategory: 'diagnostic', transform: (valueArg) => FritzMapper.uptimeValue(valueArg) },
  { key: 'kb_s_sent', name: 'Upload throughput', unit: 'kB/s', deviceClass: 'data_rate', stateClass: 'measurement' },
  { key: 'kb_s_received', name: 'Download throughput', unit: 'kB/s', deviceClass: 'data_rate', stateClass: 'measurement' },
  { key: 'max_kb_s_sent', name: 'Max connection upload throughput', unit: 'kbit/s', deviceClass: 'data_rate' },
  { key: 'max_kb_s_received', name: 'Max connection download throughput', unit: 'kbit/s', deviceClass: 'data_rate' },
  { key: 'gb_sent', name: 'GB sent', unit: 'GB', deviceClass: 'data_size', stateClass: 'total_increasing' },
  { key: 'gb_received', name: 'GB received', unit: 'GB', deviceClass: 'data_size', stateClass: 'total_increasing' },
  { key: 'link_kb_s_sent', name: 'Link upload throughput', unit: 'kbit/s', deviceClass: 'data_rate', entityCategory: 'diagnostic' },
  { key: 'link_kb_s_received', name: 'Link download throughput', unit: 'kbit/s', deviceClass: 'data_rate', entityCategory: 'diagnostic' },
  { key: 'link_noise_margin_sent', name: 'Link upload noise margin', unit: 'dB', entityCategory: 'diagnostic', enabledByDefault: false },
  { key: 'link_noise_margin_received', name: 'Link download noise margin', unit: 'dB', entityCategory: 'diagnostic', enabledByDefault: false },
  { key: 'link_attenuation_sent', name: 'Link upload power attenuation', unit: 'dB', entityCategory: 'diagnostic', enabledByDefault: false },
  { key: 'link_attenuation_received', name: 'Link download power attenuation', unit: 'dB', entityCategory: 'diagnostic', enabledByDefault: false },
  { key: 'device_uptime', name: 'Device uptime', deviceClass: 'timestamp', entityCategory: 'diagnostic', transform: (valueArg) => FritzMapper.uptimeValue(valueArg) },
  { key: 'cpu_temperature', name: 'CPU temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement', entityCategory: 'diagnostic' },
];

export class FritzMapper {
  public static toSnapshot(configArg: IFritzConfig, connectedArg?: boolean, eventsArg: IFritzEvent[] = []): IFritzSnapshot {
    const source = configArg.snapshot;
    const manualSnapshots = (configArg.manualEntries || [])
      .map((entryArg) => entryArg.snapshot)
      .filter((snapshotArg): snapshotArg is IFritzSnapshot => Boolean(snapshotArg));
    const manualData = this.mergeManualEntries(configArg.manualEntries || []);
    const router = this.routerInfo(configArg, source, manualSnapshots, manualData.router);
    const devices = this.uniqueClients([
      ...(source?.devices || []),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.devices || []),
      ...(configArg.devices || []),
      ...(configArg.clients || []),
      ...manualData.devices,
    ]);
    const interfaces = this.uniqueInterfaces([
      ...(source?.interfaces || []),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.interfaces || []),
      ...(configArg.interfaces || []),
      ...manualData.interfaces,
    ]);
    const connection = this.connectionInfo([
      source?.connection,
      ...manualSnapshots.map((snapshotArg) => snapshotArg.connection),
      configArg.connection,
      manualData.connection,
    ], router);
    const sensors = this.sensorMap([
      source?.sensors,
      ...manualSnapshots.map((snapshotArg) => snapshotArg.sensors),
      configArg.sensors,
      manualData.sensors,
    ], connection, devices, interfaces);
    const wifiNetworks = this.uniqueWifiNetworks([
      ...(source?.wifiNetworks || []),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.wifiNetworks || []),
      ...(configArg.wifiNetworks || []),
      ...manualData.wifiNetworks,
    ]);
    const portForwards = this.uniquePortForwards([
      ...(source?.portForwards || []),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.portForwards || []),
      ...(configArg.portForwards || []),
      ...manualData.portForwards,
    ]);
    const callDeflections = this.uniqueCallDeflections([
      ...(source?.callDeflections || []),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.callDeflections || []),
      ...(configArg.callDeflections || []),
      ...manualData.callDeflections,
    ]);
    const update = this.updateInfo(source?.update, ...manualSnapshots.map((snapshotArg) => snapshotArg.update), configArg.update, manualData.update, router);
    const actions = this.uniqueActions([
      ...(source?.actions || []),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.actions || []),
      ...(configArg.actions || []),
      ...manualData.actions,
      ...this.actionsFromRouter(router),
      ...this.actionsFromClients(devices),
      ...this.actionsFromRepresentedControls(wifiNetworks, portForwards, callDeflections),
    ]);
    const hasManualData = Boolean(
      source
      || manualSnapshots.length
      || configArg.router
      || configArg.devices?.length
      || configArg.clients?.length
      || configArg.interfaces?.length
      || configArg.connection
      || configArg.sensors
      || configArg.wifiNetworks?.length
      || configArg.portForwards?.length
      || configArg.callDeflections?.length
      || configArg.update
      || manualData.hasData
    );

    return {
      connected: connectedArg ?? configArg.connected ?? source?.connected ?? hasManualData,
      source: source?.source || (hasManualData ? 'manual' : 'runtime'),
      updatedAt: source?.updatedAt || new Date().toISOString(),
      router,
      devices,
      interfaces,
      connection,
      sensors,
      wifiNetworks,
      portForwards,
      callDeflections,
      update,
      actions,
      events: [...(source?.events || []), ...(configArg.events || []), ...eventsArg],
      error: source?.error,
      metadata: {
        ...source?.metadata,
        ...configArg.metadata,
        liveTr064Implemented: false,
        commandExecutorConfigured: Boolean(configArg.commandExecutor || configArg.nativeClient?.executeCommand),
      },
    };
  }

  public static toDevices(snapshotArg: IFritzSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [this.routerDevice(snapshotArg, updatedAt)];
    for (const client of snapshotArg.devices) {
      devices.push(this.clientDevice(client, snapshotArg, updatedAt));
    }
    return devices;
  }

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

    entities.push(this.entity('binary_sensor', `${routerName} Integration connected`, routerDeviceId, `${uniqueBase}_integration_connected`, snapshotArg.connected ? 'on' : 'off', usedIds, {
      deviceClass: 'connectivity',
      host: snapshotArg.router.host,
      port: snapshotArg.router.port,
      ssl: snapshotArg.router.ssl,
    }, true));

    if (snapshotArg.sensors.is_connected !== undefined) {
      entities.push(this.entity('binary_sensor', `${routerName} Connection`, routerDeviceId, `${uniqueBase}_is_connected`, snapshotArg.sensors.is_connected ? 'on' : 'off', usedIds, {
        deviceClass: 'connectivity',
        entityCategory: 'diagnostic',
        nativeKey: 'is_connected',
      }, snapshotArg.connected));
    }
    if (snapshotArg.sensors.is_linked !== undefined) {
      entities.push(this.entity('binary_sensor', `${routerName} Link`, routerDeviceId, `${uniqueBase}_is_linked`, snapshotArg.sensors.is_linked ? 'on' : 'off', usedIds, {
        deviceClass: 'plug',
        entityCategory: 'diagnostic',
        nativeKey: 'is_linked',
      }, snapshotArg.connected));
    }

    for (const descriptor of connectionSensorDescriptors) {
      const rawValue = snapshotArg.sensors[descriptor.key];
      if (rawValue === undefined) {
        continue;
      }
      const value = this.sensorValue(rawValue, descriptor);
      if (value === undefined) {
        continue;
      }
      entities.push(this.entity('sensor', `${routerName} ${descriptor.name}`, routerDeviceId, `${uniqueBase}_${this.slug(descriptor.key)}`, value, usedIds, {
        nativeKey: descriptor.key,
        unit: descriptor.unit,
        deviceClass: descriptor.deviceClass,
        stateClass: descriptor.stateClass,
        entityCategory: descriptor.entityCategory,
        enabledByDefault: descriptor.enabledByDefault,
      }, snapshotArg.connected));
    }

    for (const iface of snapshotArg.interfaces) {
      this.pushInterfaceEntities(entities, snapshotArg, iface, usedIds);
    }

    for (const client of snapshotArg.devices) {
      this.pushClientEntities(entities, snapshotArg, client, usedIds);
    }

    for (const wifi of snapshotArg.wifiNetworks) {
      this.pushWifiEntities(entities, snapshotArg, wifi, usedIds);
    }

    for (const portForward of snapshotArg.portForwards) {
      this.pushPortForwardEntities(entities, snapshotArg, portForward, usedIds);
    }

    for (const deflection of snapshotArg.callDeflections) {
      this.pushCallDeflectionEntities(entities, snapshotArg, deflection, usedIds);
    }

    if (this.hasUpdate(snapshotArg)) {
      entities.push(this.updateEntity(snapshotArg, usedIds));
    }

    for (const action of this.snapshotActions(snapshotArg)) {
      const button = this.actionButton(snapshotArg, action, usedIds);
      if (button) {
        entities.push(button);
      }
    }

    return entities;
  }

  public static commandForService(snapshotArg: IFritzSnapshot, requestArg: IServiceCallRequest): IFritzCommand | undefined {
    if (requestArg.domain === fritzDomain && (requestArg.service === 'reboot' || requestArg.service === 'reconnect' || requestArg.service === 'firmware_update' || requestArg.service === 'cleanup')) {
      const action = this.snapshotActions(snapshotArg).find((actionArg) => actionArg.target === 'router' && actionArg.action === this.routerActionFromService(requestArg.service));
      return action ? this.command(snapshotArg, requestArg, action) : undefined;
    }

    if (requestArg.domain === fritzDomain && (requestArg.service === 'set_guest_wifi_password' || requestArg.service === 'dial')) {
      const action = this.snapshotActions(snapshotArg).find((actionArg) => actionArg.target === 'service' && actionArg.action === requestArg.service);
      if (!action) {
        return undefined;
      }
      if (requestArg.service === 'set_guest_wifi_password' && !this.validGuestPasswordRequest(requestArg.data)) {
        return undefined;
      }
      if (requestArg.service === 'dial' && !this.stringValue(requestArg.data?.number)) {
        return undefined;
      }
      return this.command(snapshotArg, requestArg, action);
    }

    if (requestArg.domain === fritzDomain && requestArg.service === 'wake_on_lan') {
      const mac = this.normalizeMac(this.stringValue(requestArg.data?.mac) || this.stringValue(requestArg.data?.macAddress));
      const action = this.snapshotActions(snapshotArg).find((actionArg) => actionArg.target === 'client' && actionArg.action === 'wake_on_lan' && (!actionArg.mac || this.normalizeMac(actionArg.mac) === mac));
      return action && mac ? this.command(snapshotArg, requestArg, { ...action, mac }) : undefined;
    }

    const targetEntity = this.findTargetEntity(snapshotArg, requestArg);
    if (!targetEntity) {
      return undefined;
    }

    if (requestArg.domain === 'button' && requestArg.service === 'press') {
      const nativeAction = this.stringValue(targetEntity.attributes?.nativeAction) as TFritzAction | undefined;
      if (!nativeAction) {
        return undefined;
      }
      const action = this.snapshotActions(snapshotArg).find((actionArg) => actionArg.entityId === targetEntity.id || actionArg.action === nativeAction && this.actionMatchesEntity(actionArg, targetEntity));
      return action ? this.command(snapshotArg, requestArg, action, targetEntity) : undefined;
    }

    if ((requestArg.domain === 'switch' && (requestArg.service === 'turn_on' || requestArg.service === 'turn_off')) || requestArg.service === 'set_state') {
      const enabled = requestArg.service === 'turn_on' || requestArg.data?.enabled === true || requestArg.data?.state === true;
      const action = this.switchActionForEntity(snapshotArg, targetEntity);
      return action ? this.command(snapshotArg, requestArg, action, targetEntity, { enabled }) : undefined;
    }

    if (requestArg.domain === 'update' && requestArg.service === 'install') {
      const action = this.snapshotActions(snapshotArg).find((actionArg) => actionArg.target === 'router' && actionArg.action === 'firmware_update');
      return action ? this.command(snapshotArg, requestArg, action, targetEntity) : undefined;
    }

    return undefined;
  }

  public static toIntegrationEvent(eventArg: IFritzEvent): IIntegrationEvent {
    return {
      type: eventArg.type === 'command_failed' || eventArg.type === 'error' ? 'error' : 'state_changed',
      integrationDomain: fritzDomain,
      deviceId: eventArg.deviceId,
      entityId: eventArg.entityId,
      data: eventArg,
      timestamp: eventArg.timestamp || Date.now(),
    };
  }

  public static normalizeMac(valueArg?: string): string | undefined {
    if (!valueArg) {
      return undefined;
    }
    const compact = valueArg.replace(/[^0-9a-f]/gi, '').toLowerCase();
    return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
  }

  public static defaultPort(sslArg = fritzDefaultSsl): number {
    return sslArg ? fritzDefaultHttpsPort : fritzDefaultHttpPort;
  }

  public static protocol(sslArg = fritzDefaultSsl): TFritzProtocol {
    return sslArg ? 'https' : 'http';
  }

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

  public static uptimeValue(valueArg: unknown): unknown {
    if (valueArg instanceof Date) {
      return valueArg.toISOString();
    }
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return new Date(Date.now() - valueArg * 1000).toISOString();
    }
    return valueArg;
  }

  private static routerInfo(configArg: IFritzConfig, sourceArg: IFritzSnapshot | undefined, manualSnapshotsArg: IFritzSnapshot[], manualRouterArg?: IFritzRouterInfo): IFritzRouterInfo {
    const manualRouter = manualRouterArg || manualSnapshotsArg.find((snapshotArg) => snapshotArg.router)?.router;
    const router = {
      ...sourceArg?.router,
      ...manualRouter,
      ...configArg.router,
    };
    const ssl = configArg.ssl ?? router.ssl ?? sourceArg?.router.ssl ?? fritzDefaultSsl;
    const host = configArg.host || router.host || sourceArg?.router.host;
    const port = configArg.port || router.port || (host ? this.defaultPort(ssl) : undefined);
    const serialMac = this.normalizeMac(configArg.uniqueId || router.serialNumber || sourceArg?.router.serialNumber);
    const mac = this.normalizeMac(router.macAddress || serialMac);
    const protocol = this.protocol(ssl);
    return {
      ...router,
      id: router.id || configArg.uniqueId || router.serialNumber || mac || (host ? `${host}:${port || this.defaultPort(ssl)}` : undefined) || configArg.name || 'fritz',
      host,
      port,
      ssl,
      name: configArg.name || router.name || configArg.model || router.model || host || 'FRITZ!Box',
      model: configArg.model || router.model || 'FRITZ!Box',
      macAddress: mac || router.macAddress,
      serialNumber: router.serialNumber || configArg.uniqueId,
      manufacturer: router.manufacturer || manufacturer,
      meshRole: this.meshRole(router.meshRole) || 'none',
      isRouter: router.isRouter ?? router.wanEnabled ?? true,
      wanEnabled: router.wanEnabled ?? router.isRouter ?? true,
      ipv6Active: router.ipv6Active ?? false,
      configurationUrl: router.configurationUrl || (host ? `${protocol}://${host}` : undefined),
    };
  }

  private static mergeManualEntries(entriesArg: IFritzManualEntry[]): {
    router?: IFritzRouterInfo;
    devices: IFritzClientDevice[];
    interfaces: IFritzInterfaceStats[];
    connection?: IFritzConnectionInfo;
    sensors?: IFritzSensorMap;
    wifiNetworks: IFritzWifiNetwork[];
    portForwards: IFritzPortForward[];
    callDeflections: IFritzCallDeflection[];
    update?: IFritzUpdateInfo;
    actions: IFritzActionDescriptor[];
    hasData: boolean;
  } {
    const devices: IFritzClientDevice[] = [];
    const interfaces: IFritzInterfaceStats[] = [];
    const wifiNetworks: IFritzWifiNetwork[] = [];
    const portForwards: IFritzPortForward[] = [];
    const callDeflections: IFritzCallDeflection[] = [];
    const actions: IFritzActionDescriptor[] = [];
    const sensors: IFritzSensorMap = {};
    let router: IFritzRouterInfo | undefined;
    let connection: IFritzConnectionInfo | undefined;
    let update: IFritzUpdateInfo | undefined;
    let hasData = false;
    for (const entry of entriesArg) {
      if (entry.router) {
        router = { ...router, ...entry.router };
        hasData = true;
      } else if (!router && (entry.host || entry.name || entry.model || entry.macAddress || entry.serialNumber)) {
        router = {
          id: entry.id || entry.serialNumber || entry.macAddress || entry.host,
          host: entry.host,
          port: entry.port,
          ssl: entry.ssl,
          name: entry.name,
          model: entry.model,
          serialNumber: entry.serialNumber,
          macAddress: entry.macAddress,
          manufacturer: entry.manufacturer,
        };
        hasData = true;
      }
      if (entry.connection) {
        connection = { ...connection, ...entry.connection };
        hasData = true;
      }
      if (entry.update) {
        update = { ...update, ...entry.update };
        hasData = true;
      }
      devices.push(...(entry.devices || []), ...(entry.clients || []));
      interfaces.push(...(entry.interfaces || []));
      wifiNetworks.push(...(entry.wifiNetworks || []));
      portForwards.push(...(entry.portForwards || []));
      callDeflections.push(...(entry.callDeflections || []));
      Object.assign(sensors, entry.sensors || {});
      actions.push(...(entry.actions || []));
      hasData = hasData || Boolean(entry.devices?.length || entry.clients?.length || entry.interfaces?.length || entry.sensors || entry.wifiNetworks?.length || entry.portForwards?.length || entry.callDeflections?.length || entry.actions?.length);
    }
    return { router, devices, interfaces, connection, sensors: Object.keys(sensors).length ? sensors : undefined, wifiNetworks, portForwards, callDeflections, update, actions, hasData };
  }

  private static connectionInfo(sourcesArg: Array<IFritzConnectionInfo | undefined>, routerArg: IFritzRouterInfo): IFritzConnectionInfo {
    const connection: IFritzConnectionInfo = {};
    for (const source of sourcesArg) {
      Object.assign(connection, source || {});
    }
    connection.connection ||= routerArg.connectionType;
    connection.wanEnabled ??= routerArg.wanEnabled ?? routerArg.isRouter;
    connection.ipv6Active ??= routerArg.ipv6Active;
    return this.cleanAttributes(connection) as IFritzConnectionInfo;
  }

  private static sensorMap(sourcesArg: Array<IFritzSensorMap | undefined>, connectionArg: IFritzConnectionInfo, devicesArg: IFritzClientDevice[], interfacesArg: IFritzInterfaceStats[]): IFritzSensorMap {
    const sensors: IFritzSensorMap = {};
    for (const source of sourcesArg) {
      Object.assign(sensors, source || {});
    }
    sensors.is_connected ??= this.booleanValue(connectionArg.isConnected);
    sensors.is_linked ??= this.booleanValue(connectionArg.isLinked);
    sensors.external_ip ??= this.stringValue(connectionArg.externalIp);
    sensors.external_ipv6 ??= this.stringValue(connectionArg.externalIPv6 || connectionArg.externalIpv6);
    sensors.connection_uptime ??= connectionArg.connectionUptime;
    sensors.device_uptime ??= connectionArg.deviceUptime;
    sensors.kb_s_sent ??= this.numberValue(connectionArg.kbSent) ?? this.dividePair(connectionArg.transmissionRate, 0, 1000);
    sensors.kb_s_received ??= this.numberValue(connectionArg.kbReceived) ?? this.dividePair(connectionArg.transmissionRate, 1, 1000);
    sensors.max_kb_s_sent ??= this.numberValue(connectionArg.maxKbSent) ?? this.dividePair(connectionArg.maxBitRate, 0, 1000);
    sensors.max_kb_s_received ??= this.numberValue(connectionArg.maxKbReceived) ?? this.dividePair(connectionArg.maxBitRate, 1, 1000);
    sensors.link_kb_s_sent ??= this.numberValue(connectionArg.linkKbSent) ?? this.dividePair(connectionArg.maxLinkedBitRate, 0, 1000);
    sensors.link_kb_s_received ??= this.numberValue(connectionArg.linkKbReceived) ?? this.dividePair(connectionArg.maxLinkedBitRate, 1, 1000);
    sensors.link_noise_margin_sent ??= this.dividePair(connectionArg.noiseMargin, 0, 10);
    sensors.link_noise_margin_received ??= this.dividePair(connectionArg.noiseMargin, 1, 10);
    sensors.link_attenuation_sent ??= this.dividePair(connectionArg.attenuation, 0, 10);
    sensors.link_attenuation_received ??= this.dividePair(connectionArg.attenuation, 1, 10);
    sensors.gb_sent ??= this.numberValue(connectionArg.gbSent) ?? this.bytesToGigabytes(connectionArg.bytesSent);
    sensors.gb_received ??= this.numberValue(connectionArg.gbReceived) ?? this.bytesToGigabytes(connectionArg.bytesReceived);
    sensors.cpu_temperature ??= this.numberValue(connectionArg.cpuTemperature);
    if (interfacesArg.length) {
      sensors.gb_received ??= this.bytesToGigabytes(this.sumInterfaces(interfacesArg, 'rxBytes', 'downloadBytes'));
      sensors.gb_sent ??= this.bytesToGigabytes(this.sumInterfaces(interfacesArg, 'txBytes', 'uploadBytes'));
      sensors.kb_s_received ??= this.sumInterfaces(interfacesArg, 'rxRateKbps', 'downloadRateKbps');
      sensors.kb_s_sent ??= this.sumInterfaces(interfacesArg, 'txRateKbps', 'uploadRateKbps');
    }
    if (sensors.is_connected === undefined && devicesArg.length) {
      sensors.is_connected = true;
    }
    return this.cleanAttributes(sensors) as IFritzSensorMap;
  }

  private static updateInfo(...sourcesArg: Array<IFritzUpdateInfo | IFritzRouterInfo | undefined>): IFritzUpdateInfo | undefined {
    const update: IFritzUpdateInfo = {};
    for (const source of sourcesArg) {
      if (!source) {
        continue;
      }
      const router = source as IFritzRouterInfo;
      const updateSource = source as IFritzUpdateInfo;
      update.installedVersion = updateSource.installedVersion || router.currentFirmware || router.firmware || update.installedVersion;
      update.latestVersion = updateSource.latestVersion || router.latestFirmware || update.latestVersion;
      update.releaseUrl = updateSource.releaseUrl || router.releaseUrl || update.releaseUrl;
      update.updateAvailable = updateSource.updateAvailable ?? router.updateAvailable ?? update.updateAvailable;
      update.metadata = { ...update.metadata, ...updateSource.metadata };
    }
    const cleaned = this.cleanAttributes(update as Record<string, unknown>) as IFritzUpdateInfo;
    return Object.keys(cleaned).length ? cleaned : undefined;
  }

  private static routerDevice(snapshotArg: IFritzSnapshot, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'connectivity', value: snapshotArg.connected ? 'online' : 'offline', updatedAt: updatedAtArg },
    ];
    this.addFeatureState(features, state, 'external_ip', 'External IP', snapshotArg.sensors.external_ip, updatedAtArg);
    this.addFeatureState(features, state, 'download_throughput', 'Download throughput', snapshotArg.sensors.kb_s_received, updatedAtArg, 'kB/s');
    this.addFeatureState(features, state, 'upload_throughput', 'Upload throughput', snapshotArg.sensors.kb_s_sent, updatedAtArg, 'kB/s');
    this.addFeatureState(features, state, 'download_total', 'GB received', snapshotArg.sensors.gb_received, updatedAtArg, 'GB');
    this.addFeatureState(features, state, 'upload_total', 'GB sent', snapshotArg.sensors.gb_sent, updatedAtArg, 'GB');
    this.addFeatureState(features, state, 'cpu_temperature', 'CPU temperature', snapshotArg.sensors.cpu_temperature, updatedAtArg, 'C');
    if (this.hasUpdate(snapshotArg)) {
      features.push({ id: 'firmware_update', capability: 'switch', name: 'Firmware update available', readable: true, writable: snapshotArg.router.actions?.includes('firmware_update') || false });
      state.push({ featureId: 'firmware_update', value: snapshotArg.update?.updateAvailable === true, updatedAt: updatedAtArg });
    }
    for (const wifi of snapshotArg.wifiNetworks) {
      const key = `wifi_${this.slug(this.wifiId(wifi))}`;
      features.push({ id: key, capability: 'switch', name: `Wi-Fi ${this.wifiName(wifi)}`, readable: true, writable: true });
      state.push({ featureId: key, value: wifi.enabled !== false, updatedAt: updatedAtArg });
    }

    return {
      id: this.routerDeviceId(snapshotArg),
      integrationDomain: fritzDomain,
      name: this.routerName(snapshotArg),
      protocol: 'http',
      manufacturer: snapshotArg.router.manufacturer || manufacturer,
      model: snapshotArg.router.model || 'FRITZ!Box',
      online: snapshotArg.connected,
      features,
      state,
      metadata: this.cleanAttributes({
        host: snapshotArg.router.host,
        port: snapshotArg.router.port,
        ssl: snapshotArg.router.ssl,
        macAddress: snapshotArg.router.macAddress,
        serialNumber: snapshotArg.router.serialNumber,
        firmware: snapshotArg.router.firmware || snapshotArg.router.currentFirmware,
        latestFirmware: snapshotArg.router.latestFirmware || snapshotArg.update?.latestVersion,
        configurationUrl: snapshotArg.router.configurationUrl,
        meshRole: snapshotArg.router.meshRole,
        connectionType: snapshotArg.router.connectionType || snapshotArg.connection.connection,
        source: snapshotArg.source,
        liveTr064Implemented: false,
      }),
    };
  }

  private static clientDevice(clientArg: IFritzClientDevice, snapshotArg: IFritzSnapshot, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'presence', capability: 'sensor', name: 'Presence', readable: true, writable: false },
      { id: 'ip_address', capability: 'sensor', name: 'IP Address', readable: true, writable: false },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'presence', value: clientArg.connected !== false, updatedAt: updatedAtArg },
      { featureId: 'ip_address', value: clientArg.ipAddress || clientArg.ip || null, updatedAt: updatedAtArg },
    ];
    if (clientArg.wanAccess !== undefined && clientArg.wanAccess !== null) {
      features.push({ id: 'internet_access', capability: 'switch', name: 'Internet access', readable: true, writable: true });
      state.push({ featureId: 'internet_access', value: clientArg.wanAccess, updatedAt: updatedAtArg });
    }
    if (this.clientHasAction(clientArg, 'wake_on_lan')) {
      features.push({ id: 'wake_on_lan', capability: 'switch', name: 'Wake on LAN', readable: false, writable: true });
      state.push({ featureId: 'wake_on_lan', value: false, updatedAt: updatedAtArg });
    }

    return {
      id: this.clientDeviceId(clientArg),
      integrationDomain: fritzDomain,
      name: this.clientName(clientArg),
      protocol: 'unknown',
      manufacturer: clientArg.manufacturer || manufacturer,
      model: clientArg.model || 'FRITZ!Box tracked device',
      online: clientArg.connected !== false && snapshotArg.connected,
      features,
      state,
      metadata: this.cleanAttributes({
        mac: this.clientMac(clientArg),
        ipAddress: clientArg.ipAddress || clientArg.ip,
        hostname: clientArg.hostname || clientArg.name,
        connectedTo: clientArg.connectedTo,
        connectionType: clientArg.connectionType,
        ssid: clientArg.ssid,
        lastActivity: this.dateString(clientArg.lastActivity),
      }),
    };
  }

  private static pushInterfaceEntities(entitiesArg: IIntegrationEntity[], snapshotArg: IFritzSnapshot, ifaceArg: IFritzInterfaceStats, usedIdsArg: Map<string, number>): void {
    const deviceId = this.routerDeviceId(snapshotArg);
    const ifaceKey = this.slug(ifaceArg.id || ifaceArg.name);
    const ifaceName = ifaceArg.label || ifaceArg.name;
    const values: Array<[string, string, unknown, string | undefined, Record<string, unknown>]> = [
      ['rx_bytes', 'Download', this.bytesToGigabytes(ifaceArg.rxBytes ?? ifaceArg.downloadBytes), 'GB', { deviceClass: 'data_size', stateClass: 'total_increasing' }],
      ['tx_bytes', 'Upload', this.bytesToGigabytes(ifaceArg.txBytes ?? ifaceArg.uploadBytes), 'GB', { deviceClass: 'data_size', stateClass: 'total_increasing' }],
      ['rx_rate', 'Download throughput', ifaceArg.rxRateKbps ?? ifaceArg.downloadRateKbps, 'kB/s', { deviceClass: 'data_rate', stateClass: 'measurement' }],
      ['tx_rate', 'Upload throughput', ifaceArg.txRateKbps ?? ifaceArg.uploadRateKbps, 'kB/s', { deviceClass: 'data_rate', stateClass: 'measurement' }],
    ];
    for (const [key, name, value, unit, attrs] of values) {
      if (value === undefined) {
        continue;
      }
      entitiesArg.push(this.entity('sensor', `${this.routerName(snapshotArg)} ${ifaceName} ${name}`, deviceId, `${this.uniqueBase(snapshotArg)}_interface_${ifaceKey}_${key}`, value, usedIdsArg, {
        ...attrs,
        unit,
        interface: ifaceArg.name,
        interfaceType: ifaceArg.type,
        ssid: ifaceArg.ssid,
      }, snapshotArg.connected && ifaceArg.connected !== false));
    }
    if (ifaceArg.connected !== undefined) {
      entitiesArg.push(this.entity('binary_sensor', `${this.routerName(snapshotArg)} ${ifaceName} Link`, deviceId, `${this.uniqueBase(snapshotArg)}_interface_${ifaceKey}_link`, ifaceArg.connected ? 'on' : 'off', usedIdsArg, {
        deviceClass: 'connectivity',
        interface: ifaceArg.name,
        interfaceType: ifaceArg.type,
      }, snapshotArg.connected));
    }
  }

  private static pushClientEntities(entitiesArg: IIntegrationEntity[], snapshotArg: IFritzSnapshot, clientArg: IFritzClientDevice, usedIdsArg: Map<string, number>): void {
    const mac = this.clientMac(clientArg);
    const deviceId = this.clientDeviceId(clientArg);
    entitiesArg.push(this.entity('binary_sensor', `${this.clientName(clientArg)} Connected`, deviceId, `${this.slug(mac || this.clientName(clientArg))}_connected`, clientArg.connected !== false ? 'on' : 'off', usedIdsArg, {
      deviceClass: 'connectivity',
      nativePlatform: 'device_tracker',
      mac,
      ipAddress: clientArg.ipAddress || clientArg.ip,
      hostname: clientArg.hostname || clientArg.name,
      connectedTo: clientArg.connectedTo,
      connectionType: clientArg.connectionType,
      ssid: clientArg.ssid,
      lastActivity: this.dateString(clientArg.lastActivity),
    }, snapshotArg.connected));
    if (clientArg.wanAccess !== undefined && clientArg.wanAccess !== null) {
      entitiesArg.push(this.entity('switch', `${this.clientName(clientArg)} Internet access`, deviceId, `${this.slug(mac || this.clientName(clientArg))}_internet_access`, clientArg.wanAccess ? 'on' : 'off', usedIdsArg, {
        nativeType: 'client_wan_access',
        nativeAction: 'set_wan_access',
        mac,
        ipAddress: clientArg.ipAddress || clientArg.ip,
        writable: true,
      }, snapshotArg.connected && Boolean(clientArg.ipAddress || clientArg.ip)));
    }
  }

  private static pushWifiEntities(entitiesArg: IIntegrationEntity[], snapshotArg: IFritzSnapshot, wifiArg: IFritzWifiNetwork, usedIdsArg: Map<string, number>): void {
    const name = `${this.routerName(snapshotArg)} Wi-Fi ${this.wifiName(wifiArg)}`;
    entitiesArg.push(this.entity('switch', name, this.routerDeviceId(snapshotArg), `${this.uniqueBase(snapshotArg)}_wi_fi_${this.slug(this.wifiId(wifiArg))}`, wifiArg.enabled === false ? 'off' : 'on', usedIdsArg, {
      nativeType: 'wifi_network',
      nativeAction: 'set_wifi_enabled',
      networkId: this.wifiId(wifiArg),
      networkIndex: wifiArg.index,
      ssid: wifiArg.ssid,
      standard: wifiArg.standard,
      bssid: wifiArg.bssid,
      macAddressControl: wifiArg.macAddressControl,
      guest: wifiArg.guest,
      band: wifiArg.band,
      entityCategory: 'config',
      writable: true,
    }, snapshotArg.connected));
  }

  private static pushPortForwardEntities(entitiesArg: IIntegrationEntity[], snapshotArg: IFritzSnapshot, portArg: IFritzPortForward, usedIdsArg: Map<string, number>): void {
    const name = `${this.routerName(snapshotArg)} Port forward ${this.portForwardName(portArg)}`;
    entitiesArg.push(this.entity('switch', name, this.routerDeviceId(snapshotArg), `${this.uniqueBase(snapshotArg)}_port_forward_${this.slug(this.portForwardId(portArg))}`, portArg.enabled ? 'on' : 'off', usedIdsArg, {
      nativeType: 'port_forward',
      nativeAction: 'set_port_forward_enabled',
      portForwardId: this.portForwardId(portArg),
      index: portArg.index,
      connectionType: portArg.connectionType,
      internalClient: portArg.internalClient,
      internalPort: portArg.internalPort,
      externalPort: portArg.externalPort,
      protocol: portArg.protocol,
      entityCategory: 'config',
      writable: true,
    }, snapshotArg.connected));
  }

  private static pushCallDeflectionEntities(entitiesArg: IIntegrationEntity[], snapshotArg: IFritzSnapshot, deflectionArg: IFritzCallDeflection, usedIdsArg: Map<string, number>): void {
    const name = `${this.routerName(snapshotArg)} Call deflection ${deflectionArg.id}`;
    entitiesArg.push(this.entity('switch', name, this.routerDeviceId(snapshotArg), `${this.uniqueBase(snapshotArg)}_call_deflection_${this.slug(String(deflectionArg.id))}`, deflectionArg.enabled ? 'on' : 'off', usedIdsArg, {
      nativeType: 'call_deflection',
      nativeAction: 'set_call_deflection_enabled',
      deflectionId: deflectionArg.id,
      type: deflectionArg.type,
      number: deflectionArg.number,
      deflectionToNumber: deflectionArg.deflectionToNumber,
      mode: deflectionArg.mode,
      outgoing: deflectionArg.outgoing,
      phonebookId: deflectionArg.phonebookId,
      entityCategory: 'config',
      writable: true,
    }, snapshotArg.connected));
  }

  private static updateEntity(snapshotArg: IFritzSnapshot, usedIdsArg: Map<string, number>): IIntegrationEntity {
    const update = snapshotArg.update || {};
    const installedVersion = update.installedVersion || snapshotArg.router.currentFirmware || snapshotArg.router.firmware;
    const latestVersion = update.updateAvailable ? update.latestVersion || snapshotArg.router.latestFirmware : installedVersion;
    return this.entity('update', `${this.routerName(snapshotArg)} FRITZ!OS`, this.routerDeviceId(snapshotArg), `${this.uniqueBase(snapshotArg)}_fritz_os_update`, update.updateAvailable ? 'on' : 'off', usedIdsArg, {
      nativeType: 'firmware_update',
      nativeAction: 'firmware_update',
      installedVersion,
      latestVersion,
      releaseUrl: update.releaseUrl || snapshotArg.router.releaseUrl,
      entityCategory: 'config',
      writable: this.snapshotActions(snapshotArg).some((actionArg) => actionArg.target === 'router' && actionArg.action === 'firmware_update'),
    }, snapshotArg.connected);
  }

  private static actionButton(snapshotArg: IFritzSnapshot, actionArg: IFritzActionDescriptor, usedIdsArg: Map<string, number>): IIntegrationEntity | undefined {
    if (actionArg.target === 'router' && this.isRouterAction(actionArg.action)) {
      return this.entity('button', `${this.routerName(snapshotArg)} ${this.title(actionArg.action)}`, this.routerDeviceId(snapshotArg), `${this.uniqueBase(snapshotArg)}_${this.slug(actionArg.action)}`, 'available', usedIdsArg, {
        nativeType: 'router_action',
        nativeAction: actionArg.action,
        actionTarget: 'router',
        entityCategory: 'config',
        writable: true,
      }, snapshotArg.connected, actionArg.entityId);
    }
    if (actionArg.target !== 'client' || actionArg.action !== 'wake_on_lan' || !actionArg.mac) {
      return undefined;
    }
    const client = snapshotArg.devices.find((clientArg) => this.clientMac(clientArg) === this.normalizeMac(actionArg.mac));
    if (!client) {
      return undefined;
    }
    return this.entity('button', `${this.clientName(client)} Wake on LAN`, this.clientDeviceId(client), `${this.slug(actionArg.mac)}_wake_on_lan`, 'available', usedIdsArg, {
      nativeType: 'client_action',
      nativeAction: 'wake_on_lan',
      actionTarget: 'client',
      mac: this.clientMac(client),
      entityCategory: 'config',
      enabledByDefault: false,
      writable: true,
    }, snapshotArg.connected, actionArg.entityId);
  }

  private static command(snapshotArg: IFritzSnapshot, requestArg: IServiceCallRequest, actionArg: IFritzActionDescriptor, entityArg?: IIntegrationEntity, payloadArg: Record<string, unknown> = {}): IFritzCommand {
    return {
      type: actionArg.target === 'router' ? 'router.action' : actionArg.target === 'client' ? 'client.action' : actionArg.target === 'service' ? 'service.action' : 'switch.set',
      service: requestArg.service,
      action: actionArg.action,
      target: requestArg.target,
      routerId: this.routerDeviceId(snapshotArg),
      mac: actionArg.mac ? this.normalizeMac(actionArg.mac) : this.normalizeMac(this.stringValue(entityArg?.attributes?.mac)),
      entityId: entityArg?.id || actionArg.entityId || requestArg.target.entityId,
      deviceId: entityArg?.deviceId || actionArg.deviceId || requestArg.target.deviceId,
      payload: this.cleanAttributes({ ...(requestArg.data || {}), ...payloadArg, actionId: actionArg.id, actionMetadata: actionArg.metadata }),
    };
  }

  private static switchActionForEntity(snapshotArg: IFritzSnapshot, entityArg: IIntegrationEntity): IFritzActionDescriptor | undefined {
    const nativeType = this.stringValue(entityArg.attributes?.nativeType);
    const actions = this.snapshotActions(snapshotArg);
    if (nativeType === 'wifi_network') {
      const id = this.stringValue(entityArg.attributes?.networkId);
      return actions.find((actionArg) => actionArg.target === 'wifi' && actionArg.action === 'set_wifi_enabled' && String(actionArg.id) === String(id));
    }
    if (nativeType === 'port_forward') {
      const id = this.stringValue(entityArg.attributes?.portForwardId);
      return actions.find((actionArg) => actionArg.target === 'port_forward' && actionArg.action === 'set_port_forward_enabled' && String(actionArg.id) === String(id));
    }
    if (nativeType === 'call_deflection') {
      const id = this.stringValue(entityArg.attributes?.deflectionId);
      return actions.find((actionArg) => actionArg.target === 'call_deflection' && actionArg.action === 'set_call_deflection_enabled' && String(actionArg.id) === String(id));
    }
    if (nativeType === 'client_wan_access') {
      const mac = this.normalizeMac(this.stringValue(entityArg.attributes?.mac));
      return actions.find((actionArg) => actionArg.target === 'client' && actionArg.action === 'set_wan_access' && (!actionArg.mac || this.normalizeMac(actionArg.mac) === mac));
    }
    return undefined;
  }

  private static actionMatchesEntity(actionArg: IFritzActionDescriptor, entityArg: IIntegrationEntity): boolean {
    if (actionArg.target === 'router') {
      return entityArg.attributes?.actionTarget === 'router';
    }
    if (actionArg.target === 'client') {
      return this.normalizeMac(actionArg.mac) === this.normalizeMac(this.stringValue(entityArg.attributes?.mac));
    }
    return false;
  }

  private static findTargetEntity(snapshotArg: IFritzSnapshot, requestArg: IServiceCallRequest): IIntegrationEntity | undefined {
    const targetEntityId = requestArg.target.entityId;
    if (!targetEntityId) {
      return undefined;
    }
    return this.toEntities(snapshotArg).find((entityArg) => entityArg.id === targetEntityId || entityArg.uniqueId === targetEntityId);
  }

  private static routerActionFromService(serviceArg: string): TFritzRouterAction | undefined {
    if (serviceArg === 'reboot' || serviceArg === 'reconnect' || serviceArg === 'firmware_update' || serviceArg === 'cleanup') {
      return serviceArg;
    }
    return undefined;
  }

  private static actionsFromRouter(routerArg: IFritzRouterInfo): IFritzActionDescriptor[] {
    return (routerArg.actions || []).map((actionArg) => ({ target: 'router', action: actionArg }));
  }

  private static actionsFromClients(devicesArg: IFritzClientDevice[]): IFritzActionDescriptor[] {
    const actions: IFritzActionDescriptor[] = [];
    for (const device of devicesArg) {
      const mac = this.clientMac(device);
      if (!mac) {
        continue;
      }
      for (const action of device.actions || []) {
        actions.push({ target: 'client', action, mac });
      }
      if (device.connectionType?.toLowerCase() === 'lan') {
        actions.push({ target: 'client', action: 'wake_on_lan', mac });
      }
      if (device.wanAccess !== undefined && device.wanAccess !== null) {
        actions.push({ target: 'client', action: 'set_wan_access', mac });
      }
    }
    return actions;
  }

  private static actionsFromRepresentedControls(wifiArg: IFritzWifiNetwork[], portArg: IFritzPortForward[], deflectionsArg: IFritzCallDeflection[]): IFritzActionDescriptor[] {
    return [
      ...wifiArg.map((wifiEntryArg) => ({ target: 'wifi' as const, action: 'set_wifi_enabled' as const, id: this.wifiId(wifiEntryArg) })),
      ...portArg.map((portEntryArg) => ({ target: 'port_forward' as const, action: 'set_port_forward_enabled' as const, id: this.portForwardId(portEntryArg) })),
      ...deflectionsArg.map((deflectionArg) => ({ target: 'call_deflection' as const, action: 'set_call_deflection_enabled' as const, id: deflectionArg.id })),
    ];
  }

  private static snapshotActions(snapshotArg: IFritzSnapshot): IFritzActionDescriptor[] {
    return this.uniqueActions([
      ...(snapshotArg.actions || []),
      ...this.actionsFromRouter(snapshotArg.router),
      ...this.actionsFromClients(snapshotArg.devices),
      ...this.actionsFromRepresentedControls(snapshotArg.wifiNetworks, snapshotArg.portForwards, snapshotArg.callDeflections),
    ]);
  }

  private static uniqueClients(devicesArg: IFritzClientDevice[]): IFritzClientDevice[] {
    const seen = new Map<string, IFritzClientDevice>();
    for (const device of devicesArg) {
      const key = this.clientMac(device) || device.id || device.ipAddress || device.ip || device.name || device.hostname;
      if (!key) {
        continue;
      }
      seen.set(key, { ...seen.get(key), ...device, mac: this.clientMac(device) || device.mac });
    }
    return [...seen.values()];
  }

  private static uniqueInterfaces(interfacesArg: IFritzInterfaceStats[]): IFritzInterfaceStats[] {
    const seen = new Map<string, IFritzInterfaceStats>();
    for (const iface of interfacesArg) {
      const key = iface.id || iface.name;
      if (!key) {
        continue;
      }
      seen.set(key, { ...seen.get(key), ...iface });
    }
    return [...seen.values()];
  }

  private static uniqueWifiNetworks(networksArg: IFritzWifiNetwork[]): IFritzWifiNetwork[] {
    const seen = new Map<string, IFritzWifiNetwork>();
    for (const network of networksArg) {
      const key = this.wifiId(network);
      seen.set(key, { ...seen.get(key), ...network });
    }
    return [...seen.values()];
  }

  private static uniquePortForwards(portForwardsArg: IFritzPortForward[]): IFritzPortForward[] {
    const seen = new Map<string, IFritzPortForward>();
    for (const portForward of portForwardsArg) {
      const key = this.portForwardId(portForward);
      seen.set(key, { ...seen.get(key), ...portForward });
    }
    return [...seen.values()];
  }

  private static uniqueCallDeflections(deflectionsArg: IFritzCallDeflection[]): IFritzCallDeflection[] {
    const seen = new Map<string, IFritzCallDeflection>();
    for (const deflection of deflectionsArg) {
      seen.set(String(deflection.id), { ...seen.get(String(deflection.id)), ...deflection });
    }
    return [...seen.values()];
  }

  private static uniqueActions(actionsArg: IFritzActionDescriptor[]): IFritzActionDescriptor[] {
    const seen = new Map<string, IFritzActionDescriptor>();
    for (const action of actionsArg) {
      const mac = this.normalizeMac(action.mac);
      const key = [action.target, action.action, mac || action.id || action.entityId || action.deviceId || 'router'].join(':');
      seen.set(key, { ...action, mac });
    }
    return [...seen.values()];
  }

  private static routerDeviceId(snapshotArg: IFritzSnapshot): string {
    return `${fritzDomain}.router.${this.uniqueBase(snapshotArg)}`;
  }

  private static clientDeviceId(clientArg: IFritzClientDevice): string {
    return `${fritzDomain}.client.${this.slug(this.clientMac(clientArg) || clientArg.id || clientArg.ipAddress || clientArg.ip || this.clientName(clientArg))}`;
  }

  private static routerName(snapshotArg: IFritzSnapshot): string {
    return snapshotArg.router.name || snapshotArg.router.host || 'FRITZ!Box';
  }

  private static clientName(clientArg: IFritzClientDevice): string {
    return clientArg.name || clientArg.hostname || clientArg.macAddress || clientArg.mac || clientArg.ipAddress || clientArg.ip || 'Unknown device';
  }

  private static clientMac(clientArg: IFritzClientDevice): string | undefined {
    return this.normalizeMac(clientArg.macAddress || clientArg.mac);
  }

  private static uniqueBase(snapshotArg: IFritzSnapshot): string {
    return this.slug(snapshotArg.router.macAddress || snapshotArg.router.serialNumber || snapshotArg.router.id || snapshotArg.router.host || this.routerName(snapshotArg));
  }

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

  private static wifiId(wifiArg: IFritzWifiNetwork): string {
    return String(wifiArg.id || wifiArg.index || wifiArg.switchName || wifiArg.name || wifiArg.ssid || 'wifi');
  }

  private static wifiName(wifiArg: IFritzWifiNetwork): string {
    return wifiArg.switchName || wifiArg.name || wifiArg.ssid || (wifiArg.index ? `Network ${wifiArg.index}` : 'Network');
  }

  private static portForwardId(portArg: IFritzPortForward): string {
    return String(portArg.id || portArg.index || [portArg.connectionType, portArg.internalClient, portArg.externalPort, portArg.protocol, portArg.description].filter(Boolean).join('_') || 'port_forward');
  }

  private static portForwardName(portArg: IFritzPortForward): string {
    return portArg.description || [portArg.externalPort, portArg.protocol].filter(Boolean).join('/') || this.portForwardId(portArg);
  }

  private static hasUpdate(snapshotArg: IFritzSnapshot): boolean {
    return Boolean(snapshotArg.update || snapshotArg.router.currentFirmware || snapshotArg.router.firmware || snapshotArg.router.latestFirmware || snapshotArg.router.updateAvailable !== undefined);
  }

  private static clientHasAction(clientArg: IFritzClientDevice, actionArg: TFritzClientAction): boolean {
    return Boolean(clientArg.actions?.includes(actionArg) || actionArg === 'wake_on_lan' && clientArg.connectionType?.toLowerCase() === 'lan');
  }

  private static isRouterAction(actionArg: TFritzAction): actionArg is TFritzRouterAction {
    return actionArg === 'firmware_update' || actionArg === 'reboot' || actionArg === 'reconnect' || actionArg === 'cleanup';
  }

  private static validGuestPasswordRequest(dataArg?: Record<string, unknown>): boolean {
    const password = this.stringValue(dataArg?.password);
    const length = this.numberValue(dataArg?.length);
    if (password && (password.length < 8 || password.length > 63)) {
      return false;
    }
    if (length !== undefined && (length < 8 || length > 63)) {
      return false;
    }
    return true;
  }

  private static sensorValue(valueArg: unknown, descriptorArg: TSensorDescriptor | undefined): unknown {
    if (valueArg === undefined || valueArg === null) {
      return valueArg;
    }
    return descriptorArg?.transform ? descriptorArg.transform(valueArg) : valueArg;
  }

  private static addFeatureState(featuresArg: plugins.shxInterfaces.data.IDeviceFeature[], stateArg: plugins.shxInterfaces.data.IDeviceState[], idArg: string, nameArg: string, valueArg: unknown, updatedAtArg: string, unitArg?: string): void {
    if (valueArg === undefined) {
      return;
    }
    featuresArg.push({ id: idArg, capability: 'sensor', name: nameArg, readable: true, writable: false, unit: unitArg });
    stateArg.push({ featureId: idArg, value: this.deviceStateValue(valueArg), updatedAt: updatedAtArg });
  }

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

  private static sumInterfaces(interfacesArg: IFritzInterfaceStats[], primaryKeyArg: keyof IFritzInterfaceStats, fallbackKeyArg: keyof IFritzInterfaceStats): number | undefined {
    let total = 0;
    let found = false;
    for (const iface of interfacesArg) {
      const value = this.numberValue(iface[primaryKeyArg]) ?? this.numberValue(iface[fallbackKeyArg]);
      if (value !== undefined) {
        total += value;
        found = true;
      }
    }
    return found ? total : undefined;
  }

  private static bytesToGigabytes(valueArg: number | undefined): number | undefined {
    return valueArg === undefined ? undefined : Math.round(valueArg / 100000000) / 10;
  }

  private static dividePair(valueArg: unknown, indexArg: 0 | 1, divisorArg: number): number | undefined {
    if (!Array.isArray(valueArg)) {
      return undefined;
    }
    const value = this.numberValue(valueArg[indexArg]);
    return value === undefined ? undefined : Math.round(value / divisorArg * 10) / 10;
  }

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

  private static 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;
  }

  private static booleanValue(valueArg: unknown): boolean | undefined {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'number') {
      return valueArg !== 0;
    }
    if (typeof valueArg === 'string') {
      const normalized = valueArg.toLowerCase();
      if (['true', '1', 'on', 'yes', 'connected', 'up'].includes(normalized)) {
        return true;
      }
      if (['false', '0', 'off', 'no', 'disconnected', 'down'].includes(normalized)) {
        return false;
      }
    }
    return undefined;
  }

  private static dateString(valueArg: IFritzClientDevice['lastActivity']): string | undefined {
    if (valueArg instanceof Date) {
      return valueArg.toISOString();
    }
    if (typeof valueArg === 'number') {
      return new Date(valueArg).toISOString();
    }
    return typeof valueArg === 'string' ? valueArg : undefined;
  }

  private static title(valueArg: string): string {
    return valueArg.replace(/_/g, ' ').replace(/\b\w/g, (matchArg) => matchArg.toUpperCase());
  }

  private static meshRole(valueArg: unknown): TFritzMeshRole | undefined {
    return valueArg === 'none' || valueArg === 'master' || valueArg === 'slave' ? valueArg : undefined;
  }

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