import type {
  ILinkplayCommandExecutorResult,
  ILinkplayCommandRequest,
  ILinkplayConfig,
  ILinkplayDeviceInfo,
  ILinkplayDeviceProperties,
  ILinkplayMetaInfo,
  ILinkplayMultiroomGroup,
  ILinkplayPlayerProperties,
  ILinkplayPlayerStatus,
  ILinkplayPreset,
  ILinkplayRawCommandRequest,
  ILinkplaySnapshot,
  ILinkplaySource,
  ILinkplaySpeaker,
  TLinkplayAudioOutputHardwareMode,
  TLinkplayLoopMode,
  TLinkplayPlayingMode,
  TLinkplayProtocol,
  TLinkplayRepeatMode,
} from './linkplay.types.js';
import {
  linkplayAudioOutputHardwareCodeToMode,
  linkplayAudioOutputHardwareModeToCode,
  linkplayDefaultPort,
  linkplayDefaultTimeoutMs,
  linkplayLoopModeToRepeat,
  linkplayNormalEqualizerModeToNumber,
  linkplayNormalEqualizerNumberToMode,
  linkplayPlayModeSendMap,
  linkplayProjectLookup,
  linkplayRepeatToLoopMode,
  linkplaySourceMap,
} from './linkplay.types.js';

const linkplayCommands = {
  deviceStatus: 'getStatusEx',
  playerStatus: 'getPlayerStatusEx',
  metaInfo: 'getMetaInfo',
  next: 'setPlayerCmd:next',
  previous: 'setPlayerCmd:prev',
  unmute: 'setPlayerCmd:mute:0',
  mute: 'setPlayerCmd:mute:1',
  resume: 'setPlayerCmd:resume',
  pause: 'setPlayerCmd:pause',
  stop: 'setPlayerCmd:stop',
  play: (valueArg: string) => `setPlayerCmd:play:${valueArg}`,
  seek: (valueArg: number) => `setPlayerCmd:seek:${valueArg}`,
  volume: (valueArg: number) => `setPlayerCmd:vol:${valueArg}`,
  equalizerMode: (valueArg: string) => `setPlayerCmd:equalizer:${valueArg}`,
  loopMode: (valueArg: TLinkplayLoopMode) => `setPlayerCmd:loopmode:${valueArg}`,
  switchMode: (valueArg: string) => `setPlayerCmd:switchmode:${valueArg}`,
  playPreset: (valueArg: number) => `MCUKeyShortClick:${valueArg}`,
  timesync: (valueArg: string) => `timeSync:${valueArg}`,
  restart: 'reboot',
  multiroomList: 'multiroom:getSlaveList',
  multiroomUngroup: 'multiroom:ungroup',
  multiroomKick: (ethArg: string) => `multiroom:SlaveKickout:${ethArg}`,
  multiroomVolume: (valueArg: number) => `setPlayerCmd:slave_vol:${valueArg}`,
  multiroomMute: 'setPlayerCmd:slave_mute:mute',
  multiroomUnmute: 'setPlayerCmd:slave_mute:unmute',
  multiroomJoin: (leaderEthArg: string) => `ConnectMasterAp:JoinGroupMaster:eth${leaderEthArg}:wifi0.0.0.0`,
  audioOutputHardwareMode: 'getNewAudioOutputHardwareMode',
  setAudioOutputHardwareMode: (valueArg: string) => `setAudioOutputHardwareMode:${valueArg}`,
  wiimEqualizerOff: 'EQOff',
  wiimEqualizerLoad: (valueArg: string) => `EQLoad:${valueArg}`,
} as const;

const inputModeMap: Array<{ flag: number; mode: TLinkplayPlayingMode }> = [
  { flag: 2, mode: '40' },
  { flag: 4, mode: '41' },
  { flag: 8, mode: '21' },
  { flag: 16, mode: '43' },
  { flag: 32, mode: '44' },
  { flag: 64, mode: '45' },
  { flag: 128, mode: '46' },
  { flag: 256, mode: '47' },
  { flag: 512, mode: '48' },
  { flag: 1024, mode: '49' },
  { flag: 2048, mode: '50' },
  { flag: 8192, mode: '16' },
  { flag: 16384, mode: '53' },
  { flag: 32768, mode: '51' },
  { flag: 65536, mode: '54' },
  { flag: 262144, mode: '56' },
  { flag: 524288, mode: '57' },
  { flag: 2097152, mode: '99' },
  { flag: 4194304, mode: '58' },
];

export class LinkplayHttpError extends Error {
  constructor(public readonly status: number, messageArg: string) {
    super(messageArg);
    this.name = 'LinkplayHttpError';
  }
}

export class LinkplayCommandError extends Error {
  constructor(public readonly command: string, messageArg: string) {
    super(`LinkPlay command ${command} failed: ${messageArg}`);
    this.name = 'LinkplayCommandError';
  }
}

export class LinkplayClient {
  private currentSnapshot?: ILinkplaySnapshot;

  constructor(private readonly config: ILinkplayConfig) {
    this.currentSnapshot = config.snapshot ? this.normalizeSnapshot(this.cloneValue(config.snapshot), 'snapshot') : undefined;
  }

  public async getSnapshot(): Promise<ILinkplaySnapshot> {
    if (this.currentSnapshot && (!this.config.host || this.config.snapshot)) {
      return this.normalizeSnapshot(this.cloneValue(this.currentSnapshot), this.currentSnapshot.source || 'snapshot');
    }
    if (!this.config.host && !this.config.commandExecutor) {
      return this.normalizeSnapshot(this.snapshotFromConfig(false, 'LinkPlay refresh requires config.host, config.snapshot, or commandExecutor.'), 'runtime');
    }
    if (!this.config.host && this.config.commandExecutor) {
      return this.normalizeSnapshot(this.snapshotFromConfig(false, 'LinkPlay executor configs need a snapshot for state mapping.'), 'runtime');
    }

    try {
      this.currentSnapshot = await this.fetchSnapshot();
      return this.cloneValue(this.currentSnapshot);
    } catch (errorArg) {
      return this.normalizeSnapshot(this.snapshotFromConfig(false, errorArg instanceof Error ? errorArg.message : String(errorArg)), 'runtime');
    }
  }

  public async validateConnection(): Promise<ILinkplaySnapshot> {
    const snapshot = await this.fetchSnapshot();
    if (!snapshot.speakers[0]?.uuid) {
      throw new Error('LinkPlay device did not provide a unique identifier.');
    }
    return snapshot;
  }

  public async execute(requestArg: ILinkplayCommandRequest): Promise<unknown> {
    if (requestArg.command === 'play') {
      return this.sendCommand(linkplayCommands.resume, await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'pause') {
      return this.sendCommand(linkplayCommands.pause, await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'stop') {
      return this.sendCommand(linkplayCommands.stop, await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'next_track') {
      return this.sendCommand(linkplayCommands.next, await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'previous_track') {
      return this.sendCommand(linkplayCommands.previous, await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'play_media') {
      const mediaId = requestArg.url || requestArg.mediaId;
      if (!mediaId) {
        throw new Error('LinkPlay play_media requires mediaId or url.');
      }
      return this.sendCommand(linkplayCommands.play(mediaId), await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'seek') {
      return this.sendCommand(linkplayCommands.seek(Math.max(0, Math.round(this.requiredNumber(requestArg.position, 'LinkPlay seek requires position.')))), await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'set_volume') {
      return this.sendCommand(linkplayCommands.volume(this.volumePercent(requestArg)), await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'mute') {
      if (typeof requestArg.muted !== 'boolean') {
        throw new Error('LinkPlay mute requires muted.');
      }
      return this.sendCommand(requestArg.muted ? linkplayCommands.mute : linkplayCommands.unmute, await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'select_source') {
      return this.selectSource(requestArg);
    }
    if (requestArg.command === 'set_repeat') {
      return this.setRepeat(requestArg);
    }
    if (requestArg.command === 'set_sound_mode') {
      return this.setSoundMode(requestArg);
    }
    if (requestArg.command === 'play_preset') {
      return this.playPreset(requestArg);
    }
    if (requestArg.command === 'timesync') {
      return this.sendCommand(linkplayCommands.timesync(this.timestamp()), await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'restart') {
      return this.sendCommand(linkplayCommands.restart, await this.speakerForRequest(requestArg));
    }
    if (requestArg.command === 'set_audio_output_hardware_mode') {
      return this.setAudioOutputHardwareMode(requestArg);
    }
    if (requestArg.command === 'join') {
      return this.joinPlayers(requestArg);
    }
    if (requestArg.command === 'unjoin') {
      return this.unjoinPlayer(requestArg);
    }
    if (requestArg.command === 'group_volume_set') {
      return this.setGroupVolume(requestArg);
    }
    if (requestArg.command === 'group_mute') {
      return this.setGroupMute(requestArg);
    }
    if (requestArg.command === 'raw_command') {
      if (!requestArg.rawCommand) {
        throw new Error('LinkPlay raw_command requires rawCommand.');
      }
      return this.sendCommand(requestArg.rawCommand, await this.speakerForRequest(requestArg));
    }
    throw new Error(`Unsupported LinkPlay command: ${requestArg.command}`);
  }

  public async command(rawCommandArg: string, speakerUuidArg?: string): Promise<unknown> {
    return this.execute({ command: 'raw_command', rawCommand: rawCommandArg, speakerUuid: speakerUuidArg });
  }

  public async destroy(): Promise<void> {}

  private async fetchSnapshot(): Promise<ILinkplaySnapshot> {
    const deviceProperties = await this.sendJsonCommand<ILinkplayDeviceProperties>(linkplayCommands.deviceStatus);
    const playerProperties = await this.sendJsonCommand<ILinkplayPlayerProperties>(linkplayCommands.playerStatus);
    const device = this.deviceInfoFromProperties(deviceProperties);
    const metaInfo = await this.sendJsonCommand<ILinkplayMetaInfo>(linkplayCommands.metaInfo).catch(() => undefined);
    const player = this.playerStatusFromProperties(playerProperties, metaInfo);
    const speaker: ILinkplaySpeaker = {
      uuid: device.uuid,
      name: device.name,
      device,
      player,
      available: true,
      sources: this.sourcesFromDevice(device),
      presets: this.presetsFromDevice(device),
    };
    const audioMode = await this.sendJsonCommand<Record<string, unknown>>(linkplayCommands.audioOutputHardwareMode).catch(() => undefined);
    if (audioMode) {
      speaker.audioOutputHardwareMode = linkplayAudioOutputHardwareCodeToMode[String(audioMode.hardware)] || String(audioMode.hardware || '');
    }
    const multirooms = await this.multiroomsFromDevice(speaker).catch(() => []);
    return this.normalizeSnapshot({
      speakers: [speaker],
      multirooms,
      online: true,
      updatedAt: new Date().toISOString(),
      source: 'http',
      raw: { deviceProperties, playerProperties, metaInfo },
    }, 'http');
  }

  private async selectSource(requestArg: ILinkplayCommandRequest): Promise<unknown> {
    const source = this.requiredString(requestArg.source, 'LinkPlay select_source requires source.');
    const mode = this.sourceMode(source);
    const commandValue = linkplayPlayModeSendMap[mode];
    if (!commandValue) {
      throw new Error(`Unsupported LinkPlay source: ${source}`);
    }
    return this.sendCommand(linkplayCommands.switchMode(commandValue), await this.speakerForRequest(requestArg));
  }

  private async setRepeat(requestArg: ILinkplayCommandRequest): Promise<unknown> {
    const repeat = this.requiredString(requestArg.repeat, 'LinkPlay set_repeat requires repeat.') as TLinkplayRepeatMode;
    const loopMode = linkplayRepeatToLoopMode[repeat];
    if (!loopMode) {
      throw new Error(`Unsupported LinkPlay repeat mode: ${repeat}`);
    }
    return this.sendCommand(linkplayCommands.loopMode(loopMode), await this.speakerForRequest(requestArg));
  }

  private async setSoundMode(requestArg: ILinkplayCommandRequest): Promise<unknown> {
    const soundMode = this.requiredString(requestArg.soundMode, 'LinkPlay set_sound_mode requires soundMode.');
    const speaker = await this.speakerForRequest(requestArg);
    if (speaker?.device.manufacturer === 'WiiM') {
      return this.sendCommand(soundMode === 'None' ? linkplayCommands.wiimEqualizerOff : linkplayCommands.wiimEqualizerLoad(soundMode), speaker);
    }
    const modeNumber = linkplayNormalEqualizerModeToNumber[soundMode];
    if (!modeNumber) {
      throw new Error(`Unsupported LinkPlay sound mode: ${soundMode}`);
    }
    return this.sendCommand(linkplayCommands.equalizerMode(modeNumber), speaker);
  }

  private async playPreset(requestArg: ILinkplayCommandRequest): Promise<unknown> {
    const preset = this.requiredNumber(requestArg.preset, 'LinkPlay play_preset requires preset.');
    const speaker = await this.speakerForRequest(requestArg);
    const maxPreset = speaker?.device.maxPresets || 10;
    if (!Number.isInteger(preset) || preset < 1 || preset > maxPreset) {
      throw new Error(`LinkPlay preset must be between 1 and ${maxPreset}.`);
    }
    return this.sendCommand(linkplayCommands.playPreset(preset), speaker);
  }

  private async setAudioOutputHardwareMode(requestArg: ILinkplayCommandRequest): Promise<unknown> {
    const mode = this.requiredString(requestArg.hardwareMode, 'LinkPlay set_audio_output_hardware_mode requires hardwareMode.');
    const code = linkplayAudioOutputHardwareModeToCode[mode] || mode;
    if (!['1', '2', '3', '4'].includes(code)) {
      throw new Error(`Unsupported LinkPlay audio output hardware mode: ${mode}`);
    }
    return this.sendCommand(linkplayCommands.setAudioOutputHardwareMode(code), await this.speakerForRequest(requestArg));
  }

  private async joinPlayers(requestArg: ILinkplayCommandRequest): Promise<unknown[]> {
    const snapshot = await this.getSnapshot();
    const leader = this.requiredSpeaker(snapshot, requestArg.speakerUuid);
    if (!requestArg.groupMemberUuids?.length) {
      throw new Error('LinkPlay join requires groupMemberUuids.');
    }
    const leaderEth = this.requiredString(leader.device.ethernetAddress, 'LinkPlay join requires leader ethernet address.');
    const results: unknown[] = [];
    for (const memberUuid of requestArg.groupMemberUuids.filter((uuidArg) => uuidArg !== leader.uuid)) {
      const follower = this.requiredSpeaker(snapshot, memberUuid);
      results.push(await this.sendCommand(linkplayCommands.multiroomJoin(leaderEth), follower));
    }
    return results;
  }

  private async unjoinPlayer(requestArg: ILinkplayCommandRequest): Promise<unknown> {
    const snapshot = await this.getSnapshot();
    const speaker = this.requiredSpeaker(snapshot, requestArg.speakerUuid);
    const group = this.groupForSpeaker(snapshot, speaker.uuid);
    if (!group) {
      return undefined;
    }
    const leader = this.requiredSpeaker(snapshot, group.leaderUuid);
    if (speaker.uuid === leader.uuid) {
      return this.sendCommand(linkplayCommands.multiroomUngroup, leader);
    }
    const followerEth = this.requiredString(speaker.device.ethernetAddress, 'LinkPlay unjoin requires follower ethernet address.');
    return this.sendCommand(linkplayCommands.multiroomKick(followerEth), leader);
  }

  private async setGroupVolume(requestArg: ILinkplayCommandRequest): Promise<unknown> {
    const snapshot = await this.getSnapshot();
    const speaker = this.requiredSpeaker(snapshot, requestArg.speakerUuid);
    const group = this.groupForSpeaker(snapshot, speaker.uuid);
    const leader = this.requiredSpeaker(snapshot, group?.leaderUuid || speaker.uuid);
    return this.sendCommand(linkplayCommands.multiroomVolume(this.volumePercent(requestArg)), leader);
  }

  private async setGroupMute(requestArg: ILinkplayCommandRequest): Promise<unknown> {
    if (typeof requestArg.muted !== 'boolean') {
      throw new Error('LinkPlay group_mute requires muted.');
    }
    const snapshot = await this.getSnapshot();
    const speaker = this.requiredSpeaker(snapshot, requestArg.speakerUuid);
    const group = this.groupForSpeaker(snapshot, speaker.uuid);
    const leader = this.requiredSpeaker(snapshot, group?.leaderUuid || speaker.uuid);
    return this.sendCommand(requestArg.muted ? linkplayCommands.multiroomMute : linkplayCommands.multiroomUnmute, leader);
  }

  private async sendCommand(commandArg: string, speakerArg?: ILinkplaySpeaker): Promise<unknown> {
    if (this.config.commandExecutor) {
      return this.executorResultToOk(commandArg, await this.config.commandExecutor.execute(this.rawRequest(commandArg, false, speakerArg)));
    }
    const response = await this.requestText(commandArg, speakerArg);
    if (response.trim() !== 'OK') {
      throw new LinkplayCommandError(commandArg, `expected OK, received ${response}`);
    }
    return { response };
  }

  private async sendJsonCommand<TValue = Record<string, unknown>>(commandArg: string, speakerArg?: ILinkplaySpeaker): Promise<TValue> {
    if (this.config.commandExecutor) {
      const result = await this.config.commandExecutor.execute(this.rawRequest(commandArg, true, speakerArg));
      return this.executorResultToJson<TValue>(commandArg, result);
    }
    const text = await this.requestText(commandArg, speakerArg);
    try {
      return JSON.parse(text) as TValue;
    } catch (errorArg) {
      throw new LinkplayCommandError(commandArg, `invalid JSON response: ${text}`);
    }
  }

  private async requestText(commandArg: string, speakerArg?: ILinkplaySpeaker): Promise<string> {
    const request = this.rawRequest(commandArg, false, speakerArg);
    if (!request.host) {
      throw new Error('LinkPlay HTTP command requires config.host or commandExecutor. Static snapshots are read-only.');
    }
    const abortController = new AbortController();
    const timeout = globalThis.setTimeout(() => abortController.abort(), this.config.timeoutMs || linkplayDefaultTimeoutMs);
    try {
      const response = await globalThis.fetch(request.url || this.commandUrl(request.protocol, request.host, request.port, commandArg), {
        method: 'GET',
        signal: abortController.signal,
      });
      const text = await response.text();
      if (!response.ok) {
        throw new LinkplayHttpError(response.status, `LinkPlay request ${commandArg} failed with HTTP ${response.status}: ${text}`);
      }
      return text;
    } finally {
      globalThis.clearTimeout(timeout);
    }
  }

  private rawRequest(commandArg: string, expectJsonArg: boolean, speakerArg?: ILinkplaySpeaker): ILinkplayRawCommandRequest {
    const protocol = speakerArg?.device.protocol || this.config.protocol || 'http';
    const host = speakerArg?.device.host || this.config.host;
    const port = speakerArg?.device.port || this.config.port || defaultPort(protocol);
    return {
      command: commandArg,
      host,
      port,
      protocol,
      expectJson: expectJsonArg,
      speakerUuid: speakerArg?.uuid,
      speakerName: speakerArg?.name || speakerArg?.device.name,
      url: host ? this.commandUrl(protocol, host, port, commandArg) : undefined,
    };
  }

  private commandUrl(protocolArg: TLinkplayProtocol, hostArg: string, portArg: number, commandArg: string): string {
    const includePort = !(protocolArg === 'http' && portArg === 80) && !(protocolArg === 'https' && portArg === 443);
    const encodedHost = hostArg.includes(':') && !hostArg.startsWith('[') ? `[${hostArg}]` : hostArg;
    const url = new URL(`${protocolArg}://${encodedHost}${includePort ? `:${portArg}` : ''}/httpapi.asp`);
    url.searchParams.set('command', commandArg);
    return url.toString();
  }

  private executorResultToOk(commandArg: string, resultArg: unknown): unknown {
    if (resultArg === undefined || resultArg === true) {
      return resultArg;
    }
    if (typeof resultArg === 'string') {
      if (resultArg.trim() === 'OK') {
        return { response: resultArg };
      }
      throw new LinkplayCommandError(commandArg, `executor returned ${resultArg}`);
    }
    if (this.isRecord(resultArg)) {
      const result = resultArg as ILinkplayCommandExecutorResult;
      if (result.ok === false || result.success === false || result.status && result.status >= 400) {
        throw new LinkplayCommandError(commandArg, JSON.stringify(resultArg));
      }
      if (typeof result.response === 'string' && result.response.trim() !== 'OK') {
        throw new LinkplayCommandError(commandArg, `executor returned ${result.response}`);
      }
    }
    return resultArg;
  }

  private executorResultToJson<TValue>(commandArg: string, resultArg: unknown): TValue {
    if (typeof resultArg === 'string') {
      try {
        return JSON.parse(resultArg) as TValue;
      } catch {
        throw new LinkplayCommandError(commandArg, `executor returned invalid JSON: ${resultArg}`);
      }
    }
    if (this.isRecord(resultArg) && 'data' in resultArg) {
      return resultArg.data as TValue;
    }
    if (this.isRecord(resultArg)) {
      return resultArg as TValue;
    }
    throw new LinkplayCommandError(commandArg, 'executor returned no JSON data.');
  }

  private deviceInfoFromProperties(propertiesArg: ILinkplayDeviceProperties): ILinkplayDeviceInfo {
    const project = stringValue(propertiesArg.project);
    const projectInfo = project ? linkplayProjectLookup[project] : undefined;
    const protocol = this.config.protocol || 'http';
    const host = this.config.host;
    const port = this.config.port || defaultPort(protocol);
    const uuid = stringValue(propertiesArg.uuid) || this.config.uniqueId || (host ? `${host}:${port}` : 'linkplay');
    const name = stringValue(propertiesArg.DeviceName) || this.config.name || host || 'LinkPlay';
    return {
      uuid,
      name,
      host,
      port,
      protocol,
      endpoint: host ? `${protocol}://${host}${port === defaultPort(protocol) ? '' : `:${port}`}` : undefined,
      manufacturer: this.config.manufacturer || projectInfo?.manufacturer || 'Generic',
      model: this.config.model || projectInfo?.model || 'Generic',
      modelId: this.config.modelId || (projectInfo?.model === 'Generic' ? undefined : project),
      project,
      macAddress: this.macAddress(propertiesArg),
      hardwareVersion: stringValue(propertiesArg.hardware),
      firmwareVersion: stringValue(propertiesArg.firmware),
      ethernetAddress: this.ethernetAddress(propertiesArg),
      playModeSupport: this.playModeSupport(propertiesArg),
      maxPresets: numberValue(propertiesArg.preset_key) || 10,
      properties: propertiesArg,
    };
  }

  private playerStatusFromProperties(propertiesArg: ILinkplayPlayerProperties, metaInfoArg?: ILinkplayMetaInfo): ILinkplayPlayerStatus {
    const playMode = stringValue(propertiesArg.mode) as TLinkplayPlayingMode | undefined;
    const loopMode = stringValue(propertiesArg.loop) as TLinkplayLoopMode | undefined;
    const eq = stringValue(propertiesArg.eq);
    const status = validStatus(stringValue(propertiesArg.status)) || 'stop';
    return {
      status,
      playMode,
      loopMode,
      equalizerMode: eq ? linkplayNormalEqualizerNumberToMode[eq] || eq : undefined,
      volume: numberValue(propertiesArg.vol),
      muted: stringValue(propertiesArg.mute) === '1',
      title: decodeHexString(stringValue(propertiesArg.Title)),
      artist: decodeHexString(stringValue(propertiesArg.Artist)),
      album: decodeHexString(stringValue(propertiesArg.Album)),
      albumArtUrl: metaInfoArg?.metaData?.albumArtURI,
      currentPositionMs: numberValue(propertiesArg.curpos),
      totalLengthMs: numberValue(propertiesArg.totlen),
      source: playMode ? linkplaySourceMap[playMode] || 'other' : undefined,
      speakerType: stringValue(propertiesArg.type),
      channelType: stringValue(propertiesArg.ch),
      properties: propertiesArg,
      metaInfo: metaInfoArg,
    };
  }

  private sourcesFromDevice(deviceArg: ILinkplayDeviceInfo): ILinkplaySource[] {
    return (deviceArg.playModeSupport || ['10']).map((modeArg) => ({
      mode: modeArg,
      name: linkplaySourceMap[modeArg] || modeArg,
      commandValue: linkplayPlayModeSendMap[modeArg],
      available: true,
    }));
  }

  private presetsFromDevice(deviceArg: ILinkplayDeviceInfo): ILinkplayPreset[] {
    const maxPresets = deviceArg.maxPresets || 10;
    return Array.from({ length: maxPresets }, (_itemArg, indexArg) => ({ number: indexArg + 1, name: `Preset ${indexArg + 1}` }));
  }

  private async multiroomsFromDevice(speakerArg: ILinkplaySpeaker): Promise<ILinkplayMultiroomGroup[]> {
    const response = await this.sendJsonCommand<Record<string, unknown>>(linkplayCommands.multiroomList, speakerArg);
    const followerRecords = Array.isArray(response.slave_list) ? response.slave_list.filter((itemArg): itemArg is Record<string, unknown> => this.isRecord(itemArg)) : [];
    const followerUuids = followerRecords.map((itemArg) => stringValue(itemArg.uuid)).filter((valueArg): valueArg is string => Boolean(valueArg));
    if (!followerUuids.length) {
      return [];
    }
    return [{
      id: `multiroom_${speakerArg.uuid}`,
      name: `${speakerArg.name || speakerArg.device.name} Multiroom`,
      leaderUuid: speakerArg.uuid,
      followerUuids,
    }];
  }

  private normalizeSnapshot(snapshotArg: ILinkplaySnapshot, sourceArg: ILinkplaySnapshot['source']): ILinkplaySnapshot {
    const speakers = (snapshotArg.speakers || []).map((speakerArg) => this.normalizeSpeaker(speakerArg));
    const sources = snapshotArg.sources || uniqueSources(speakers.flatMap((speakerArg) => speakerArg.sources || []));
    const presets = snapshotArg.presets || uniquePresets(speakers.flatMap((speakerArg) => speakerArg.presets || []));
    return {
      ...snapshotArg,
      speakers,
      multirooms: snapshotArg.multirooms || [],
      sources,
      presets,
      online: Boolean(snapshotArg.online),
      updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
      source: snapshotArg.source || sourceArg,
    };
  }

  private normalizeSpeaker(speakerArg: ILinkplaySpeaker): ILinkplaySpeaker {
    const uuid = speakerArg.uuid || speakerArg.device.uuid;
    const device = {
      ...speakerArg.device,
      uuid,
      name: speakerArg.device.name || speakerArg.name || this.config.name || this.config.host || 'LinkPlay',
      host: speakerArg.device.host || this.config.host,
      port: speakerArg.device.port || this.config.port || (speakerArg.device.host || this.config.host ? defaultPort(speakerArg.device.protocol || this.config.protocol || 'http') : undefined),
      protocol: speakerArg.device.protocol || this.config.protocol || 'http',
      playModeSupport: speakerArg.device.playModeSupport || ['10'],
      maxPresets: speakerArg.device.maxPresets || 10,
    };
    return {
      ...speakerArg,
      uuid,
      name: speakerArg.name || device.name,
      device,
      player: {
        ...speakerArg.player,
        status: speakerArg.player.status || 'stop',
      },
      available: speakerArg.available ?? true,
      sources: speakerArg.sources || this.sourcesFromDevice(device),
      presets: speakerArg.presets || this.presetsFromDevice(device),
    };
  }

  private snapshotFromConfig(onlineArg: boolean, errorArg?: string): ILinkplaySnapshot {
    const protocol = this.config.protocol || 'http';
    const host = this.config.host;
    const port = this.config.port || (host ? defaultPort(protocol) : undefined);
    const uuid = this.config.uniqueId || (host && port ? `${host}:${port}` : 'linkplay');
    const device: ILinkplayDeviceInfo = {
      uuid,
      name: this.config.name || host || 'LinkPlay',
      host,
      port,
      protocol,
      manufacturer: this.config.manufacturer || 'Generic',
      model: this.config.model || 'Generic',
      modelId: this.config.modelId,
      macAddress: this.config.macAddress,
      playModeSupport: ['10'],
      maxPresets: 10,
    };
    return {
      speakers: [{ uuid, name: device.name, device, player: { status: onlineArg ? 'stop' : 'stop' }, available: onlineArg }],
      multirooms: [],
      online: onlineArg,
      updatedAt: new Date().toISOString(),
      source: 'runtime',
      error: errorArg,
    };
  }

  private async speakerForRequest(requestArg: ILinkplayCommandRequest): Promise<ILinkplaySpeaker | undefined> {
    const snapshot = await this.getSnapshot();
    if (requestArg.speakerUuid) {
      return this.requiredSpeaker(snapshot, requestArg.speakerUuid);
    }
    return snapshot.speakers.length === 1 ? snapshot.speakers[0] : undefined;
  }

  private requiredSpeaker(snapshotArg: ILinkplaySnapshot, speakerUuidArg: string | undefined): ILinkplaySpeaker {
    if (speakerUuidArg) {
      const speaker = snapshotArg.speakers.find((speakerArg) => speakerArg.uuid === speakerUuidArg || speakerArg.device.uuid === speakerUuidArg);
      if (speaker) {
        return speaker;
      }
      throw new Error(`LinkPlay speaker not found: ${speakerUuidArg}`);
    }
    if (snapshotArg.speakers.length === 1) {
      return snapshotArg.speakers[0];
    }
    throw new Error('LinkPlay service call requires data.speaker_uuid or a target LinkPlay media_player entity.');
  }

  private groupForSpeaker(snapshotArg: ILinkplaySnapshot, speakerUuidArg: string): ILinkplayMultiroomGroup | undefined {
    return (snapshotArg.multirooms || []).find((groupArg) => groupArg.leaderUuid === speakerUuidArg || groupArg.followerUuids.includes(speakerUuidArg));
  }

  private sourceMode(sourceArg: string): TLinkplayPlayingMode {
    const byName = Object.entries(linkplaySourceMap).find(([_modeArg, nameArg]) => nameArg.toLowerCase() === sourceArg.toLowerCase());
    if (byName) {
      return byName[0] as TLinkplayPlayingMode;
    }
    if (linkplayPlayModeSendMap[sourceArg]) {
      return sourceArg as TLinkplayPlayingMode;
    }
    throw new Error(`Unknown LinkPlay source: ${sourceArg}`);
  }

  private playModeSupport(propertiesArg: ILinkplayDeviceProperties): TLinkplayPlayingMode[] {
    const support = stringValue(propertiesArg.plm_support);
    const modes: TLinkplayPlayingMode[] = ['10'];
    if (!support) {
      return modes;
    }
    const flags = Number.parseInt(support, 16);
    if (!Number.isFinite(flags)) {
      return modes;
    }
    for (const item of inputModeMap) {
      if ((flags & item.flag) === item.flag) {
        modes.push(item.mode);
      }
    }
    return [...new Set(modes)];
  }

  private macAddress(propertiesArg: ILinkplayDeviceProperties): string | undefined {
    return firstUsableMac(stringValue(propertiesArg.ETH_MAC), stringValue(propertiesArg.STA_MAC), stringValue(propertiesArg.MAC));
  }

  private ethernetAddress(propertiesArg: ILinkplayDeviceProperties): string | undefined {
    return firstUsableIp(stringValue(propertiesArg.eth2), stringValue(propertiesArg.eth0), stringValue(propertiesArg.apcli0));
  }

  private volumePercent(requestArg: ILinkplayCommandRequest): number {
    const value = requestArg.volume ?? (typeof requestArg.volumeLevel === 'number' ? requestArg.volumeLevel * 100 : undefined);
    if (typeof value !== 'number' || !Number.isFinite(value)) {
      throw new Error('LinkPlay volume command requires volumeLevel or volume.');
    }
    return Math.max(0, Math.min(100, Math.round(value)));
  }

  private timestamp(): string {
    const now = new Date();
    const parts = [
      now.getFullYear(),
      now.getMonth() + 1,
      now.getDate(),
      now.getHours(),
      now.getMinutes(),
      now.getSeconds(),
    ];
    return `${parts[0]}${parts.slice(1).map((partArg) => String(partArg).padStart(2, '0')).join('')}`;
  }

  private requiredString(valueArg: unknown, errorArg: string): string {
    if (typeof valueArg !== 'string' || !valueArg) {
      throw new Error(errorArg);
    }
    return valueArg;
  }

  private requiredNumber(valueArg: unknown, errorArg: string): number {
    if (typeof valueArg !== 'number' || !Number.isFinite(valueArg)) {
      throw new Error(errorArg);
    }
    return valueArg;
  }

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

  private cloneValue<TValue>(valueArg: TValue): TValue {
    return valueArg === undefined ? valueArg : JSON.parse(JSON.stringify(valueArg)) as TValue;
  }
}

const defaultPort = (protocolArg: TLinkplayProtocol): number => protocolArg === 'https' ? 443 : linkplayDefaultPort;

const stringValue = (valueArg: unknown): string | undefined => {
  if (typeof valueArg === 'string' && valueArg.trim()) {
    return valueArg.trim();
  }
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return String(valueArg);
  }
  return 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 validStatus = (valueArg: string | undefined): 'play' | 'pause' | 'stop' | 'load' | undefined => {
  return valueArg === 'play' || valueArg === 'pause' || valueArg === 'stop' || valueArg === 'load' ? valueArg : undefined;
};

const decodeHexString = (valueArg: string | undefined): string | undefined => {
  if (!valueArg) {
    return undefined;
  }
  if (!/^(?:[0-9a-f]{2})+$/i.test(valueArg)) {
    return valueArg;
  }
  try {
    return Buffer.from(valueArg, 'hex').toString('utf8');
  } catch {
    return valueArg;
  }
};

const firstUsableMac = (...valuesArg: Array<string | undefined>): string | undefined => {
  return valuesArg.find((valueArg) => valueArg && valueArg !== '00:00:00:00:00:00');
};

const firstUsableIp = (...valuesArg: Array<string | undefined>): string | undefined => {
  return valuesArg.find((valueArg) => valueArg && valueArg !== '0.0.0.0');
};

const uniqueSources = (sourcesArg: ILinkplaySource[]): ILinkplaySource[] => {
  const seen = new Set<string>();
  return sourcesArg.filter((sourceArg) => {
    const key = sourceArg.mode || sourceArg.name;
    if (seen.has(key)) {
      return false;
    }
    seen.add(key);
    return true;
  });
};

const uniquePresets = (presetsArg: ILinkplayPreset[]): ILinkplayPreset[] => {
  const seen = new Set<number>();
  return presetsArg.filter((presetArg) => {
    if (seen.has(presetArg.number)) {
      return false;
    }
    seen.add(presetArg.number);
    return true;
  }).sort((leftArg, rightArg) => leftArg.number - rightArg.number);
};

export const linkplayRepeatFromLoopMode = (loopModeArg: TLinkplayLoopMode | undefined): TLinkplayRepeatMode | undefined => {
  return loopModeArg ? linkplayLoopModeToRepeat[loopModeArg] : undefined;
};

export const linkplaySourceName = (modeArg: TLinkplayPlayingMode | undefined): string | undefined => {
  return modeArg ? linkplaySourceMap[modeArg] || 'other' : undefined;
};

export const linkplayAudioOutputHardwareMode = (modeArg: TLinkplayAudioOutputHardwareMode | string | undefined): string | undefined => modeArg;
