import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type {
  IBondCommandRequest,
  IBondConfig,
  IBondDeviceRaw,
  IBondDeviceSnapshot,
  IBondRawData,
  IBondSnapshot,
  TBondSnapshotSource,
} from './bond.types.js';
import { bondActions, bondDefaultPort, bondDeviceTypes, bondDisplayName, bondDomain } from './bond.types.js';

interface IBondSnapshotOptions {
  config: IBondConfig;
  rawData?: Partial<IBondRawData>;
  online?: boolean;
  source?: TBondSnapshotSource;
  error?: string;
}

interface IBondEntityDefinition {
  key: string;
  platform: TEntityPlatform;
  name: string;
  state: unknown;
  subDevice?: string;
  available?: boolean;
  attributes?: Record<string, unknown>;
}

interface IBondButtonDescription {
  key: string;
  name: string;
  mutuallyExclusive?: string;
  argument?: number;
}

const buttonStepSize = 10;

const buttonDescriptions: IBondButtonDescription[] = [
  { key: bondActions.togglePower, name: 'Toggle Power', mutuallyExclusive: bondActions.turnOn },
  { key: bondActions.toggleLight, name: 'Toggle Light', mutuallyExclusive: bondActions.turnLightOn },
  { key: bondActions.increaseBrightness, name: 'Increase Brightness', mutuallyExclusive: bondActions.setBrightness, argument: buttonStepSize },
  { key: bondActions.decreaseBrightness, name: 'Decrease Brightness', mutuallyExclusive: bondActions.setBrightness, argument: buttonStepSize },
  { key: bondActions.toggleUpLight, name: 'Toggle Up Light', mutuallyExclusive: bondActions.turnUpLightOn },
  { key: bondActions.toggleDownLight, name: 'Toggle Down Light', mutuallyExclusive: bondActions.turnDownLightOn },
  { key: bondActions.startDimmer, name: 'Start Dimmer', mutuallyExclusive: bondActions.setBrightness },
  { key: bondActions.toggleLightTemp, name: 'Toggle Light Temperature' },
  { key: bondActions.startUpLightDimmer, name: 'Start Up Light Dimmer', mutuallyExclusive: bondActions.setUpLightBrightness },
  { key: bondActions.startDownLightDimmer, name: 'Start Down Light Dimmer', mutuallyExclusive: bondActions.setDownLightBrightness },
  { key: bondActions.startIncreasingBrightness, name: 'Start Increasing Brightness', mutuallyExclusive: bondActions.setBrightness },
  { key: bondActions.startDecreasingBrightness, name: 'Start Decreasing Brightness', mutuallyExclusive: bondActions.setBrightness },
  { key: bondActions.increaseUpLightBrightness, name: 'Increase Up Light Brightness', mutuallyExclusive: bondActions.setUpLightBrightness, argument: buttonStepSize },
  { key: bondActions.decreaseUpLightBrightness, name: 'Decrease Up Light Brightness', mutuallyExclusive: bondActions.setUpLightBrightness, argument: buttonStepSize },
  { key: bondActions.increaseDownLightBrightness, name: 'Increase Down Light Brightness', mutuallyExclusive: bondActions.setDownLightBrightness, argument: buttonStepSize },
  { key: bondActions.decreaseDownLightBrightness, name: 'Decrease Down Light Brightness', mutuallyExclusive: bondActions.setDownLightBrightness, argument: buttonStepSize },
  { key: bondActions.cycleUpLightBrightness, name: 'Cycle Up Light Brightness', mutuallyExclusive: bondActions.setUpLightBrightness, argument: buttonStepSize },
  { key: bondActions.cycleDownLightBrightness, name: 'Cycle Down Light Brightness', mutuallyExclusive: bondActions.setDownLightBrightness, argument: buttonStepSize },
  { key: bondActions.cycleBrightness, name: 'Cycle Brightness', mutuallyExclusive: bondActions.setBrightness, argument: buttonStepSize },
  { key: bondActions.increaseSpeed, name: 'Increase Speed', mutuallyExclusive: bondActions.setSpeed, argument: 1 },
  { key: bondActions.decreaseSpeed, name: 'Decrease Speed', mutuallyExclusive: bondActions.setSpeed, argument: 1 },
  { key: bondActions.toggleDirection, name: 'Toggle Direction', mutuallyExclusive: bondActions.setDirection },
  { key: bondActions.increaseTemperature, name: 'Increase Temperature', argument: 1 },
  { key: bondActions.decreaseTemperature, name: 'Decrease Temperature', argument: 1 },
  { key: bondActions.increaseFlame, name: 'Increase Flame', argument: buttonStepSize },
  { key: bondActions.decreaseFlame, name: 'Decrease Flame', argument: buttonStepSize },
  { key: bondActions.toggleOpen, name: 'Toggle Open', mutuallyExclusive: bondActions.open },
  { key: bondActions.increasePosition, name: 'Increase Position', mutuallyExclusive: bondActions.setPosition, argument: buttonStepSize },
  { key: bondActions.decreasePosition, name: 'Decrease Position', mutuallyExclusive: bondActions.setPosition, argument: buttonStepSize },
  { key: bondActions.openNext, name: 'Open Next' },
  { key: bondActions.closeNext, name: 'Close Next' },
];

const stopButtonDescription: IBondButtonDescription = { key: bondActions.stop, name: 'Stop Actions' };
const presetButtonDescription: IBondButtonDescription = { key: bondActions.preset, name: 'Preset' };

export class BondMapper {
  public static toSnapshot(optionsArg: IBondSnapshotOptions): IBondSnapshot {
    if (optionsArg.config.snapshot && !optionsArg.rawData) {
      return this.normalizeSnapshot(this.clone(optionsArg.config.snapshot), optionsArg.config, optionsArg.source || 'snapshot', optionsArg.error);
    }

    const rawData = this.rawData(optionsArg.config, optionsArg.rawData);
    const version = rawData.version || {};
    const devices = this.devices(rawData.devices || []);
    const hubId = stringValue(version.bondid) || optionsArg.config.bondId || optionsArg.config.uniqueId || optionsArg.config.host || 'bond';
    const isBridge = this.isBridgeFromSerial(hubId);
    const hubName = optionsArg.config.name
      || (isBridge ? stringValue(rawData.bridge?.name) : devices[0]?.name)
      || stringValue(rawData.bridge?.name)
      || (hubId !== 'bond' ? `Bond ${hubId}` : bondDisplayName);
    const online = optionsArg.online ?? optionsArg.config.online ?? Boolean(rawData.version || rawData.devices?.length || optionsArg.source === 'http' || optionsArg.source === 'client');
    const source = optionsArg.source || (rawData.version ? 'http' : Object.keys(rawData).length ? 'manual' : 'runtime');

    const snapshot: IBondSnapshot = {
      hub: {
        id: hubId,
        name: hubName,
        host: endpointHost(optionsArg.config.host || optionsArg.config.url),
        port: endpointPort(optionsArg.config.host || optionsArg.config.url, optionsArg.config.port),
        manufacturer: stringValue(version.make) || optionsArg.config.manufacturer || 'Olibra',
        model: stringValue(version.model) || optionsArg.config.model,
        target: stringValue(version.target),
        firmware: stringValue(version.fw_ver),
        hardware: stringValue(version.mcu_ver),
        location: isBridge ? stringValue(rawData.bridge?.location) : devices[0]?.location,
        isBridge,
        version,
        bridge: rawData.bridge,
      },
      devices,
      capabilities: {
        localControl: this.hasLocalControl(optionsArg.config),
        pushUpdates: true,
        fan: devices.some((deviceArg) => this.isFan(deviceArg)),
        cover: devices.some((deviceArg) => this.isCover(deviceArg)),
        light: devices.some((deviceArg) => this.isLightDevice(deviceArg) || this.supportsLight(deviceArg) || this.isFireplace(deviceArg)),
        fireplace: devices.some((deviceArg) => this.isFireplace(deviceArg)),
        switch: devices.some((deviceArg) => this.isGeneric(deviceArg)),
        buttons: devices.some((deviceArg) => this.buttonEntities(deviceArg).length > 0),
      },
      rawData: Object.keys(rawData).length ? rawData : undefined,
      online,
      updatedAt: rawData.fetchedAt || new Date().toISOString(),
      source,
      error: optionsArg.error,
    };

    return this.normalizeSnapshot(snapshot, optionsArg.config, source, optionsArg.error);
  }

  public static toDevices(snapshotArg: IBondSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [{
      id: this.hubDeviceId(snapshotArg),
      integrationDomain: bondDomain,
      name: snapshotArg.hub.name,
      protocol: snapshotArg.hub.host ? 'http' : 'unknown',
      manufacturer: snapshotArg.hub.manufacturer,
      model: snapshotArg.hub.target || snapshotArg.hub.model || 'Bond Bridge',
      online: snapshotArg.online,
      features: [
        { id: 'online', capability: 'sensor', name: 'Online', readable: true, writable: false },
        { id: 'device_count', capability: 'sensor', name: 'Device count', readable: true, writable: false },
      ],
      state: [
        { featureId: 'online', value: snapshotArg.online, updatedAt },
        { featureId: 'device_count', value: snapshotArg.devices.length, updatedAt },
      ],
      metadata: this.cleanAttributes({
        bondId: snapshotArg.hub.id,
        host: snapshotArg.hub.host,
        port: snapshotArg.hub.port,
        firmware: snapshotArg.hub.firmware,
        hardware: snapshotArg.hub.hardware,
        target: snapshotArg.hub.target,
        location: snapshotArg.hub.location,
        source: snapshotArg.source,
        error: snapshotArg.error,
      }),
    }];

    for (const device of snapshotArg.devices) {
      const features = this.featuresForDevice(snapshotArg, device);
      devices.push({
        id: this.deviceId(snapshotArg, device),
        integrationDomain: bondDomain,
        name: device.name,
        protocol: snapshotArg.hub.host ? 'http' : 'unknown',
        manufacturer: snapshotArg.hub.manufacturer,
        model: [device.brandingProfile, device.template].filter(Boolean).join(' ') || this.deviceTypeName(device.type),
        online: snapshotArg.online,
        features,
        state: this.stateForDevice(device, features, updatedAt),
        metadata: this.cleanAttributes({
          bondId: snapshotArg.hub.id,
          rawDeviceId: device.deviceId,
          type: device.type,
          location: device.location,
          template: device.template,
          brandingProfile: device.brandingProfile,
          trustState: device.trustState,
          supportedActions: device.supportedActions,
          viaDevice: this.hubDeviceId(snapshotArg),
        }),
      });
    }
    return devices;
  }

  public static toEntities(snapshotArg: IBondSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    for (const device of snapshotArg.devices) {
      if (this.isFan(device)) {
        entities.push(this.entity(snapshotArg, device, {
          key: 'fan',
          platform: 'fan',
          name: device.name,
          state: onOffState(device.state.power),
          attributes: {
            percentage: this.fanPercentage(device),
            speed: numberValue(device.state.speed),
            maxSpeed: this.maxSpeed(device),
            direction: directionName(device.state.direction),
            breeze: Array.isArray(device.state.breeze) ? Boolean(device.state.breeze[0]) : undefined,
          },
        }));
      }

      if (this.isCover(device)) {
        entities.push(this.entity(snapshotArg, device, {
          key: 'cover',
          platform: 'cover',
          name: device.name,
          state: coverState(device.state.open),
          attributes: {
            currentPosition: typeof device.state.position === 'number' ? this.bondToHomeAssistantPosition(device.state.position) : undefined,
            bondPosition: device.state.position,
          },
        }));
      }

      if (this.isGeneric(device)) {
        entities.push(this.entity(snapshotArg, device, {
          key: 'switch',
          platform: 'switch',
          name: device.name,
          state: onOffState(device.state.power),
        }));
      }

      entities.push(...this.lightEntities(snapshotArg, device));
      entities.push(...this.buttonEntities(device).map((buttonArg) => this.entity(snapshotArg, device, {
        key: `button_${slug(buttonArg.key)}`,
        platform: 'button',
        name: `${device.name} ${buttonArg.name}`,
        state: 'available',
        subDevice: buttonArg.key.toLowerCase(),
        attributes: {
          action: buttonArg.key,
          argument: buttonArg.argument,
        },
      })));
    }
    return entities;
  }

  public static commandForService(snapshotArg: IBondSnapshot, requestArg: IServiceCallRequest): IBondCommandRequest | undefined {
    if (requestArg.domain === bondDomain && ['action', 'execute_action', 'send_action'].includes(requestArg.service)) {
      const deviceId = stringValue(requestArg.data?.deviceId) || stringValue(requestArg.data?.device_id) || this.deviceForTarget(snapshotArg, requestArg)?.deviceId;
      const action = stringValue(requestArg.data?.action);
      if (!deviceId || !action) {
        return undefined;
      }
      const argument = requestArg.data && 'argument' in requestArg.data ? requestArg.data.argument : undefined;
      return { deviceId, action, argument, service: `${requestArg.domain}.${requestArg.service}`, target: requestArg.target, data: requestArg.data };
    }

    if (requestArg.domain === bondDomain && requestArg.service === 'set_state_belief') {
      const deviceId = stringValue(requestArg.data?.deviceId) || stringValue(requestArg.data?.device_id) || this.deviceForTarget(snapshotArg, requestArg)?.deviceId;
      const statePatch = recordValue(requestArg.data?.statePatch) || recordValue(requestArg.data?.state) || undefined;
      if (!deviceId || !statePatch) {
        return undefined;
      }
      return { deviceId, action: bondActions.setStateBelief, statePatch, service: `${requestArg.domain}.${requestArg.service}`, target: requestArg.target, data: requestArg.data };
    }

    const entity = this.entityForTarget(snapshotArg, requestArg);
    const device = entity ? this.deviceByRawId(snapshotArg, stringValue(entity.attributes?.deviceId)) : this.deviceForTarget(snapshotArg, requestArg);
    if (!device) {
      return undefined;
    }
    const service = `${requestArg.domain}.${requestArg.service}`;

    const serviceDomain = requestArg.domain === bondDomain && entity ? entity.platform : requestArg.domain;

    if (serviceDomain === 'button' && requestArg.service === 'press') {
      const action = stringValue(entity?.attributes?.action) || stringValue(requestArg.data?.action);
      if (!action || !this.hasAction(device, action)) {
        return undefined;
      }
      const argument = entity?.attributes?.argument ?? requestArg.data?.argument;
      return { deviceId: device.deviceId, action, argument, service, target: requestArg.target, data: requestArg.data };
    }

    if (serviceDomain === 'fan') {
      return this.fanCommand(device, requestArg, service);
    }
    if (serviceDomain === 'light') {
      return this.lightCommand(device, entity, requestArg, service);
    }
    if (serviceDomain === 'cover') {
      return this.coverCommand(device, requestArg, service);
    }
    if (serviceDomain === 'switch') {
      return this.switchCommand(device, requestArg, service);
    }
    return undefined;
  }

  public static offlineSnapshot(configArg: IBondConfig, errorArg: string): IBondSnapshot {
    return this.toSnapshot({ config: configArg, online: false, source: 'runtime', error: errorArg });
  }

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

  public static isBridgeFromSerial(serialArg: string | undefined): boolean {
    if (!serialArg) {
      return true;
    }
    return /^[A-C]\w*$/i.test(serialArg) || /^Z[ZX]\w*$/i.test(serialArg) || /^ZP\w*$/i.test(serialArg);
  }

  public static deviceId(snapshotArg: IBondSnapshot, deviceArg: IBondDeviceSnapshot): string {
    return `bond.device.${slug(`${snapshotArg.hub.id}_${deviceArg.deviceId}`)}`;
  }

  public static hubDeviceId(snapshotArg: IBondSnapshot): string {
    return `bond.hub.${slug(snapshotArg.hub.id)}`;
  }

  private static normalizeSnapshot(snapshotArg: IBondSnapshot, configArg: IBondConfig, sourceArg: TBondSnapshotSource, errorArg?: string): IBondSnapshot {
    const localControl = this.hasLocalControl(configArg);
    return {
      ...snapshotArg,
      hub: {
        ...snapshotArg.hub,
        host: snapshotArg.hub.host || endpointHost(configArg.host || configArg.url),
        port: snapshotArg.hub.port || endpointPort(configArg.host || configArg.url, configArg.port),
      },
      capabilities: {
        ...snapshotArg.capabilities,
        localControl,
      },
      source: sourceArg,
      error: errorArg ?? snapshotArg.error,
    };
  }

  private static rawData(configArg: IBondConfig, rawDataArg: Partial<IBondRawData> | undefined): IBondRawData {
    const rawData = rawDataArg || configArg.rawData || {};
    return {
      version: rawData.version,
      bridge: rawData.bridge,
      deviceIndex: rawData.deviceIndex,
      devices: rawData.devices,
      fetchedAt: rawData.fetchedAt,
    };
  }

  private static devices(devicesArg: IBondDeviceRaw[]): IBondDeviceSnapshot[] {
    return devicesArg.map((deviceArg) => {
      const attrs = deviceArg.attrs || {};
      const props = deviceArg.props || {};
      const state = deviceArg.state || {};
      const supportedActions = Array.isArray(attrs.actions) ? attrs.actions.filter((actionArg) => typeof actionArg === 'string') : [];
      return {
        deviceId: deviceArg.deviceId,
        name: stringValue(attrs.name) || `Bond Device ${deviceArg.deviceId}`,
        type: stringValue(attrs.type) || bondDeviceTypes.genericDevice,
        location: stringValue(attrs.location),
        template: stringValue(attrs.template),
        brandingProfile: stringValue(props.branding_profile),
        trustState: props.trust_state === true,
        supportedActions,
        attrs,
        props,
        state,
      };
    });
  }

  private static featuresForDevice(snapshotArg: IBondSnapshot, deviceArg: IBondDeviceSnapshot): plugins.shxInterfaces.data.IDeviceFeature[] {
    const writable = snapshotArg.capabilities.localControl;
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [];
    if (this.isFan(deviceArg) || this.isGeneric(deviceArg) || this.isFireplace(deviceArg)) {
      features.push({ id: 'power', capability: 'switch', name: 'Power', readable: true, writable });
    }
    if (this.isFan(deviceArg) && this.hasAction(deviceArg, bondActions.setSpeed)) {
      features.push({ id: 'speed', capability: 'fan', name: 'Speed', readable: true, writable });
    }
    if (this.isFan(deviceArg) && this.hasAction(deviceArg, bondActions.setDirection)) {
      features.push({ id: 'direction', capability: 'fan', name: 'Direction', readable: true, writable });
    }
    if (this.supportsLight(deviceArg) || this.isLightDevice(deviceArg)) {
      features.push({ id: 'light', capability: 'light', name: 'Light', readable: true, writable });
    }
    if ((this.supportsLight(deviceArg) || this.isLightDevice(deviceArg)) && this.hasAction(deviceArg, bondActions.setBrightness)) {
      features.push({ id: 'brightness', capability: 'light', name: 'Brightness', readable: true, writable, unit: '%' });
    }
    if (this.isFireplace(deviceArg)) {
      features.push({ id: 'flame', capability: 'light', name: 'Flame', readable: true, writable, unit: '%' });
    }
    if (this.isCover(deviceArg)) {
      features.push({ id: 'open', capability: 'cover', name: 'Open', readable: true, writable });
      if (this.hasAction(deviceArg, bondActions.setPosition)) {
        features.push({ id: 'position', capability: 'cover', name: 'Position', readable: true, writable, unit: '%' });
      }
    }
    if (features.length === 0) {
      features.push({ id: 'state', capability: 'sensor', name: 'State', readable: true, writable: false });
    }
    return features;
  }

  private static stateForDevice(deviceArg: IBondDeviceSnapshot, featuresArg: plugins.shxInterfaces.data.IDeviceFeature[], updatedAtArg: string): plugins.shxInterfaces.data.IDeviceState[] {
    return featuresArg.map((featureArg) => ({
      featureId: featureArg.id,
      value: this.deviceStateValue(this.featureValue(deviceArg, featureArg.id)),
      updatedAt: updatedAtArg,
    }));
  }

  private static featureValue(deviceArg: IBondDeviceSnapshot, featureArg: string): unknown {
    switch (featureArg) {
      case 'power':
        return boolState(deviceArg.state.power);
      case 'speed':
        return numberValue(deviceArg.state.speed) ?? null;
      case 'direction':
        return directionName(deviceArg.state.direction) ?? null;
      case 'light':
        return boolState(deviceArg.state.light);
      case 'brightness':
        return numberValue(deviceArg.state.brightness) ?? null;
      case 'flame':
        return numberValue(deviceArg.state.flame) ?? null;
      case 'open':
        return boolState(deviceArg.state.open);
      case 'position':
        return typeof deviceArg.state.position === 'number' ? this.bondToHomeAssistantPosition(deviceArg.state.position) : null;
      default:
        return deviceArg.state;
    }
  }

  private static entity(snapshotArg: IBondSnapshot, deviceArg: IBondDeviceSnapshot, definitionArg: IBondEntityDefinition): IIntegrationEntity {
    const subDeviceId = definitionArg.subDevice || definitionArg.key;
    const baseId = slug(deviceArg.name) || slug(deviceArg.deviceId);
    const entityId = `${definitionArg.platform}.${baseId}${definitionArg.key === definitionArg.platform ? '' : `_${slug(definitionArg.key)}`}`;
    return {
      id: entityId,
      uniqueId: `${snapshotArg.hub.id}_${deviceArg.deviceId}_${slug(subDeviceId)}`,
      integrationDomain: bondDomain,
      deviceId: this.deviceId(snapshotArg, deviceArg),
      platform: definitionArg.platform,
      name: definitionArg.name,
      state: definitionArg.state,
      available: definitionArg.available ?? snapshotArg.online,
      attributes: this.cleanAttributes({
        bondId: snapshotArg.hub.id,
        deviceId: deviceArg.deviceId,
        type: deviceArg.type,
        subDevice: definitionArg.subDevice,
        assumedState: snapshotArg.hub.isBridge && !deviceArg.trustState,
        supportedActions: deviceArg.supportedActions,
        ...definitionArg.attributes,
      }),
    };
  }

  private static lightEntities(snapshotArg: IBondSnapshot, deviceArg: IBondDeviceSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    if (this.isFan(deviceArg) && this.supportsLight(deviceArg) && !(this.supportsUpLight(deviceArg) && this.supportsDownLight(deviceArg))) {
      entities.push(this.lightEntity(snapshotArg, deviceArg, 'light', 'Light', onOffState(deviceArg.state.light), {
        brightness: this.homeAssistantBrightness(deviceArg.state.brightness),
        brightnessPercent: deviceArg.state.brightness,
      }));
    }
    if (this.isFan(deviceArg) && this.supportsUpLight(deviceArg)) {
      entities.push(this.lightEntity(snapshotArg, deviceArg, 'up_light', 'Up Light', onOffState(deviceArg.state.up_light || deviceArg.state.light)));
    }
    if (this.isFan(deviceArg) && this.supportsDownLight(deviceArg)) {
      entities.push(this.lightEntity(snapshotArg, deviceArg, 'down_light', 'Down Light', onOffState(deviceArg.state.down_light || deviceArg.state.light)));
    }
    if (this.isFireplace(deviceArg)) {
      entities.push(this.lightEntity(snapshotArg, deviceArg, 'fireplace', 'Fireplace', onOffState(deviceArg.state.power), {
        brightness: this.homeAssistantBrightness(deviceArg.state.flame),
        flamePercent: deviceArg.state.flame,
      }));
      if (this.supportsLight(deviceArg)) {
        entities.push(this.lightEntity(snapshotArg, deviceArg, 'light', 'Light', onOffState(deviceArg.state.light), {
          brightness: this.homeAssistantBrightness(deviceArg.state.brightness),
          brightnessPercent: deviceArg.state.brightness,
        }));
      }
    }
    if (this.isLightDevice(deviceArg)) {
      entities.push(this.lightEntity(snapshotArg, deviceArg, 'light', 'Light', onOffState(deviceArg.state.light), {
        brightness: this.homeAssistantBrightness(deviceArg.state.brightness),
        brightnessPercent: deviceArg.state.brightness,
      }, deviceArg.name));
    }
    return entities;
  }

  private static lightEntity(snapshotArg: IBondSnapshot, deviceArg: IBondDeviceSnapshot, keyArg: string, suffixArg: string, stateArg: unknown, attributesArg: Record<string, unknown> = {}, nameArg?: string): IIntegrationEntity {
    return this.entity(snapshotArg, deviceArg, {
      key: keyArg === 'light' ? 'light' : `light_${keyArg}`,
      platform: 'light',
      name: nameArg || `${deviceArg.name} ${suffixArg}`,
      state: stateArg,
      subDevice: keyArg,
      attributes: attributesArg,
    });
  }

  private static buttonEntities(deviceArg: IBondDeviceSnapshot): IBondButtonDescription[] {
    const buttons = buttonDescriptions.filter((descriptionArg) => this.hasAction(deviceArg, descriptionArg.key) && (!descriptionArg.mutuallyExclusive || !this.hasAction(deviceArg, descriptionArg.mutuallyExclusive)));
    if (buttons.length > 0 && this.hasAction(deviceArg, stopButtonDescription.key)) {
      buttons.push(stopButtonDescription);
    }
    if (this.hasAction(deviceArg, presetButtonDescription.key)) {
      buttons.push(presetButtonDescription);
    }
    return buttons;
  }

  private static fanCommand(deviceArg: IBondDeviceSnapshot, requestArg: IServiceCallRequest, serviceArg: string): IBondCommandRequest | undefined {
    if (requestArg.service === 'turn_on') {
      const presetMode = stringValue(requestArg.data?.preset_mode);
      if (presetMode?.toLowerCase() === 'breeze' && this.hasAction(deviceArg, bondActions.breezeOn)) {
        return { deviceId: deviceArg.deviceId, action: bondActions.breezeOn, service: serviceArg, target: requestArg.target, data: requestArg.data };
      }
      const percentage = numberValue(requestArg.data?.percentage);
      if (percentage !== undefined) {
        return this.fanPercentageCommand(deviceArg, percentage, serviceArg, requestArg);
      }
      return this.actionCommand(deviceArg, bondActions.turnOn, serviceArg, requestArg);
    }
    if (requestArg.service === 'turn_off') {
      return this.actionCommand(deviceArg, bondActions.turnOff, serviceArg, requestArg);
    }
    if (requestArg.service === 'set_percentage') {
      const percentage = numberValue(requestArg.data?.percentage);
      return percentage === undefined ? undefined : this.fanPercentageCommand(deviceArg, percentage, serviceArg, requestArg);
    }
    if (requestArg.service === 'set_direction') {
      const direction = stringValue(requestArg.data?.direction)?.toLowerCase();
      const bondDirection = direction === 'reverse' ? -1 : 1;
      return this.actionCommand(deviceArg, bondActions.setDirection, serviceArg, requestArg, bondDirection);
    }
    if (requestArg.service === 'set_fan_speed_tracked_state') {
      const speed = numberValue(requestArg.data?.speed ?? requestArg.data?.percentage);
      if (speed === undefined) {
        return undefined;
      }
      if (speed === 0) {
        return { deviceId: deviceArg.deviceId, action: bondActions.setStateBelief, statePatch: { power: 0 }, service: serviceArg, target: requestArg.target, data: requestArg.data };
      }
      return { deviceId: deviceArg.deviceId, action: bondActions.setStateBelief, statePatch: { power: 1, speed: this.percentageToBondSpeed(deviceArg, speed) }, service: serviceArg, target: requestArg.target, data: requestArg.data };
    }
    return undefined;
  }

  private static fanPercentageCommand(deviceArg: IBondDeviceSnapshot, percentageArg: number, serviceArg: string, requestArg: IServiceCallRequest): IBondCommandRequest | undefined {
    if (percentageArg <= 0) {
      return this.actionCommand(deviceArg, bondActions.turnOff, serviceArg, requestArg);
    }
    return this.actionCommand(deviceArg, bondActions.setSpeed, serviceArg, requestArg, this.percentageToBondSpeed(deviceArg, percentageArg));
  }

  private static lightCommand(deviceArg: IBondDeviceSnapshot, entityArg: IIntegrationEntity | undefined, requestArg: IServiceCallRequest, serviceArg: string): IBondCommandRequest | undefined {
    const key = stringValue(entityArg?.attributes?.subDevice) || this.lightKeyFromEntity(entityArg) || 'light';
    if (requestArg.service === 'turn_on') {
      const brightness = numberValue(requestArg.data?.brightness);
      if (brightness !== undefined) {
        if (key === 'fireplace') {
          return this.actionCommand(deviceArg, bondActions.setFlame, serviceArg, requestArg, this.homeAssistantToPercent(brightness));
        }
        return this.actionCommand(deviceArg, this.brightnessActionForLightKey(key), serviceArg, requestArg, this.homeAssistantToPercent(brightness));
      }
      return this.actionCommand(deviceArg, this.turnOnActionForLightKey(key), serviceArg, requestArg);
    }
    if (requestArg.service === 'turn_off') {
      return this.actionCommand(deviceArg, this.turnOffActionForLightKey(key), serviceArg, requestArg);
    }
    if (requestArg.service === 'set_light_power_tracked_state') {
      const power = booleanValue(requestArg.data?.power_state);
      return power === undefined ? undefined : { deviceId: deviceArg.deviceId, action: bondActions.setStateBelief, statePatch: key === 'fireplace' ? { power: Number(power) } : { light: Number(power) }, service: serviceArg, target: requestArg.target, data: requestArg.data };
    }
    if (requestArg.service === 'set_light_brightness_tracked_state') {
      const brightness = numberValue(requestArg.data?.brightness);
      return brightness === undefined ? undefined : { deviceId: deviceArg.deviceId, action: bondActions.setStateBelief, statePatch: { brightness: this.homeAssistantToPercent(brightness) }, service: serviceArg, target: requestArg.target, data: requestArg.data };
    }
    return undefined;
  }

  private static coverCommand(deviceArg: IBondDeviceSnapshot, requestArg: IServiceCallRequest, serviceArg: string): IBondCommandRequest | undefined {
    if (requestArg.service === 'open_cover') {
      return this.actionCommand(deviceArg, bondActions.open, serviceArg, requestArg);
    }
    if (requestArg.service === 'close_cover') {
      return this.actionCommand(deviceArg, bondActions.close, serviceArg, requestArg);
    }
    if (requestArg.service === 'stop_cover' || requestArg.service === 'stop_cover_tilt') {
      return this.actionCommand(deviceArg, bondActions.hold, serviceArg, requestArg);
    }
    if (requestArg.service === 'open_cover_tilt') {
      return this.actionCommand(deviceArg, bondActions.tiltOpen, serviceArg, requestArg);
    }
    if (requestArg.service === 'close_cover_tilt') {
      return this.actionCommand(deviceArg, bondActions.tiltClose, serviceArg, requestArg);
    }
    if (requestArg.service === 'set_cover_position') {
      const position = numberValue(requestArg.data?.position);
      return position === undefined ? undefined : this.actionCommand(deviceArg, bondActions.setPosition, serviceArg, requestArg, this.homeAssistantToBondPosition(position));
    }
    return undefined;
  }

  private static switchCommand(deviceArg: IBondDeviceSnapshot, requestArg: IServiceCallRequest, serviceArg: string): IBondCommandRequest | undefined {
    if (requestArg.service === 'turn_on') {
      return this.actionCommand(deviceArg, bondActions.turnOn, serviceArg, requestArg);
    }
    if (requestArg.service === 'turn_off') {
      return this.actionCommand(deviceArg, bondActions.turnOff, serviceArg, requestArg);
    }
    if (requestArg.service === 'set_switch_power_tracked_state') {
      const power = booleanValue(requestArg.data?.power_state);
      return power === undefined ? undefined : { deviceId: deviceArg.deviceId, action: bondActions.setStateBelief, statePatch: { power: Number(power) }, service: serviceArg, target: requestArg.target, data: requestArg.data };
    }
    return undefined;
  }

  private static actionCommand(deviceArg: IBondDeviceSnapshot, actionArg: string, serviceArg: string, requestArg: IServiceCallRequest, argumentArg?: unknown): IBondCommandRequest | undefined {
    if (!this.hasAction(deviceArg, actionArg)) {
      return undefined;
    }
    return { deviceId: deviceArg.deviceId, action: actionArg, argument: argumentArg, service: serviceArg, target: requestArg.target, data: requestArg.data };
  }

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

  private static deviceForTarget(snapshotArg: IBondSnapshot, requestArg: IServiceCallRequest): IBondDeviceSnapshot | undefined {
    const dataDeviceId = stringValue(requestArg.data?.deviceId) || stringValue(requestArg.data?.device_id);
    if (dataDeviceId) {
      return this.deviceByRawId(snapshotArg, dataDeviceId);
    }
    const target = requestArg.target.deviceId;
    if (!target) {
      return undefined;
    }
    return snapshotArg.devices.find((deviceArg) => target === deviceArg.deviceId || target === this.deviceId(snapshotArg, deviceArg));
  }

  private static deviceByRawId(snapshotArg: IBondSnapshot, deviceIdArg: string | undefined): IBondDeviceSnapshot | undefined {
    return snapshotArg.devices.find((deviceArg) => deviceArg.deviceId === deviceIdArg);
  }

  private static hasLocalControl(configArg: IBondConfig): boolean {
    return Boolean(((configArg.host || configArg.url) && (configArg.accessToken || configArg.token)) || configArg.commandExecutor || configArg.client?.execute);
  }

  private static hasAction(deviceArg: IBondDeviceSnapshot, actionArg: string): boolean {
    return deviceArg.supportedActions.includes(actionArg);
  }

  private static isFan(deviceArg: IBondDeviceSnapshot): boolean {
    return deviceArg.type === bondDeviceTypes.ceilingFan;
  }

  private static isCover(deviceArg: IBondDeviceSnapshot): boolean {
    return deviceArg.type === bondDeviceTypes.motorizedShades;
  }

  private static isFireplace(deviceArg: IBondDeviceSnapshot): boolean {
    return deviceArg.type === bondDeviceTypes.fireplace;
  }

  private static isLightDevice(deviceArg: IBondDeviceSnapshot): boolean {
    return deviceArg.type === bondDeviceTypes.light;
  }

  private static isGeneric(deviceArg: IBondDeviceSnapshot): boolean {
    return deviceArg.type === bondDeviceTypes.genericDevice;
  }

  private static supportsLight(deviceArg: IBondDeviceSnapshot): boolean {
    return this.hasAction(deviceArg, bondActions.turnLightOn) || this.hasAction(deviceArg, bondActions.turnLightOff);
  }

  private static supportsUpLight(deviceArg: IBondDeviceSnapshot): boolean {
    return this.hasAction(deviceArg, bondActions.turnUpLightOn) || this.hasAction(deviceArg, bondActions.turnUpLightOff);
  }

  private static supportsDownLight(deviceArg: IBondDeviceSnapshot): boolean {
    return this.hasAction(deviceArg, bondActions.turnDownLightOn) || this.hasAction(deviceArg, bondActions.turnDownLightOff);
  }

  private static maxSpeed(deviceArg: IBondDeviceSnapshot): number {
    return numberValue(deviceArg.props.max_speed) || 3;
  }

  private static percentageToBondSpeed(deviceArg: IBondDeviceSnapshot, percentageArg: number): number {
    const maxSpeed = this.maxSpeed(deviceArg);
    return Math.min(maxSpeed, Math.max(1, Math.ceil(1 + ((maxSpeed - 1) * percentageArg) / 100)));
  }

  private static fanPercentage(deviceArg: IBondDeviceSnapshot): number {
    const power = boolState(deviceArg.state.power);
    const speed = numberValue(deviceArg.state.speed);
    if (!power || !speed) {
      return 0;
    }
    const maxSpeed = this.maxSpeed(deviceArg);
    return Math.min(100, Math.max(0, Math.round(((speed - 1) * 100) / Math.max(1, maxSpeed - 1))));
  }

  private static bondToHomeAssistantPosition(positionArg: number): number {
    return Math.abs(positionArg - 100);
  }

  private static homeAssistantToBondPosition(positionArg: number): number {
    return 100 - positionArg;
  }

  private static homeAssistantBrightness(valueArg: unknown): number | undefined {
    const brightness = numberValue(valueArg);
    return brightness === undefined ? undefined : Math.round((brightness * 255) / 100);
  }

  private static homeAssistantToPercent(valueArg: number): number {
    return Math.round((valueArg * 100) / 255);
  }

  private static lightKeyFromEntity(entityArg: IIntegrationEntity | undefined): string | undefined {
    if (!entityArg) {
      return undefined;
    }
    if (entityArg.id.includes('up_light')) {
      return 'up_light';
    }
    if (entityArg.id.includes('down_light')) {
      return 'down_light';
    }
    if (entityArg.id.includes('fireplace')) {
      return 'fireplace';
    }
    return 'light';
  }

  private static turnOnActionForLightKey(keyArg: string): string {
    if (keyArg === 'up_light') {
      return bondActions.turnUpLightOn;
    }
    if (keyArg === 'down_light') {
      return bondActions.turnDownLightOn;
    }
    if (keyArg === 'fireplace') {
      return bondActions.turnOn;
    }
    return bondActions.turnLightOn;
  }

  private static turnOffActionForLightKey(keyArg: string): string {
    if (keyArg === 'up_light') {
      return bondActions.turnUpLightOff;
    }
    if (keyArg === 'down_light') {
      return bondActions.turnDownLightOff;
    }
    if (keyArg === 'fireplace') {
      return bondActions.turnOff;
    }
    return bondActions.turnLightOff;
  }

  private static brightnessActionForLightKey(keyArg: string): string {
    if (keyArg === 'up_light') {
      return bondActions.setUpLightBrightness;
    }
    if (keyArg === 'down_light') {
      return bondActions.setDownLightBrightness;
    }
    return bondActions.setBrightness;
  }

  private static deviceTypeName(typeArg: string): string {
    const names: Record<string, string> = {
      [bondDeviceTypes.ceilingFan]: 'Ceiling Fan',
      [bondDeviceTypes.motorizedShades]: 'Motorized Shades',
      [bondDeviceTypes.fireplace]: 'Fireplace',
      [bondDeviceTypes.airConditioner]: 'Air Conditioner',
      [bondDeviceTypes.garageDoor]: 'Garage Door',
      [bondDeviceTypes.bidet]: 'Bidet',
      [bondDeviceTypes.light]: 'Light',
      [bondDeviceTypes.genericDevice]: 'Generic Device',
    };
    return names[typeArg] || `Bond ${typeArg}`;
  }

  private static cleanAttributes(attributesArg: Record<string, unknown>): Record<string, unknown> {
    const result: Record<string, unknown> = {};
    for (const [key, value] of Object.entries(attributesArg)) {
      if (value !== undefined) {
        result[key] = value;
      }
    }
    return result;
  }

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

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

const endpointHost = (valueArg: string | undefined): string | undefined => parseEndpoint(valueArg)?.host || valueArg?.replace(/^https?:\/\//i, '').replace(/\/.*/, '').replace(/:\d+$/, '');

const endpointPort = (valueArg: string | undefined, fallbackArg?: number): number | undefined => parseEndpoint(valueArg)?.port || fallbackArg || (valueArg ? bondDefaultPort : undefined);

const parseEndpoint = (valueArg: string | undefined): { host: string; port?: number } | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    const url = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg) ? valueArg : `http://${valueArg}`);
    return { host: url.hostname, port: url.port ? Number(url.port) : undefined };
  } catch {
    return undefined;
  }
};

const slug = (valueArg: unknown): string => String(valueArg ?? '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'bond';

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const 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;
};

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

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

const boolState = (valueArg: unknown): boolean | undefined => booleanValue(valueArg);

const onOffState = (valueArg: unknown): string => {
  const value = boolState(valueArg);
  return value === undefined ? 'unknown' : value ? 'on' : 'off';
};

const coverState = (valueArg: unknown): string => {
  const value = boolState(valueArg);
  return value === undefined ? 'unknown' : value ? 'open' : 'closed';
};

const directionName = (valueArg: unknown): string | undefined => {
  const value = numberValue(valueArg);
  if (value === 1) {
    return 'forward';
  }
  if (value === -1) {
    return 'reverse';
  }
  return undefined;
};
