import * as plugins from '../../plugins.js';
import type { IIntegrationEntity } from '../../core/types.js';
import type {
  IGlancesConfig,
  IGlancesDiskIoSensorData,
  IGlancesDiskSensorData,
  IGlancesEnvironmentSensorData,
  IGlancesGpuSensorData,
  IGlancesHaSensorData,
  IGlancesHostInfo,
  IGlancesNetworkSensorData,
  IGlancesRawData,
  IGlancesSensorState,
  IGlancesSnapshot,
  TGlancesApiVersion,
  TGlancesSensorCategory,
  TGlancesSensorValue,
  TGlancesSnapshotSource,
} from './glances.types.js';
import { glancesDefaultPort, glancesDomain } from './glances.types.js';

interface IGlancesSnapshotOptions {
  config: IGlancesConfig;
  sensorData?: IGlancesHaSensorData;
  rawData?: IGlancesRawData;
  online?: boolean;
  source?: TGlancesSnapshotSource;
  apiVersion?: TGlancesApiVersion;
  error?: string;
}

interface IGlancesSensorDefinition {
  key: string;
  name: string;
  category: TGlancesSensorCategory;
  value: unknown;
  unit?: string;
  label?: string;
  deviceClass?: string;
  stateClass?: string;
  entityCategory?: string;
  numeric?: boolean;
  attributes?: Record<string, unknown>;
}

export class GlancesMapper {
  public static toSnapshot(optionsArg: IGlancesSnapshotOptions): IGlancesSnapshot {
    if (optionsArg.config.snapshot) {
      return this.normalizeSnapshot(optionsArg.config.snapshot, optionsArg.config, optionsArg.source || 'snapshot');
    }

    const rawData = optionsArg.rawData || optionsArg.config.rawData || optionsArg.config.allData;
    const apiVersion = optionsArg.apiVersion || optionsArg.config.apiVersion || optionsArg.config.version;
    const sensorData = optionsArg.sensorData
      || optionsArg.config.haSensorData
      || optionsArg.config.sensorData
      || (rawData ? this.haSensorDataFromRawData(rawData, apiVersion || 4) : {});
    const online = optionsArg.online ?? optionsArg.config.online ?? Boolean(rawData || Object.keys(sensorData).length);
    const host = this.hostInfo(optionsArg.config, rawData, apiVersion);
    const updatedAt = new Date().toISOString();
    const snapshot: IGlancesSnapshot = {
      host,
      sensorData: this.clone(sensorData),
      sensors: this.sensorsFromHaSensorData(sensorData),
      rawData: rawData ? this.clone(rawData) : undefined,
      online,
      updatedAt,
      source: optionsArg.source || (rawData || Object.keys(sensorData).length ? 'manual' : 'runtime'),
      apiVersion,
      error: optionsArg.error,
    };
    return this.normalizeSnapshot(snapshot, optionsArg.config, snapshot.source || 'runtime');
  }

  public static toDevices(snapshotArg: IGlancesSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    return [{
      id: this.hostDeviceId(snapshotArg),
      integrationDomain: glancesDomain,
      name: this.hostName(snapshotArg),
      protocol: snapshotArg.host.host ? 'http' : 'unknown',
      manufacturer: 'Glances',
      model: snapshotArg.host.osName || snapshotArg.host.platform || 'System Monitor',
      online: snapshotArg.online,
      features: [
        { id: 'connection', capability: 'sensor', name: 'Connection', readable: true, writable: false },
        ...snapshotArg.sensors.map((sensorArg) => ({
          id: sensorArg.key,
          capability: 'sensor' as const,
          name: sensorArg.name,
          readable: true,
          writable: false,
          unit: sensorArg.unit,
        })),
      ],
      state: [
        { featureId: 'connection', value: snapshotArg.online ? 'online' : 'offline', updatedAt },
        ...snapshotArg.sensors.map((sensorArg) => ({ featureId: sensorArg.key, value: sensorArg.value, updatedAt })),
      ],
      metadata: this.cleanAttributes({
        host: snapshotArg.host.host,
        port: snapshotArg.host.port,
        ssl: snapshotArg.host.ssl,
        apiVersion: snapshotArg.apiVersion || snapshotArg.host.apiVersion,
        hostname: snapshotArg.host.hostname,
        platform: snapshotArg.host.platform,
        version: snapshotArg.host.version,
        source: snapshotArg.source,
        rawPlugins: snapshotArg.rawData ? Object.keys(snapshotArg.rawData).sort() : undefined,
        error: snapshotArg.error,
      }),
    }];
  }

  public static toEntities(snapshotArg: IGlancesSnapshot): IIntegrationEntity[] {
    const deviceId = this.hostDeviceId(snapshotArg);
    const uniqueBase = this.uniqueBase(snapshotArg);
    const usedIds = new Map<string, number>();
    return snapshotArg.sensors.map((sensorArg) => {
      const id = this.entityId(this.hostName(snapshotArg), sensorArg, usedIds);
      return {
        id,
        uniqueId: `${glancesDomain}_${uniqueBase}_${sensorArg.key}`,
        integrationDomain: glancesDomain,
        deviceId,
        platform: 'sensor',
        name: sensorArg.name,
        state: sensorArg.value,
        attributes: this.cleanAttributes({
          key: sensorArg.key,
          category: sensorArg.category,
          label: sensorArg.label,
          unitOfMeasurement: sensorArg.unit,
          deviceClass: sensorArg.deviceClass,
          stateClass: sensorArg.stateClass,
          entityCategory: sensorArg.entityCategory,
          source: snapshotArg.source,
          apiVersion: snapshotArg.apiVersion,
          ...sensorArg.attributes,
        }),
        available: snapshotArg.online && sensorArg.available,
      };
    });
  }

  public static haSensorDataFromRawData(rawDataArg: IGlancesRawData, apiVersionArg: TGlancesApiVersion): IGlancesHaSensorData {
    const sensorData: IGlancesHaSensorData = {};

    const fsData = this.arrayRecords(rawDataArg.fs);
    if (fsData.length) {
      sensorData.fs = {};
      for (const disk of fsData) {
        const label = this.stringValue(disk.mnt_point) || this.stringValue(disk.mount_point) || this.stringValue(disk.device_name);
        const used = this.numberValue(disk.used);
        const size = this.numberValue(disk.size);
        const percent = this.numberValue(disk.percent);
        const free = this.numberValue(disk.free) ?? (size !== undefined && used !== undefined ? size - used : undefined);
        if (!label) {
          continue;
        }
        sensorData.fs[label] = this.cleanAttributes({
          disk_use: used !== undefined ? this.round(used / 1024 ** 3, 1) : undefined,
          disk_use_percent: percent,
          disk_size: size !== undefined ? this.round(size / 1024 ** 3, 1) : undefined,
          disk_free: free !== undefined ? this.round(free / 1024 ** 3, 1) : undefined,
        }) as IGlancesDiskSensorData;
      }
    }

    const rawSensors = this.arrayRecords(rawDataArg.sensors);
    if (rawSensors.length) {
      sensorData.sensors = {};
      for (const sensor of rawSensors) {
        const label = this.stringValue(sensor.label);
        const type = this.stringValue(sensor.type);
        const value = this.numberValue(sensor.value);
        if (!label || !type || value === undefined) {
          continue;
        }
        sensorData.sensors[label] = {
          ...(sensorData.sensors[label] || {}),
          [type]: value,
        };
      }
    }

    const mem = this.recordValue(rawDataArg.mem);
    if (mem) {
      sensorData.mem = this.cleanAttributes({
        memory_use_percent: this.numberValue(mem.percent),
        memory_use: this.bytesToMib(mem.used),
        memory_free: this.bytesToMib(mem.free),
      });
    }

    const memswap = this.recordValue(rawDataArg.memswap);
    if (memswap) {
      sensorData.memswap = this.cleanAttributes({
        swap_use_percent: this.numberValue(memswap.percent),
        swap_use: this.bytesToGib(memswap.used),
        swap_free: this.bytesToGib(memswap.free),
      });
    }

    const load = this.recordValue(rawDataArg.load);
    if (load) {
      sensorData.load = this.cleanAttributes({
        processor_load: this.numberValue(load.min15),
        processor_load_1m: this.numberValue(load.min1),
        processor_load_5m: this.numberValue(load.min5),
      });
    }

    const processcount = this.recordValue(rawDataArg.processcount);
    if (processcount) {
      sensorData.processcount = this.cleanAttributes({
        process_running: this.numberValue(processcount.running),
        process_total: this.numberValue(processcount.total),
        process_thread: this.numberValue(processcount.thread),
        process_sleeping: this.numberValue(processcount.sleeping),
      });
    }

    const quicklook = this.recordValue(rawDataArg.quicklook);
    const cpu = this.numberValue(quicklook?.cpu);
    if (cpu !== undefined) {
      sensorData.cpu = { cpu_use_percent: cpu };
    }

    const percpu = this.arrayRecords(rawDataArg.percpu);
    if (percpu.length) {
      sensorData.percpu = {};
      for (const item of percpu) {
        const cpuNumber = this.stringValue(item.cpu_number) || this.stringValue(item.key);
        const total = this.numberValue(item.total);
        if (cpuNumber && total !== undefined) {
          sensorData.percpu[cpuNumber] = { cpu_use_percent: total };
        }
      }
    }

    const networks = this.arrayRecords(rawDataArg.network);
    if (networks.length) {
      sensorData.network = {};
      for (const network of networks) {
        const label = this.stringValue(network.interface_name) || this.stringValue(network.name);
        if (!label) {
          continue;
        }
        let rx: number | undefined;
        let tx: number | undefined;
        if (apiVersionArg <= 3) {
          const timeSinceUpdate = this.numberValue(network.time_since_update);
          if (timeSinceUpdate && timeSinceUpdate > 0) {
            const rxBytes = this.numberValue(network.rx);
            const txBytes = this.numberValue(network.tx);
            rx = rxBytes !== undefined ? Math.round(rxBytes / timeSinceUpdate) : undefined;
            tx = txBytes !== undefined ? Math.round(txBytes / timeSinceUpdate) : undefined;
          }
        } else {
          rx = this.numberValue(network.bytes_recv_rate_per_sec);
          tx = this.numberValue(network.bytes_sent_rate_per_sec);
        }
        sensorData.network[label] = this.cleanAttributes({
          is_up: this.booleanValue(network.is_up),
          rx,
          tx,
          speed: this.bytesToGib(network.speed),
        }) as IGlancesNetworkSensorData;
      }
    }

    const containersData = apiVersionArg <= 3
      ? this.arrayRecords(this.recordValue(rawDataArg.dockers)?.containers || this.recordValue(rawDataArg.containers)?.containers)
      : this.arrayRecords(rawDataArg.containers);
    if (containersData.length) {
      const activeContainers = containersData.filter((containerArg) => {
        const status = this.stringValue(containerArg.status || containerArg.Status)?.toLowerCase();
        return status === 'running' || status === 'healthy';
      });
      sensorData.docker = {
        docker_active: activeContainers.length,
        docker_cpu_use: this.round(activeContainers.reduce((sumArg, containerArg) => sumArg + (this.numberValue(this.recordValue(containerArg.cpu)?.total) || 0), 0), 1),
        docker_memory_use: this.round(activeContainers.reduce((sumArg, containerArg) => sumArg + (this.numberValue(this.recordValue(containerArg.memory)?.usage) || 0), 0) / 1024 ** 2, 1),
      };
      sensorData.containers = {};
      for (const container of activeContainers) {
        const name = this.stringValue(container.name);
        if (!name) {
          continue;
        }
        sensorData.containers[name] = {
          container_cpu_use: this.round(this.numberValue(this.recordValue(container.cpu)?.total) || 0, 1),
          container_memory_use: this.round((this.numberValue(this.recordValue(container.memory)?.usage) || 0) / 1024 ** 2, 1),
        };
      }
    }

    const raid = this.recordValue(rawDataArg.raid);
    if (raid) {
      sensorData.raid = raid as Record<string, Record<string, unknown>>;
    }
    if (rawDataArg.uptime !== undefined) {
      sensorData.uptime = rawDataArg.uptime;
    }

    const gpu = this.arrayRecords(rawDataArg.gpu);
    if (gpu.length) {
      sensorData.gpu = {};
      for (const item of gpu) {
        const name = this.stringValue(item.name) || 'GPU';
        const gpuId = this.stringValue(item.gpu_id) || this.stringValue(item.id) || '0';
        sensorData.gpu[`${name} (GPU ${gpuId})`] = this.cleanAttributes({
          temperature: this.numberValue(item.temperature) || 0,
          mem: this.numberValue(item.mem) || 0,
          proc: this.numberValue(item.proc) || 0,
          fan_speed: this.numberValue(item.fan_speed) || 0,
        }) as IGlancesGpuSensorData;
      }
    }

    const diskio = this.arrayRecords(rawDataArg.diskio);
    if (diskio.length) {
      sensorData.diskio = {};
      for (const disk of diskio) {
        const name = this.stringValue(disk.disk_name) || this.stringValue(disk.name);
        const timeSinceUpdate = this.numberValue(disk.time_since_update);
        if (!name || !timeSinceUpdate || timeSinceUpdate <= 0) {
          continue;
        }
        const readBytes = this.numberValue(disk.read_bytes);
        const writeBytes = this.numberValue(disk.write_bytes);
        sensorData.diskio[name] = this.cleanAttributes({
          read: readBytes !== undefined ? Math.round(readBytes / timeSinceUpdate) : undefined,
          write: writeBytes !== undefined ? Math.round(writeBytes / timeSinceUpdate) : undefined,
        }) as IGlancesDiskIoSensorData;
      }
    }

    return sensorData;
  }

  public static sensorsFromHaSensorData(sensorDataArg: IGlancesHaSensorData): IGlancesSensorState[] {
    const sensors: IGlancesSensorState[] = [];
    this.pushNumberSensor(sensors, { key: 'cpu_use_percent', name: 'CPU Usage', category: 'cpu', value: sensorDataArg.cpu?.cpu_use_percent, unit: '%', stateClass: 'measurement' });
    for (const [label, cpu] of Object.entries(sensorDataArg.percpu || {})) {
      this.pushNumberSensor(sensors, { key: `cpu_${this.slug(label)}_use_percent`, name: `CPU ${label} Usage`, category: 'cpu', value: cpu.cpu_use_percent, unit: '%', label, stateClass: 'measurement', entityCategory: 'diagnostic' });
    }

    this.pushNumberSensor(sensors, { key: 'memory_use_percent', name: 'Memory Usage', category: 'memory', value: sensorDataArg.mem?.memory_use_percent, unit: '%', stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'memory_use', name: 'Memory Use', category: 'memory', value: sensorDataArg.mem?.memory_use, unit: 'MiB', deviceClass: 'data_size', stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'memory_free', name: 'Memory Free', category: 'memory', value: sensorDataArg.mem?.memory_free, unit: 'MiB', deviceClass: 'data_size', stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'swap_use_percent', name: 'Swap Usage', category: 'memory', value: sensorDataArg.memswap?.swap_use_percent, unit: '%', stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'swap_use', name: 'Swap Use', category: 'memory', value: sensorDataArg.memswap?.swap_use, unit: 'GiB', deviceClass: 'data_size', stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'swap_free', name: 'Swap Free', category: 'memory', value: sensorDataArg.memswap?.swap_free, unit: 'GiB', deviceClass: 'data_size', stateClass: 'measurement' });

    for (const [label, disk] of Object.entries(sensorDataArg.fs || {})) {
      const labelSlug = this.slug(label);
      this.pushNumberSensor(sensors, { key: `disk_${labelSlug}_use_percent`, name: `${label} Disk Usage`, category: 'disk', value: disk.disk_use_percent, unit: '%', label, stateClass: 'measurement' });
      this.pushNumberSensor(sensors, { key: `disk_${labelSlug}_used`, name: `${label} Disk Used`, category: 'disk', value: disk.disk_use, unit: 'GiB', label, deviceClass: 'data_size', stateClass: 'measurement' });
      this.pushNumberSensor(sensors, { key: `disk_${labelSlug}_size`, name: `${label} Disk Size`, category: 'disk', value: disk.disk_size, unit: 'GiB', label, deviceClass: 'data_size', stateClass: 'measurement' });
      this.pushNumberSensor(sensors, { key: `disk_${labelSlug}_free`, name: `${label} Disk Free`, category: 'disk', value: disk.disk_free, unit: 'GiB', label, deviceClass: 'data_size', stateClass: 'measurement' });
    }
    for (const [label, disk] of Object.entries(sensorDataArg.diskio || {})) {
      const labelSlug = this.slug(label);
      this.pushNumberSensor(sensors, { key: `diskio_${labelSlug}_read`, name: `${label} Disk Read`, category: 'disk', value: disk.read, unit: 'B/s', label, deviceClass: 'data_rate', stateClass: 'measurement' });
      this.pushNumberSensor(sensors, { key: `diskio_${labelSlug}_write`, name: `${label} Disk Write`, category: 'disk', value: disk.write, unit: 'B/s', label, deviceClass: 'data_rate', stateClass: 'measurement' });
    }

    for (const [label, network] of Object.entries(sensorDataArg.network || {})) {
      const labelSlug = this.slug(label);
      const attributes = { isUp: network.is_up, speedGib: network.speed };
      this.pushNumberSensor(sensors, { key: `network_${labelSlug}_rx`, name: `${label} RX`, category: 'network', value: network.rx, unit: 'B/s', label, deviceClass: 'data_rate', stateClass: 'measurement', attributes });
      this.pushNumberSensor(sensors, { key: `network_${labelSlug}_tx`, name: `${label} TX`, category: 'network', value: network.tx, unit: 'B/s', label, deviceClass: 'data_rate', stateClass: 'measurement', attributes });
    }

    this.pushNumberSensor(sensors, { key: 'processor_load', name: 'CPU Load', category: 'load', value: sensorDataArg.load?.processor_load, stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'processor_load_1m', name: 'CPU Load 1m', category: 'load', value: sensorDataArg.load?.processor_load_1m, stateClass: 'measurement', entityCategory: 'diagnostic' });
    this.pushNumberSensor(sensors, { key: 'processor_load_5m', name: 'CPU Load 5m', category: 'load', value: sensorDataArg.load?.processor_load_5m, stateClass: 'measurement', entityCategory: 'diagnostic' });

    this.pushNumberSensor(sensors, { key: 'process_running', name: 'Running', category: 'process', value: sensorDataArg.processcount?.process_running, stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'process_total', name: 'Total', category: 'process', value: sensorDataArg.processcount?.process_total, stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'process_thread', name: 'Threads', category: 'process', value: sensorDataArg.processcount?.process_thread, stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'process_sleeping', name: 'Sleeping', category: 'process', value: sensorDataArg.processcount?.process_sleeping, stateClass: 'measurement' });

    for (const [label, environment] of Object.entries(sensorDataArg.sensors || {})) {
      for (const [key, value] of Object.entries(environment)) {
        const isTemperature = key.startsWith('temperature');
        const isFan = key === 'fan_speed';
        const name = isTemperature ? `${label} Temperature` : isFan ? `${label} Fan Speed` : `${label} ${this.titleFromKey(key)}`;
        this.pushNumberSensor(sensors, {
          key: `sensor_${this.slug(label)}_${this.slug(key)}`,
          name,
          category: isTemperature ? 'temperature' : 'other',
          value,
          unit: isTemperature ? '°C' : isFan ? 'rpm' : key === 'battery' ? '%' : undefined,
          label,
          deviceClass: isTemperature ? 'temperature' : key === 'battery' ? 'battery' : undefined,
          stateClass: 'measurement',
          attributes: { sensorType: key },
        });
      }
    }

    this.pushNumberSensor(sensors, { key: 'docker_active', name: 'Containers Active', category: 'container', value: sensorDataArg.docker?.docker_active, stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'docker_cpu_use', name: 'Containers CPU Usage', category: 'container', value: sensorDataArg.docker?.docker_cpu_use, unit: '%', stateClass: 'measurement' });
    this.pushNumberSensor(sensors, { key: 'docker_memory_use', name: 'Containers Memory Used', category: 'container', value: sensorDataArg.docker?.docker_memory_use, unit: 'MiB', deviceClass: 'data_size', stateClass: 'measurement' });
    for (const [label, container] of Object.entries(sensorDataArg.containers || {})) {
      const labelSlug = this.slug(label);
      this.pushNumberSensor(sensors, { key: `container_${labelSlug}_cpu_use`, name: `${label} Container CPU Usage`, category: 'container', value: container.container_cpu_use, unit: '%', label, stateClass: 'measurement' });
      this.pushNumberSensor(sensors, { key: `container_${labelSlug}_memory_use`, name: `${label} Container Memory Used`, category: 'container', value: container.container_memory_use, unit: 'MiB', label, deviceClass: 'data_size', stateClass: 'measurement' });
    }

    for (const [label, gpu] of Object.entries(sensorDataArg.gpu || {})) {
      const labelSlug = this.slug(label);
      this.pushNumberSensor(sensors, { key: `gpu_${labelSlug}_temperature`, name: `${label} Temperature`, category: 'gpu', value: gpu.temperature, unit: '°C', label, deviceClass: 'temperature', stateClass: 'measurement' });
      this.pushNumberSensor(sensors, { key: `gpu_${labelSlug}_memory_usage`, name: `${label} Memory Usage`, category: 'gpu', value: gpu.mem, unit: '%', label, stateClass: 'measurement' });
      this.pushNumberSensor(sensors, { key: `gpu_${labelSlug}_processor_usage`, name: `${label} Processor Usage`, category: 'gpu', value: gpu.proc, unit: '%', label, stateClass: 'measurement' });
      this.pushNumberSensor(sensors, { key: `gpu_${labelSlug}_fan_speed`, name: `${label} Fan Speed`, category: 'gpu', value: gpu.fan_speed, unit: '%', label, stateClass: 'measurement' });
    }

    for (const [label, raid] of Object.entries(sensorDataArg.raid || {})) {
      const labelSlug = this.slug(label);
      this.pushNumberSensor(sensors, { key: `raid_${labelSlug}_available`, name: `${label} Available`, category: 'raid', value: raid.available, label, stateClass: 'measurement' });
      this.pushNumberSensor(sensors, { key: `raid_${labelSlug}_used`, name: `${label} Used`, category: 'raid', value: raid.used, label, stateClass: 'measurement' });
    }

    return sensors;
  }

  public static hostDeviceId(snapshotArg: IGlancesSnapshot): string {
    return `${glancesDomain}.host.${this.uniqueBase(snapshotArg)}`;
  }

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

  private static normalizeSnapshot(snapshotArg: IGlancesSnapshot, configArg: IGlancesConfig, sourceArg: TGlancesSnapshotSource): IGlancesSnapshot {
    const rawData = snapshotArg.rawData ? this.clone(snapshotArg.rawData) : configArg.rawData || configArg.allData;
    const apiVersion = snapshotArg.apiVersion || configArg.apiVersion || configArg.version;
    const sensorData = Object.keys(snapshotArg.sensorData || {}).length
      ? this.clone(snapshotArg.sensorData)
      : rawData ? this.haSensorDataFromRawData(rawData, apiVersion || 4) : {};
    const host = {
      ...this.hostInfo(configArg, rawData, apiVersion),
      ...snapshotArg.host,
    };
    host.port = host.port || (host.host ? configArg.port || glancesDefaultPort : configArg.port);
    host.name = host.name || configArg.name || host.hostname || host.host || 'Glances';
    host.id = host.id || configArg.uniqueId || (host.host ? `${host.host}:${host.port || glancesDefaultPort}` : undefined) || host.hostname || host.name;
    return {
      ...snapshotArg,
      host,
      sensorData,
      sensors: snapshotArg.sensors?.length ? this.clone(snapshotArg.sensors) : this.sensorsFromHaSensorData(sensorData),
      rawData: rawData ? this.clone(rawData) : undefined,
      online: snapshotArg.online,
      updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
      source: snapshotArg.source || sourceArg,
      apiVersion,
    };
  }

  private static hostInfo(configArg: IGlancesConfig, rawDataArg: IGlancesRawData | undefined, apiVersionArg: TGlancesApiVersion | undefined): IGlancesHostInfo {
    const system = this.recordValue(rawDataArg?.system);
    const host = configArg.host;
    const port = configArg.port || (host ? glancesDefaultPort : undefined);
    const hostname = this.stringValue(system?.hostname) || this.stringValue(system?.host);
    return {
      id: configArg.uniqueId || (host ? `${host}:${port}` : undefined) || hostname || configArg.name,
      name: configArg.name || hostname || host || 'Glances',
      host,
      port,
      ssl: configArg.ssl,
      apiVersion: apiVersionArg,
      hostname,
      osName: this.stringValue(system?.os_name) || this.stringValue(system?.osName) || this.stringValue(system?.linux_distro),
      platform: this.stringValue(system?.platform),
      version: this.stringValue(system?.hr_name) || this.stringValue(system?.version),
    };
  }

  private static pushNumberSensor(sensorsArg: IGlancesSensorState[], definitionArg: IGlancesSensorDefinition): void {
    const value = this.numberValue(definitionArg.value);
    if (value === undefined) {
      return;
    }
    sensorsArg.push(this.sensor({ ...definitionArg, value, numeric: true }));
  }

  private static sensor(definitionArg: IGlancesSensorDefinition): IGlancesSensorState {
    const value = this.sensorValue(definitionArg.value);
    return {
      key: this.slug(definitionArg.key),
      name: definitionArg.name,
      category: definitionArg.category,
      value,
      unit: definitionArg.unit,
      label: definitionArg.label,
      deviceClass: definitionArg.deviceClass,
      stateClass: definitionArg.stateClass,
      entityCategory: definitionArg.entityCategory,
      available: value !== null && (!definitionArg.numeric || typeof value === 'number'),
      attributes: this.cleanAttributes(definitionArg.attributes || {}),
    };
  }

  private static sensorValue(valueArg: unknown): TGlancesSensorValue {
    if (typeof valueArg === 'number') {
      return Number.isFinite(valueArg) ? valueArg : null;
    }
    if (typeof valueArg === 'string' || typeof valueArg === 'boolean') {
      return valueArg;
    }
    return null;
  }

  private static entityId(hostNameArg: string, sensorArg: IGlancesSensorState, usedIdsArg: Map<string, number>): string {
    const baseId = `sensor.${this.slug(`${hostNameArg} ${sensorArg.name}`)}`;
    const seen = usedIdsArg.get(baseId) || 0;
    usedIdsArg.set(baseId, seen + 1);
    return seen ? `${baseId}_${seen + 1}` : baseId;
  }

  private static hostName(snapshotArg: IGlancesSnapshot): string {
    return snapshotArg.host.name || snapshotArg.host.hostname || snapshotArg.host.host || 'Glances';
  }

  private static uniqueBase(snapshotArg: IGlancesSnapshot): string {
    return this.slug(snapshotArg.host.id || snapshotArg.host.hostname || snapshotArg.host.host || this.hostName(snapshotArg));
  }

  private static bytesToMib(valueArg: unknown): number | undefined {
    const value = this.numberValue(valueArg);
    return value === undefined ? undefined : this.round(value / 1024 ** 2, 1);
  }

  private static bytesToGib(valueArg: unknown): number | undefined {
    const value = this.numberValue(valueArg);
    return value === undefined ? undefined : this.round(value / 1024 ** 3, 1);
  }

  private static round(valueArg: number, digitsArg: number): number {
    const factor = 10 ** digitsArg;
    return Math.round(valueArg * factor) / factor;
  }

  private static titleFromKey(valueArg: string): string {
    return valueArg.split('_').map((partArg) => partArg ? `${partArg[0].toUpperCase()}${partArg.slice(1)}` : '').join(' ');
  }

  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 arrayRecords(valueArg: unknown): Array<Record<string, unknown>> {
    return Array.isArray(valueArg) ? valueArg.filter((itemArg): itemArg is Record<string, unknown> => Boolean(this.recordValue(itemArg))) : [];
  }

  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 stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
  }

  private static booleanValue(valueArg: unknown): boolean | undefined {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'string') {
      if (valueArg.toLowerCase() === 'true') {
        return true;
      }
      if (valueArg.toLowerCase() === 'false') {
        return false;
      }
    }
    return undefined;
  }

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

  private static clone<TValue>(valueArg: TValue): TValue {
    return JSON.parse(JSON.stringify(valueArg)) as TValue;
  }
}
