import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type {
  IMatterAttribute,
  IMatterCluster,
  IMatterConfig,
  IMatterDeviceInfo,
  IMatterDeviceType,
  IMatterEndpoint,
  IMatterNode,
  IMatterServerCommand,
  IMatterServerEvent,
  IMatterServiceCommandArgs,
  IMatterSnapshot,
  TMatterEntityPlatform,
  TMatterNodeId,
} from './matter.types.js';

const clusters = {
  descriptor: 29,
  powerSource: 47,
  bridgedDeviceBasicInformation: 57,
  booleanState: 69,
  airQuality: 91,
  smokeCoAlarm: 92,
  onOff: 6,
  levelControl: 8,
  basicInformation: 40,
  doorLock: 257,
  windowCovering: 258,
  thermostat: 513,
  colorControl: 768,
  illuminanceMeasurement: 1024,
  temperatureMeasurement: 1026,
  pressureMeasurement: 1027,
  flowMeasurement: 1028,
  relativeHumidityMeasurement: 1029,
  occupancySensing: 1030,
} as const;

const globalAttributes = {
  acceptedCommandList: 65529,
  attributeList: 65531,
  featureMap: 65532,
  clusterRevision: 65533,
} as const;

const deviceTypes: Record<number, string> = {
  0x000a: 'Door Lock',
  0x000e: 'Aggregator',
  0x000f: 'Generic Switch',
  0x0013: 'Bridged Node',
  0x0016: 'Root Node',
  0x0015: 'Contact Sensor',
  0x002c: 'Air Quality Sensor',
  0x0041: 'Water Freeze Detector',
  0x0043: 'Water Leak Detector',
  0x0044: 'Rain Sensor',
  0x0072: 'Room Air Conditioner',
  0x0076: 'Smoke CO Alarm',
  0x0100: 'On/Off Light',
  0x0101: 'Dimmable Light',
  0x0103: 'On/Off Light Switch',
  0x0104: 'Dimmer Switch',
  0x0105: 'Color Dimmer Switch',
  0x0106: 'Light Sensor',
  0x0107: 'Occupancy Sensor',
  0x010a: 'On/Off Plug-in Unit',
  0x010b: 'Dimmable Plug-in Unit',
  0x010c: 'Color Temperature Light',
  0x010d: 'Extended Color Light',
  0x0202: 'Window Covering',
  0x0301: 'Thermostat',
  0x0302: 'Temperature Sensor',
  0x0305: 'Pressure Sensor',
  0x0306: 'Flow Sensor',
  0x0307: 'Humidity Sensor',
};

const clusterNames: Record<number, string> = {
  [clusters.descriptor]: 'Descriptor',
  [clusters.powerSource]: 'PowerSource',
  [clusters.bridgedDeviceBasicInformation]: 'BridgedDeviceBasicInformation',
  [clusters.booleanState]: 'BooleanState',
  [clusters.airQuality]: 'AirQuality',
  [clusters.smokeCoAlarm]: 'SmokeCoAlarm',
  [clusters.onOff]: 'OnOff',
  [clusters.levelControl]: 'LevelControl',
  [clusters.basicInformation]: 'BasicInformation',
  [clusters.doorLock]: 'DoorLock',
  [clusters.windowCovering]: 'WindowCovering',
  [clusters.thermostat]: 'Thermostat',
  [clusters.colorControl]: 'ColorControl',
  [clusters.illuminanceMeasurement]: 'IlluminanceMeasurement',
  [clusters.temperatureMeasurement]: 'TemperatureMeasurement',
  [clusters.pressureMeasurement]: 'PressureMeasurement',
  [clusters.flowMeasurement]: 'FlowMeasurement',
  [clusters.relativeHumidityMeasurement]: 'RelativeHumidityMeasurement',
  [clusters.occupancySensing]: 'OccupancySensing',
};

const attributeNames: Record<number, Record<number, string>> = {
  [clusters.descriptor]: { 0: 'DeviceTypeList', 1: 'ServerList', 2: 'ClientList', 3: 'PartsList' },
  [clusters.basicInformation]: { 1: 'VendorName', 2: 'VendorID', 3: 'ProductName', 4: 'ProductID', 5: 'NodeLabel', 8: 'HardwareVersionString', 10: 'SoftwareVersionString', 14: 'ProductLabel', 15: 'SerialNumber' },
  [clusters.bridgedDeviceBasicInformation]: { 1: 'VendorName', 3: 'ProductName', 5: 'NodeLabel', 8: 'HardwareVersionString', 10: 'SoftwareVersionString', 14: 'ProductLabel', 15: 'SerialNumber', 17: 'Reachable' },
  [clusters.onOff]: { 0: 'OnOff' },
  [clusters.levelControl]: { 0: 'CurrentLevel', 2: 'MinLevel', 3: 'MaxLevel' },
  [clusters.colorControl]: { 0: 'CurrentHue', 1: 'CurrentSaturation', 3: 'CurrentX', 4: 'CurrentY', 7: 'ColorTemperatureMireds', 8: 'ColorMode' },
  [clusters.windowCovering]: { 0: 'Type', 10: 'OperationalStatus', 14: 'CurrentPositionLiftPercent100ths', 15: 'CurrentPositionTiltPercent100ths' },
  [clusters.doorLock]: { 0: 'LockState', 1: 'LockType', 2: 'ActuatorEnabled', 3: 'DoorState' },
  [clusters.thermostat]: { 0: 'LocalTemperature', 2: 'Occupancy', 17: 'OccupiedCoolingSetpoint', 18: 'OccupiedHeatingSetpoint', 27: 'ControlSequenceOfOperation', 28: 'SystemMode' },
  [clusters.powerSource]: { 12: 'BatPercentRemaining', 14: 'BatChargeLevel', 26: 'BatChargeState' },
  [clusters.booleanState]: { 0: 'StateValue' },
  [clusters.occupancySensing]: { 0: 'Occupancy' },
  [clusters.airQuality]: { 0: 'AirQuality' },
  [clusters.temperatureMeasurement]: { 0: 'MeasuredValue' },
  [clusters.relativeHumidityMeasurement]: { 0: 'MeasuredValue' },
  [clusters.illuminanceMeasurement]: { 0: 'MeasuredValue' },
  [clusters.pressureMeasurement]: { 0: 'MeasuredValue' },
  [clusters.flowMeasurement]: { 0: 'MeasuredValue' },
};

const lightDeviceTypes = new Set([0x0100, 0x0101, 0x010c, 0x010d]);
const plugDeviceTypes = new Set([0x010a, 0x010b]);
const rootOnlyDeviceTypes = new Set([0x0013, 0x0016]);
const infrastructureClusters = new Set<number>([clusters.descriptor, clusters.basicInformation, clusters.bridgedDeviceBasicInformation]);
const lockTimedRequestTimeoutMs = 10000;

export class MatterMapper {
  public static toSnapshot(configArg: IMatterConfig, connectedArg = false, eventsArg: IMatterServerEvent[] = []): IMatterSnapshot {
    if (configArg.snapshot) {
      return {
        ...configArg.snapshot,
        nodes: configArg.snapshot.nodes || [],
        events: [...(configArg.snapshot.events || []), ...eventsArg],
        connected: connectedArg || configArg.snapshot.connected,
        url: configArg.snapshot.url || this.urlFromConfig(configArg),
      };
    }
    return {
      serverInfo: configArg.serverInfo,
      nodes: configArg.nodes || [],
      events: [...(configArg.events || []), ...eventsArg],
      connected: connectedArg,
      url: this.urlFromConfig(configArg),
    };
  }

  public static toDevices(snapshotArg: IMatterSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = new Date().toISOString();
    const entities = this.toEntities(snapshotArg);
    const entitiesByDevice = new Map<string, IIntegrationEntity[]>();
    for (const entity of entities) {
      const list = entitiesByDevice.get(entity.deviceId) || [];
      list.push(entity);
      entitiesByDevice.set(entity.deviceId, list);
    }

    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [{
      id: this.controllerDeviceId(snapshotArg),
      integrationDomain: 'matter',
      name: 'Matter Server',
      protocol: 'matter',
      manufacturer: 'Matter',
      model: 'Matter Server',
      online: snapshotArg.connected || Boolean(snapshotArg.nodes.length),
      features: [
        { id: 'connection', capability: 'sensor', name: 'Connection', readable: true, writable: false },
        { id: 'node_count', capability: 'sensor', name: 'Node count', readable: true, writable: false },
      ],
      state: [
        { featureId: 'connection', value: snapshotArg.connected ? 'connected' : 'configured', updatedAt },
        { featureId: 'node_count', value: snapshotArg.nodes.length, updatedAt },
      ],
      metadata: {
        url: snapshotArg.url,
        schemaVersion: snapshotArg.serverInfo?.schema_version,
        sdkVersion: snapshotArg.serverInfo?.sdk_version,
        fabricId: this.safeStateValue(snapshotArg.serverInfo?.fabric_id),
        compressedFabricId: this.safeStateValue(snapshotArg.serverInfo?.compressed_fabric_id),
      },
    }];

    for (const node of snapshotArg.nodes) {
      let addedNodeDevice = false;
      for (const endpoint of this.nodeEndpoints(node)) {
        const endpointEntities = entitiesByDevice.get(this.endpointDeviceId(node, endpoint.endpointId)) || [];
        if (!endpointEntities.length && !this.isUsefulEndpoint(endpoint)) {
          continue;
        }
        devices.push(this.deviceForEndpoint(node, endpoint, endpointEntities, updatedAt));
        addedNodeDevice = true;
      }
      if (!addedNodeDevice) {
        devices.push(this.fallbackDeviceForNode(node, updatedAt));
      }
    }
    return devices;
  }

  public static toEntities(snapshotArg: IMatterSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    for (const node of snapshotArg.nodes) {
      for (const endpoint of this.nodeEndpoints(node)) {
        entities.push(...this.entitiesForEndpoint(node, endpoint));
      }
    }
    return entities;
  }

  public static toEndpoints(snapshotArg: IMatterSnapshot): IMatterEndpoint[] {
    return snapshotArg.nodes.flatMap((nodeArg) => this.nodeEndpoints(nodeArg));
  }

  public static commandForService(snapshotArg: IMatterSnapshot, requestArg: IServiceCallRequest): IMatterServerCommand<IMatterServiceCommandArgs> | undefined {
    const target = this.findTargetEntity(snapshotArg, requestArg);
    if (!target) {
      return undefined;
    }

    const platform = String(target.platform);
    if (requestArg.service === 'turn_on' && (platform === 'light' || platform === 'switch')) {
      return this.deviceCommand(target, clusters.onOff, 'On');
    }
    if (requestArg.service === 'turn_off' && (platform === 'light' || platform === 'switch')) {
      return this.deviceCommand(target, clusters.onOff, 'Off');
    }
    if (requestArg.service === 'open_cover' && platform === 'cover') {
      return this.deviceCommand(target, clusters.windowCovering, 'UpOrOpen');
    }
    if (requestArg.service === 'close_cover' && platform === 'cover') {
      return this.deviceCommand(target, clusters.windowCovering, 'DownOrClose');
    }
    if (requestArg.service === 'set_position' && platform === 'cover') {
      const position = this.numberValue(requestArg.data?.position ?? requestArg.data?.value);
      if (position === undefined) {
        return undefined;
      }
      return this.deviceCommand(target, clusters.windowCovering, 'GoToLiftPercentage', {
        liftPercent100thsValue: (100 - this.clamp(position, 0, 100)) * 100,
      });
    }
    if (requestArg.service === 'lock' && platform === 'lock') {
      return this.lockCommand(target, 'LockDoor', requestArg.data?.code);
    }
    if (requestArg.service === 'unlock' && platform === 'lock') {
      return this.lockCommand(target, 'UnlockDoor', requestArg.data?.code);
    }
    if (requestArg.service === 'set_value') {
      return this.setValueCommand(target, requestArg.data || {});
    }
    return undefined;
  }

  public static nodeEndpoints(nodeArg: IMatterNode): IMatterEndpoint[] {
    const attributesByEndpoint = new Map<number, IMatterAttribute[]>();
    for (const [path, value] of Object.entries(nodeArg.attributes || {})) {
      const parsed = this.parseAttributePath(path);
      if (!parsed) {
        continue;
      }
      const attribute: IMatterAttribute = {
        path,
        endpointId: parsed.endpointId,
        clusterId: parsed.clusterId,
        attributeId: parsed.attributeId,
        clusterName: this.clusterName(parsed.clusterId),
        attributeName: this.attributeName(parsed.clusterId, parsed.attributeId),
        value,
        readable: true,
        writable: this.attributeWritable(parsed.clusterId, parsed.attributeId),
      };
      const list = attributesByEndpoint.get(parsed.endpointId) || [];
      list.push(attribute);
      attributesByEndpoint.set(parsed.endpointId, list);
    }

    const rootAttributes = attributesByEndpoint.get(0) || [];
    return [...attributesByEndpoint.entries()]
      .sort(([left], [right]) => left - right)
      .map(([endpointId, attributes]) => {
        const serverList = this.numberList(this.attributeValue(attributes, clusters.descriptor, 1));
        const clusterIds = new Set<number>(serverList);
        for (const attribute of attributes) {
          clusterIds.add(attribute.clusterId);
        }
        const endpoint: IMatterEndpoint = {
          nodeId: nodeArg.node_id,
          endpointId,
          attributes,
          deviceTypes: this.deviceTypesFromValue(this.attributeValue(attributes, clusters.descriptor, 0)),
          clusters: [],
          deviceInfo: this.deviceInfo(rootAttributes, attributes),
        };
        endpoint.clusters = [...clusterIds].sort((left, right) => left - right).map((clusterId) => ({
          id: clusterId,
          name: this.clusterName(clusterId),
          attributes: attributes.filter((attributeArg) => attributeArg.clusterId === clusterId),
        } satisfies IMatterCluster));
        return endpoint;
      });
  }

  private static entitiesForEndpoint(nodeArg: IMatterNode, endpointArg: IMatterEndpoint): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    const hasOnOff = this.hasCluster(endpointArg, clusters.onOff) && this.hasAttribute(endpointArg, clusters.onOff, 0);
    if (hasOnOff && this.isLightEndpoint(endpointArg)) {
      entities.push(this.lightEntity(nodeArg, endpointArg));
    } else if (hasOnOff) {
      entities.push(this.switchEntity(nodeArg, endpointArg));
    }
    if (this.hasCluster(endpointArg, clusters.windowCovering)) {
      entities.push(this.coverEntity(nodeArg, endpointArg));
    }
    if (this.hasCluster(endpointArg, clusters.doorLock) && this.hasAttribute(endpointArg, clusters.doorLock, 0)) {
      entities.push(this.lockEntity(nodeArg, endpointArg));
    }
    if (this.hasCluster(endpointArg, clusters.thermostat)) {
      entities.push(this.climateEntity(nodeArg, endpointArg));
    }
    entities.push(...this.binarySensorEntities(nodeArg, endpointArg));
    entities.push(...this.sensorEntities(nodeArg, endpointArg));
    return entities;
  }

  private static lightEntity(nodeArg: IMatterNode, endpointArg: IMatterEndpoint): IIntegrationEntity {
    const onOff = this.attributeValue(endpointArg.attributes, clusters.onOff, 0);
    const currentLevel = this.numberValue(this.attributeValue(endpointArg.attributes, clusters.levelControl, 0));
    const minLevel = this.numberValue(this.attributeValue(endpointArg.attributes, clusters.levelControl, 2)) || 1;
    const maxLevel = this.numberValue(this.attributeValue(endpointArg.attributes, clusters.levelControl, 3)) || 254;
    const brightness = currentLevel === undefined ? undefined : Math.round(this.renormalize(currentLevel, [minLevel, maxLevel], [1, 255]));
    return this.entity(nodeArg, endpointArg, 'light', 'light', 'Light', onOff === true ? 'on' : onOff === false ? 'off' : 'unknown', clusters.onOff, 0, {
      brightness,
      colorMode: this.attributeValue(endpointArg.attributes, clusters.colorControl, 8),
      colorTemperatureMireds: this.attributeValue(endpointArg.attributes, clusters.colorControl, 7),
      currentHue: this.attributeValue(endpointArg.attributes, clusters.colorControl, 0),
      currentSaturation: this.attributeValue(endpointArg.attributes, clusters.colorControl, 1),
      writable: true,
      commands: ['On', 'Off', 'Toggle', 'MoveToLevelWithOnOff'],
    }, true);
  }

  private static switchEntity(nodeArg: IMatterNode, endpointArg: IMatterEndpoint): IIntegrationEntity {
    const onOff = this.attributeValue(endpointArg.attributes, clusters.onOff, 0);
    return this.entity(nodeArg, endpointArg, 'switch', 'switch', 'Switch', onOff === true ? 'on' : onOff === false ? 'off' : 'unknown', clusters.onOff, 0, {
      writable: true,
      commands: ['On', 'Off', 'Toggle'],
    }, true);
  }

  private static coverEntity(nodeArg: IMatterNode, endpointArg: IMatterEndpoint): IIntegrationEntity {
    const operationalStatus = this.numberValue(this.attributeValue(endpointArg.attributes, clusters.windowCovering, 10)) || 0;
    const rawPosition = this.numberValue(this.attributeValue(endpointArg.attributes, clusters.windowCovering, 14));
    const rawTilt = this.numberValue(this.attributeValue(endpointArg.attributes, clusters.windowCovering, 15));
    const position = rawPosition === undefined ? undefined : 100 - Math.floor(rawPosition / 100);
    const tiltPosition = rawTilt === undefined ? undefined : 100 - Math.floor(rawTilt / 100);
    let state = 'idle';
    if ((operationalStatus & 0b11) === 0b01) {
      state = 'opening';
    } else if ((operationalStatus & 0b11) === 0b10) {
      state = 'closing';
    } else if (position !== undefined) {
      state = position <= 0 ? 'closed' : 'open';
    }
    return this.entity(nodeArg, endpointArg, 'cover', 'cover', 'Cover', state, clusters.windowCovering, 10, {
      position,
      tiltPosition,
      coverType: this.attributeValue(endpointArg.attributes, clusters.windowCovering, 0),
      writable: true,
      commands: ['UpOrOpen', 'DownOrClose', 'StopMotion', 'GoToLiftPercentage', 'GoToTiltPercentage'],
    }, true);
  }

  private static lockEntity(nodeArg: IMatterNode, endpointArg: IMatterEndpoint): IIntegrationEntity {
    const lockState = this.numberValue(this.attributeValue(endpointArg.attributes, clusters.doorLock, 0));
    const state = lockState === 1 ? 'locked' : lockState === 2 ? 'unlocked' : lockState === 3 ? 'open' : 'unknown';
    return this.entity(nodeArg, endpointArg, 'lock', 'lock', 'Lock', state, clusters.doorLock, 0, {
      lockState,
      lockType: this.attributeValue(endpointArg.attributes, clusters.doorLock, 1),
      actuatorEnabled: this.attributeValue(endpointArg.attributes, clusters.doorLock, 2),
      writable: true,
      commands: ['LockDoor', 'UnlockDoor'],
    }, true);
  }

  private static climateEntity(nodeArg: IMatterNode, endpointArg: IMatterEndpoint): IIntegrationEntity {
    const mode = this.numberValue(this.attributeValue(endpointArg.attributes, clusters.thermostat, 28));
    const heatingPath = this.attributePath(endpointArg.endpointId, clusters.thermostat, 18);
    const coolingPath = this.attributePath(endpointArg.endpointId, clusters.thermostat, 17);
    return this.entity(nodeArg, endpointArg, 'climate', 'climate', 'Climate', this.thermostatMode(mode), clusters.thermostat, 0, {
      currentTemperature: this.temperatureValue(this.attributeValue(endpointArg.attributes, clusters.thermostat, 0)),
      occupiedCoolingSetpoint: this.temperatureValue(this.attributeValue(endpointArg.attributes, clusters.thermostat, 17)),
      occupiedHeatingSetpoint: this.temperatureValue(this.attributeValue(endpointArg.attributes, clusters.thermostat, 18)),
      systemMode: mode,
      writable: this.hasAttribute(endpointArg, clusters.thermostat, 17) || this.hasAttribute(endpointArg, clusters.thermostat, 18),
      writableAttributePath: this.hasAttribute(endpointArg, clusters.thermostat, 18) ? heatingPath : coolingPath,
    }, this.hasAttribute(endpointArg, clusters.thermostat, 17) || this.hasAttribute(endpointArg, clusters.thermostat, 18));
  }

  private static binarySensorEntities(nodeArg: IMatterNode, endpointArg: IMatterEndpoint): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    if (this.hasAttribute(endpointArg, clusters.occupancySensing, 0)) {
      const value = this.numberValue(this.attributeValue(endpointArg.attributes, clusters.occupancySensing, 0));
      entities.push(this.entity(nodeArg, endpointArg, 'binary_sensor', 'occupancy', 'Occupancy', value === undefined ? undefined : (value & 1) === 1, clusters.occupancySensing, 0, { deviceClass: 'occupancy' }));
    }
    if (this.hasAttribute(endpointArg, clusters.booleanState, 0)) {
      const value = this.attributeValue(endpointArg.attributes, clusters.booleanState, 0);
      const sensorType = this.booleanSensorType(endpointArg);
      entities.push(this.entity(nodeArg, endpointArg, 'binary_sensor', sensorType.key, sensorType.name, sensorType.inverted ? !Boolean(value) : Boolean(value), clusters.booleanState, 0, { deviceClass: sensorType.deviceClass }));
    }
    if (this.hasAttribute(endpointArg, clusters.doorLock, 3)) {
      const doorState = this.numberValue(this.attributeValue(endpointArg.attributes, clusters.doorLock, 3));
      const isOpen = doorState === undefined ? undefined : [0, 2, 3, 5].includes(doorState);
      entities.push(this.entity(nodeArg, endpointArg, 'binary_sensor', 'door', 'Door', isOpen, clusters.doorLock, 3, { deviceClass: 'door' }));
    }
    return entities;
  }

  private static sensorEntities(nodeArg: IMatterNode, endpointArg: IMatterEndpoint): IIntegrationEntity[] {
    const definitions: Array<{ clusterId: number; attributeId: number; key: string; name: string; unit?: string; deviceClass?: string; transform?: (valueArg: unknown) => unknown }> = [
      { clusterId: clusters.temperatureMeasurement, attributeId: 0, key: 'temperature', name: 'Temperature', unit: '°C', deviceClass: 'temperature', transform: (valueArg) => this.temperatureValue(valueArg) },
      { clusterId: clusters.relativeHumidityMeasurement, attributeId: 0, key: 'humidity', name: 'Humidity', unit: '%', deviceClass: 'humidity', transform: (valueArg) => this.scaledNumber(valueArg, 100) },
      { clusterId: clusters.illuminanceMeasurement, attributeId: 0, key: 'illuminance', name: 'Illuminance', unit: 'lx', deviceClass: 'illuminance', transform: (valueArg) => this.illuminanceValue(valueArg) },
      { clusterId: clusters.pressureMeasurement, attributeId: 0, key: 'pressure', name: 'Pressure', deviceClass: 'pressure' },
      { clusterId: clusters.flowMeasurement, attributeId: 0, key: 'flow', name: 'Flow' },
      { clusterId: clusters.powerSource, attributeId: 12, key: 'battery', name: 'Battery', unit: '%', deviceClass: 'battery', transform: (valueArg) => this.scaledNumber(valueArg, 2) },
      { clusterId: clusters.airQuality, attributeId: 0, key: 'air_quality', name: 'Air quality', transform: (valueArg) => this.airQualityValue(valueArg) },
    ];
    const entities: IIntegrationEntity[] = [];
    for (const definition of definitions) {
      if (!this.hasAttribute(endpointArg, definition.clusterId, definition.attributeId)) {
        continue;
      }
      const rawValue = this.attributeValue(endpointArg.attributes, definition.clusterId, definition.attributeId);
      entities.push(this.entity(nodeArg, endpointArg, 'sensor', definition.key, definition.name, definition.transform ? definition.transform(rawValue) : rawValue, definition.clusterId, definition.attributeId, {
        unit: definition.unit,
        deviceClass: definition.deviceClass,
      }));
    }
    return entities;
  }

  private static entity(nodeArg: IMatterNode, endpointArg: IMatterEndpoint, platformArg: TMatterEntityPlatform, keyArg: string, nameArg: string, stateArg: unknown, clusterIdArg: number, attributeIdArg: number, attributesArg: Record<string, unknown> = {}, writableArg = false): IIntegrationEntity {
    const deviceName = this.endpointName(nodeArg, endpointArg);
    const featureId = this.slug(`${keyArg}_${clusterIdArg}_${attributeIdArg}`);
    return {
      id: `${platformArg}.${this.slug(`${deviceName} ${nameArg}`)}`,
      uniqueId: `matter_${this.slug(`${String(nodeArg.node_id)}_${endpointArg.endpointId}_${keyArg}_${clusterIdArg}_${attributeIdArg}`)}`,
      integrationDomain: 'matter',
      deviceId: this.endpointDeviceId(nodeArg, endpointArg.endpointId),
      platform: platformArg as TEntityPlatform,
      name: nameArg,
      state: stateArg ?? 'unknown',
      attributes: {
        ...attributesArg,
        nodeId: nodeArg.node_id,
        endpointId: endpointArg.endpointId,
        clusterId: clusterIdArg,
        attributeId: attributeIdArg,
        attributePath: this.attributePath(endpointArg.endpointId, clusterIdArg, attributeIdArg),
        clusterName: this.clusterName(clusterIdArg),
        attributeName: this.attributeName(clusterIdArg, attributeIdArg),
        featureId,
        readable: true,
        writable: writableArg,
      },
      available: nodeArg.available !== false && this.reachable(endpointArg),
    };
  }

  private static deviceForEndpoint(nodeArg: IMatterNode, endpointArg: IMatterEndpoint, entitiesArg: IIntegrationEntity[], updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'availability', capability: 'sensor', name: 'Availability', readable: true, writable: false },
      ...entitiesArg.map((entityArg) => this.featureForEntity(entityArg)),
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'availability', value: nodeArg.available !== false && this.reachable(endpointArg) ? 'online' : 'offline', updatedAt: updatedAtArg },
      ...entitiesArg.map((entityArg) => ({ featureId: this.stringValue(entityArg.attributes?.featureId) || this.slug(entityArg.uniqueId), value: this.safeStateValue(entityArg.state), updatedAt: updatedAtArg })),
    ];
    return {
      id: this.endpointDeviceId(nodeArg, endpointArg.endpointId),
      integrationDomain: 'matter',
      name: this.endpointName(nodeArg, endpointArg),
      protocol: 'matter',
      manufacturer: endpointArg.deviceInfo.vendorName,
      model: endpointArg.deviceInfo.productLabel || endpointArg.deviceInfo.productName || endpointArg.deviceTypes[0]?.name,
      online: nodeArg.available !== false && this.reachable(endpointArg),
      features,
      state,
      metadata: {
        nodeId: this.safeStateValue(nodeArg.node_id),
        endpointId: endpointArg.endpointId,
        deviceTypes: endpointArg.deviceTypes.map((typeArg) => ({ id: typeArg.id, name: typeArg.name, revision: typeArg.revision })),
        vendorId: endpointArg.deviceInfo.vendorId,
        productId: endpointArg.deviceInfo.productId,
        serialNumber: endpointArg.deviceInfo.serialNumber,
        hardwareVersion: endpointArg.deviceInfo.hardwareVersionString,
        softwareVersion: endpointArg.deviceInfo.softwareVersionString,
        matterVersion: nodeArg.matter_version,
      },
    };
  }

  private static fallbackDeviceForNode(nodeArg: IMatterNode, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    const endpoint = this.nodeEndpoints(nodeArg)[0];
    return {
      id: `matter.node.${this.slug(String(nodeArg.node_id))}`,
      integrationDomain: 'matter',
      name: endpoint ? this.endpointName(nodeArg, endpoint) : `Matter node ${String(nodeArg.node_id)}`,
      protocol: 'matter',
      manufacturer: endpoint?.deviceInfo.vendorName,
      model: endpoint?.deviceInfo.productName,
      online: nodeArg.available !== false,
      features: [{ id: 'availability', capability: 'sensor', name: 'Availability', readable: true, writable: false }],
      state: [{ featureId: 'availability', value: nodeArg.available !== false ? 'online' : 'offline', updatedAt: updatedAtArg }],
      metadata: { nodeId: this.safeStateValue(nodeArg.node_id), matterVersion: nodeArg.matter_version },
    };
  }

  private static featureForEntity(entityArg: IIntegrationEntity): plugins.shxInterfaces.data.IDeviceFeature {
    return {
      id: this.stringValue(entityArg.attributes?.featureId) || this.slug(entityArg.uniqueId),
      capability: this.capabilityForPlatform(String(entityArg.platform)),
      name: entityArg.name,
      readable: entityArg.attributes?.readable !== false,
      writable: entityArg.attributes?.writable === true,
      unit: this.stringValue(entityArg.attributes?.unit),
    };
  }

  private static capabilityForPlatform(platformArg: string): plugins.shxInterfaces.data.TDeviceCapability {
    if (platformArg === 'light') {
      return 'light';
    }
    if (platformArg === 'cover') {
      return 'cover';
    }
    if (platformArg === 'climate') {
      return 'climate';
    }
    if (platformArg === 'fan') {
      return 'fan';
    }
    return platformArg === 'lock' || platformArg === 'switch' ? 'switch' : 'sensor';
  }

  private static findTargetEntity(snapshotArg: IMatterSnapshot, 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 && entityArg.attributes?.writable === true)
        || entities.find((entityArg) => entityArg.deviceId === requestArg.target.deviceId);
    }
    return entities.find((entityArg) => entityArg.attributes?.writable === true);
  }

  private static setValueCommand(entityArg: IIntegrationEntity, dataArg: Record<string, unknown>): IMatterServerCommand<IMatterServiceCommandArgs> | undefined {
    const platform = String(entityArg.platform);
    const value = dataArg.value ?? dataArg.brightness ?? dataArg.temperature;
    if ((platform === 'light' || platform === 'switch') && typeof value === 'boolean') {
      return this.deviceCommand(entityArg, clusters.onOff, value ? 'On' : 'Off');
    }
    if (platform === 'light') {
      const brightness = this.numberValue(dataArg.brightness ?? dataArg.value);
      if (brightness !== undefined) {
        return this.deviceCommand(entityArg, clusters.levelControl, 'MoveToLevelWithOnOff', {
          level: this.clamp(Math.round(this.renormalize(brightness, [0, 255], [1, 254])), 1, 254),
          transitionTime: this.numberValue(dataArg.transition) ? Math.round((this.numberValue(dataArg.transition) || 0) * 10) : 0,
        });
      }
    }
    if (platform === 'lock' && typeof value === 'boolean') {
      return this.lockCommand(entityArg, value ? 'LockDoor' : 'UnlockDoor', dataArg.code);
    }
    const nodeId = this.nodeIdFromEntity(entityArg);
    const attributePath = this.stringValue(dataArg.attribute_path)
      || this.stringValue(dataArg.attributePath)
      || this.stringValue(entityArg.attributes?.writableAttributePath)
      || this.stringValue(entityArg.attributes?.attributePath);
    if (nodeId === undefined || !attributePath || value === undefined) {
      return undefined;
    }
    const writeValue = platform === 'climate' && typeof value === 'number' ? Math.round(value * 100) : value;
    return {
      command: 'write_attribute',
      requireSchema: 4,
      args: {
        node_id: nodeId,
        attribute_path: attributePath,
        value: writeValue,
      },
    };
  }

  private static deviceCommand(entityArg: IIntegrationEntity, clusterIdArg: number, commandNameArg: string, payloadArg: Record<string, unknown> = {}): IMatterServerCommand<IMatterServiceCommandArgs> | undefined {
    const nodeId = this.nodeIdFromEntity(entityArg);
    const endpointId = this.numberValue(entityArg.attributes?.endpointId);
    if (nodeId === undefined || endpointId === undefined) {
      return undefined;
    }
    return {
      command: 'device_command',
      args: {
        node_id: nodeId,
        endpoint_id: endpointId,
        cluster_id: clusterIdArg,
        command_name: commandNameArg,
        payload: payloadArg,
        response_type: null,
      },
    };
  }

  private static lockCommand(entityArg: IIntegrationEntity, commandNameArg: string, codeArg: unknown): IMatterServerCommand<IMatterServiceCommandArgs> | undefined {
    const payload: Record<string, unknown> = {};
    if (typeof codeArg === 'string' && codeArg) {
      payload.PINCode = codeArg;
    }
    const command = this.deviceCommand(entityArg, clusters.doorLock, commandNameArg, payload);
    if (command?.args) {
      command.args.timed_request_timeout_ms = lockTimedRequestTimeoutMs;
    }
    return command;
  }

  private static deviceInfo(rootAttributesArg: IMatterAttribute[], attributesArg: IMatterAttribute[]): IMatterDeviceInfo {
    const endpointInfoCluster = this.hasAttributeList(attributesArg, clusters.bridgedDeviceBasicInformation) ? clusters.bridgedDeviceBasicInformation : clusters.basicInformation;
    return {
      vendorName: this.stringValue(this.attributeValue(attributesArg, endpointInfoCluster, 1)) || this.stringValue(this.attributeValue(rootAttributesArg, clusters.basicInformation, 1)),
      vendorId: this.numberValue(this.attributeValue(attributesArg, endpointInfoCluster, 2)) || this.numberValue(this.attributeValue(rootAttributesArg, clusters.basicInformation, 2)),
      productName: this.stringValue(this.attributeValue(attributesArg, endpointInfoCluster, 3)) || this.stringValue(this.attributeValue(rootAttributesArg, clusters.basicInformation, 3)),
      productId: this.numberValue(this.attributeValue(attributesArg, endpointInfoCluster, 4)) || this.numberValue(this.attributeValue(rootAttributesArg, clusters.basicInformation, 4)),
      nodeLabel: this.cleanName(this.stringValue(this.attributeValue(attributesArg, endpointInfoCluster, 5))) || this.cleanName(this.stringValue(this.attributeValue(rootAttributesArg, clusters.basicInformation, 5))),
      hardwareVersionString: this.stringValue(this.attributeValue(attributesArg, endpointInfoCluster, 8)) || this.stringValue(this.attributeValue(rootAttributesArg, clusters.basicInformation, 8)),
      softwareVersionString: this.stringValue(this.attributeValue(attributesArg, endpointInfoCluster, 10)) || this.stringValue(this.attributeValue(rootAttributesArg, clusters.basicInformation, 10)),
      productLabel: this.cleanName(this.stringValue(this.attributeValue(attributesArg, endpointInfoCluster, 14))) || this.cleanName(this.stringValue(this.attributeValue(rootAttributesArg, clusters.basicInformation, 14))),
      serialNumber: this.stringValue(this.attributeValue(attributesArg, endpointInfoCluster, 15)) || this.stringValue(this.attributeValue(rootAttributesArg, clusters.basicInformation, 15)),
    };
  }

  private static deviceTypesFromValue(valueArg: unknown): IMatterDeviceType[] {
    if (!Array.isArray(valueArg)) {
      return [];
    }
    return valueArg.map((itemArg) => {
      if (typeof itemArg === 'number') {
        return { id: itemArg, name: deviceTypes[itemArg] || `Device type ${itemArg}`, raw: itemArg };
      }
      const item = this.asRecord(itemArg);
      const id = this.numberValue(item.deviceType ?? item.device_type ?? item.deviceTypeId ?? item.id) || 0;
      return {
        id,
        name: deviceTypes[id] || `Device type ${id}`,
        revision: this.numberValue(item.revision),
        raw: itemArg,
      };
    }).filter((typeArg) => typeArg.id > 0);
  }

  private static booleanSensorType(endpointArg: IMatterEndpoint): { key: string; name: string; deviceClass: string; inverted?: boolean } {
    if (this.hasDeviceType(endpointArg, [0x0015])) {
      return { key: 'contact', name: 'Contact', deviceClass: 'door', inverted: true };
    }
    if (this.hasDeviceType(endpointArg, [0x0043])) {
      return { key: 'water_leak', name: 'Water leak', deviceClass: 'moisture' };
    }
    if (this.hasDeviceType(endpointArg, [0x0041])) {
      return { key: 'freeze', name: 'Freeze', deviceClass: 'cold' };
    }
    if (this.hasDeviceType(endpointArg, [0x0044])) {
      return { key: 'rain', name: 'Rain', deviceClass: 'moisture' };
    }
    return { key: 'state', name: 'State', deviceClass: 'problem' };
  }

  private static isLightEndpoint(endpointArg: IMatterEndpoint): boolean {
    if (endpointArg.deviceTypes.some((typeArg) => lightDeviceTypes.has(typeArg.id))) {
      return true;
    }
    if (endpointArg.deviceTypes.some((typeArg) => plugDeviceTypes.has(typeArg.id))) {
      return false;
    }
    const name = `${endpointArg.deviceInfo.nodeLabel || ''} ${endpointArg.deviceInfo.productName || ''} ${endpointArg.deviceTypes.map((typeArg) => typeArg.name).join(' ')}`.toLowerCase();
    return name.includes('light') && (this.hasCluster(endpointArg, clusters.levelControl) || this.hasCluster(endpointArg, clusters.colorControl));
  }

  private static isUsefulEndpoint(endpointArg: IMatterEndpoint): boolean {
    if (endpointArg.endpointId === 0) {
      return false;
    }
    return endpointArg.deviceTypes.some((typeArg) => !rootOnlyDeviceTypes.has(typeArg.id))
      || endpointArg.clusters.some((clusterArg) => !infrastructureClusters.has(clusterArg.id));
  }

  private static reachable(endpointArg: IMatterEndpoint): boolean {
    const value = this.attributeValue(endpointArg.attributes, clusters.bridgedDeviceBasicInformation, 17);
    return typeof value === 'boolean' ? value : true;
  }

  private static endpointName(nodeArg: IMatterNode, endpointArg: IMatterEndpoint): string {
    return endpointArg.deviceInfo.nodeLabel
      || endpointArg.deviceInfo.productLabel
      || endpointArg.deviceInfo.productName
      || endpointArg.deviceTypes.find((typeArg) => !rootOnlyDeviceTypes.has(typeArg.id))?.name
      || `Matter node ${String(nodeArg.node_id)}${endpointArg.endpointId ? ` endpoint ${endpointArg.endpointId}` : ''}`;
  }

  private static endpointDeviceId(nodeArg: IMatterNode, endpointIdArg: number): string {
    return `matter.node.${this.slug(String(nodeArg.node_id))}.endpoint.${endpointIdArg}`;
  }

  private static controllerDeviceId(snapshotArg: IMatterSnapshot): string {
    return `matter.server.${this.slug(String(snapshotArg.serverInfo?.compressed_fabric_id || snapshotArg.url || 'local'))}`;
  }

  private static nodeIdFromEntity(entityArg: IIntegrationEntity): TMatterNodeId | undefined {
    const value = entityArg.attributes?.nodeId;
    if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'string') {
      return value;
    }
    return undefined;
  }

  private static parseAttributePath(pathArg: string): { endpointId: number; clusterId: number; attributeId: number } | undefined {
    const parts = pathArg.split('/').map((partArg) => Number(partArg));
    if (parts.length !== 3 || parts.some((partArg) => !Number.isFinite(partArg))) {
      return undefined;
    }
    return { endpointId: parts[0], clusterId: parts[1], attributeId: parts[2] };
  }

  private static attributeValue(attributesArg: IMatterAttribute[], clusterIdArg: number, attributeIdArg: number): unknown {
    return attributesArg.find((attributeArg) => attributeArg.clusterId === clusterIdArg && attributeArg.attributeId === attributeIdArg)?.value;
  }

  private static hasAttribute(endpointArg: IMatterEndpoint, clusterIdArg: number, attributeIdArg: number): boolean {
    return endpointArg.attributes.some((attributeArg) => attributeArg.clusterId === clusterIdArg && attributeArg.attributeId === attributeIdArg);
  }

  private static hasAttributeList(attributesArg: IMatterAttribute[], clusterIdArg: number): boolean {
    return attributesArg.some((attributeArg) => attributeArg.clusterId === clusterIdArg);
  }

  private static hasCluster(endpointArg: IMatterEndpoint, clusterIdArg: number): boolean {
    return endpointArg.clusters.some((clusterArg) => clusterArg.id === clusterIdArg);
  }

  private static hasDeviceType(endpointArg: IMatterEndpoint, typeIdsArg: number[]): boolean {
    return endpointArg.deviceTypes.some((typeArg) => typeIdsArg.includes(typeArg.id));
  }

  private static numberList(valueArg: unknown): number[] {
    if (!Array.isArray(valueArg)) {
      return [];
    }
    return valueArg.map((itemArg) => this.numberValue(itemArg)).filter((itemArg): itemArg is number => itemArg !== undefined);
  }

  private static attributeWritable(clusterIdArg: number, attributeIdArg: number): boolean {
    return clusterIdArg === clusters.thermostat && [17, 18].includes(attributeIdArg);
  }

  private static attributePath(endpointIdArg: number, clusterIdArg: number, attributeIdArg: number): string {
    return `${endpointIdArg}/${clusterIdArg}/${attributeIdArg}`;
  }

  private static clusterName(clusterIdArg: number): string {
    return clusterNames[clusterIdArg] || `Cluster ${clusterIdArg}`;
  }

  private static attributeName(clusterIdArg: number, attributeIdArg: number): string {
    return attributeNames[clusterIdArg]?.[attributeIdArg]
      || (attributeIdArg === globalAttributes.acceptedCommandList ? 'AcceptedCommandList' : undefined)
      || (attributeIdArg === globalAttributes.attributeList ? 'AttributeList' : undefined)
      || (attributeIdArg === globalAttributes.featureMap ? 'FeatureMap' : undefined)
      || (attributeIdArg === globalAttributes.clusterRevision ? 'ClusterRevision' : undefined)
      || `Attribute ${attributeIdArg}`;
  }

  private static thermostatMode(valueArg?: number): string {
    return ({ 0: 'off', 1: 'auto', 3: 'cool', 4: 'heat', 5: 'emergency_heat', 7: 'fan_only', 8: 'dry', 9: 'sleep' } as Record<number, string>)[valueArg ?? -1] || 'unknown';
  }

  private static airQualityValue(valueArg: unknown): unknown {
    const value = this.numberValue(valueArg);
    return ({ 0: 'unknown', 1: 'good', 2: 'fair', 3: 'moderate', 4: 'poor', 5: 'very_poor', 6: 'extremely_poor' } as Record<number, string>)[value ?? -1] || valueArg;
  }

  private static temperatureValue(valueArg: unknown): number | undefined {
    return this.scaledNumber(valueArg, 100);
  }

  private static illuminanceValue(valueArg: unknown): number | undefined {
    const value = this.numberValue(valueArg);
    if (value === undefined) {
      return undefined;
    }
    return Math.round(Math.pow(10, (value - 1) / 10000) * 100) / 100;
  }

  private static scaledNumber(valueArg: unknown, divisorArg: number): number | undefined {
    const value = this.numberValue(valueArg);
    return value === undefined ? undefined : Math.round((value / divisorArg) * 100) / 100;
  }

  private static renormalize(valueArg: number, fromArg: [number, number], toArg: [number, number]): number {
    const fromSpan = fromArg[1] - fromArg[0] || 1;
    return ((valueArg - fromArg[0]) / fromSpan) * (toArg[1] - toArg[0]) + toArg[0];
  }

  private static clamp(valueArg: number, minArg: number, maxArg: number): number {
    return Math.max(minArg, Math.min(maxArg, valueArg));
  }

  private static urlFromConfig(configArg: IMatterConfig): string | undefined {
    if (configArg.url) {
      return configArg.url;
    }
    if (configArg.host) {
      return `ws://${configArg.host}:${configArg.port || 5580}/ws`;
    }
    return undefined;
  }

  private static cleanName(valueArg?: string): string | undefined {
    const cleaned = valueArg?.replace(/\x00/g, '').trim();
    return cleaned || undefined;
  }

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

  private static numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg === 'bigint' && valueArg <= BigInt(Number.MAX_SAFE_INTEGER) && valueArg >= BigInt(Number.MIN_SAFE_INTEGER)) {
      return Number(valueArg);
    }
    if (typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg))) {
      return Number(valueArg);
    }
    return undefined;
  }

  private static safeStateValue(valueArg: unknown): plugins.shxInterfaces.data.TDeviceStateValue {
    if (typeof valueArg === 'bigint') {
      return valueArg.toString();
    }
    if (Array.isArray(valueArg)) {
      return JSON.stringify(valueArg);
    }
    if (typeof valueArg === 'string' || typeof valueArg === 'number' || typeof valueArg === 'boolean' || valueArg === null || this.isRecord(valueArg)) {
      return valueArg;
    }
    return valueArg === undefined ? null : String(valueArg);
  }

  private static asRecord(valueArg: unknown): Record<string, unknown> {
    return this.isRecord(valueArg) ? valueArg : {};
  }

  private static isRecord(valueArg: unknown): valueArg is Record<string, unknown> {
    return typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
  }

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