import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IServiceCallRequest } from '../../core/types.js';
import type {
  IHunterDouglasPowerViewCommandRequest,
  IHunterDouglasPowerViewConfig,
  IHunterDouglasPowerViewHubRawData,
  IHunterDouglasPowerViewRawData,
  IHunterDouglasPowerViewRoomRaw,
  IHunterDouglasPowerViewSceneRaw,
  IHunterDouglasPowerViewShadeCapabilities,
  IHunterDouglasPowerViewShadePosition,
  IHunterDouglasPowerViewShadeRaw,
  IHunterDouglasPowerViewShadeSnapshot,
  IHunterDouglasPowerViewSnapshot,
  THunterDouglasPowerViewSnapshotSource,
} from './hunterdouglas_powerview.types.js';
import { hunterDouglasPowerViewDefaultPort, hunterDouglasPowerViewDisplayName, hunterDouglasPowerViewDomain, hunterDouglasPowerViewManufacturer } from './hunterdouglas_powerview.types.js';

interface IHunterDouglasPowerViewSnapshotOptions {
  config: IHunterDouglasPowerViewConfig;
  rawData?: Partial<IHunterDouglasPowerViewRawData>;
  online?: boolean;
  source?: THunterDouglasPowerViewSnapshotSource;
  error?: string;
}

const maxPositionV2 = 65535;

const capabilityNames: Record<number, string> = {
  0: 'Bottom Up',
  1: 'Bottom Up Tilt On Closed',
  2: 'Bottom Up Tilt Anywhere',
  3: 'Vertical',
  4: 'Vertical Tilt Anywhere',
  5: 'Tilt Only',
  6: 'Top Down',
  7: 'Top Down Bottom Up',
  8: 'Dual Overlapped',
  9: 'Dual Overlapped Tilt 90',
  10: 'Dual Overlapped Tilt 180',
  11: 'Illuminated Shades',
};

const shadeTypeNames: Record<number, string> = {
  1: 'Designer Roller',
  4: 'Roman',
  5: 'Bottom Up',
  6: 'Duette',
  7: 'Top Down',
  8: 'Duette Top Down Bottom Up',
  9: 'Duette DuoLite Top Down Bottom Up',
  10: 'Duette and Applause SkyLift',
  18: 'Pirouette',
  19: 'Provenance Woven Wood',
  23: 'Silhouette',
  26: 'Skyline Panel Left Stack',
  27: 'Skyline Panel Right Stack',
  28: 'Skyline Panel Split Stack',
  31: 'Vignette',
  32: 'Vignette',
  33: 'Duette Architella Top Down Bottom Up',
  38: 'Silhouette Duolite',
  40: 'Everwood Alternative Wood Blinds',
  42: 'M25T Roller Blind',
  43: 'Facette',
  44: 'Twist',
  47: 'Pleated Top Down Bottom Up',
  49: 'AC Roller',
  51: 'Venetian Tilt Anywhere',
  52: 'Banded Shades',
  53: 'Sonnette',
  54: 'Vertical Slats Left Stack',
  55: 'Vertical Slats Right Stack',
  56: 'Vertical Slats Split Stack',
  57: 'Carole Roman Shades',
  62: 'Venetian Tilt Anywhere',
  65: 'Vignette Duolite',
  66: 'Palm Beach Shutters',
  69: 'Curtain Left Stack',
  70: 'Curtain Right Stack',
  71: 'Curtain Split Stack',
  72: 'Silhouette',
  79: 'Duolite Lift',
  84: 'Vignette',
  95: 'Aura Illuminated Roller',
};

export class HunterDouglasPowerViewMapper {
  public static toSnapshot(optionsArg: IHunterDouglasPowerViewSnapshotOptions): IHunterDouglasPowerViewSnapshot {
    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 apiVersion = optionsArg.config.apiVersion || rawData.apiVersion || this.apiVersionFromRaw(rawData) || 2;
    const hubRaw = rawData.hub || {};
    const homeRaw = rawData.home || {};
    const hubNameFromHome = this.hubNameFromHome(hubRaw, homeRaw);
    const hubId = stringValue(optionsArg.config.uniqueId)
      || stringValue(optionsArg.config.serialNumber)
      || this.hubSerial(hubRaw)
      || this.hubMac(hubRaw)
      || endpointHost(optionsArg.config.host || optionsArg.config.url)
      || 'powerview';
    const hubName = optionsArg.config.name
      || hubNameFromHome
      || this.hubName(hubRaw)
      || (hubId !== 'powerview' ? `PowerView ${hubId}` : hunterDouglasPowerViewDisplayName);
    const rooms = this.rooms(rawData.rooms || []);
    const roomNames = new Map(rooms.map((roomArg) => [roomArg.id, roomArg.name]));
    const shades = this.shades(rawData.shades || [], apiVersion, roomNames);
    const scenes = this.scenes(rawData.scenes || [], roomNames);
    const online = optionsArg.online ?? optionsArg.config.online ?? Boolean(rawData.hub || rawData.shades?.length || rawData.scenes?.length || optionsArg.source === 'http' || optionsArg.source === 'client');
    const source = optionsArg.source || (rawData.hub ? 'http' : Object.keys(rawData).length ? 'manual' : 'runtime');

    const snapshot: IHunterDouglasPowerViewSnapshot = {
      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: optionsArg.config.manufacturer || hunterDouglasPowerViewManufacturer,
        model: optionsArg.config.model || this.hubModel(hubRaw, apiVersion),
        firmware: this.firmwareString(this.hubFirmware(hubRaw)),
        hardware: stringValue(this.hubFirmware(hubRaw)?.name),
        apiVersion,
        role: this.hubRole(hubRaw, apiVersion),
        macAddress: optionsArg.config.macAddress || this.normalizeMac(this.hubMac(hubRaw)),
        serialNumber: stringValue(optionsArg.config.serialNumber) || this.hubSerial(hubRaw),
        rawData: Object.keys(hubRaw).length ? hubRaw : undefined,
      },
      rooms,
      scenes,
      shades,
      capabilities: {
        localControl: this.hasLocalControl(optionsArg.config),
        covers: shades.length > 0,
        scenes: scenes.length > 0,
        rooms: rooms.length > 0,
        tilt: shades.some((shadeArg) => shadeArg.capabilities.tilt),
        stop: true,
      },
      rawData: Object.keys(rawData).length ? rawData as IHunterDouglasPowerViewRawData : 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: IHunterDouglasPowerViewSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const writable = snapshotArg.capabilities.localControl;
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [{
      id: this.hubDeviceId(snapshotArg),
      integrationDomain: hunterDouglasPowerViewDomain,
      name: snapshotArg.hub.name,
      protocol: snapshotArg.hub.host ? 'http' : 'unknown',
      manufacturer: snapshotArg.hub.manufacturer,
      model: snapshotArg.hub.model || `PowerView Generation ${snapshotArg.hub.apiVersion}`,
      online: snapshotArg.online,
      features: [
        { id: 'online', capability: 'sensor', name: 'Online', readable: true, writable: false },
        { id: 'shade_count', capability: 'sensor', name: 'Shade count', readable: true, writable: false },
        { id: 'scene_count', capability: 'sensor', name: 'Scene count', readable: true, writable: false },
      ],
      state: [
        { featureId: 'online', value: snapshotArg.online, updatedAt },
        { featureId: 'shade_count', value: snapshotArg.shades.length, updatedAt },
        { featureId: 'scene_count', value: snapshotArg.scenes.length, updatedAt },
      ],
      metadata: this.cleanAttributes({
        apiVersion: snapshotArg.hub.apiVersion,
        role: snapshotArg.hub.role,
        macAddress: snapshotArg.hub.macAddress,
        serialNumber: snapshotArg.hub.serialNumber,
        firmware: snapshotArg.hub.firmware,
        source: snapshotArg.source,
        error: snapshotArg.error,
      }),
    }];

    for (const shade of snapshotArg.shades) {
      const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
        { id: 'position', capability: 'cover', name: 'Position', readable: true, writable, unit: '%' },
      ];
      if (shade.capabilities.tilt) {
        features.push({ id: 'tilt', capability: 'cover', name: 'Tilt', readable: true, writable, unit: '%' });
      }
      if (shade.batteryPercent !== undefined) {
        features.push({ id: 'battery', capability: 'sensor', name: 'Battery', readable: true, writable: false, unit: '%' });
      }
      devices.push({
        id: this.shadeDeviceId(snapshotArg, shade),
        integrationDomain: hunterDouglasPowerViewDomain,
        name: shade.name,
        room: shade.roomName,
        protocol: snapshotArg.hub.host ? 'http' : 'unknown',
        manufacturer: snapshotArg.hub.manufacturer,
        model: shade.typeName || capabilityNames[Number(shade.capabilityType)] || 'PowerView Shade',
        online: snapshotArg.online,
        features,
        state: this.stateForShade(shade, features, updatedAt),
        metadata: this.cleanAttributes({
          shadeId: shade.shadeId,
          roomId: shade.roomId,
          capabilityType: shade.capabilityType,
          type: shade.type,
          typeName: shade.typeName,
          firmware: shade.firmware,
          powerSource: shade.powerSource,
          signalStrength: shade.signalStrength,
          viaDevice: this.hubDeviceId(snapshotArg),
        }),
      });
    }
    return devices;
  }

  public static toEntities(snapshotArg: IHunterDouglasPowerViewSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    for (const shade of snapshotArg.shades) {
      entities.push({
        id: `cover.${slug(shade.name)}`,
        uniqueId: `${snapshotArg.hub.id}_${shade.shadeId}_cover`,
        integrationDomain: hunterDouglasPowerViewDomain,
        deviceId: this.shadeDeviceId(snapshotArg, shade),
        platform: 'cover',
        name: shade.name,
        state: this.coverState(shade),
        available: snapshotArg.online,
        attributes: this.cleanAttributes({
          shadeId: shade.shadeId,
          roomId: shade.roomId,
          roomName: shade.roomName,
          currentPosition: this.homeAssistantPrimaryPosition(shade),
          currentTiltPosition: shade.positions.tilt,
          primaryPosition: shade.positions.primary,
          secondaryPosition: shade.positions.secondary,
          batteryPercent: shade.batteryPercent,
          powerSource: shade.powerSource,
          assumedState: shade.powerSource ? shade.powerSource !== 'Hardwired' : undefined,
          capabilityType: shade.capabilityType,
          supportedFeatures: this.supportedCoverFeatures(shade),
        }),
      });
    }

    for (const scene of snapshotArg.scenes) {
      entities.push({
        id: `button.${slug(scene.name)}_scene`,
        uniqueId: `${snapshotArg.hub.id}_${scene.sceneId}_scene`,
        integrationDomain: hunterDouglasPowerViewDomain,
        deviceId: this.hubDeviceId(snapshotArg),
        platform: 'button',
        name: `${scene.name} Scene`,
        state: 'available',
        available: snapshotArg.online,
        attributes: this.cleanAttributes({
          sceneId: scene.sceneId,
          sceneEntityId: `scene.${slug(scene.name)}`,
          roomId: scene.roomId,
          roomName: scene.roomName,
        }),
      });
    }
    return entities;
  }

  public static commandForService(snapshotArg: IHunterDouglasPowerViewSnapshot, requestArg: IServiceCallRequest): IHunterDouglasPowerViewCommandRequest | undefined {
    if (requestArg.domain === hunterDouglasPowerViewDomain && requestArg.service === 'refresh') {
      return { action: 'refresh', service: `${requestArg.domain}.${requestArg.service}`, target: requestArg.target, data: requestArg.data };
    }
    if (requestArg.domain === hunterDouglasPowerViewDomain && requestArg.service === 'raw_request') {
      const path = stringValue(requestArg.data?.path);
      if (!path) {
        return undefined;
      }
      return {
        action: 'raw_request',
        path,
        method: this.methodValue(requestArg.data?.method) || 'GET',
        body: requestArg.data?.body,
        query: recordValue(requestArg.data?.query) as Record<string, string | number | boolean | undefined> | undefined,
        service: `${requestArg.domain}.${requestArg.service}`,
        target: requestArg.target,
        data: requestArg.data,
      };
    }

    const scene = this.sceneForTarget(snapshotArg, requestArg);
    if (scene && ((requestArg.domain === 'scene' && ['turn_on', 'activate'].includes(requestArg.service)) || (requestArg.domain === 'button' && requestArg.service === 'press') || (requestArg.domain === hunterDouglasPowerViewDomain && ['activate_scene', 'scene_activate'].includes(requestArg.service)))) {
      return { action: 'activate_scene', sceneId: scene.sceneId, service: `${requestArg.domain}.${requestArg.service}`, target: requestArg.target, data: requestArg.data };
    }

    if (requestArg.domain === hunterDouglasPowerViewDomain && ['activate_scene', 'scene_activate'].includes(requestArg.service)) {
      const sceneId = stringValue(requestArg.data?.sceneId) || stringValue(requestArg.data?.scene_id);
      return sceneId ? { action: 'activate_scene', sceneId, service: `${requestArg.domain}.${requestArg.service}`, target: requestArg.target, data: requestArg.data } : undefined;
    }

    const shade = this.shadeForTarget(snapshotArg, requestArg);
    if (!shade) {
      return undefined;
    }
    const service = `${requestArg.domain}.${requestArg.service}`;
    if (requestArg.domain === 'cover' || requestArg.domain === hunterDouglasPowerViewDomain) {
      if (requestArg.service === 'open_cover') {
        return { action: 'move_shade', shadeId: shade.shadeId, positions: this.openPosition(shade), service, target: requestArg.target, data: requestArg.data };
      }
      if (requestArg.service === 'close_cover') {
        return { action: 'move_shade', shadeId: shade.shadeId, positions: this.closePosition(shade), service, target: requestArg.target, data: requestArg.data };
      }
      if (requestArg.service === 'stop_cover' || requestArg.service === 'stop_cover_tilt') {
        return { action: 'stop_shade', shadeId: shade.shadeId, service, target: requestArg.target, data: requestArg.data };
      }
      if (requestArg.service === 'set_cover_position') {
        const position = numberValue(requestArg.data?.position);
        return position === undefined ? undefined : { action: 'move_shade', shadeId: shade.shadeId, positions: this.targetPrimaryPosition(shade, position), service, target: requestArg.target, data: requestArg.data };
      }
      if (requestArg.service === 'open_cover_tilt') {
        return { action: 'move_shade', shadeId: shade.shadeId, positions: { tilt: 100 }, service, target: requestArg.target, data: requestArg.data };
      }
      if (requestArg.service === 'close_cover_tilt') {
        return { action: 'move_shade', shadeId: shade.shadeId, positions: { tilt: 0 }, service, target: requestArg.target, data: requestArg.data };
      }
      if (requestArg.service === 'set_cover_tilt_position') {
        const tilt = numberValue(requestArg.data?.tilt_position ?? requestArg.data?.tiltPosition);
        return tilt === undefined ? undefined : { action: 'move_shade', shadeId: shade.shadeId, positions: { tilt }, service, target: requestArg.target, data: requestArg.data };
      }
    }
    return undefined;
  }

  public static offlineSnapshot(configArg: IHunterDouglasPowerViewConfig, errorArg: string): IHunterDouglasPowerViewSnapshot {
    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 hubDeviceId(snapshotArg: IHunterDouglasPowerViewSnapshot): string {
    return `hunterdouglas_powerview.hub.${slug(snapshotArg.hub.id)}`;
  }

  public static shadeDeviceId(snapshotArg: IHunterDouglasPowerViewSnapshot, shadeArg: IHunterDouglasPowerViewShadeSnapshot): string {
    return `hunterdouglas_powerview.shade.${slug(`${snapshotArg.hub.id}_${shadeArg.shadeId}`)}`;
  }

  public static rawPositionsForApi(positionsArg: IHunterDouglasPowerViewShadePosition, apiVersionArg: number): Record<string, unknown> {
    if (apiVersionArg >= 3) {
      const raw: Record<string, unknown> = {};
      for (const key of ['primary', 'secondary', 'tilt', 'velocity'] as const) {
        const value = positionsArg[key];
        if (value !== undefined) {
          raw[key] = key === 'velocity' ? value : Math.round(clamp(value, 0, 100)) / 100;
        }
      }
      return raw;
    }

    const raw: Record<string, unknown> = {};
    if (positionsArg.primary !== undefined) {
      raw.posKind1 = 1;
      raw.position1 = this.percentToV2(positionsArg.primary);
    }
    if (positionsArg.secondary !== undefined) {
      const kindKey = positionsArg.primary === undefined ? 'posKind1' : 'posKind2';
      const positionKey = positionsArg.primary === undefined ? 'position1' : 'position2';
      raw[kindKey] = 2;
      raw[positionKey] = this.percentToV2(positionsArg.secondary);
    }
    if (positionsArg.tilt !== undefined && !(positionsArg.primary !== undefined && positionsArg.secondary !== undefined)) {
      const hasPrimaryOrSecondary = positionsArg.primary !== undefined || positionsArg.secondary !== undefined;
      raw[hasPrimaryOrSecondary ? 'posKind2' : 'posKind1'] = 3;
      raw[hasPrimaryOrSecondary ? 'position2' : 'position1'] = this.percentToV2(positionsArg.tilt);
    }
    return raw;
  }

  private static normalizeSnapshot(snapshotArg: IHunterDouglasPowerViewSnapshot, configArg: IHunterDouglasPowerViewConfig, sourceArg: THunterDouglasPowerViewSnapshotSource, errorArg?: string): IHunterDouglasPowerViewSnapshot {
    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),
        url: snapshotArg.hub.url || endpointUrl(configArg.host || configArg.url, configArg.port),
      },
      capabilities: {
        ...snapshotArg.capabilities,
        localControl: this.hasLocalControl(configArg),
      },
      source: sourceArg,
      error: errorArg ?? snapshotArg.error,
    };
  }

  private static rawData(configArg: IHunterDouglasPowerViewConfig, rawDataArg: Partial<IHunterDouglasPowerViewRawData> | undefined): Partial<IHunterDouglasPowerViewRawData> {
    const rawData = rawDataArg || configArg.rawData || {};
    return {
      apiVersion: rawData.apiVersion,
      hub: rawData.hub,
      home: rawData.home,
      rooms: rawData.rooms,
      scenes: rawData.scenes,
      shades: rawData.shades,
      fetchedAt: rawData.fetchedAt,
    };
  }

  private static rooms(roomsArg: IHunterDouglasPowerViewRoomRaw[]): IHunterDouglasPowerViewSnapshot['rooms'] {
    return roomsArg.map((roomArg) => unwrapRoom(roomArg)).filter((roomArg) => roomArg.id !== undefined).map((roomArg) => ({
      id: String(roomArg.id),
      name: decodePowerViewName(stringValue(roomArg.ptName) || stringValue(roomArg.name) || `Room ${roomArg.id}`),
      rawData: roomArg,
    }));
  }

  private static scenes(scenesArg: IHunterDouglasPowerViewSceneRaw[], roomNamesArg: Map<string, string>): IHunterDouglasPowerViewSnapshot['scenes'] {
    return scenesArg.map((sceneArg) => unwrapScene(sceneArg)).filter((sceneArg) => sceneArg.id !== undefined).map((sceneArg) => {
      const roomId = this.roomId(sceneArg);
      return {
        sceneId: String(sceneArg.id),
        name: decodePowerViewName(stringValue(sceneArg.ptName) || stringValue(sceneArg.name) || `Scene ${sceneArg.id}`),
        roomId,
        roomName: roomId ? roomNamesArg.get(roomId) : undefined,
        rawData: sceneArg,
      };
    });
  }

  private static shades(shadesArg: IHunterDouglasPowerViewShadeRaw[], apiVersionArg: number, roomNamesArg: Map<string, string>): IHunterDouglasPowerViewShadeSnapshot[] {
    return shadesArg.map((shadeArg) => unwrapShade(shadeArg)).filter((shadeArg) => shadeArg.id !== undefined).map((shadeArg) => {
      const roomId = this.roomId(shadeArg);
      const capabilityType = numberValue(shadeArg.capabilities);
      const type = numberValue(shadeArg.type);
      return {
        shadeId: String(shadeArg.id),
        name: decodePowerViewName(stringValue(shadeArg.ptName) || stringValue(shadeArg.name) || `Shade ${shadeArg.id}`),
        roomId,
        roomName: roomId ? roomNamesArg.get(roomId) : undefined,
        type: shadeArg.type,
        typeName: type !== undefined ? shadeTypeNames[type] : undefined,
        capabilityType: shadeArg.capabilities,
        capabilities: this.capabilities(capabilityType),
        positions: this.positions(shadeArg.positions, apiVersionArg),
        firmware: this.firmwareString(shadeArg.firmware),
        batteryPercent: this.batteryPercent(shadeArg, apiVersionArg),
        powerSource: this.powerSource(shadeArg, apiVersionArg),
        signalStrength: this.signalStrength(shadeArg, apiVersionArg),
        rawData: shadeArg,
      };
    });
  }

  private static positions(rawArg: Record<string, unknown> | undefined, apiVersionArg: number): IHunterDouglasPowerViewShadePosition {
    if (!rawArg) {
      return {};
    }
    if (apiVersionArg >= 3 || 'primary' in rawArg || 'secondary' in rawArg || 'tilt' in rawArg) {
      return this.cleanPosition({
        primary: this.apiV3ToPercent(numberValue(rawArg.primary)),
        secondary: this.apiV3ToPercent(numberValue(rawArg.secondary)),
        tilt: this.apiV3ToPercent(numberValue(rawArg.tilt)),
        velocity: numberValue(rawArg.velocity),
      });
    }

    const position: IHunterDouglasPowerViewShadePosition = {};
    const pairs = [
      { kind: numberValue(rawArg.posKind1), value: numberValue(rawArg.position1) },
      { kind: numberValue(rawArg.posKind2), value: numberValue(rawArg.position2) },
    ];
    for (const pair of pairs) {
      if (pair.kind === undefined || pair.value === undefined) {
        continue;
      }
      if (pair.kind === 1) {
        position.primary = this.v2ToPercent(pair.value);
      } else if (pair.kind === 2) {
        position.secondary = this.v2ToPercent(pair.value);
      } else if (pair.kind === 3) {
        position.tilt = this.v2ToPercent(pair.value);
      }
    }
    return this.cleanPosition(position);
  }

  private static capabilities(capabilityTypeArg: number | undefined): IHunterDouglasPowerViewShadeCapabilities {
    const type = capabilityTypeArg ?? 0;
    return {
      primary: type !== 5,
      secondary: [7, 8, 9, 10, 11].includes(type),
      tilt: [1, 2, 4, 5, 9, 10].includes(type),
      stop: true,
      topDown: type === 6,
      tiltOnly: type === 5,
      dualRail: type === 7,
      dualOverlapped: [8, 9, 10, 11].includes(type),
      light: type === 11,
    };
  }

  private static supportedCoverFeatures(shadeArg: IHunterDouglasPowerViewShadeSnapshot): string[] {
    const features = ['open', 'close', 'set_position'];
    if (shadeArg.capabilities.stop) {
      features.push('stop');
    }
    if (shadeArg.capabilities.tilt) {
      features.push('open_tilt', 'close_tilt', 'set_tilt_position', 'stop_tilt');
    }
    return features;
  }

  private static homeAssistantPrimaryPosition(shadeArg: IHunterDouglasPowerViewShadeSnapshot): number | undefined {
    if (shadeArg.capabilities.tiltOnly) {
      return undefined;
    }
    const primary = shadeArg.positions.primary;
    if (primary === undefined) {
      return undefined;
    }
    return shadeArg.capabilities.topDown ? 100 - primary : primary;
  }

  private static coverState(shadeArg: IHunterDouglasPowerViewShadeSnapshot): string {
    const position = this.homeAssistantPrimaryPosition(shadeArg) ?? shadeArg.positions.tilt;
    if (position === undefined) {
      return 'unknown';
    }
    return position <= 0 ? 'closed' : 'open';
  }

  private static targetPrimaryPosition(shadeArg: IHunterDouglasPowerViewShadeSnapshot, positionArg: number): IHunterDouglasPowerViewShadePosition {
    return { primary: shadeArg.capabilities.topDown ? 100 - clamp(positionArg, 0, 100) : clamp(positionArg, 0, 100) };
  }

  private static openPosition(shadeArg: IHunterDouglasPowerViewShadeSnapshot): IHunterDouglasPowerViewShadePosition {
    if (shadeArg.capabilities.tiltOnly) {
      return { tilt: 100 };
    }
    if (shadeArg.capabilities.topDown) {
      return { primary: 0 };
    }
    if (shadeArg.capabilities.dualRail) {
      return { primary: 100, secondary: 0 };
    }
    return { primary: 100 };
  }

  private static closePosition(shadeArg: IHunterDouglasPowerViewShadeSnapshot): IHunterDouglasPowerViewShadePosition {
    if (shadeArg.capabilities.tiltOnly) {
      return { tilt: 0 };
    }
    if (shadeArg.capabilities.topDown) {
      return { primary: 100 };
    }
    if (shadeArg.capabilities.dualRail) {
      return { primary: 0, secondary: 0 };
    }
    return { primary: 0 };
  }

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

  private static featureValue(shadeArg: IHunterDouglasPowerViewShadeSnapshot, featureArg: string): unknown {
    if (featureArg === 'position') {
      return this.homeAssistantPrimaryPosition(shadeArg) ?? null;
    }
    if (featureArg === 'tilt') {
      return shadeArg.positions.tilt ?? null;
    }
    if (featureArg === 'battery') {
      return shadeArg.batteryPercent ?? null;
    }
    return null;
  }

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

  private static sceneForTarget(snapshotArg: IHunterDouglasPowerViewSnapshot, requestArg: IServiceCallRequest): IHunterDouglasPowerViewSnapshot['scenes'][number] | undefined {
    const dataSceneId = stringValue(requestArg.data?.sceneId) || stringValue(requestArg.data?.scene_id);
    if (dataSceneId) {
      return snapshotArg.scenes.find((sceneArg) => sceneArg.sceneId === dataSceneId);
    }
    const target = requestArg.target.entityId || requestArg.target.deviceId;
    if (!target) {
      return undefined;
    }
    return snapshotArg.scenes.find((sceneArg) => target === sceneArg.sceneId || target === `scene.${slug(sceneArg.name)}` || target === `button.${slug(sceneArg.name)}_scene` || target === `${snapshotArg.hub.id}_${sceneArg.sceneId}_scene`);
  }

  private static shadeForTarget(snapshotArg: IHunterDouglasPowerViewSnapshot, requestArg: IServiceCallRequest): IHunterDouglasPowerViewShadeSnapshot | undefined {
    const dataShadeId = stringValue(requestArg.data?.shadeId) || stringValue(requestArg.data?.shade_id);
    if (dataShadeId) {
      return snapshotArg.shades.find((shadeArg) => shadeArg.shadeId === dataShadeId);
    }
    const target = requestArg.target.entityId || requestArg.target.deviceId;
    if (!target) {
      return undefined;
    }
    return snapshotArg.shades.find((shadeArg) => target === shadeArg.shadeId || target === `cover.${slug(shadeArg.name)}` || target === this.shadeDeviceId(snapshotArg, shadeArg) || target === `${snapshotArg.hub.id}_${shadeArg.shadeId}_cover`);
  }

  private static hubSerial(rawArg: IHunterDouglasPowerViewHubRawData): string | undefined {
    return stringValue(valueAt(rawArg, ['userData', 'serialNumber'])) || stringValue(valueAt(rawArg, ['config', 'serialNumber']));
  }

  private static hubMac(rawArg: IHunterDouglasPowerViewHubRawData): string | undefined {
    return stringValue(valueAt(rawArg, ['userData', 'macAddress'])) || stringValue(valueAt(rawArg, ['config', 'networkStatus', 'primaryMacAddress']));
  }

  private static hubName(rawArg: IHunterDouglasPowerViewHubRawData): string | undefined {
    const name = stringValue(valueAt(rawArg, ['userData', 'hubName'])) || stringValue(valueAt(rawArg, ['config', 'hubName']));
    return name ? decodePowerViewName(name) : undefined;
  }

  private static hubNameFromHome(hubRawArg: IHunterDouglasPowerViewHubRawData, homeRawArg: IHunterDouglasPowerViewHubRawData): string | undefined {
    const serial = this.hubSerial(hubRawArg);
    const mac = this.hubMac(hubRawArg);
    const gateways = Array.isArray(homeRawArg.gateways) ? homeRawArg.gateways : [];
    const gateway = gateways.find((gatewayArg) => gatewayArg.serial === serial || gatewayArg.mac === mac);
    return stringValue(gateway?.name);
  }

  private static hubFirmware(rawArg: IHunterDouglasPowerViewHubRawData): IHunterDouglasPowerViewHubRawData['firmware'] | undefined {
    return recordValue(valueAt(rawArg, ['userData', 'firmware', 'mainProcessor']))
      || recordValue(valueAt(rawArg, ['config', 'firmware', 'mainProcessor']))
      || recordValue(valueAt(rawArg, ['firmware', 'mainProcessor']));
  }

  private static hubModel(rawArg: IHunterDouglasPowerViewHubRawData, apiVersionArg: number): string {
    const firmwareName = stringValue(this.hubFirmware(rawArg)?.name);
    if (firmwareName === 'PV_Gen3') {
      return 'Powerview Generation 3';
    }
    if (firmwareName === 'PV Hub2.0') {
      return 'Powerview Generation 2';
    }
    if (firmwareName === 'PowerView Hub') {
      return 'Powerview Generation 1';
    }
    return firmwareName || `Powerview Generation ${apiVersionArg}`;
  }

  private static hubRole(rawArg: IHunterDouglasPowerViewHubRawData, apiVersionArg: number): 'Primary' | 'Secondary' | 'Unknown' {
    if (apiVersionArg <= 2) {
      return 'Primary';
    }
    const mgwStatus = valueAt(rawArg, ['config', 'mgwStatus', 'running']);
    const primary = valueAt(rawArg, ['config', 'mgwConfig', 'primary']);
    if (mgwStatus === false || (mgwStatus === true && primary === true)) {
      return 'Primary';
    }
    if (mgwStatus === true && primary === false) {
      return 'Secondary';
    }
    return 'Unknown';
  }

  private static apiVersionFromRaw(rawArg: Partial<IHunterDouglasPowerViewRawData>): number | undefined {
    const revision = numberValue(this.hubFirmware(rawArg.hub || {})?.revision);
    if (revision !== undefined) {
      return revision;
    }
    const fwVersion = stringValue(rawArg.hub?.fwVersion);
    if (fwVersion) {
      return Number(fwVersion.split('.')[0]) || undefined;
    }
    return undefined;
  }

  private static roomId(rawArg: IHunterDouglasPowerViewSceneRaw | IHunterDouglasPowerViewShadeRaw): string | undefined {
    const roomId = rawArg.roomId ?? (Array.isArray(rawArg.roomIds) ? rawArg.roomIds[0] : undefined);
    return roomId === undefined ? undefined : String(roomId);
  }

  private static firmwareString(firmwareArg: Record<string, unknown> | undefined): string | undefined {
    if (!firmwareArg) {
      return undefined;
    }
    const revision = numberValue(firmwareArg.revision);
    const subRevision = numberValue(firmwareArg.subRevision);
    const build = numberValue(firmwareArg.build);
    return revision !== undefined && subRevision !== undefined && build !== undefined ? `${revision}.${subRevision}.${build}` : undefined;
  }

  private static batteryPercent(shadeArg: IHunterDouglasPowerViewShadeRaw, apiVersionArg: number): number | undefined {
    if (apiVersionArg >= 3) {
      const levels: Record<number, number> = { 4: 100, 3: 100, 2: 50, 1: 20, 0: 0 };
      const status = numberValue(shadeArg.batteryStatus);
      return status === undefined ? undefined : levels[status] ?? 0;
    }
    const strength = numberValue(shadeArg.batteryStrength);
    return strength === undefined ? undefined : Math.min(100, Math.round((strength / 180) * 100));
  }

  private static powerSource(shadeArg: IHunterDouglasPowerViewShadeRaw, apiVersionArg: number): string | undefined {
    const raw = numberValue(apiVersionArg >= 3 ? shadeArg.powerType : shadeArg.batteryKind);
    if (raw === undefined) {
      return undefined;
    }
    if (apiVersionArg >= 3) {
      return ({ 0: 'Battery', 1: 'Hardwired', 2: 'Rechargable', 11: 'Rechargable-Fixed', 12: 'Hardwired-Fixed' } as Record<number, string>)[raw];
    }
    return ({ 1: 'Hardwired', 2: 'Battery', 3: 'Rechargable', 11: 'Rechargable-Fixed', 12: 'Hardwired-Fixed' } as Record<number, string>)[raw];
  }

  private static signalStrength(shadeArg: IHunterDouglasPowerViewShadeRaw, apiVersionArg: number): number | undefined {
    const value = numberValue(shadeArg.signalStrength);
    if (value === undefined) {
      return undefined;
    }
    return apiVersionArg >= 3 ? value : Math.round((value / 4) * 100);
  }

  private static apiV3ToPercent(valueArg: number | undefined): number | undefined {
    if (valueArg === undefined) {
      return undefined;
    }
    return Math.round(clamp(valueArg <= 1 ? valueArg * 100 : valueArg, 0, 100));
  }

  private static v2ToPercent(valueArg: number): number {
    return Math.round(clamp((valueArg / maxPositionV2) * 100, 0, 100));
  }

  private static percentToV2(valueArg: number): number {
    return Math.round((clamp(valueArg, 0, 100) / 100) * maxPositionV2);
  }

  private static cleanPosition(positionArg: IHunterDouglasPowerViewShadePosition): IHunterDouglasPowerViewShadePosition {
    const result: IHunterDouglasPowerViewShadePosition = {};
    for (const [key, value] of Object.entries(positionArg)) {
      if (value !== undefined && Number.isFinite(value)) {
        (result as Record<string, number>)[key] = value;
      }
    }
    return result;
  }

  private static methodValue(valueArg: unknown): IHunterDouglasPowerViewCommandRequest['method'] | undefined {
    const method = stringValue(valueArg)?.toUpperCase();
    return method === 'GET' || method === 'PUT' || method === 'POST' || method === 'DELETE' ? method : undefined;
  }

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

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

const unwrapRoom = (valueArg: IHunterDouglasPowerViewRoomRaw): IHunterDouglasPowerViewRoomRaw => valueArg.room || valueArg;
const unwrapScene = (valueArg: IHunterDouglasPowerViewSceneRaw): IHunterDouglasPowerViewSceneRaw => valueArg.scene || valueArg;
const unwrapShade = (valueArg: IHunterDouglasPowerViewShadeRaw): IHunterDouglasPowerViewShadeRaw => valueArg.shade || 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 ? hunterDouglasPowerViewDefaultPort : undefined);
const endpointUrl = (valueArg: string | undefined, portArg?: number): string | undefined => {
  const parsed = parseEndpoint(valueArg);
  const host = parsed?.host || endpointHost(valueArg);
  if (!host) {
    return undefined;
  }
  const protocol = parsed?.protocol || 'http';
  const port = portArg || parsed?.port;
  const portSuffix = port && !((protocol === 'http' && port === 80) || (protocol === 'https' && port === 443)) ? `:${port}` : '';
  return `${protocol}://${host}${portSuffix}`;
};

const parseEndpoint = (valueArg: string | undefined): { protocol: 'http' | 'https'; 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 { protocol: url.protocol === 'https:' ? 'https' : 'http', 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, '') || 'powerview';
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 recordValue = (valueArg: unknown): Record<string, unknown> | undefined => valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : undefined;
const valueAt = (valueArg: Record<string, unknown> | undefined, pathArg: string[]): unknown => {
  let current: unknown = valueArg;
  for (const key of pathArg) {
    if (!current || typeof current !== 'object' || Array.isArray(current)) {
      return undefined;
    }
    current = (current as Record<string, unknown>)[key];
  }
  return current;
};
const clamp = (valueArg: number, minArg: number, maxArg: number): number => Math.min(maxArg, Math.max(minArg, valueArg));

export const decodePowerViewName = (valueArg: string): string => {
  const trimmed = valueArg.trim();
  if (!trimmed || !/^[A-Za-z0-9+/]+={0,2}$/.test(trimmed) || trimmed.length % 4 !== 0) {
    return valueArg;
  }
  try {
    const decoded = Buffer.from(trimmed, 'base64').toString('utf8');
    const encoded = Buffer.from(decoded, 'utf8').toString('base64').replace(/=+$/, '');
    return encoded === trimmed.replace(/=+$/, '') && decoded.trim() ? decoded : valueArg;
  } catch {
    return valueArg;
  }
};
