import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IIntegrationEvent, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type {
  ISynologySrmActionDescriptor,
  ISynologySrmClientDevice,
  ISynologySrmCommand,
  ISynologySrmConfig,
  ISynologySrmEvent,
  ISynologySrmInterfaceStats,
  ISynologySrmManualEntry,
  ISynologySrmNetworkInfo,
  ISynologySrmRouterInfo,
  ISynologySrmSensorMap,
  ISynologySrmSnapshot,
  ISynologySrmSwitchInfo,
  ISynologySrmSystemInfo,
  ISynologySrmTrafficInfo,
  TSynologySrmAction,
  TSynologySrmClientAction,
  TSynologySrmRouterAction,
} from './synology_srm.types.js';
import { synologySrmDefaultHttpPort, synologySrmDefaultHttpsPort, synologySrmDefaultSsl, synologySrmDomain } from './synology_srm.types.js';

type TSynologySrmSensorDescriptor = {
  key: keyof ISynologySrmSensorMap & string;
  name: string;
  unit?: string;
  deviceClass?: string;
  stateClass?: string;
  entityCategory?: string;
  enabledByDefault?: boolean;
};

const manufacturer = 'Synology';
const routerSensorDescriptors: TSynologySrmSensorDescriptor[] = [
  { key: 'connectedClients', name: 'Connected clients', unit: 'clients', stateClass: 'measurement' },
  { key: 'downloadRate', name: 'Download speed', unit: 'B/s', deviceClass: 'data_rate', stateClass: 'measurement' },
  { key: 'uploadRate', name: 'Upload speed', unit: 'B/s', deviceClass: 'data_rate', stateClass: 'measurement' },
  { key: 'downloadBytes', name: 'Download', unit: 'B', deviceClass: 'data_size', stateClass: 'total_increasing' },
  { key: 'uploadBytes', name: 'Upload', unit: 'B', deviceClass: 'data_size', stateClass: 'total_increasing' },
  { key: 'cpuLoad', name: 'CPU load', unit: '%', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: 'memoryUsage', name: 'Memory usage', unit: '%', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: 'temperature', name: 'Temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: 'uptimeSeconds', name: 'Uptime', unit: 's', deviceClass: 'duration', stateClass: 'total', entityCategory: 'diagnostic' },
];

export class SynologySrmMapper {
  public static toSnapshot(configArg: ISynologySrmConfig, connectedArg?: boolean, eventsArg: ISynologySrmEvent[] = []): ISynologySrmSnapshot {
    const source = configArg.snapshot;
    const sourceRecord = this.recordValue(source);
    const manualSnapshots = (configArg.manualEntries || [])
      .map((entryArg) => entryArg.snapshot)
      .filter((snapshotArg): snapshotArg is ISynologySrmSnapshot => Boolean(snapshotArg));
    const manualData = this.mergeManualEntries(configArg.manualEntries || []);
    const router = this.routerInfo(configArg, source, manualSnapshots, manualData.router);
    const system = this.systemInfo(configArg, source, manualSnapshots, manualData.system, router);
    const rawNetwork = this.networkInfo(configArg, source, manualSnapshots, manualData.network, router);
    const interfaces = this.uniqueInterfaces([
      ...(source?.interfaces || []),
      ...this.arrayValues(source?.network?.interfaces),
      ...manualSnapshots.flatMap((snapshotArg) => [...(snapshotArg.interfaces || []), ...this.arrayValues(snapshotArg.network?.interfaces)]),
      ...(configArg.interfaces || []),
      ...this.arrayValues(configArg.network?.interfaces),
      ...manualData.interfaces,
    ]);
    const network = { ...rawNetwork, interfaces };
    const clients = this.uniqueClients([
      ...(source?.clients || []),
      ...this.arrayValues(sourceRecord?.devices),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.clients || []),
      ...(configArg.clients || []),
      ...(configArg.devices || []),
      ...manualData.clients,
    ]);
    const traffic = this.trafficInfo([source?.traffic, configArg.traffic, manualData.traffic], interfaces, clients);
    const sensors = this.sensorMap([source?.sensors, configArg.sensors, manualData.sensors], traffic, system, clients);
    const switches = this.uniqueSwitches([
      ...this.switchValues(source?.switches),
      ...manualSnapshots.flatMap((snapshotArg) => this.switchValues(snapshotArg.switches)),
      ...this.switchValues(configArg.switches),
      ...manualData.switches,
      ...this.switchesFromClients(clients),
    ]);
    const actions = this.uniqueActions([
      ...(source?.actions || []),
      ...manualSnapshots.flatMap((snapshotArg) => snapshotArg.actions || []),
      ...(configArg.actions || []),
      ...manualData.actions,
      ...this.actionsFromRouter(router),
      ...this.actionsFromClients(clients),
      ...this.actionsFromSwitches(switches),
    ]);
    const hasManualData = Boolean(
      source
      || manualSnapshots.length
      || configArg.router
      || configArg.system
      || configArg.network
      || configArg.interfaces?.length
      || configArg.clients?.length
      || configArg.devices?.length
      || configArg.traffic
      || configArg.sensors
      || configArg.switches
      || configArg.actions?.length
      || manualData.hasData
    );

    return {
      connected: connectedArg ?? configArg.connected ?? configArg.online ?? source?.connected ?? hasManualData,
      source: source?.source || (source ? 'snapshot' : hasManualData ? 'manual' : 'runtime'),
      updatedAt: source?.updatedAt || new Date().toISOString(),
      router,
      system,
      network,
      interfaces,
      clients,
      traffic,
      sensors,
      switches,
      actions,
      events: [...(source?.events || []), ...(configArg.events || []), ...eventsArg],
      error: source?.error,
      metadata: this.cleanAttributes({
        ...source?.metadata,
        ...configArg.metadata,
        upstreamDeviceTrackerOnly: true,
        liveSrmApiImplemented: false,
        commandExecutorConfigured: Boolean(configArg.commandExecutor || configArg.nativeClient?.executeCommand),
      }),
    };
  }

  public static toDevices(snapshotArg: ISynologySrmSnapshot): 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.clients) {
      devices.push(this.clientDevice(client, snapshotArg, updatedAt));
    }
    return devices;
  }

  public static toEntities(snapshotArg: ISynologySrmSnapshot): 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));

    for (const descriptor of routerSensorDescriptors) {
      const value = snapshotArg.sensors[descriptor.key];
      if (value === undefined || value === null) {
        continue;
      }
      entities.push(this.entity('sensor', `${routerName} ${descriptor.name}`, routerDeviceId, `${uniqueBase}_${this.slug(descriptor.key)}`, value, usedIds, {
        nativeKey: descriptor.key,
        unit: descriptor.unit,
        unitOfMeasurement: 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.clients) {
      this.pushClientEntities(entities, snapshotArg, client, usedIds);
    }
    for (const switchArg of snapshotArg.switches) {
      this.pushSwitchEntity(entities, snapshotArg, switchArg, 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: ISynologySrmSnapshot, requestArg: IServiceCallRequest): ISynologySrmCommand | undefined {
    const actions = this.snapshotActions(snapshotArg);
    const targetEntity = this.findTargetEntity(snapshotArg, requestArg);

    if (requestArg.domain === synologySrmDomain && requestArg.service === 'reboot') {
      const action = actions.find((actionArg) => actionArg.target === 'router' && actionArg.action === 'reboot');
      return action ? this.command(snapshotArg, requestArg, action) : undefined;
    }

    if (requestArg.domain === synologySrmDomain && (requestArg.service === 'block_client' || requestArg.service === 'unblock_client' || requestArg.service === 'set_client_blocked')) {
      const mac = this.normalizeMac(this.stringValue(requestArg.data?.mac) || this.stringValue(requestArg.data?.macAddress) || this.stringValue(targetEntity?.attributes?.mac));
      const blocked = requestArg.service === 'block_client'
        || this.booleanValue(requestArg.data?.blocked) === true
        || this.booleanValue(requestArg.data?.enabled) === false;
      const action = actions.find((actionArg) => actionArg.action === 'set_client_blocked' && (!actionArg.mac || this.normalizeMac(actionArg.mac) === mac));
      return action && mac ? this.command(snapshotArg, requestArg, { ...action, mac }, targetEntity, { blocked, enabled: !blocked }) : undefined;
    }

    if (requestArg.domain === 'button' && requestArg.service === 'press' && targetEntity?.attributes?.nativeAction) {
      const nativeAction = this.stringValue(targetEntity.attributes.nativeAction) as TSynologySrmAction | undefined;
      const action = actions.find((actionArg) => 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') {
      if (!targetEntity) {
        return undefined;
      }
      const enabled = requestArg.service === 'turn_on' || this.booleanValue(requestArg.data?.enabled) === true || this.booleanValue(requestArg.data?.state) === true;
      const action = this.switchActionForEntity(snapshotArg, targetEntity);
      return action ? this.command(snapshotArg, requestArg, action, targetEntity, this.switchPayload(action, enabled)) : undefined;
    }

    return undefined;
  }

  public static toIntegrationEvent(eventArg: ISynologySrmEvent): IIntegrationEvent {
    return {
      type: eventArg.type === 'command_failed' || eventArg.type === 'refresh_failed' || eventArg.type === 'error' ? 'error' : 'state_changed',
      integrationDomain: synologySrmDomain,
      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 = synologySrmDefaultSsl): number {
    return sslArg === false ? synologySrmDefaultHttpPort : synologySrmDefaultHttpsPort;
  }

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

  private static routerInfo(configArg: ISynologySrmConfig, sourceArg: ISynologySrmSnapshot | undefined, manualSnapshotsArg: ISynologySrmSnapshot[], manualRouterArg?: ISynologySrmRouterInfo): ISynologySrmRouterInfo {
    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 ?? synologySrmDefaultSsl;
    const host = configArg.host || router.host || sourceArg?.router.host;
    const port = configArg.port || router.port || (host ? this.defaultPort(ssl) : undefined);
    const mac = this.normalizeMac(router.macAddress || sourceArg?.router.macAddress);
    const name = configArg.name || router.name || router.hostname || host || 'Synology SRM';
    return this.cleanAttributes({
      ...router,
      id: router.id || configArg.uniqueId || router.serialNumber || mac || (host ? `${host}:${port || this.defaultPort(ssl)}` : undefined) || name,
      host,
      port,
      ssl,
      verifySsl: configArg.verifySsl ?? router.verifySsl,
      name,
      hostname: router.hostname || host,
      macAddress: mac || router.macAddress,
      manufacturer: router.manufacturer || manufacturer,
      model: router.model || 'SRM Router',
      configurationUrl: router.configurationUrl || (host ? `${ssl ? 'https' : 'http'}://${host}${port && !this.isDefaultWebPort(ssl, port) ? `:${port}` : ''}` : undefined),
    }) as ISynologySrmRouterInfo;
  }

  private static systemInfo(configArg: ISynologySrmConfig, sourceArg: ISynologySrmSnapshot | undefined, manualSnapshotsArg: ISynologySrmSnapshot[], manualSystemArg: ISynologySrmSystemInfo | undefined, routerArg: ISynologySrmRouterInfo): ISynologySrmSystemInfo {
    const records = [sourceArg?.system, ...manualSnapshotsArg.map((snapshotArg) => snapshotArg.system), manualSystemArg, configArg.system, routerArg]
      .map((recordArg) => this.recordValue(recordArg))
      .filter((recordArg): recordArg is Record<string, unknown> => Boolean(recordArg));
    const version = this.firstString(records, 'versionString', 'version_string', 'firmware', 'version');
    return this.cleanAttributes({
      ...sourceArg?.system,
      ...manualSystemArg,
      ...configArg.system,
      hostname: this.firstString(records, 'hostname', 'hostName', 'host_name') || routerArg.hostname,
      firmware: this.firstString(records, 'firmware') || version,
      version,
      versionString: version,
      uptimeSeconds: this.firstNumber(records, 'uptimeSeconds', 'uptime_seconds', 'uptime'),
      temperature: this.firstNumber(records, 'temperature', 'system_temp', 'cpu_temperature'),
      cpuLoad: this.firstNumber(records, 'cpuLoad', 'cpu_load', 'cpu_usage', 'cpu_total_usage'),
      memoryUsage: this.firstNumber(records, 'memoryUsage', 'memory_usage', 'mem_usage_perc'),
      memoryTotal: this.firstNumber(records, 'memoryTotal', 'memory_total', 'mem_total'),
      memoryFree: this.firstNumber(records, 'memoryFree', 'memory_free', 'mem_free'),
    }) as ISynologySrmSystemInfo;
  }

  private static networkInfo(configArg: ISynologySrmConfig, sourceArg: ISynologySrmSnapshot | undefined, manualSnapshotsArg: ISynologySrmSnapshot[], manualNetworkArg: ISynologySrmNetworkInfo | undefined, routerArg: ISynologySrmRouterInfo): ISynologySrmNetworkInfo {
    const records = [sourceArg?.network, ...manualSnapshotsArg.map((snapshotArg) => snapshotArg.network), manualNetworkArg, configArg.network]
      .map((recordArg) => this.recordValue(recordArg))
      .filter((recordArg): recordArg is Record<string, unknown> => Boolean(recordArg));
    const macs = this.uniqueStrings([
      routerArg.macAddress,
      ...records.flatMap((recordArg) => this.stringArray(recordArg.macs)),
      ...records.flatMap((recordArg) => this.stringArray(recordArg.mac)),
      ...records.flatMap((recordArg) => this.stringArray(recordArg.macAddress)),
    ].map((valueArg) => this.normalizeMac(valueArg || undefined) || valueArg).filter((valueArg): valueArg is string => Boolean(valueArg)));
    return this.cleanAttributes({
      ...sourceArg?.network,
      ...manualNetworkArg,
      ...configArg.network,
      hostname: this.firstString(records, 'hostname', 'hostName', 'host_name') || routerArg.hostname,
      wanIp: this.firstString(records, 'wanIp', 'wan_ip', 'externalIp', 'external_ip'),
      wanIpv6: this.firstString(records, 'wanIpv6', 'wan_ipv6', 'externalIpv6', 'external_ipv6'),
      macs,
    }) as ISynologySrmNetworkInfo;
  }

  private static trafficInfo(sourcesArg: Array<ISynologySrmTrafficInfo | undefined>, interfacesArg: ISynologySrmInterfaceStats[], clientsArg: ISynologySrmClientDevice[]): ISynologySrmTrafficInfo {
    const traffic: ISynologySrmTrafficInfo = {};
    for (const source of sourcesArg) {
      Object.assign(traffic, source || {});
    }
    traffic.downloadBytes ??= traffic.rxBytes ?? this.sumInterfaces(interfacesArg, 'rxBytes', 'downloadBytes');
    traffic.uploadBytes ??= traffic.txBytes ?? this.sumInterfaces(interfacesArg, 'txBytes', 'uploadBytes');
    traffic.downloadRate ??= traffic.rxRate ?? this.sumInterfaces(interfacesArg, 'rxRate', 'downloadRate') ?? this.sumClients(clientsArg, 'transferRxRate');
    traffic.uploadRate ??= traffic.txRate ?? this.sumInterfaces(interfacesArg, 'txRate', 'uploadRate') ?? this.sumClients(clientsArg, 'transferTxRate');
    return this.cleanAttributes(traffic as Record<string, unknown>) as ISynologySrmTrafficInfo;
  }

  private static sensorMap(sourcesArg: Array<ISynologySrmSensorMap | undefined>, trafficArg: ISynologySrmTrafficInfo, systemArg: ISynologySrmSystemInfo, clientsArg: ISynologySrmClientDevice[]): ISynologySrmSensorMap {
    const sensors: ISynologySrmSensorMap = {};
    for (const source of sourcesArg) {
      Object.assign(sensors, source || {});
    }
    sensors.connectedClients ??= clientsArg.filter((clientArg) => clientArg.connected !== false).length;
    sensors.downloadBytes ??= trafficArg.downloadBytes ?? trafficArg.rxBytes;
    sensors.uploadBytes ??= trafficArg.uploadBytes ?? trafficArg.txBytes;
    sensors.downloadRate ??= trafficArg.downloadRate ?? trafficArg.rxRate;
    sensors.uploadRate ??= trafficArg.uploadRate ?? trafficArg.txRate;
    sensors.cpuLoad ??= systemArg.cpuLoad;
    sensors.memoryUsage ??= systemArg.memoryUsage;
    sensors.temperature ??= systemArg.temperature;
    sensors.uptimeSeconds ??= systemArg.uptimeSeconds;
    return this.cleanAttributes(sensors as Record<string, unknown>) as ISynologySrmSensorMap;
  }

  private static mergeManualEntries(entriesArg: ISynologySrmManualEntry[]): {
    router?: ISynologySrmRouterInfo;
    system?: ISynologySrmSystemInfo;
    network?: ISynologySrmNetworkInfo;
    clients: ISynologySrmClientDevice[];
    interfaces: ISynologySrmInterfaceStats[];
    traffic?: ISynologySrmTrafficInfo;
    sensors?: ISynologySrmSensorMap;
    switches: unknown[];
    actions: ISynologySrmActionDescriptor[];
    hasData: boolean;
  } {
    const clients: ISynologySrmClientDevice[] = [];
    const interfaces: ISynologySrmInterfaceStats[] = [];
    const switches: unknown[] = [];
    const actions: ISynologySrmActionDescriptor[] = [];
    const sensors: ISynologySrmSensorMap = {};
    let router: ISynologySrmRouterInfo | undefined;
    let system: ISynologySrmSystemInfo | undefined;
    let network: ISynologySrmNetworkInfo | undefined;
    let traffic: ISynologySrmTrafficInfo | 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,
          verifySsl: entry.verifySsl,
          name: entry.name,
          model: entry.model,
          serialNumber: entry.serialNumber,
          macAddress: entry.macAddress,
          manufacturer: entry.manufacturer,
        };
        hasData = true;
      }
      if (entry.system) {
        system = { ...system, ...entry.system };
        hasData = true;
      }
      if (entry.network) {
        network = { ...network, ...entry.network };
        hasData = true;
      }
      if (entry.traffic) {
        traffic = { ...traffic, ...entry.traffic };
        hasData = true;
      }
      clients.push(...(entry.clients || []), ...(entry.devices || []));
      interfaces.push(...(entry.interfaces || []));
      for (const iface of this.arrayValues(entry.network?.interfaces)) {
        const normalized = this.normalizeInterface(iface);
        if (normalized) {
          interfaces.push(normalized);
        }
      }
      switches.push(...this.switchValues(entry.switches));
      actions.push(...(entry.actions || []));
      Object.assign(sensors, entry.sensors || {});
      hasData = hasData || Boolean(entry.clients?.length || entry.devices?.length || entry.interfaces?.length || entry.sensors || entry.switches || entry.actions?.length);
    }
    return { router, system, network, clients, interfaces, traffic, sensors: Object.keys(sensors).length ? sensors : undefined, switches, actions, hasData };
  }

  private static routerDevice(snapshotArg: ISynologySrmSnapshot, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
      { id: 'connected_clients', capability: 'sensor', name: 'Connected clients', readable: true, writable: false },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'connectivity', value: snapshotArg.connected ? 'online' : 'offline', updatedAt: updatedAtArg },
      { featureId: 'connected_clients', value: snapshotArg.sensors.connectedClients ?? snapshotArg.clients.filter((clientArg) => clientArg.connected !== false).length, updatedAt: updatedAtArg },
    ];
    this.addFeatureState(features, state, 'download_rate', 'Download speed', snapshotArg.sensors.downloadRate, updatedAtArg, 'B/s');
    this.addFeatureState(features, state, 'upload_rate', 'Upload speed', snapshotArg.sensors.uploadRate, updatedAtArg, 'B/s');
    this.addFeatureState(features, state, 'firmware', 'SRM version', snapshotArg.system.versionString || snapshotArg.system.version || snapshotArg.router.versionString || snapshotArg.router.firmware, updatedAtArg);
    for (const switchArg of snapshotArg.switches.filter((switchArg) => switchArg.target !== 'client' && !switchArg.mac)) {
      const key = this.switchKey(switchArg);
      features.push({ id: key, capability: 'switch', name: switchArg.name || this.title(key), readable: true, writable: true });
      state.push({ featureId: key, value: switchArg.enabled, updatedAt: updatedAtArg });
    }
    return {
      id: this.routerDeviceId(snapshotArg),
      integrationDomain: synologySrmDomain,
      name: this.routerName(snapshotArg),
      protocol: snapshotArg.router.host ? 'http' : 'unknown',
      manufacturer: snapshotArg.router.manufacturer || manufacturer,
      model: snapshotArg.router.model || 'SRM Router',
      online: snapshotArg.connected,
      features,
      state,
      metadata: this.cleanAttributes({
        host: snapshotArg.router.host,
        port: snapshotArg.router.port,
        ssl: snapshotArg.router.ssl,
        verifySsl: snapshotArg.router.verifySsl,
        macAddress: snapshotArg.router.macAddress,
        serialNumber: snapshotArg.router.serialNumber,
        firmware: snapshotArg.system.versionString || snapshotArg.system.version || snapshotArg.router.firmware,
        configurationUrl: snapshotArg.router.configurationUrl,
        source: snapshotArg.source,
        liveSrmApiImplemented: false,
      }),
    };
  }

  private static clientDevice(clientArg: ISynologySrmClientDevice, snapshotArg: ISynologySrmSnapshot, 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 },
    ];
    this.addFeatureState(features, state, 'signal_strength', 'Signal strength', clientArg.signalStrength, updatedAtArg, 'dBm');
    if (clientArg.banned !== undefined) {
      features.push({ id: 'network_access', capability: 'switch', name: 'Network access', readable: true, writable: true });
      state.push({ featureId: 'network_access', value: !clientArg.banned, updatedAt: updatedAtArg });
    }
    return {
      id: this.clientDeviceId(clientArg),
      integrationDomain: synologySrmDomain,
      name: this.clientName(clientArg),
      protocol: 'unknown',
      manufacturer: clientArg.manufacturer || manufacturer,
      model: clientArg.model || clientArg.deviceType || 'SRM tracked client',
      online: clientArg.connected !== false && snapshotArg.connected,
      features,
      state,
      metadata: this.cleanAttributes({
        mac: this.clientMac(clientArg),
        ipAddress: clientArg.ipAddress || clientArg.ip,
        ip6Address: clientArg.ip6Address,
        hostname: clientArg.hostname || clientArg.name,
        connection: clientArg.connection,
        band: clientArg.band,
        wireless: clientArg.wireless,
        guest: clientArg.guest,
        meshNodeId: clientArg.meshNodeId,
      }),
    };
  }

  private static pushInterfaceEntities(entitiesArg: IIntegrationEntity[], snapshotArg: ISynologySrmSnapshot, ifaceArg: ISynologySrmInterfaceStats, 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', ifaceArg.rxBytes ?? ifaceArg.downloadBytes, 'B', { deviceClass: 'data_size', stateClass: 'total_increasing' }],
      ['tx_bytes', 'Upload', ifaceArg.txBytes ?? ifaceArg.uploadBytes, 'B', { deviceClass: 'data_size', stateClass: 'total_increasing' }],
      ['rx_rate', 'Download speed', ifaceArg.rxRate ?? ifaceArg.downloadRate, 'B/s', { deviceClass: 'data_rate', stateClass: 'measurement' }],
      ['tx_rate', 'Upload speed', ifaceArg.txRate ?? ifaceArg.uploadRate, 'B/s', { deviceClass: 'data_rate', stateClass: 'measurement' }],
    ];
    for (const [key, name, value, unit, attrs] of values) {
      if (value === undefined || value === null) {
        continue;
      }
      entitiesArg.push(this.entity('sensor', `${this.routerName(snapshotArg)} ${ifaceName} ${name}`, deviceId, `${this.uniqueBase(snapshotArg)}_interface_${ifaceKey}_${key}`, value, usedIdsArg, {
        ...attrs,
        nativeKey: key,
        unit,
        unitOfMeasurement: unit,
        interface: ifaceArg.name,
        interfaceType: ifaceArg.type,
      }, 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: ISynologySrmSnapshot, clientArg: ISynologySrmClientDevice, usedIdsArg: Map<string, number>): void {
    const deviceId = this.clientDeviceId(clientArg);
    const mac = this.clientMac(clientArg);
    const name = this.clientName(clientArg);
    entitiesArg.push(this.entity('binary_sensor', `${name} Connected`, deviceId, `${this.slug(mac || name)}_connected`, clientArg.connected !== false ? 'on' : 'off', usedIdsArg, {
      deviceClass: 'connectivity',
      nativePlatform: 'device_tracker',
      ...this.clientExtraAttributes(clientArg),
    }, snapshotArg.connected));
    this.pushNumericClientSensor(entitiesArg, snapshotArg, clientArg, usedIdsArg, 'signal_strength', 'Signal strength', clientArg.signalStrength, 'dBm', 'signal_strength');
    this.pushNumericClientSensor(entitiesArg, snapshotArg, clientArg, usedIdsArg, 'current_rate', 'Current rate', clientArg.currentRate, undefined, 'current_rate');
    this.pushNumericClientSensor(entitiesArg, snapshotArg, clientArg, usedIdsArg, 'max_rate', 'Max rate', clientArg.maxRate, undefined, 'max_rate');
    this.pushNumericClientSensor(entitiesArg, snapshotArg, clientArg, usedIdsArg, 'transfer_rx_rate', 'Transfer RX rate', clientArg.transferRxRate, 'B/s', 'transfer_rx_rate');
    this.pushNumericClientSensor(entitiesArg, snapshotArg, clientArg, usedIdsArg, 'transfer_tx_rate', 'Transfer TX rate', clientArg.transferTxRate, 'B/s', 'transfer_tx_rate');
  }

  private static pushSwitchEntity(entitiesArg: IIntegrationEntity[], snapshotArg: ISynologySrmSnapshot, switchArg: ISynologySrmSwitchInfo, usedIdsArg: Map<string, number>): void {
    const mac = this.normalizeMac(switchArg.mac);
    const client = mac ? snapshotArg.clients.find((clientArg) => this.clientMac(clientArg) === mac) : undefined;
    const deviceId = client ? this.clientDeviceId(client) : this.routerDeviceId(snapshotArg);
    const name = switchArg.name || (client ? `${this.clientName(client)} ${this.switchTitle(switchArg)}` : `${this.routerName(snapshotArg)} ${this.switchTitle(switchArg)}`);
    entitiesArg.push(this.entity('switch', name, deviceId, `${this.uniqueBase(snapshotArg)}_${this.slug(this.switchKey(switchArg))}`, switchArg.enabled ? 'on' : 'off', usedIdsArg, {
      nativeType: switchArg.type,
      nativeAction: switchArg.action || 'set_switch_enabled',
      switchKey: this.switchKey(switchArg),
      targetType: switchArg.target,
      mac,
      entityCategory: switchArg.target === 'client' ? undefined : 'config',
      writable: true,
    }, snapshotArg.connected && (!client || client.connected !== false), switchArg.entityId));
  }

  private static actionButton(snapshotArg: ISynologySrmSnapshot, actionArg: ISynologySrmActionDescriptor, usedIdsArg: Map<string, number>): IIntegrationEntity | undefined {
    if (actionArg.target !== 'router' || actionArg.action !== 'reboot') {
      return undefined;
    }
    return this.entity('button', `${this.routerName(snapshotArg)} Reboot`, this.routerDeviceId(snapshotArg), `${this.uniqueBase(snapshotArg)}_reboot`, 'available', usedIdsArg, {
      nativeType: 'router_action',
      nativeAction: 'reboot',
      actionTarget: 'router',
      entityCategory: 'config',
      writable: true,
    }, snapshotArg.connected, actionArg.entityId);
  }

  private static pushNumericClientSensor(entitiesArg: IIntegrationEntity[], snapshotArg: ISynologySrmSnapshot, clientArg: ISynologySrmClientDevice, usedIdsArg: Map<string, number>, keyArg: string, nameArg: string, valueArg: unknown, unitArg: string | undefined, nativeKeyArg: string): void {
    if (valueArg === undefined || valueArg === null) {
      return;
    }
    const name = this.clientName(clientArg);
    const mac = this.clientMac(clientArg) || name;
    entitiesArg.push(this.entity('sensor', `${name} ${nameArg}`, this.clientDeviceId(clientArg), `${this.slug(mac)}_${keyArg}`, valueArg, usedIdsArg, {
      nativePlatform: 'device_tracker',
      nativeKey: nativeKeyArg,
      unit: unitArg,
      unitOfMeasurement: unitArg,
      mac: this.clientMac(clientArg),
    }, snapshotArg.connected && clientArg.connected !== false));
  }

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

  private static switchPayload(actionArg: ISynologySrmActionDescriptor, enabledArg: boolean): Record<string, unknown> {
    if (actionArg.action === 'set_client_blocked') {
      return { enabled: enabledArg, blocked: !enabledArg };
    }
    return { enabled: enabledArg };
  }

  private static switchActionForEntity(snapshotArg: ISynologySrmSnapshot, entityArg: IIntegrationEntity): ISynologySrmActionDescriptor | undefined {
    const nativeAction = this.stringValue(entityArg.attributes?.nativeAction) as TSynologySrmAction | undefined;
    const switchKey = this.stringValue(entityArg.attributes?.switchKey);
    const mac = this.normalizeMac(this.stringValue(entityArg.attributes?.mac));
    return this.snapshotActions(snapshotArg).find((actionArg) => {
      if (actionArg.target !== 'switch') {
        return false;
      }
      const actionMatches = !nativeAction || actionArg.action === nativeAction;
      const switchMatches = !switchKey || actionArg.switchKey === switchKey;
      const macMatches = !mac || !actionArg.mac || this.normalizeMac(actionArg.mac) === mac;
      return actionMatches && switchMatches && macMatches;
    });
  }

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

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

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

  private static actionsFromClients(clientsArg: ISynologySrmClientDevice[]): ISynologySrmActionDescriptor[] {
    const actions: ISynologySrmActionDescriptor[] = [];
    for (const client of clientsArg) {
      const mac = this.clientMac(client);
      if (!mac) {
        continue;
      }
      for (const action of client.actions || []) {
        actions.push({ target: 'client', action, mac });
      }
      if (client.banned !== undefined) {
        actions.push({ target: 'switch', action: 'set_client_blocked', mac, switchKey: `${this.slug(mac)}_network_access` });
      }
      if (client.highQos !== undefined) {
        actions.push({ target: 'switch', action: 'set_client_high_qos', mac, switchKey: `${this.slug(mac)}_high_qos` });
      }
      if (client.lowQos !== undefined) {
        actions.push({ target: 'switch', action: 'set_client_low_qos', mac, switchKey: `${this.slug(mac)}_low_qos` });
      }
      if (client.parentalControlled !== undefined) {
        actions.push({ target: 'switch', action: 'set_client_parental_control', mac, switchKey: `${this.slug(mac)}_parental_control` });
      }
    }
    return actions;
  }

  private static actionsFromSwitches(switchesArg: ISynologySrmSwitchInfo[]): ISynologySrmActionDescriptor[] {
    return switchesArg.map((switchArg) => ({
      target: 'switch' as const,
      action: switchArg.action || 'set_switch_enabled',
      mac: this.normalizeMac(switchArg.mac),
      switchKey: this.switchKey(switchArg),
      entityId: switchArg.entityId,
      deviceId: switchArg.deviceId,
    }));
  }

  private static snapshotActions(snapshotArg: ISynologySrmSnapshot): ISynologySrmActionDescriptor[] {
    return this.uniqueActions([
      ...(snapshotArg.actions || []),
      ...this.actionsFromRouter(snapshotArg.router),
      ...this.actionsFromClients(snapshotArg.clients),
      ...this.actionsFromSwitches(snapshotArg.switches),
    ]);
  }

  private static uniqueClients(valuesArg: unknown[]): ISynologySrmClientDevice[] {
    const seen = new Map<string, ISynologySrmClientDevice>();
    for (const value of valuesArg) {
      const client = this.normalizeClient(value);
      const key = client ? this.clientMac(client) || client.id || client.ipAddress || client.ip || this.clientName(client) : undefined;
      if (!client || !key) {
        continue;
      }
      seen.set(key, { ...seen.get(key), ...client, mac: this.clientMac(client) || client.mac });
    }
    return [...seen.values()];
  }

  private static uniqueInterfaces(valuesArg: unknown[]): ISynologySrmInterfaceStats[] {
    const seen = new Map<string, ISynologySrmInterfaceStats>();
    for (const value of valuesArg) {
      const iface = this.normalizeInterface(value);
      const key = iface?.id || iface?.name;
      if (!iface || !key) {
        continue;
      }
      seen.set(key, { ...seen.get(key), ...iface });
    }
    return [...seen.values()];
  }

  private static uniqueSwitches(valuesArg: unknown[]): ISynologySrmSwitchInfo[] {
    const seen = new Map<string, ISynologySrmSwitchInfo>();
    for (const value of valuesArg) {
      const switchValue = this.normalizeSwitch(value);
      const key = switchValue ? this.switchKey(switchValue) : undefined;
      if (!switchValue || !key) {
        continue;
      }
      seen.set(key, { ...seen.get(key), ...switchValue });
    }
    return [...seen.values()];
  }

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

  private static normalizeClient(valueArg: unknown): ISynologySrmClientDevice | undefined {
    const record = this.recordValue(valueArg);
    if (!record) {
      return undefined;
    }
    const mac = this.normalizeMac(this.firstRecordString(record, 'mac', 'macAddress', 'mac_address'));
    const hostname = this.firstRecordString(record, 'hostname', 'hostName', 'host_name', 'name');
    const ipAddress = this.firstRecordString(record, 'ipAddress', 'ip_address', 'ip_addr', 'ip');
    const id = this.firstRecordString(record, 'id') || mac || ipAddress || hostname;
    if (!id) {
      return undefined;
    }
    return this.cleanAttributes({
      ...record,
      id,
      mac,
      macAddress: mac,
      hostname,
      name: this.firstRecordString(record, 'name') || hostname,
      ipAddress,
      ip: ipAddress,
      ip6Address: this.firstRecordString(record, 'ip6Address', 'ip6_addr', 'ipv6Address', 'ipv6_address', 'ipv6'),
      connected: this.firstRecordBoolean(record, 'connected', 'online', 'is_online'),
      wireless: this.firstRecordBoolean(record, 'wireless', 'is_wireless'),
      guest: this.firstRecordBoolean(record, 'guest', 'is_guest'),
      banned: this.firstRecordBoolean(record, 'banned', 'is_banned', 'is_baned'),
      highQos: this.firstRecordBoolean(record, 'highQos', 'high_qos', 'is_high_qos'),
      lowQos: this.firstRecordBoolean(record, 'lowQos', 'low_qos', 'is_low_qos'),
      qos: this.firstRecordBoolean(record, 'qos', 'is_qos'),
      parentalControlled: this.firstRecordBoolean(record, 'parentalControlled', 'parental_controlled', 'is_parental_controlled', 'is_parental_controled'),
      band: this.firstRecordString(record, 'band'),
      connection: this.firstRecordString(record, 'connection'),
      currentRate: this.firstRecordNumber(record, 'currentRate', 'current_rate'),
      maxRate: this.firstRecordNumber(record, 'maxRate', 'max_rate'),
      rateQuality: this.firstRecordNumber(record, 'rateQuality', 'rate_quality') ?? this.firstRecordString(record, 'rateQuality', 'rate_quality'),
      signalStrength: this.firstRecordNumber(record, 'signalStrength', 'signal_strength', 'signalstrength'),
      transferRxRate: this.firstRecordNumber(record, 'transferRxRate', 'transfer_rx_rate', 'transferRXRate'),
      transferTxRate: this.firstRecordNumber(record, 'transferTxRate', 'transfer_tx_rate', 'transferTXRate'),
      meshNodeId: this.firstRecordString(record, 'meshNodeId', 'mesh_node_id'),
      deviceType: this.firstRecordString(record, 'deviceType', 'device_type', 'dev_type'),
      manualDeviceType: this.firstRecordBoolean(record, 'manualDeviceType', 'is_manual_dev_type'),
      manualHostname: this.firstRecordBoolean(record, 'manualHostname', 'is_manual_hostname'),
      beamforming: this.firstRecordBoolean(record, 'beamforming', 'is_beamforming_on'),
      actions: this.clientActions(record.actions),
    }) as ISynologySrmClientDevice;
  }

  private static normalizeInterface(valueArg: unknown): ISynologySrmInterfaceStats | undefined {
    const record = this.recordValue(valueArg);
    if (!record) {
      return undefined;
    }
    const name = this.firstRecordString(record, 'name', 'id', 'ifname', 'interface');
    if (!name) {
      return undefined;
    }
    return this.cleanAttributes({
      ...record,
      id: this.firstRecordString(record, 'id') || name,
      name,
      label: this.firstRecordString(record, 'label', 'displayName', 'display_name'),
      type: this.firstRecordString(record, 'type', 'connection'),
      connected: this.firstRecordBoolean(record, 'connected', 'is_up', 'up'),
      macAddress: this.normalizeMac(this.firstRecordString(record, 'macAddress', 'mac_address', 'mac')),
      ipAddress: this.firstRecordString(record, 'ipAddress', 'ip_address', 'ip', 'ip_addr'),
      ipv6Address: this.firstRecordString(record, 'ipv6Address', 'ipv6_address', 'ipv6', 'ip6_addr'),
      rxBytes: this.firstRecordNumber(record, 'rxBytes', 'rx_bytes', 'downloadBytes', 'download_bytes'),
      txBytes: this.firstRecordNumber(record, 'txBytes', 'tx_bytes', 'uploadBytes', 'upload_bytes'),
      rxRate: this.firstRecordNumber(record, 'rxRate', 'rx_rate', 'downloadRate', 'download_rate'),
      txRate: this.firstRecordNumber(record, 'txRate', 'tx_rate', 'uploadRate', 'upload_rate'),
    }) as ISynologySrmInterfaceStats;
  }

  private static normalizeSwitch(valueArg: unknown): ISynologySrmSwitchInfo | undefined {
    const record = this.recordValue(valueArg);
    if (!record) {
      return undefined;
    }
    const key = this.firstRecordString(record, 'key', 'id', 'name');
    const enabled = this.firstRecordBoolean(record, 'enabled', 'is_on', 'value', 'state');
    if (!key || enabled === undefined) {
      return undefined;
    }
    return this.cleanAttributes({
      ...record,
      id: this.firstRecordString(record, 'id') || key,
      key,
      name: this.firstRecordString(record, 'name') || this.title(key),
      enabled,
      type: this.firstRecordString(record, 'type') || key,
      target: this.switchTarget(record.target),
      mac: this.normalizeMac(this.firstRecordString(record, 'mac', 'macAddress', 'mac_address')),
      action: this.actionValue(record.action || record.nativeAction || record.native_action),
    }) as ISynologySrmSwitchInfo;
  }

  private static switchesFromClients(clientsArg: ISynologySrmClientDevice[]): ISynologySrmSwitchInfo[] {
    const switches: ISynologySrmSwitchInfo[] = [];
    for (const client of clientsArg) {
      const mac = this.clientMac(client);
      if (!mac) {
        continue;
      }
      if (client.banned !== undefined) {
        switches.push({ key: `${this.slug(mac)}_network_access`, name: `${this.clientName(client)} Network access`, enabled: !client.banned, type: 'client_access', target: 'client', mac, action: 'set_client_blocked' });
      }
      if (client.highQos !== undefined) {
        switches.push({ key: `${this.slug(mac)}_high_qos`, name: `${this.clientName(client)} High QoS`, enabled: client.highQos, type: 'client_high_qos', target: 'client', mac, action: 'set_client_high_qos' });
      }
      if (client.lowQos !== undefined) {
        switches.push({ key: `${this.slug(mac)}_low_qos`, name: `${this.clientName(client)} Low QoS`, enabled: client.lowQos, type: 'client_low_qos', target: 'client', mac, action: 'set_client_low_qos' });
      }
      if (client.parentalControlled !== undefined) {
        switches.push({ key: `${this.slug(mac)}_parental_control`, name: `${this.clientName(client)} Parental control`, enabled: client.parentalControlled, type: 'client_parental_control', target: 'client', mac, action: 'set_client_parental_control' });
      }
    }
    return switches;
  }

  private static switchValues(valueArg: unknown): unknown[] {
    if (Array.isArray(valueArg)) {
      return valueArg;
    }
    const record = this.recordValue(valueArg);
    if (!record) {
      return [];
    }
    return Object.entries(record).map(([key, value]) => this.recordValue(value) ? { key, ...this.recordValue(value) } : { key, value });
  }

  private static clientExtraAttributes(clientArg: ISynologySrmClientDevice): Record<string, unknown> {
    return this.cleanAttributes({
      band: clientArg.band,
      connection: clientArg.connection,
      current_rate: clientArg.currentRate,
      dev_type: clientArg.deviceType,
      hostname: clientArg.hostname,
      ip6_addr: clientArg.ip6Address,
      ip_addr: clientArg.ipAddress || clientArg.ip,
      is_banned: clientArg.banned,
      is_beamforming_on: clientArg.beamforming,
      is_guest: clientArg.guest,
      is_high_qos: clientArg.highQos,
      is_low_qos: clientArg.lowQos,
      is_manual_dev_type: clientArg.manualDeviceType,
      is_manual_hostname: clientArg.manualHostname,
      is_online: clientArg.connected,
      is_parental_controlled: clientArg.parentalControlled,
      is_qos: clientArg.qos,
      is_wireless: clientArg.wireless,
      mac: this.clientMac(clientArg),
      max_rate: clientArg.maxRate,
      mesh_node_id: clientArg.meshNodeId,
      rate_quality: clientArg.rateQuality,
      signal_strength: clientArg.signalStrength,
      transfer_rx_rate: clientArg.transferRxRate,
      transfer_tx_rate: clientArg.transferTxRate,
    });
  }

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

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

  private static routerName(snapshotArg: ISynologySrmSnapshot): string {
    return snapshotArg.router.name || snapshotArg.router.hostname || snapshotArg.router.host || 'Synology SRM';
  }

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

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

  private static uniqueBase(snapshotArg: ISynologySrmSnapshot): 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: `${synologySrmDomain}_${this.slug(uniqueIdArg)}`,
      integrationDomain: synologySrmDomain,
      deviceId: deviceIdArg,
      platform: platformArg,
      name: nameArg,
      state: stateArg,
      attributes: this.cleanAttributes(attributesArg),
      available: availableArg,
    };
  }

  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 || valueArg === null) {
      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 switchKey(switchArg: ISynologySrmSwitchInfo): string {
    return switchArg.key || switchArg.id || switchArg.name || 'switch';
  }

  private static switchTitle(switchArg: ISynologySrmSwitchInfo): string {
    return switchArg.name || this.title(this.switchKey(switchArg));
  }

  private static switchTarget(valueArg: unknown): ISynologySrmSwitchInfo['target'] | undefined {
    return valueArg === 'router' || valueArg === 'client' || valueArg === 'system' ? valueArg : undefined;
  }

  private static actionValue(valueArg: unknown): TSynologySrmAction | undefined {
    if (valueArg === 'reboot' || valueArg === 'block_client' || valueArg === 'unblock_client' || valueArg === 'set_client_blocked' || valueArg === 'set_client_high_qos' || valueArg === 'set_client_low_qos' || valueArg === 'set_client_parental_control' || valueArg === 'set_switch_enabled') {
      return valueArg;
    }
    return undefined;
  }

  private static clientActions(valueArg: unknown): TSynologySrmClientAction[] | undefined {
    if (!Array.isArray(valueArg)) {
      return undefined;
    }
    const actions = valueArg.filter((actionArg): actionArg is TSynologySrmClientAction => actionArg === 'block_client' || actionArg === 'unblock_client' || actionArg === 'set_client_blocked' || actionArg === 'set_client_high_qos' || actionArg === 'set_client_low_qos' || actionArg === 'set_client_parental_control');
    return actions.length ? actions : undefined;
  }

  private static sumInterfaces(interfacesArg: ISynologySrmInterfaceStats[], primaryKeyArg: keyof ISynologySrmInterfaceStats, fallbackKeyArg: keyof ISynologySrmInterfaceStats): 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 sumClients(clientsArg: ISynologySrmClientDevice[], keyArg: keyof ISynologySrmClientDevice): number | undefined {
    let total = 0;
    let found = false;
    for (const client of clientsArg) {
      const value = this.numberValue(client[keyArg]);
      if (value !== undefined) {
        total += value;
        found = true;
      }
    }
    return found ? total : undefined;
  }

  private static isDefaultWebPort(sslArg: boolean, portArg: number): boolean {
    return (sslArg && portArg === 443) || (!sslArg && portArg === 80);
  }

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

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

  private static arrayValues(valueArg: unknown): unknown[] {
    return Array.isArray(valueArg) ? valueArg : [];
  }

  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') {
      if (valueArg === 1) return true;
      if (valueArg === 0) return false;
    }
    if (typeof valueArg === 'string') {
      if (['true', '1', 'yes', 'on', 'online'].includes(valueArg.toLowerCase())) return true;
      if (['false', '0', 'no', 'off', 'offline'].includes(valueArg.toLowerCase())) return false;
    }
    return undefined;
  }

  private static firstRecordString(recordArg: Record<string, unknown>, ...keysArg: string[]): string | undefined {
    for (const key of keysArg) {
      const value = this.stringValue(recordArg[key]);
      if (value !== undefined) {
        return value;
      }
    }
    return undefined;
  }

  private static firstRecordNumber(recordArg: Record<string, unknown>, ...keysArg: string[]): number | undefined {
    for (const key of keysArg) {
      const value = this.numberValue(recordArg[key]);
      if (value !== undefined) {
        return value;
      }
    }
    return undefined;
  }

  private static firstRecordBoolean(recordArg: Record<string, unknown>, ...keysArg: string[]): boolean | undefined {
    for (const key of keysArg) {
      const value = this.booleanValue(recordArg[key]);
      if (value !== undefined) {
        return value;
      }
    }
    return undefined;
  }

  private static firstString(recordsArg: Record<string, unknown>[], ...keysArg: string[]): string | undefined {
    for (const record of recordsArg) {
      const value = this.firstRecordString(record, ...keysArg);
      if (value !== undefined) {
        return value;
      }
    }
    return undefined;
  }

  private static firstNumber(recordsArg: Record<string, unknown>[], ...keysArg: string[]): number | undefined {
    for (const record of recordsArg) {
      const value = this.firstRecordNumber(record, ...keysArg);
      if (value !== undefined) {
        return value;
      }
    }
    return undefined;
  }

  private static stringArray(valueArg: unknown): string[] {
    if (Array.isArray(valueArg)) {
      return valueArg.map((entryArg) => this.stringValue(entryArg)).filter((entryArg): entryArg is string => Boolean(entryArg));
    }
    const value = this.stringValue(valueArg);
    return value ? [value] : [];
  }

  private static uniqueStrings(valuesArg: string[]): string[] {
    return [...new Set(valuesArg.filter(Boolean))];
  }

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