import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type { IFullyKioskClientCommand, IFullyKioskDeviceInfo, IFullyKioskSettings, IFullyKioskSnapshot, TFullyKioskImageKind } from './fully_kiosk.types.js';
import { fullyKioskDefaultPort, fullyKioskDomain, fullyKioskMusicStream } from './fully_kiosk.types.js';

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

interface IFullyKioskSensorDescription {
  key: string;
  name: string;
  unit?: string;
  deviceClass?: string;
  stateClass?: string;
  entityCategory?: string;
  value?: (valueArg: unknown) => unknown;
  attributes?: (valueArg: unknown) => Record<string, unknown>;
}

const binarySensors = [
  { key: 'kioskMode', name: 'Kiosk mode' },
  { key: 'plugged', name: 'Plugged in', deviceClass: 'plug' },
  { key: 'isDeviceAdmin', name: 'Device admin' },
];

const sensors: IFullyKioskSensorDescription[] = [
  { key: 'batteryLevel', name: 'Battery', unit: '%', deviceClass: 'battery', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: 'batteryTemperature', name: 'Battery temperature', unit: 'C', deviceClass: 'temperature', stateClass: 'measurement', entityCategory: 'diagnostic' },
  { key: 'currentPage', name: 'Current page', entityCategory: 'diagnostic', value: truncateUrl, attributes: urlAttributes },
  { key: 'screenOrientation', name: 'Screen orientation', entityCategory: 'diagnostic' },
  { key: 'foregroundApp', name: 'Foreground app', entityCategory: 'diagnostic' },
  { key: 'internalStorageFreeSpace', name: 'Internal storage free space', unit: 'MB', deviceClass: 'data_size', stateClass: 'measurement', entityCategory: 'diagnostic', value: bytesToMegabytes },
  { key: 'internalStorageTotalSpace', name: 'Internal storage total space', unit: 'MB', deviceClass: 'data_size', stateClass: 'measurement', entityCategory: 'diagnostic', value: bytesToMegabytes },
  { key: 'ramFreeMemory', name: 'RAM free memory', unit: 'MB', deviceClass: 'data_size', stateClass: 'measurement', entityCategory: 'diagnostic', value: bytesToMegabytes },
  { key: 'ramTotalMemory', name: 'RAM total memory', unit: 'MB', deviceClass: 'data_size', stateClass: 'measurement', entityCategory: 'diagnostic', value: bytesToMegabytes },
];

const switches = [
  { key: 'screensaver', name: 'Screensaver', state: (snapshotArg: IFullyKioskSnapshot) => booleanValue(snapshotArg.deviceInfo.isInScreensaver), onCommand: 'startScreensaver', offCommand: 'stopScreensaver' },
  { key: 'maintenance', name: 'Maintenance mode', state: (snapshotArg: IFullyKioskSnapshot) => booleanValue(snapshotArg.deviceInfo.maintenanceMode), onCommand: 'enableLockedMode', offCommand: 'disableLockedMode', entityCategory: 'config' },
  { key: 'kiosk', name: 'Kiosk locked', state: (snapshotArg: IFullyKioskSnapshot) => booleanValue(snapshotArg.deviceInfo.kioskLocked), onCommand: 'lockKiosk', offCommand: 'unlockKiosk', entityCategory: 'config' },
  { key: 'motion_detection', name: 'Motion detection', state: (snapshotArg: IFullyKioskSnapshot) => booleanValue(snapshotArg.settings.motionDetection), onCommand: 'setBooleanSetting', offCommand: 'setBooleanSetting', settingKey: 'motionDetection', entityCategory: 'config' },
  { key: 'screen_on', name: 'Screen on', state: (snapshotArg: IFullyKioskSnapshot) => booleanValue(snapshotArg.deviceInfo.screenOn), onCommand: 'screenOn', offCommand: 'screenOff' },
];

const buttons = [
  { key: 'restart_app', name: 'Restart browser', command: 'restartApp', entityCategory: 'config' },
  { key: 'reboot_device', name: 'Restart device', command: 'rebootDevice', entityCategory: 'config' },
  { key: 'to_foreground', name: 'To foreground', command: 'toForeground', entityCategory: 'config' },
  { key: 'to_background', name: 'To background', command: 'toBackground', entityCategory: 'config' },
  { key: 'load_start_url', name: 'Load start URL', command: 'loadStartUrl', entityCategory: 'config' },
  { key: 'clear_cache', name: 'Clear cache', command: 'clearCache', entityCategory: 'config' },
  { key: 'trigger_motion', name: 'Trigger motion', command: 'triggerMotion', entityCategory: 'config' },
];

const numbers = [
  { key: 'timeToScreensaverV2', name: 'Screensaver time', min: 0, max: 86400, step: 1, unit: 's' },
  { key: 'screensaverBrightness', name: 'Screensaver brightness', min: 0, max: 255, step: 1 },
  { key: 'timeToScreenOffV2', name: 'Screen off time', min: 0, max: 86400, step: 1, unit: 's' },
  { key: 'screenBrightness', name: 'Screen brightness', min: 0, max: 255, step: 1 },
];

const fullyKioskServiceCommands: Record<string, string> = {
  screen_on: 'screenOn',
  screen_off: 'screenOff',
  force_sleep: 'forceSleep',
  start_screensaver: 'startScreensaver',
  stop_screensaver: 'stopScreensaver',
  lock_kiosk: 'lockKiosk',
  unlock_kiosk: 'unlockKiosk',
  enable_locked_mode: 'enableLockedMode',
  disable_locked_mode: 'disableLockedMode',
  restart_app: 'restartApp',
  reboot_device: 'rebootDevice',
  to_foreground: 'toForeground',
  to_background: 'toBackground',
  load_start_url: 'loadStartUrl',
  clear_cache: 'clearCache',
  clear_webstorage: 'clearWebstorage',
  clear_cookies: 'clearCookies',
  reset_webview: 'resetWebview',
  trigger_motion: 'triggerMotion',
  stop_sound: 'stopSound',
  stop_text_to_speech: 'stopTextToSpeech',
};

export class FullyKioskMapper {
  public static toDevices(snapshotArg: IFullyKioskSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'connection', capability: 'sensor', name: 'Connection', readable: true, writable: false },
      { id: 'battery', capability: 'sensor', name: 'Battery', readable: true, writable: false, unit: '%' },
      { id: 'current_page', capability: 'sensor', name: 'Current page', readable: true, writable: false },
      { id: 'screen_on', capability: 'switch', name: 'Screen on', readable: true, writable: true },
      { id: 'screensaver', capability: 'switch', name: 'Screensaver', readable: true, writable: true },
      { id: 'screenshot', capability: 'camera', name: 'Screenshot', readable: true, writable: false },
      { id: 'media_player', capability: 'media', name: 'Media player', readable: true, writable: true },
      { id: 'notification', capability: 'notification', name: 'Notification', readable: false, writable: true },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'connection', value: snapshotArg.online ? 'online' : 'offline', updatedAt },
      { featureId: 'battery', value: primitiveState(snapshotArg.deviceInfo.batteryLevel), updatedAt },
      { featureId: 'current_page', value: stringOrNull(snapshotArg.deviceInfo.currentPage), updatedAt },
      { featureId: 'screen_on', value: booleanValue(snapshotArg.deviceInfo.screenOn), updatedAt },
      { featureId: 'screensaver', value: booleanValue(snapshotArg.deviceInfo.isInScreensaver), updatedAt },
      { featureId: 'screenshot', value: { available: snapshotArg.online }, updatedAt },
      { featureId: 'media_player', value: snapshotArg.deviceInfo.soundUrlPlaying ? 'playing' : 'idle', updatedAt },
    ];

    return [{
      id: this.deviceId(snapshotArg),
      integrationDomain: fullyKioskDomain,
      name: this.deviceName(snapshotArg),
      protocol: 'http',
      manufacturer: stringOrUndefined(snapshotArg.deviceInfo.deviceManufacturer) || 'Fully Kiosk',
      model: stringOrUndefined(snapshotArg.deviceInfo.deviceModel) || 'Android kiosk',
      online: snapshotArg.online,
      features,
      state,
      metadata: this.cleanAttributes({
        uniqueId: snapshotArg.uniqueId,
        deviceID: snapshotArg.deviceInfo.deviceID,
        macAddress: snapshotArg.macAddress,
        host: snapshotArg.host,
        port: snapshotArg.port,
        appVersionName: snapshotArg.deviceInfo.appVersionName,
        ip4: snapshotArg.deviceInfo.ip4,
        currentPage: snapshotArg.deviceInfo.currentPage,
        source: snapshotArg.source,
        error: snapshotArg.error,
      }),
    }];
  }

  public static toEntities(snapshotArg: IFullyKioskSnapshot): IIntegrationEntity[] {
    const deviceId = this.deviceId(snapshotArg);
    const uniqueBase = this.uniqueBase(snapshotArg);
    const baseName = this.deviceName(snapshotArg);
    const usedIds = new Map<string, number>();
    const entities: IIntegrationEntity[] = [];

    entities.push(this.entity(snapshotArg, usedIds, deviceId, uniqueBase, {
      key: 'online',
      platform: 'binary_sensor',
      name: `${baseName} Online`,
      state: snapshotArg.online ? 'on' : 'off',
      available: true,
      attributes: { deviceClass: 'connectivity', source: snapshotArg.source, error: snapshotArg.error },
    }));

    for (const description of binarySensors) {
      if (description.key in snapshotArg.deviceInfo) {
        entities.push(this.entity(snapshotArg, usedIds, deviceId, uniqueBase, {
          key: description.key,
          platform: 'binary_sensor',
          name: `${baseName} ${description.name}`,
          state: booleanValue(snapshotArg.deviceInfo[description.key]) ? 'on' : 'off',
          available: snapshotArg.online,
          attributes: this.cleanAttributes({ deviceClass: description.deviceClass, entityCategory: 'diagnostic' }),
        }));
      }
    }

    for (const description of sensors) {
      if (description.key in snapshotArg.deviceInfo) {
        const rawValue = snapshotArg.deviceInfo[description.key];
        const value = description.value ? description.value(rawValue) : rawValue;
        entities.push(this.entity(snapshotArg, usedIds, deviceId, uniqueBase, {
          key: description.key,
          platform: 'sensor',
          name: `${baseName} ${description.name}`,
          state: primitiveState(value),
          available: snapshotArg.online && value !== undefined,
          attributes: this.cleanAttributes({
            unit: description.unit,
            deviceClass: description.deviceClass,
            stateClass: description.stateClass,
            entityCategory: description.entityCategory,
            ...description.attributes?.(rawValue),
          }),
        }));
      }
    }

    for (const description of switches) {
      const isOn = description.state(snapshotArg);
      entities.push(this.entity(snapshotArg, usedIds, deviceId, uniqueBase, {
        key: description.key,
        platform: 'switch',
        name: `${baseName} ${description.name}`,
        state: isOn ? 'on' : 'off',
        available: snapshotArg.online,
        attributes: this.cleanAttributes({
          fullyKioskSwitchKey: description.key,
          onCommand: description.onCommand,
          offCommand: description.offCommand,
          settingKey: description.settingKey,
          entityCategory: description.entityCategory,
        }),
      }));
    }

    for (const description of buttons) {
      entities.push(this.entity(snapshotArg, usedIds, deviceId, uniqueBase, {
        key: description.key,
        platform: 'button',
        name: `${baseName} ${description.name}`,
        state: 'idle',
        available: snapshotArg.online,
        attributes: { fullyKioskButtonCommand: description.command, entityCategory: description.entityCategory },
      }));
    }

    for (const description of numbers) {
      if (description.key in snapshotArg.settings) {
        entities.push(this.entity(snapshotArg, usedIds, deviceId, uniqueBase, {
          key: description.key,
          platform: 'number',
          name: `${baseName} ${description.name}`,
          state: numberOrNull(snapshotArg.settings[description.key]),
          available: snapshotArg.online,
          attributes: this.cleanAttributes({
            fullyKioskSettingKey: description.key,
            min: description.min,
            max: description.max,
            step: description.step,
            unit: description.unit,
            entityCategory: 'config',
          }),
        }));
      }
    }

    entities.push(this.entity(snapshotArg, usedIds, deviceId, uniqueBase, {
      key: 'camshot',
      platform: 'camera' as TEntityPlatform,
      name: `${baseName} Camera`,
      state: snapshotArg.online ? 'idle' : 'unavailable',
      available: snapshotArg.online,
      attributes: { imageKind: 'camshot', supportedFeatures: ['snapshot'], requiresAuth: true },
    }));
    entities.push(this.entity(snapshotArg, usedIds, deviceId, uniqueBase, {
      key: 'screenshot',
      platform: 'camera' as TEntityPlatform,
      name: `${baseName} Screenshot`,
      state: snapshotArg.online ? 'idle' : 'unavailable',
      available: snapshotArg.online,
      attributes: { imageKind: 'screenshot', supportedFeatures: ['snapshot'], requiresAuth: true },
    }));
    entities.push(this.entity(snapshotArg, usedIds, deviceId, uniqueBase, {
      key: 'mediaplayer',
      platform: 'media_player',
      name: `${baseName} Media player`,
      state: snapshotArg.deviceInfo.soundUrlPlaying ? 'playing' : 'idle',
      available: snapshotArg.online,
      attributes: { supportedFeatures: ['play_media', 'stop', 'volume_set'], assumedState: true },
    }));

    return entities;
  }

  public static commandForService(snapshotArg: IFullyKioskSnapshot, requestArg: IServiceCallRequest): IFullyKioskClientCommand | { error: string } | undefined {
    if (requestArg.domain === fullyKioskDomain) {
      return this.fullyKioskServiceCommand(requestArg);
    }

    if (requestArg.domain === 'switch' && ['turn_on', 'turn_off'].includes(requestArg.service)) {
      const target = this.targetSwitch(snapshotArg, requestArg);
      if ('error' in target) {
        return target;
      }
      return this.switchCommand(target.key, requestArg.service === 'turn_on', requestArg, target.entity);
    }

    if (requestArg.domain === 'button' && requestArg.service === 'press') {
      const entity = this.findTargetEntity(snapshotArg, requestArg);
      const cmd = stringOrUndefined(entity?.attributes?.fullyKioskButtonCommand);
      return cmd ? this.sendCommand(cmd, requestArg, {}, entity) : undefined;
    }

    if (requestArg.domain === 'number' && ['set_value', 'set_native_value'].includes(requestArg.service)) {
      const entity = this.findTargetEntity(snapshotArg, requestArg);
      const settingKey = stringOrUndefined(entity?.attributes?.fullyKioskSettingKey);
      if (!settingKey) {
        return { error: 'Fully Kiosk number services require a Fully Kiosk number entity target.' };
      }
      const value = requestArg.data?.value ?? requestArg.data?.native_value;
      if (typeof value !== 'number' && typeof value !== 'string') {
        return { error: 'Fully Kiosk number services require numeric data.value.' };
      }
      return this.setStringSetting(settingKey, value, requestArg, entity);
    }

    if (requestArg.domain === 'camera' && ['snapshot', 'camera_image', 'camera_snapshot', 'snapshot_image'].includes(requestArg.service)) {
      const entity = this.findTargetEntity(snapshotArg, requestArg);
      const imageKind = imageKindFromEntity(entity) || 'camshot';
      return this.imageCommand(imageKind, requestArg, entity);
    }

    if (requestArg.domain === 'media_player') {
      return this.mediaPlayerCommand(requestArg);
    }

    if (requestArg.domain === 'notify' && requestArg.service === 'send_message') {
      const message = stringOrUndefined(requestArg.data?.message);
      if (!message) {
        return { error: 'Fully Kiosk notify.send_message requires data.message.' };
      }
      const targetText = `${requestArg.target.entityId || ''} ${requestArg.data?.type || ''}`.toLowerCase();
      return this.sendCommand(targetText.includes('tts') ? 'textToSpeech' : 'setOverlayMessage', requestArg, { text: message });
    }

    return undefined;
  }

  public static deviceId(snapshotArg: IFullyKioskSnapshot): string {
    return `fully_kiosk.device.${this.uniqueBase(snapshotArg)}`;
  }

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

  private static fullyKioskServiceCommand(requestArg: IServiceCallRequest): IFullyKioskClientCommand | { error: string } | undefined {
    if (requestArg.service === 'refresh') {
      return { type: 'refresh', service: requestArg.service, target: requestArg.target, data: requestArg.data };
    }
    if (requestArg.service === 'snapshot' || requestArg.service === 'status') {
      return undefined;
    }
    if (requestArg.service === 'load_url') {
      const url = stringOrUndefined(requestArg.data?.url);
      return url ? this.sendCommand('loadUrl', requestArg, { url }) : { error: 'Fully Kiosk load_url requires data.url.' };
    }
    if (requestArg.service === 'start_application') {
      const app = stringOrUndefined(requestArg.data?.application || requestArg.data?.package);
      return app ? this.sendCommand('startApplication', requestArg, { package: app }) : { error: 'Fully Kiosk start_application requires data.application.' };
    }
    if (requestArg.service === 'set_config') {
      const key = stringOrUndefined(requestArg.data?.key);
      if (!key || requestArg.data?.value === undefined) {
        return { error: 'Fully Kiosk set_config requires data.key and data.value.' };
      }
      const boolean = booleanLike(requestArg.data.value);
      return boolean === undefined
        ? this.setStringSetting(key, requestArg.data.value, requestArg)
        : this.setBooleanSetting(key, boolean, requestArg);
    }
    if (requestArg.service === 'set_string_setting') {
      const key = stringOrUndefined(requestArg.data?.key);
      return key && requestArg.data?.value !== undefined ? this.setStringSetting(key, requestArg.data.value, requestArg) : { error: 'Fully Kiosk set_string_setting requires data.key and data.value.' };
    }
    if (requestArg.service === 'set_boolean_setting') {
      const key = stringOrUndefined(requestArg.data?.key);
      const value = booleanLike(requestArg.data?.value);
      return key && value !== undefined ? this.setBooleanSetting(key, value, requestArg) : { error: 'Fully Kiosk set_boolean_setting requires data.key and boolean data.value.' };
    }
    if (requestArg.service === 'get_screenshot' || requestArg.service === 'screenshot') {
      return this.imageCommand('screenshot', requestArg);
    }
    if (requestArg.service === 'get_camshot' || requestArg.service === 'camshot') {
      return this.imageCommand('camshot', requestArg);
    }
    if (requestArg.service === 'text_to_speech') {
      const text = stringOrUndefined(requestArg.data?.text || requestArg.data?.message);
      return text ? this.sendCommand('textToSpeech', requestArg, { text }) : { error: 'Fully Kiosk text_to_speech requires data.text or data.message.' };
    }
    if (requestArg.service === 'overlay_message') {
      const text = stringOrUndefined(requestArg.data?.text || requestArg.data?.message);
      return text ? this.sendCommand('setOverlayMessage', requestArg, { text }) : { error: 'Fully Kiosk overlay_message requires data.text or data.message.' };
    }
    if (requestArg.service === 'set_audio_volume') {
      const level = numberOrNull(requestArg.data?.level ?? requestArg.data?.volume_level);
      return level === null ? { error: 'Fully Kiosk set_audio_volume requires numeric data.level.' } : this.sendCommand('setAudioVolume', requestArg, { level, stream: requestArg.data?.stream ?? fullyKioskMusicStream });
    }
    if (requestArg.service === 'play_sound') {
      const url = stringOrUndefined(requestArg.data?.url);
      return url ? this.sendCommand('playSound', requestArg, { url, stream: requestArg.data?.stream ?? fullyKioskMusicStream }) : { error: 'Fully Kiosk play_sound requires data.url.' };
    }

    const cmd = fullyKioskServiceCommands[requestArg.service];
    return cmd ? this.sendCommand(cmd, requestArg) : undefined;
  }

  private static switchCommand(keyArg: string, onArg: boolean, requestArg: IServiceCallRequest, entityArg?: IIntegrationEntity): IFullyKioskClientCommand | { error: string } {
    const description = switches.find((switchArg) => switchArg.key === keyArg);
    if (!description) {
      return { error: `Unknown Fully Kiosk switch key: ${keyArg}` };
    }
    if (description.settingKey) {
      return this.setBooleanSetting(description.settingKey, onArg, requestArg, entityArg);
    }
    return this.sendCommand(onArg ? description.onCommand : description.offCommand, requestArg, {}, entityArg);
  }

  private static mediaPlayerCommand(requestArg: IServiceCallRequest): IFullyKioskClientCommand | { error: string } | undefined {
    if (requestArg.service === 'play_media') {
      const mediaId = stringOrUndefined(requestArg.data?.media_content_id || requestArg.data?.media_id || requestArg.data?.url);
      const mediaType = stringOrUndefined(requestArg.data?.media_content_type || requestArg.data?.media_type) || '';
      if (!mediaId) {
        return { error: 'Fully Kiosk media_player.play_media requires data.media_content_id or data.url.' };
      }
      if (mediaType.startsWith('video/') || mediaType === 'video') {
        return this.sendCommand('playVideo', requestArg, { url: mediaId, stream: fullyKioskMusicStream, showControls: 1, exitOnCompletion: 1 });
      }
      if (mediaType.startsWith('audio/') || mediaType === 'music' || mediaType === 'audio' || !mediaType) {
        return this.sendCommand('playSound', requestArg, { url: mediaId, stream: fullyKioskMusicStream });
      }
      return { error: `Unsupported Fully Kiosk media type ${mediaType}.` };
    }
    if (requestArg.service === 'media_stop' || requestArg.service === 'stop') {
      const mediaType = stringOrUndefined(requestArg.data?.media_content_type || requestArg.data?.media_type) || '';
      return this.sendCommand(mediaType.startsWith('video') ? 'stopVideo' : 'stopSound', requestArg);
    }
    if (requestArg.service === 'volume_set') {
      const volume = numberOrNull(requestArg.data?.volume_level);
      return volume === null ? { error: 'Fully Kiosk media_player.volume_set requires data.volume_level.' } : this.sendCommand('setAudioVolume', requestArg, { level: Math.round(volume * 100), stream: fullyKioskMusicStream });
    }
    return undefined;
  }

  private static targetSwitch(snapshotArg: IFullyKioskSnapshot, requestArg: IServiceCallRequest): { key: string; entity?: IIntegrationEntity } | { error: string } {
    const entity = this.findTargetEntity(snapshotArg, requestArg);
    const key = stringOrUndefined(entity?.attributes?.fullyKioskSwitchKey);
    if (key) {
      return { key, entity };
    }
    if (requestArg.target.deviceId && requestArg.target.deviceId === this.deviceId(snapshotArg)) {
      return { error: 'Fully Kiosk switch services require a switch entity target because the device has multiple switches.' };
    }
    return { error: 'Fully Kiosk switch services require a Fully Kiosk switch entity target.' };
  }

  private static sendCommand(cmdArg: string, requestArg: IServiceCallRequest, paramsArg: Record<string, unknown> = {}, entityArg?: IIntegrationEntity): IFullyKioskClientCommand {
    return this.cleanAttributes({
      type: 'send_command',
      service: requestArg.service,
      cmd: cmdArg,
      params: paramsArg,
      target: requestArg.target,
      data: requestArg.data,
      entityId: entityArg?.id || requestArg.target.entityId,
      deviceId: entityArg?.deviceId || requestArg.target.deviceId,
      uniqueId: entityArg?.uniqueId,
    }) as IFullyKioskClientCommand;
  }

  private static setStringSetting(keyArg: string, valueArg: unknown, requestArg: IServiceCallRequest, entityArg?: IIntegrationEntity): IFullyKioskClientCommand {
    return this.cleanAttributes({
      type: 'set_string_setting',
      service: requestArg.service,
      settingKey: keyArg,
      value: valueArg,
      target: requestArg.target,
      data: requestArg.data,
      entityId: entityArg?.id || requestArg.target.entityId,
      deviceId: entityArg?.deviceId || requestArg.target.deviceId,
      uniqueId: entityArg?.uniqueId,
    }) as IFullyKioskClientCommand;
  }

  private static setBooleanSetting(keyArg: string, valueArg: boolean, requestArg: IServiceCallRequest, entityArg?: IIntegrationEntity): IFullyKioskClientCommand {
    return this.cleanAttributes({
      type: 'set_boolean_setting',
      service: requestArg.service,
      settingKey: keyArg,
      value: valueArg,
      target: requestArg.target,
      data: requestArg.data,
      entityId: entityArg?.id || requestArg.target.entityId,
      deviceId: entityArg?.deviceId || requestArg.target.deviceId,
      uniqueId: entityArg?.uniqueId,
    }) as IFullyKioskClientCommand;
  }

  private static imageCommand(imageKindArg: TFullyKioskImageKind, requestArg: IServiceCallRequest, entityArg?: IIntegrationEntity): IFullyKioskClientCommand {
    return this.cleanAttributes({
      type: 'snapshot_image',
      service: requestArg.service,
      imageKind: imageKindArg,
      target: requestArg.target,
      data: requestArg.data,
      entityId: entityArg?.id || requestArg.target.entityId,
      deviceId: entityArg?.deviceId || requestArg.target.deviceId,
      uniqueId: entityArg?.uniqueId,
    }) as IFullyKioskClientCommand;
  }

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

  private static entity(snapshotArg: IFullyKioskSnapshot, usedIdsArg: Map<string, number>, deviceIdArg: string, uniqueBaseArg: string, definitionArg: IFullyKioskEntityDefinition): IIntegrationEntity {
    const entitySlug = this.uniqueEntitySlug(usedIdsArg, `${this.slug(this.deviceName(snapshotArg))}_${this.slug(definitionArg.key)}`);
    return {
      id: `${definitionArg.platform}.${entitySlug}`,
      uniqueId: `fully_kiosk_${uniqueBaseArg}_${this.slug(definitionArg.key)}`,
      integrationDomain: fullyKioskDomain,
      deviceId: deviceIdArg,
      platform: definitionArg.platform,
      name: definitionArg.name,
      state: primitiveState(definitionArg.state),
      attributes: this.cleanAttributes(definitionArg.attributes || {}),
      available: definitionArg.available,
    };
  }

  private static uniqueEntitySlug(usedIdsArg: Map<string, number>, valueArg: string): string {
    const count = usedIdsArg.get(valueArg) || 0;
    usedIdsArg.set(valueArg, count + 1);
    return count ? `${valueArg}_${count + 1}` : valueArg;
  }

  private static deviceName(snapshotArg: IFullyKioskSnapshot): string {
    return snapshotArg.name || stringOrUndefined(snapshotArg.deviceInfo.deviceName) || 'Fully Kiosk Browser';
  }

  private static uniqueBase(snapshotArg: IFullyKioskSnapshot): string {
    return this.slug(snapshotArg.uniqueId || stringOrUndefined(snapshotArg.deviceInfo.deviceID) || stringOrUndefined(snapshotArg.deviceInfo.deviceId) || snapshotArg.macAddress || snapshotArg.host && `${snapshotArg.host}_${snapshotArg.port || fullyKioskDefaultPort}` || this.deviceName(snapshotArg));
  }

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

const imageKindFromEntity = (entityArg: IIntegrationEntity | undefined): TFullyKioskImageKind | undefined => {
  const value = entityArg?.attributes?.imageKind;
  return value === 'screenshot' || value === 'camshot' ? value : undefined;
};

const booleanValue = (valueArg: unknown): boolean => valueArg === true || valueArg === 'true' || valueArg === '1' || valueArg === 1;

const booleanLike = (valueArg: unknown): boolean | undefined => {
  if (typeof valueArg === 'boolean') {
    return valueArg;
  }
  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;
};

function bytesToMegabytes(valueArg: unknown): number | null {
  const value = numberOrNull(valueArg);
  return value === null ? null : Math.round(value * 0.000001 * 10) / 10;
}

function truncateUrl(valueArg: unknown): string | null {
  const value = stringOrUndefined(valueArg);
  if (!value) {
    return null;
  }
  return value.length > 255 ? value.slice(0, 255) : value;
}

function urlAttributes(valueArg: unknown): Record<string, unknown> {
  const value = stringOrUndefined(valueArg);
  return value ? { fullUrl: value, truncated: value.length > 255 } : {};
}

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

const stringOrNull = (valueArg: unknown): string | null => stringOrUndefined(valueArg) || null;

const numberOrNull = (valueArg: unknown): number | null => {
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return valueArg;
  }
  if (typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg))) {
    return Number(valueArg);
  }
  return null;
};

const primitiveState = (valueArg: unknown): string | number | boolean | null | Record<string, unknown> => {
  if (valueArg === undefined) {
    return null;
  }
  if (valueArg === null || ['string', 'number', 'boolean'].includes(typeof valueArg)) {
    return valueArg as string | number | boolean | null;
  }
  if (typeof valueArg === 'object' && !Array.isArray(valueArg)) {
    return valueArg as Record<string, unknown>;
  }
  return String(valueArg);
};
