import * as plugins from '../../plugins.js';
import type {
  IMadvrCommandModel,
  IMadvrCommandRequest,
  IMadvrCommandResult,
  IMadvrConfig,
  IMadvrDisplayState,
  IMadvrProcessorState,
  IMadvrRawState,
  IMadvrSnapshot,
  TMadvrCommandInput,
  TMadvrCommandName,
  TMadvrMenu,
  TMadvrProfileGroup,
  TMadvrRemoteKey,
  TMadvrToggle,
} from './madvr.types.js';
import { madvrDefaultPort } from './madvr.types.js';

type TMadvrCommandKind = 'single' | 'info' | 'key' | 'menu' | 'profile' | 'toggle';

interface IMadvrCommandDefinition {
  wire: string;
  kind: TMadvrCommandKind;
  informational: boolean;
}

const commandTimeoutMs = 3000;
const connectTimeoutMs = 5000;
const snapshotCollectMs = 1000;
const commandResponseCollectMs = 150;
const heartbeatCommand = 'Heartbeat\r\n';

const commandDefinitions: Record<Exclude<TMadvrCommandName, 'PowerOn'>, IMadvrCommandDefinition> = {
  PowerOff: { wire: 'PowerOff', kind: 'single', informational: false },
  Standby: { wire: 'Standby', kind: 'single', informational: false },
  Restart: { wire: 'Restart', kind: 'single', informational: false },
  ReloadSoftware: { wire: 'ReloadSoftware', kind: 'single', informational: false },
  Bye: { wire: 'Bye', kind: 'single', informational: false },
  ResetTemporary: { wire: 'ResetTemporary', kind: 'single', informational: false },
  ActivateProfile: { wire: 'ActivateProfile', kind: 'profile', informational: false },
  OpenMenu: { wire: 'OpenMenu', kind: 'menu', informational: false },
  CloseMenu: { wire: 'CloseMenu', kind: 'single', informational: false },
  KeyPress: { wire: 'KeyPress', kind: 'key', informational: false },
  KeyHold: { wire: 'KeyHold', kind: 'key', informational: false },
  GetIncomingSignalInfo: { wire: 'GetIncomingSignalInfo', kind: 'info', informational: true },
  GetOutgoingSignalInfo: { wire: 'GetOutgoingSignalInfo', kind: 'info', informational: true },
  GetAspectRatio: { wire: 'GetAspectRatio', kind: 'info', informational: true },
  GetMaskingRatio: { wire: 'GetMaskingRatio', kind: 'info', informational: true },
  GetTemperatures: { wire: 'GetTemperatures', kind: 'info', informational: true },
  GetMacAddress: { wire: 'GetMacAddress', kind: 'info', informational: true },
  Toggle: { wire: 'Toggle', kind: 'toggle', informational: false },
  ToneMapOn: { wire: 'ToneMapOn', kind: 'single', informational: false },
  ToneMapOff: { wire: 'ToneMapOff', kind: 'single', informational: false },
  Hotplug: { wire: 'Hotplug', kind: 'single', informational: false },
  RefreshLicenseInfo: { wire: 'RefreshLicenseInfo', kind: 'single', informational: false },
  Force1080p60Output: { wire: 'Force1080p60Output', kind: 'single', informational: false },
};

const remoteKeys: TMadvrRemoteKey[] = ['MENU', 'UP', 'DOWN', 'LEFT', 'RIGHT', 'OK', 'INPUT', 'SETTINGS', 'RED', 'GREEN', 'BLUE', 'YELLOW', 'POWER'];
const menus: TMadvrMenu[] = ['Info', 'Settings', 'Configuration', 'Profiles', 'TestPatterns'];
const profileGroups: TMadvrProfileGroup[] = ['SOURCE', 'DISPLAY', 'CUSTOM'];
const toggles: TMadvrToggle[] = ['ToneMap', 'HighlightRecovery', 'ContrastRecovery', 'ShadowRecovery', '3DLUT', 'ScreenBoundaries', 'Histogram', 'DebugOSD'];

export class MadvrCommandModeler {
  public static requestsFromInput(inputArg: unknown): IMadvrCommandRequest[] {
    if (typeof inputArg === 'string') {
      return [this.requestFromString(inputArg)];
    }
    if (Array.isArray(inputArg)) {
      const strings = inputArg.filter((itemArg): itemArg is string => typeof itemArg === 'string' && Boolean(itemArg.trim()));
      if (!strings.length) {
        return [];
      }
      const firstCommand = this.normalizeCommandName(strings[0]);
      const firstDefinition = firstCommand === 'PowerOn' ? undefined : commandDefinitions[firstCommand as Exclude<TMadvrCommandName, 'PowerOn'>];
      if (firstDefinition && firstDefinition.kind !== 'single' && firstDefinition.kind !== 'info' && strings.length <= 3) {
        return [{ command: firstCommand, args: strings.slice(1).map((itemArg) => itemArg.trim()).filter(Boolean) }];
      }
      return strings.flatMap((commandArg) => this.requestsFromInput(commandArg));
    }
    if (this.isCommandRequest(inputArg)) {
      return [{ ...inputArg, command: this.normalizeCommandName(inputArg.command), args: inputArg.args?.map((itemArg) => String(itemArg).trim()).filter(Boolean) }];
    }
    return [];
  }

  public static model(inputArg: TMadvrCommandInput): IMadvrCommandModel {
    const requests = this.requestsFromInput(inputArg);
    if (requests.length !== 1) {
      throw new Error('madVR command modeling requires exactly one command request.');
    }
    return this.modelRequest(requests[0]);
  }

  public static modelRequest(requestArg: IMadvrCommandRequest): IMadvrCommandModel {
    const command = this.normalizeCommandName(requestArg.command);
    const request: IMadvrCommandRequest = { ...requestArg, command };
    if (command === 'PowerOn') {
      return {
        request,
        transport: 'wake_on_lan',
        informational: false,
        requiresConnection: false,
        description: 'Wake madVR Envy with Wake-on-LAN. Requires a MAC address or custom executor.',
      };
    }

    const definition = commandDefinitions[command as Exclude<TMadvrCommandName, 'PowerOn'>];
    if (!definition) {
      throw new Error(`Unsupported madVR command: ${requestArg.command}`);
    }

    const args = this.protocolArgs(definition, request.args || []);
    const wireCommand = `${definition.wire}${args.length ? ` ${args.join(' ')}` : ''}\r\n`;
    return {
      request: { ...request, args },
      transport: 'tcp',
      wireCommand,
      informational: definition.informational,
      requiresConnection: true,
    };
  }

  private static requestFromString(valueArg: string): IMadvrCommandRequest {
    const parts = valueArg.split(',').map((partArg) => partArg.trim()).filter(Boolean);
    if (!parts.length) {
      throw new Error('madVR command cannot be empty.');
    }
    return { command: this.normalizeCommandName(parts[0]), args: parts.slice(1) };
  }

  private static protocolArgs(definitionArg: IMadvrCommandDefinition, argsArg: string[]): string[] {
    if (definitionArg.kind === 'single' || definitionArg.kind === 'info') {
      if (argsArg.length) {
        throw new Error(`${definitionArg.wire} does not accept parameters.`);
      }
      return [];
    }
    if (definitionArg.kind === 'key') {
      return [this.normalizeKey(this.requiredArg(definitionArg.wire, argsArg, 0))];
    }
    if (definitionArg.kind === 'menu') {
      return [this.normalizeMenu(this.requiredArg(definitionArg.wire, argsArg, 0))];
    }
    if (definitionArg.kind === 'toggle') {
      return [this.normalizeToggle(this.requiredArg(definitionArg.wire, argsArg, 0))];
    }
    const group = this.normalizeProfileGroup(this.requiredArg(definitionArg.wire, argsArg, 0));
    const profileNumber = argsArg[1] ? this.normalizeInteger(argsArg[1], 'profile number') : undefined;
    if (argsArg.length > 2) {
      throw new Error('ActivateProfile accepts a profile group and optional numeric profile number.');
    }
    return profileNumber ? [group, profileNumber] : [group];
  }

  private static requiredArg(commandArg: string, argsArg: string[], indexArg: number): string {
    const value = argsArg[indexArg];
    if (!value) {
      throw new Error(`${commandArg} requires parameter ${indexArg + 1}.`);
    }
    if (/\r|\n/.test(value)) {
      throw new Error('madVR command parameters cannot contain line breaks.');
    }
    return value;
  }

  private static normalizeCommandName(valueArg: string): TMadvrCommandName {
    const normalized = valueArg.replace(/[_\s-]+/g, '').toLowerCase();
    const aliases: Record<string, TMadvrCommandName> = {
      on: 'PowerOn',
      turnon: 'PowerOn',
      poweron: 'PowerOn',
      off: 'PowerOff',
      turnoff: 'PowerOff',
      poweroff: 'PowerOff',
      standby: 'Standby',
      key: 'KeyPress',
      keypress: 'KeyPress',
      keyhold: 'KeyHold',
      openmenu: 'OpenMenu',
      closemenu: 'CloseMenu',
      activateprofile: 'ActivateProfile',
      profile: 'ActivateProfile',
      toggle: 'Toggle',
      getincomingsignalinfo: 'GetIncomingSignalInfo',
      getoutgoingsignalinfo: 'GetOutgoingSignalInfo',
      getaspectratio: 'GetAspectRatio',
      getmaskingratio: 'GetMaskingRatio',
      gettemperatures: 'GetTemperatures',
      getmacaddress: 'GetMacAddress',
    };
    if (aliases[normalized]) {
      return aliases[normalized];
    }
    const direct = (['PowerOn', ...Object.keys(commandDefinitions)] as TMadvrCommandName[]).find((commandArg) => commandArg.toLowerCase() === valueArg.toLowerCase());
    if (direct) {
      return direct;
    }
    return valueArg as TMadvrCommandName;
  }

  private static normalizeKey(valueArg: string): TMadvrRemoteKey {
    const key = valueArg.toUpperCase();
    if (remoteKeys.includes(key as TMadvrRemoteKey)) {
      return key as TMadvrRemoteKey;
    }
    throw new Error(`Unsupported madVR remote key: ${valueArg}`);
  }

  private static normalizeMenu(valueArg: string): TMadvrMenu {
    const menu = menus.find((itemArg) => itemArg.toLowerCase() === valueArg.toLowerCase());
    if (menu) {
      return menu;
    }
    throw new Error(`Unsupported madVR menu: ${valueArg}`);
  }

  private static normalizeProfileGroup(valueArg: string): TMadvrProfileGroup {
    const group = valueArg.toUpperCase();
    if (profileGroups.includes(group as TMadvrProfileGroup)) {
      return group as TMadvrProfileGroup;
    }
    throw new Error(`Unsupported madVR profile group: ${valueArg}`);
  }

  private static normalizeToggle(valueArg: string): TMadvrToggle {
    const normalized = valueArg.replace(/^_/, '').toLowerCase();
    const toggle = toggles.find((itemArg) => itemArg.toLowerCase() === normalized);
    if (toggle) {
      return toggle;
    }
    throw new Error(`Unsupported madVR toggle: ${valueArg}`);
  }

  private static normalizeInteger(valueArg: string, labelArg: string): string {
    if (!/^\d+$/.test(valueArg)) {
      throw new Error(`madVR ${labelArg} must be a non-negative integer.`);
    }
    return String(Number(valueArg));
  }

  private static isCommandRequest(valueArg: unknown): valueArg is IMadvrCommandRequest {
    return typeof valueArg === 'object' && valueArg !== null && typeof (valueArg as IMadvrCommandRequest).command === 'string';
  }
}

export class MadvrClient {
  constructor(private readonly config: IMadvrConfig) {}

  public async getSnapshot(): Promise<IMadvrSnapshot> {
    if (this.config.commandExecutor?.getSnapshot) {
      return this.normalizeSnapshot(await this.config.commandExecutor.getSnapshot());
    }
    if (this.config.snapshot) {
      return this.normalizeSnapshot(this.cloneSnapshot(this.config.snapshot));
    }
    if (this.config.host) {
      try {
        return await this.getLiveSnapshot();
      } catch (errorArg) {
        return this.snapshotFromConfig(this.errorMessage(errorArg));
      }
    }
    return this.snapshotFromConfig();
  }

  public async execute(commandArg: IMadvrCommandRequest): Promise<IMadvrCommandResult> {
    const model = MadvrCommandModeler.modelRequest(commandArg);
    if (this.config.commandExecutor) {
      const result = await this.config.commandExecutor.execute(model.request, model);
      if (typeof result === 'string') {
        return { executed: true, command: model, transport: 'executor', response: result };
      }
      if (result) {
        return { ...result, command: result.command || model, transport: result.transport || 'executor' };
      }
      return { executed: true, command: model, transport: 'executor' };
    }

    if (model.transport === 'wake_on_lan') {
      const macAddress = this.config.macAddress || this.config.snapshot?.deviceInfo.macAddress || this.config.state?.mac_address || this.config.state?.macAddress;
      if (!macAddress) {
        throw new Error('madVR Envy turn_on requires a MAC address or commandExecutor for Wake-on-LAN.');
      }
      await this.sendWakeOnLan(String(macAddress));
      return { executed: true, command: model, transport: 'wake_on_lan' };
    }

    if (!model.wireCommand) {
      throw new Error(`madVR command ${model.request.command} does not have a TCP wire command.`);
    }
    const response = await this.sendTcpCommands([model.wireCommand], commandResponseCollectMs);
    this.applyLocalState(model.request);
    return { executed: true, command: model, transport: 'tcp', response };
  }

  public async executeInput(inputArg: unknown): Promise<IMadvrCommandResult[]> {
    const requests = MadvrCommandModeler.requestsFromInput(inputArg);
    if (!requests.length) {
      throw new Error('madVR command input did not contain any supported commands.');
    }
    const results: IMadvrCommandResult[] = [];
    for (const request of requests) {
      results.push(await this.execute(request));
    }
    return results;
  }

  public async destroy(): Promise<void> {
    await this.config.commandExecutor?.destroy?.();
  }

  public static parseNotificationText(textArg: string): IMadvrRawState {
    const state: IMadvrRawState = {};
    const notifications = textArg.split(/\r?\n/).map((lineArg) => lineArg.trim()).filter(Boolean);
    for (const notification of notifications) {
      if (notification === 'OK' || notification === 'WELCOME') {
        continue;
      }
      const [title, ...info] = notification.split(/\s+/);
      if (title === 'PowerOff' || title === 'Standby') {
        state.is_on = false;
        continue;
      }
      if (title === 'NoSignal') {
        state.is_signal = false;
        continue;
      }
      if (title === 'MacAddress' && info[0]) {
        state.mac_address = info[0];
        continue;
      }
      if (title === 'Temperatures') {
        state.temp_gpu = info[0];
        state.temp_hdmi = info[1];
        state.temp_cpu = info[2];
        state.temp_mainboard = info[3];
        continue;
      }
      if (title === 'IncomingSignalInfo') {
        state.is_signal = true;
        state.incoming_res = info[0];
        state.incoming_frame_rate = info[1];
        state.incoming_signal_type = info[2];
        state.incoming_color_space = info[3];
        state.incoming_bit_depth = info[4];
        state.hdr_flag = info[5]?.includes('HDR') || false;
        state.incoming_colorimetry = info[6];
        state.incoming_black_levels = info[7];
        state.incoming_aspect_ratio = info[8];
        continue;
      }
      if (title === 'OutgoingSignalInfo' || title === 'OngoingSignalInfo') {
        state.outgoing_res = info[0];
        state.outgoing_frame_rate = info[1];
        state.outgoing_signal_type = info[2];
        state.outgoing_color_space = info[3];
        state.outgoing_bit_depth = info[4];
        state.outgoing_hdr_flag = info[5]?.includes('HDR') || false;
        state.outgoing_colorimetry = info[6];
        state.outgoing_black_levels = info[7];
        continue;
      }
      if (title === 'AspectRatio') {
        state.aspect_res = info[0];
        state.aspect_dec = info[1];
        state.aspect_int = info[2];
        state.aspect_name = info.slice(3).join(' ') || undefined;
        continue;
      }
      if (title === 'MaskingRatio') {
        state.masking_res = info[0];
        state.masking_dec = info[1];
        state.masking_int = info[2];
        continue;
      }
      if ((title === 'ActivateProfile' || title === 'ActiveProfile') && info[0]) {
        state.profile_name = info[0];
        state.profile_num = info[1];
      }
    }
    return state;
  }

  private async getLiveSnapshot(): Promise<IMadvrSnapshot> {
    const commands = [
      'GetMacAddress\r\n',
      'GetTemperatures\r\n',
      'GetIncomingSignalInfo\r\n',
      'GetOutgoingSignalInfo\r\n',
      'GetAspectRatio\r\n',
      'GetMaskingRatio\r\n',
    ];
    const response = await this.sendTcpCommands(commands, this.config.snapshotCollectMs ?? snapshotCollectMs);
    const raw = { is_on: true, ...MadvrClient.parseNotificationText(response) };
    return this.snapshotFromRawState(raw, true);
  }

  private async sendTcpCommands(commandsArg: string[], collectMsArg: number): Promise<string> {
    if (!this.config.host) {
      throw new Error('madVR Envy host is required for live TCP commands.');
    }
    const host = this.config.host;
    const port = this.port();
    const timeoutMs = this.config.commandTimeoutMs ?? commandTimeoutMs;

    return new Promise<string>((resolve, reject) => {
      const socket = plugins.net.createConnection({ host, port });
      const chunks: Buffer[] = [];
      let settled = false;
      let wroteCommands = false;
      let finishTimer: ReturnType<typeof setTimeout> | undefined;
      const timeoutTimer = setTimeout(() => finish(new Error(`madVR Envy TCP command timed out after ${timeoutMs}ms.`)), timeoutMs);

      const finish = (errorArg?: Error) => {
        if (settled) {
          return;
        }
        settled = true;
        clearTimeout(timeoutTimer);
        if (finishTimer) {
          clearTimeout(finishTimer);
        }
        socket.destroy();
        if (errorArg) {
          reject(errorArg);
          return;
        }
        resolve(Buffer.concat(chunks).toString('utf8'));
      };

      const writeNext = (payloadsArg: string[], indexArg = 0) => {
        if (indexArg >= payloadsArg.length) {
          wroteCommands = true;
          finishTimer = setTimeout(() => finish(), collectMsArg);
          return;
        }
        socket.write(payloadsArg[indexArg], (errorArg?: Error | null) => {
          if (errorArg) {
            finish(errorArg);
            return;
          }
          writeNext(payloadsArg, indexArg + 1);
        });
      };

      socket.setTimeout(this.config.connectTimeoutMs ?? connectTimeoutMs, () => finish(new Error(`madVR Envy TCP connection to ${host}:${port} timed out.`)));
      socket.on('connect', () => writeNext([heartbeatCommand, ...commandsArg]));
      socket.on('data', (chunkArg) => chunks.push(Buffer.from(chunkArg)));
      socket.on('error', (errorArg) => finish(errorArg));
      socket.on('close', () => {
        if (!settled && wroteCommands) {
          finish();
        }
      });
    });
  }

  private async sendWakeOnLan(macAddressArg: string): Promise<void> {
    const packet = this.createMagicPacket(macAddressArg);
    const dgram = await import('node:dgram');
    const socket = dgram.createSocket('udp4');
    try {
      await new Promise<void>((resolve, reject) => {
        socket.once('error', reject);
        socket.bind(() => {
          socket.setBroadcast(true);
          socket.send(packet, this.config.wolPort ?? 9, this.config.wolBroadcastAddress || '255.255.255.255', (errorArg) => {
            if (errorArg) {
              reject(errorArg);
              return;
            }
            resolve();
          });
        });
      });
    } finally {
      socket.close();
    }
  }

  private createMagicPacket(macAddressArg: string): Buffer {
    let mac = macAddressArg.trim();
    if (mac.length === 17) {
      mac = mac.replaceAll(mac[2], '');
    } else if (mac.length === 14) {
      mac = mac.replaceAll(mac[4], '');
    }
    if (!/^[0-9a-fA-F]{12}$/.test(mac)) {
      throw new Error('madVR Envy Wake-on-LAN requires a valid MAC address.');
    }
    return Buffer.from(`${'F'.repeat(12)}${mac.repeat(16)}`, 'hex');
  }

  private snapshotFromConfig(errorMessageArg?: string): IMadvrSnapshot {
    return this.snapshotFromRawState(this.config.state || {}, errorMessageArg ? false : undefined, errorMessageArg);
  }

  private snapshotFromRawState(rawArg: IMadvrRawState, availableArg?: boolean, errorMessageArg?: string): IMadvrSnapshot {
    const parts = this.statePartsFromRaw(rawArg);
    const processor = this.mergeProcessor(parts.processor, this.config.processor);
    const display = this.mergeDisplay(parts.display, this.config.display);
    return this.normalizeSnapshot({
      deviceInfo: this.deviceInfoFromConfig(rawArg),
      processor: {
        ...processor,
        lastError: errorMessageArg || processor.lastError,
      },
      projector: {
        profileName: processor.profileName,
        profileNumber: processor.profileNumber,
        ...this.config.projector,
      },
      display,
      buttons: this.config.buttons,
      selects: this.config.selects,
      raw: rawArg,
      available: availableArg ?? this.config.processor?.isOn ?? this.config.state?.is_on ?? this.config.state?.isOn ?? Boolean(this.config.host || this.config.snapshot),
      updatedAt: new Date().toISOString(),
    });
  }

  private normalizeSnapshot(snapshotArg: IMadvrSnapshot): IMadvrSnapshot {
    const rawParts = snapshotArg.raw ? this.statePartsFromRaw(snapshotArg.raw) : undefined;
    const processor = this.mergeProcessor(rawParts?.processor, snapshotArg.processor);
    const display = this.mergeDisplay(rawParts?.display, snapshotArg.display);
    return {
      ...snapshotArg,
      deviceInfo: {
        ...this.deviceInfoFromConfig(snapshotArg.raw),
        ...snapshotArg.deviceInfo,
        port: snapshotArg.deviceInfo.port || this.port(),
        manufacturer: snapshotArg.deviceInfo.manufacturer || this.config.manufacturer || 'madVR',
        model: snapshotArg.deviceInfo.model || this.config.model || 'Envy',
        name: snapshotArg.deviceInfo.name || this.config.name || 'madVR Envy',
      },
      processor,
      projector: snapshotArg.projector || (processor.profileName || processor.profileNumber ? { profileName: processor.profileName, profileNumber: processor.profileNumber } : undefined),
      display,
      buttons: snapshotArg.buttons || [],
      selects: snapshotArg.selects || [],
      available: snapshotArg.available ?? true,
      updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
    };
  }

  private statePartsFromRaw(rawArg: IMadvrRawState): { processor: IMadvrProcessorState; display: IMadvrDisplayState } {
    const isOn = this.boolRaw(rawArg, 'is_on', 'isOn');
    const isSignal = this.boolRaw(rawArg, 'is_signal', 'isSignal');
    const processor: IMadvrProcessorState = {
      isOn,
      isSignal,
      power: isOn === true ? 'on' : isOn === false ? 'off' : undefined,
      incoming: {
        resolution: this.stringRaw(rawArg, 'incoming_res', 'incomingRes'),
        frameRate: this.stringRaw(rawArg, 'incoming_frame_rate', 'incomingFrameRate'),
        signalType: this.stringRaw(rawArg, 'incoming_signal_type', 'incomingSignalType'),
        colorSpace: this.stringRaw(rawArg, 'incoming_color_space', 'incomingColorSpace'),
        bitDepth: this.stringRaw(rawArg, 'incoming_bit_depth', 'incomingBitDepth'),
        hdr: this.boolRaw(rawArg, 'hdr_flag', 'hdrFlag'),
        colorimetry: this.stringRaw(rawArg, 'incoming_colorimetry', 'incomingColorimetry'),
        blackLevels: this.stringRaw(rawArg, 'incoming_black_levels', 'incomingBlackLevels'),
        aspectRatio: this.stringRaw(rawArg, 'incoming_aspect_ratio', 'incomingAspectRatio'),
      },
      outgoing: {
        resolution: this.stringRaw(rawArg, 'outgoing_res', 'outgoingRes'),
        frameRate: this.stringRaw(rawArg, 'outgoing_frame_rate', 'outgoingFrameRate'),
        signalType: this.stringRaw(rawArg, 'outgoing_signal_type', 'outgoingSignalType'),
        colorSpace: this.stringRaw(rawArg, 'outgoing_color_space', 'outgoingColorSpace'),
        bitDepth: this.stringRaw(rawArg, 'outgoing_bit_depth', 'outgoingBitDepth'),
        hdr: this.boolRaw(rawArg, 'outgoing_hdr_flag', 'outgoingHdrFlag'),
        colorimetry: this.stringRaw(rawArg, 'outgoing_colorimetry', 'outgoingColorimetry'),
        blackLevels: this.stringRaw(rawArg, 'outgoing_black_levels', 'outgoingBlackLevels'),
      },
      temperatures: {
        gpu: this.numberRaw(rawArg, 'temp_gpu', 'tempGpu'),
        hdmi: this.numberRaw(rawArg, 'temp_hdmi', 'tempHdmi'),
        cpu: this.numberRaw(rawArg, 'temp_cpu', 'tempCpu'),
        mainboard: this.numberRaw(rawArg, 'temp_mainboard', 'tempMainboard'),
      },
      profileName: this.stringRaw(rawArg, 'profile_name', 'profileName'),
      profileNumber: this.stringRaw(rawArg, 'profile_num', 'profileNumber'),
      raw: rawArg,
    };
    const display: IMadvrDisplayState = {
      hdrFlag: this.boolRaw(rawArg, 'hdr_flag', 'hdrFlag'),
      outgoingHdrFlag: this.boolRaw(rawArg, 'outgoing_hdr_flag', 'outgoingHdrFlag'),
      aspect: {
        resolution: this.stringRaw(rawArg, 'aspect_res', 'aspectRes'),
        decimal: this.numberRaw(rawArg, 'aspect_dec', 'aspectDec'),
        integer: this.stringRaw(rawArg, 'aspect_int', 'aspectInt'),
        name: this.stringRaw(rawArg, 'aspect_name', 'aspectName'),
      },
      masking: {
        resolution: this.stringRaw(rawArg, 'masking_res', 'maskingRes'),
        decimal: this.numberRaw(rawArg, 'masking_dec', 'maskingDec'),
        integer: this.stringRaw(rawArg, 'masking_int', 'maskingInt'),
      },
    };
    return { processor: this.cleanProcessor(processor), display: this.cleanDisplay(display) };
  }

  private mergeProcessor(baseArg: IMadvrProcessorState | undefined, overrideArg: IMadvrProcessorState | undefined): IMadvrProcessorState {
    const base = baseArg || {};
    const override = overrideArg || {};
    return this.cleanProcessor({
      ...base,
      ...override,
      incoming: { ...base.incoming, ...override.incoming },
      outgoing: { ...base.outgoing, ...override.outgoing },
      temperatures: { ...base.temperatures, ...override.temperatures },
      raw: { ...base.raw, ...override.raw },
    });
  }

  private mergeDisplay(baseArg: IMadvrDisplayState | undefined, overrideArg: IMadvrDisplayState | undefined): IMadvrDisplayState {
    const base = baseArg || {};
    const override = overrideArg || {};
    return this.cleanDisplay({
      ...base,
      ...override,
      aspect: { ...base.aspect, ...override.aspect },
      masking: { ...base.masking, ...override.masking },
    });
  }

  private cleanProcessor(processorArg: IMadvrProcessorState): IMadvrProcessorState {
    return this.cleanObject({
      ...processorArg,
      incoming: this.cleanObject(processorArg.incoming || {}),
      outgoing: this.cleanObject(processorArg.outgoing || {}),
      temperatures: this.cleanObject(processorArg.temperatures || {}),
      raw: processorArg.raw,
    }) as IMadvrProcessorState;
  }

  private cleanDisplay(displayArg: IMadvrDisplayState): IMadvrDisplayState {
    return this.cleanObject({
      ...displayArg,
      aspect: this.cleanObject(displayArg.aspect || {}),
      masking: this.cleanObject(displayArg.masking || {}),
    }) as IMadvrDisplayState;
  }

  private cleanObject<TValue extends object>(valueArg: TValue): Partial<TValue> {
    return Object.fromEntries(Object.entries(valueArg as Record<string, unknown>).filter(([, valueArg]) => {
      if (valueArg === undefined) {
        return false;
      }
      if (typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg) && !Object.keys(valueArg).length) {
        return false;
      }
      return true;
    })) as Partial<TValue>;
  }

  private deviceInfoFromConfig(rawArg?: IMadvrRawState): IMadvrSnapshot['deviceInfo'] {
    return {
      host: this.config.host,
      port: this.port(),
      name: this.config.name || 'madVR Envy',
      manufacturer: this.config.manufacturer || 'madVR',
      model: this.config.model || 'Envy',
      macAddress: this.config.macAddress || this.stringRaw(rawArg, 'mac_address', 'macAddress'),
      serialNumber: this.config.serialNumber,
    };
  }

  private applyLocalState(commandArg: IMadvrCommandRequest): void {
    const state = this.config.snapshot?.raw || this.config.state || {};
    if (commandArg.command === 'PowerOff' || commandArg.command === 'Standby') {
      state.is_on = false;
    }
    if (this.config.snapshot) {
      this.config.snapshot.raw = state;
      this.config.snapshot.processor.isOn = state.is_on;
      this.config.snapshot.processor.power = state.is_on === false ? 'off' : this.config.snapshot.processor.power;
      return;
    }
    this.config.state = state;
  }

  private stringRaw(rawArg: IMadvrRawState | undefined, ...keysArg: string[]): string | undefined {
    const value = this.rawValue(rawArg, ...keysArg);
    if (typeof value === 'string' && value.trim()) {
      return value.trim();
    }
    if (typeof value === 'number' && Number.isFinite(value)) {
      return String(value);
    }
    return undefined;
  }

  private numberRaw(rawArg: IMadvrRawState | undefined, ...keysArg: string[]): number | undefined {
    const value = this.rawValue(rawArg, ...keysArg);
    if (typeof value === 'number' && Number.isFinite(value)) {
      return value;
    }
    if (typeof value === 'string' && value.trim()) {
      const numberValue = Number(value);
      return Number.isFinite(numberValue) ? numberValue : undefined;
    }
    return undefined;
  }

  private boolRaw(rawArg: IMadvrRawState | undefined, ...keysArg: string[]): boolean | undefined {
    const value = this.rawValue(rawArg, ...keysArg);
    if (typeof value === 'boolean') {
      return value;
    }
    if (typeof value === 'string') {
      const normalized = value.toLowerCase();
      if (normalized === 'true' || normalized === 'on' || normalized === '1') {
        return true;
      }
      if (normalized === 'false' || normalized === 'off' || normalized === '0') {
        return false;
      }
    }
    return undefined;
  }

  private rawValue(rawArg: IMadvrRawState | undefined, ...keysArg: string[]): unknown {
    if (!rawArg) {
      return undefined;
    }
    for (const key of keysArg) {
      if (rawArg[key] !== undefined && rawArg[key] !== null) {
        return rawArg[key];
      }
    }
    return undefined;
  }

  private port(): number {
    return this.config.port || madvrDefaultPort;
  }

  private cloneSnapshot(snapshotArg: IMadvrSnapshot): IMadvrSnapshot {
    return JSON.parse(JSON.stringify(snapshotArg)) as IMadvrSnapshot;
  }

  private errorMessage(errorArg: unknown): string {
    return errorArg instanceof Error ? errorArg.message : String(errorArg);
  }
}
