import type {
  IBluesoundCommandRequest,
  IBluesoundConfig,
  IBluesoundInput,
  IBluesoundPairedPlayer,
  IBluesoundPlayerSnapshot,
  IBluesoundPreset,
  IBluesoundRawCommandRequest,
  IBluesoundRawCommandResponse,
  IBluesoundSnapshot,
  IBluesoundStatus,
  IBluesoundSyncStatus,
} from './bluesound.types.js';
import { bluesoundDefaultPort, bluesoundDefaultTimeoutMs } from './bluesound.types.js';

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

export class BluesoundClient {
  private currentSnapshot?: IBluesoundSnapshot;

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

  public async getSnapshot(): Promise<IBluesoundSnapshot> {
    if (this.config.snapshot) {
      this.currentSnapshot = this.normalizeSnapshot(this.cloneSnapshot(this.config.snapshot), 'snapshot');
      return this.cloneSnapshot(this.currentSnapshot);
    }
    if (this.currentSnapshot && !this.config.host && !this.config.commandExecutor) {
      return this.cloneSnapshot(this.currentSnapshot);
    }
    if (!this.config.host && !this.config.commandExecutor) {
      this.currentSnapshot = this.normalizeSnapshot(this.snapshotFromConfig(false, 'Bluesound refresh requires config.host, config.snapshot, or commandExecutor.'), 'runtime');
      return this.cloneSnapshot(this.currentSnapshot);
    }

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

  public async validateConnection(): Promise<IBluesoundSnapshot> {
    const syncStatus = await this.getSyncStatus();
    if (!syncStatus.mac) {
      throw new Error('Bluesound player did not provide a MAC address.');
    }
    const snapshot = await this.fetchSnapshot(syncStatus);
    return this.normalizeSnapshot(snapshot, 'http');
  }

  public async getStatus(): Promise<IBluesoundStatus> {
    if (this.currentSnapshot && !this.config.host && !this.config.commandExecutor) {
      return this.cloneValue(this.currentSnapshot.players[0]?.status || { state: 'unknown' });
    }
    const response = await this.requestText('/Status');
    return parseStatus(response);
  }

  public async getSyncStatus(): Promise<IBluesoundSyncStatus> {
    if (this.currentSnapshot && !this.config.host && !this.config.commandExecutor) {
      const syncStatus = this.currentSnapshot.players[0]?.syncStatus;
      if (!syncStatus) {
        throw new Error('Bluesound snapshot does not contain a player.');
      }
      return this.cloneValue(syncStatus);
    }
    const response = await this.requestText('/SyncStatus');
    return parseSyncStatus(response);
  }

  public async getPresets(): Promise<IBluesoundPreset[]> {
    if (this.currentSnapshot && !this.config.host && !this.config.commandExecutor) {
      return this.cloneValue(this.currentSnapshot.players[0]?.presets || []);
    }
    const response = await this.requestText('/Presets');
    return parsePresets(response);
  }

  public async getInputs(): Promise<IBluesoundInput[]> {
    if (this.currentSnapshot && !this.config.host && !this.config.commandExecutor) {
      return this.cloneValue(this.currentSnapshot.players[0]?.inputs || []);
    }
    const response = await this.requestText('/RadioBrowse', { service: 'Capture' });
    return parseInputs(response);
  }

  public async execute(requestArg: IBluesoundCommandRequest): Promise<unknown> {
    const snapshot = await this.getSnapshot();
    const player = this.resolvePlayer(snapshot, requestArg.playerId);
    if (this.isLeaderOnlyCommand(requestArg.command) && player && isGroupedFollower(player)) {
      return { skipped: true, reason: 'Grouped Bluesound followers are controlled by their leader.' };
    }

    if (requestArg.command === 'play') {
      return this.requestRaw('/Play', {}, player);
    }
    if (requestArg.command === 'pause') {
      return this.requestRaw('/Pause', {}, player);
    }
    if (requestArg.command === 'stop') {
      return this.requestRaw('/Stop', {}, player);
    }
    if (requestArg.command === 'next_track') {
      return this.requestRaw('/Skip', {}, player);
    }
    if (requestArg.command === 'previous_track') {
      return this.requestRaw('/Back', {}, player);
    }
    if (requestArg.command === 'seek') {
      if (typeof requestArg.position !== 'number' || !Number.isFinite(requestArg.position)) {
        throw new Error('Bluesound seek requires position.');
      }
      return this.requestRaw('/Play', { seek: Math.max(0, Math.round(requestArg.position)) }, player);
    }
    if (requestArg.command === 'set_volume') {
      return this.requestRaw('/Volume', { level: this.volumePercent(requestArg) }, player);
    }
    if (requestArg.command === 'mute') {
      if (typeof requestArg.muted !== 'boolean') {
        throw new Error('Bluesound mute requires muted.');
      }
      return this.requestRaw('/Volume', { mute: requestArg.muted ? 1 : 0 }, player);
    }
    if (requestArg.command === 'play_media') {
      if (!requestArg.mediaId) {
        throw new Error('Bluesound play_media requires mediaId.');
      }
      return this.requestRaw('/Play', { url: requestArg.mediaId }, player);
    }
    if (requestArg.command === 'select_source') {
      if (!requestArg.source) {
        throw new Error('Bluesound select_source requires source.');
      }
      return this.selectSource(player, requestArg.source);
    }
    if (requestArg.command === 'play_preset') {
      return this.playPreset(player, requestArg);
    }
    if (requestArg.command === 'clear_playlist') {
      return this.requestRaw('/Clear', {}, player);
    }
    if (requestArg.command === 'shuffle') {
      if (typeof requestArg.shuffle !== 'boolean') {
        throw new Error('Bluesound shuffle requires shuffle.');
      }
      return this.requestRaw('/Shuffle', { state: requestArg.shuffle ? 1 : 0 }, player);
    }
    if (requestArg.command === 'join') {
      return this.joinPlayers(snapshot, player, requestArg);
    }
    if (requestArg.command === 'unjoin') {
      return this.unjoinPlayer(snapshot, player, requestArg);
    }
    if (requestArg.command === 'add_follower') {
      if (!requestArg.follower) {
        throw new Error('Bluesound add_follower requires follower.');
      }
      return this.requestRaw('/AddSlave', { slave: requestArg.follower.ip, port: requestArg.follower.port }, player);
    }
    if (requestArg.command === 'remove_follower') {
      if (!requestArg.follower) {
        throw new Error('Bluesound remove_follower requires follower.');
      }
      return this.requestRaw('/RemoveSlave', { slave: requestArg.follower.ip, port: requestArg.follower.port }, player);
    }
    throw new Error(`Unsupported Bluesound command: ${requestArg.command}`);
  }

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

  private async fetchSnapshot(syncStatusArg?: IBluesoundSyncStatus): Promise<IBluesoundSnapshot> {
    const syncStatus = syncStatusArg || await this.getSyncStatus();
    const [status, presets, inputs] = await Promise.all([
      this.getStatus(),
      this.getPresets().catch(() => []),
      this.getInputs().catch(() => []),
    ]);
    const updatedAt = new Date().toISOString();
    return {
      players: [{
        host: this.config.host,
        port: this.config.port || bluesoundDefaultPort,
        syncStatus,
        status,
        presets,
        inputs,
        available: true,
        updatedAt,
      }],
      online: true,
      updatedAt,
      source: 'http',
    };
  }

  private snapshotFromConfig(onlineArg: boolean, errorArg?: string): IBluesoundSnapshot {
    const host = this.config.host;
    const port = this.config.port || bluesoundDefaultPort;
    const hasPlayerMetadata = Boolean(host || this.config.name || this.config.macAddress || this.config.model || this.config.modelName);
    return {
      players: hasPlayerMetadata ? [{
        host,
        port,
        syncStatus: {
          id: host ? `${host}:${port}` : this.config.name || 'bluesound',
          mac: this.config.macAddress || host || 'bluesound',
          name: this.config.name || host || 'Bluesound',
          brand: this.config.manufacturer || 'Bluesound',
          model: this.config.model,
          modelName: this.config.modelName || this.config.model,
          initialized: undefined,
        },
        status: { state: onlineArg ? 'stop' : 'unknown' },
        presets: [],
        inputs: [],
        available: onlineArg,
        updatedAt: new Date().toISOString(),
      }] : [],
      online: onlineArg,
      updatedAt: new Date().toISOString(),
      source: 'runtime',
      error: errorArg,
    };
  }

  private async selectSource(playerArg: IBluesoundPlayerSnapshot | undefined, sourceArg: string): Promise<unknown> {
    const player = this.requirePlayer(playerArg);
    const input = (player.inputs || []).find((inputArg) => sourceArg === inputArg.text || sourceArg === inputArg.id);
    if (input) {
      return this.requestRaw('/Play', { url: input.url }, player);
    }
    const preset = (player.presets || []).find((presetArg) => presetArg.name === sourceArg);
    if (preset) {
      return this.requestRaw('/Preset', { id: preset.id }, player);
    }
    throw new Error(`Unknown Bluesound source: ${sourceArg}`);
  }

  private async playPreset(playerArg: IBluesoundPlayerSnapshot | undefined, requestArg: IBluesoundCommandRequest): Promise<unknown> {
    const player = this.requirePlayer(playerArg);
    const presetId = requestArg.presetId ?? (requestArg.presetName ? (player.presets || []).find((presetArg) => presetArg.name === requestArg.presetName)?.id : undefined);
    if (typeof presetId !== 'number' || !Number.isFinite(presetId)) {
      throw new Error('Bluesound play_preset requires presetId or a matching presetName.');
    }
    return this.requestRaw('/Preset', { id: Math.round(presetId) }, player);
  }

  private async joinPlayers(snapshotArg: IBluesoundSnapshot, playerArg: IBluesoundPlayerSnapshot | undefined, requestArg: IBluesoundCommandRequest): Promise<unknown> {
    const leader = this.requirePlayer(playerArg);
    const followers = this.joinFollowers(snapshotArg, leader, requestArg);
    if (!followers.length) {
      throw new Error('Bluesound join requires followers or playerIds.');
    }
    if (followers.length === 1) {
      return this.requestRaw('/AddSlave', { slave: followers[0].ip, port: followers[0].port }, leader);
    }
    return this.requestRaw('/AddSlave', {
      slaves: followers.map((followerArg) => followerArg.ip).join(','),
      ports: followers.map((followerArg) => followerArg.port).join(','),
    }, leader);
  }

  private async unjoinPlayer(snapshotArg: IBluesoundSnapshot, playerArg: IBluesoundPlayerSnapshot | undefined, requestArg: IBluesoundCommandRequest): Promise<unknown> {
    const player = this.requirePlayer(playerArg);
    const explicitFollower = requestArg.follower || requestArg.followers?.[0];
    if (explicitFollower) {
      return this.requestRaw('/RemoveSlave', { slave: explicitFollower.ip, port: explicitFollower.port }, player);
    }

    const playerEndpoint = endpointFromPlayer(player);
    if (player.syncStatus.leader && playerEndpoint) {
      const leader = snapshotArg.players.find((candidateArg) => candidateArg.syncStatus.id === pairedPlayerId(player.syncStatus.leader as IBluesoundPairedPlayer));
      return this.requestRaw('/RemoveSlave', { slave: playerEndpoint.ip, port: playerEndpoint.port }, leader || endpointPlayer(player.syncStatus.leader, snapshotArg.updatedAt));
    }

    if (player.syncStatus.followers?.length) {
      const follower = player.syncStatus.followers[0];
      return this.requestRaw('/RemoveSlave', { slave: follower.ip, port: follower.port }, player);
    }

    return { skipped: true, reason: 'Bluesound player is not grouped.' };
  }

  private joinFollowers(snapshotArg: IBluesoundSnapshot, leaderArg: IBluesoundPlayerSnapshot, requestArg: IBluesoundCommandRequest): IBluesoundPairedPlayer[] {
    const fromDirect = requestArg.followers || [];
    const ids = (requestArg.playerIds || []).filter((playerIdArg) => playerIdArg !== leaderArg.syncStatus.id);
    const fromIds = ids.map((playerIdArg) => {
      const player = snapshotArg.players.find((candidateArg) => candidateArg.syncStatus.id === playerIdArg);
      return player ? endpointFromPlayer(player) : pairedPlayerFromId(playerIdArg);
    }).filter((valueArg): valueArg is IBluesoundPairedPlayer => Boolean(valueArg));
    const unique = new Map<string, IBluesoundPairedPlayer>();
    [...fromDirect, ...fromIds].forEach((followerArg) => unique.set(pairedPlayerId(followerArg), followerArg));
    return [...unique.values()];
  }

  private resolvePlayer(snapshotArg: IBluesoundSnapshot, playerIdArg: string | undefined): IBluesoundPlayerSnapshot | undefined {
    if (playerIdArg) {
      return snapshotArg.players.find((playerArg) => playerArg.syncStatus.id === playerIdArg);
    }
    return snapshotArg.players.length === 1 ? snapshotArg.players[0] : undefined;
  }

  private requirePlayer(playerArg: IBluesoundPlayerSnapshot | undefined): IBluesoundPlayerSnapshot {
    if (playerArg) {
      return playerArg;
    }
    throw new Error('Bluesound command requires a target player.');
  }

  private isLeaderOnlyCommand(commandArg: IBluesoundCommandRequest['command']): boolean {
    return commandArg === 'play'
      || commandArg === 'pause'
      || commandArg === 'stop'
      || commandArg === 'next_track'
      || commandArg === 'previous_track'
      || commandArg === 'seek'
      || commandArg === 'select_source'
      || commandArg === 'play_media'
      || commandArg === 'play_preset'
      || commandArg === 'clear_playlist'
      || commandArg === 'shuffle';
  }

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

  private async requestText(pathArg: string, parametersArg: Record<string, string | number | boolean> = {}): Promise<string> {
    const response = await this.requestRaw(pathArg, parametersArg);
    if (typeof response === 'string') {
      return response;
    }
    if (typeof (response as IBluesoundRawCommandResponse).body === 'string') {
      return (response as IBluesoundRawCommandResponse).body as string;
    }
    return typeof (response as IBluesoundRawCommandResponse).data === 'string' ? (response as IBluesoundRawCommandResponse).data as string : '';
  }

  private async requestRaw(pathArg: string, parametersArg: Record<string, string | number | boolean>, playerArg?: IBluesoundPlayerSnapshot): Promise<unknown> {
    const endpoint = this.endpoint(playerArg);
    const url = endpoint.host ? this.url(pathArg, parametersArg, endpoint.host, endpoint.port) : undefined;
    const request: IBluesoundRawCommandRequest = {
      method: 'GET',
      command: pathArg.replace(/^\//, ''),
      path: pathArg,
      parameters: parametersArg,
      url,
      host: endpoint.host,
      port: endpoint.port,
    };

    if (this.config.commandExecutor) {
      return this.executorResult(await this.config.commandExecutor.execute(request), pathArg);
    }
    if (!this.config.host || !endpoint.host || !url) {
      throw new Error('Bluesound command transport requires config.host or commandExecutor. Static snapshots are read-only.');
    }

    const abortController = new AbortController();
    const timeout = globalThis.setTimeout(() => abortController.abort(), this.config.timeoutMs || bluesoundDefaultTimeoutMs);
    try {
      const response = await globalThis.fetch(url, { method: 'GET', signal: abortController.signal });
      const body = await response.text();
      if (!response.ok) {
        throw new BluesoundHttpError(response.status, `Bluesound request ${pathArg} failed with HTTP ${response.status}: ${body}`);
      }
      return { status: response.status, body } satisfies IBluesoundRawCommandResponse;
    } finally {
      globalThis.clearTimeout(timeout);
    }
  }

  private executorResult(resultArg: unknown, pathArg: string): unknown {
    if (isRawCommandResponse(resultArg) && typeof resultArg.status === 'number' && resultArg.status >= 400) {
      throw new BluesoundHttpError(resultArg.status, `Bluesound request ${pathArg} failed with HTTP ${resultArg.status}: ${resultArg.body || ''}`);
    }
    return resultArg;
  }

  private endpoint(playerArg?: IBluesoundPlayerSnapshot): { host?: string; port: number } {
    const paired = playerArg ? endpointFromPlayer(playerArg) : undefined;
    return {
      host: playerArg?.host || paired?.ip || this.config.host,
      port: playerArg?.port || paired?.port || this.config.port || bluesoundDefaultPort,
    };
  }

  private url(pathArg: string, parametersArg: Record<string, string | number | boolean>, hostArg: string, portArg: number): string {
    const params = new URLSearchParams();
    for (const [key, value] of Object.entries(parametersArg)) {
      params.set(key, String(value));
    }
    const query = params.toString();
    return `http://${formatHost(hostArg)}:${portArg}${pathArg}${query ? `?${query}` : ''}`;
  }

  private normalizeSnapshot(snapshotArg: IBluesoundSnapshot, sourceArg: IBluesoundSnapshot['source']): IBluesoundSnapshot {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const players = (snapshotArg.players || []).map((playerArg) => ({
      ...playerArg,
      host: playerArg.host || this.config.host,
      port: playerArg.port || this.config.port || bluesoundDefaultPort,
      status: { ...playerArg.status, state: playerArg.status.state || 'unknown' },
      syncStatus: {
        ...playerArg.syncStatus,
        name: playerArg.syncStatus.name || this.config.name || playerArg.host || 'Bluesound',
        brand: playerArg.syncStatus.brand || this.config.manufacturer || 'Bluesound',
        model: playerArg.syncStatus.model || this.config.model,
        modelName: playerArg.syncStatus.modelName || this.config.modelName || this.config.model,
      },
      presets: playerArg.presets || [],
      inputs: playerArg.inputs || [],
      available: playerArg.available ?? snapshotArg.online,
      updatedAt: playerArg.updatedAt || updatedAt,
    }));
    return {
      ...snapshotArg,
      players,
      online: snapshotArg.online,
      updatedAt,
      source: snapshotArg.source || sourceArg,
    };
  }

  private cloneSnapshot(snapshotArg: IBluesoundSnapshot): IBluesoundSnapshot {
    return this.cloneValue(snapshotArg);
  }

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

const parseStatus = (xmlArg: string): IBluesoundStatus => {
  const status = firstElement(xmlArg, 'status');
  if (!status) {
    throw new Error('Bluesound status response missing status element.');
  }
  const child = (tagArg: string): string | undefined => childText(status.body, tagArg);
  return {
    etag: status.attributes.etag,
    inputId: child('inputId'),
    service: child('service'),
    state: child('state') || 'unknown',
    shuffle: booleanOne(child('shuffle')),
    album: child('album') || child('title3'),
    artist: child('artist') || child('title2'),
    name: child('name') || child('title1'),
    image: child('image'),
    volume: numberValue(child('volume')),
    volumeDb: numberValue(child('db')),
    mute: booleanOne(child('mute')),
    muteVolume: numberValue(child('muteVolume')),
    muteVolumeDb: numberValue(child('muteDb')),
    seconds: numberValue(child('secs')),
    totalSeconds: numberValue(child('totlen')),
    canSeek: booleanOne(child('canSeek')),
    sleep: numberValue(child('sleep')) || 0,
    groupName: child('groupName'),
    groupVolume: numberValue(child('groupVolume')),
    indexing: booleanOne(child('indexing')),
    streamUrl: child('streamUrl'),
  };
};

const parseSyncStatus = (xmlArg: string): IBluesoundSyncStatus => {
  const syncStatus = firstElement(xmlArg, 'SyncStatus');
  if (!syncStatus) {
    throw new Error('Bluesound sync response missing SyncStatus element.');
  }
  const leaderElement = firstElement(syncStatus.body, 'master');
  const leader = leaderElement?.body.trim() ? {
    ip: unescapeXml(leaderElement.body.trim()),
    port: numberValue(leaderElement.attributes.port) || bluesoundDefaultPort,
  } : undefined;
  const followers = allElements(syncStatus.body, 'slave')
    .map((elementArg) => ({
      ip: elementArg.attributes.id,
      port: numberValue(elementArg.attributes.port) || bluesoundDefaultPort,
    }))
    .filter((playerArg): playerArg is IBluesoundPairedPlayer => Boolean(playerArg.ip));
  const attrs = syncStatus.attributes;
  return {
    etag: attrs.etag,
    id: attrs.id || attrs.name || 'bluesound',
    mac: attrs.mac || attrs.id || attrs.name || 'bluesound',
    name: attrs.name || attrs.id || 'Bluesound',
    image: attrs.icon,
    initialized: attrs.initialized === undefined ? undefined : attrs.initialized === 'true',
    group: attrs.group,
    leader,
    followers: followers.length ? followers : undefined,
    zone: attrs.zone,
    zoneLeader: attrs.zoneMaster === undefined ? undefined : attrs.zoneMaster === 'true',
    zoneFollower: attrs.zoneMaster === undefined ? undefined : attrs.zoneMaster !== 'true',
    brand: attrs.brand || 'Bluesound',
    model: attrs.model,
    modelName: attrs.modelName || attrs.model,
    muteVolumeDb: numberValue(attrs.muteDb),
    muteVolume: numberValue(attrs.muteVolume),
    volumeDb: numberValue(attrs.db),
    volume: numberValue(attrs.volume),
  };
};

const parsePresets = (xmlArg: string): IBluesoundPreset[] => {
  return allElements(xmlArg, 'preset').map((elementArg) => ({
    name: elementArg.attributes.name || `Preset ${elementArg.attributes.id || ''}`.trim(),
    id: numberValue(elementArg.attributes.id) || 0,
    url: elementArg.attributes.url || '',
    image: elementArg.attributes.image,
    volume: numberValue(elementArg.attributes.volume),
  })).filter((presetArg) => Boolean(presetArg.name && presetArg.url));
};

const parseInputs = (xmlArg: string): IBluesoundInput[] => {
  return allElements(xmlArg, 'item').map((elementArg) => {
    const url = elementArg.attributes.URL || elementArg.attributes.url;
    return {
      id: elementArg.attributes.id,
      text: elementArg.attributes.text,
      image: elementArg.attributes.image,
      url: decodeURIComponentSafe(url || ''),
    };
  }).filter((inputArg) => Boolean(inputArg.url));
};

export const parseBluesoundStatusXml = parseStatus;
export const parseBluesoundSyncStatusXml = parseSyncStatus;
export const parseBluesoundPresetsXml = parsePresets;
export const parseBluesoundInputsXml = parseInputs;

const allElements = (xmlArg: string, tagArg: string): Array<{ attributes: Record<string, string>; body: string }> => {
  const escapedTag = tagArg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const regex = new RegExp(`<${escapedTag}\\b([^>]*)>([\\s\\S]*?)<\\/${escapedTag}>|<${escapedTag}\\b([^>]*)\\/>`, 'gi');
  const result: Array<{ attributes: Record<string, string>; body: string }> = [];
  let match: RegExpExecArray | null;
  while ((match = regex.exec(xmlArg))) {
    result.push({ attributes: parseAttributes(match[1] || match[3] || ''), body: match[2] || '' });
  }
  return result;
};

const firstElement = (xmlArg: string, tagArg: string): { attributes: Record<string, string>; body: string } | undefined => allElements(xmlArg, tagArg)[0];

const childText = (xmlArg: string, tagArg: string): string | undefined => {
  const child = firstElement(xmlArg, tagArg);
  const value = child?.body.trim();
  return value ? unescapeXml(value) : undefined;
};

const parseAttributes = (valueArg: string): Record<string, string> => {
  const result: Record<string, string> = {};
  const regex = /([A-Za-z_:][\w:.-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
  let match: RegExpExecArray | null;
  while ((match = regex.exec(valueArg))) {
    result[match[1]] = unescapeXml(match[2] ?? match[3] ?? '');
  }
  return result;
};

const booleanOne = (valueArg: string | undefined): boolean | undefined => {
  if (valueArg === undefined) {
    return undefined;
  }
  return valueArg === '1' || valueArg.toLowerCase() === 'true';
};

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 endpointFromPlayer = (playerArg: IBluesoundPlayerSnapshot): IBluesoundPairedPlayer | undefined => {
  if (playerArg.host) {
    return { ip: playerArg.host, port: playerArg.port || bluesoundDefaultPort };
  }
  return pairedPlayerFromId(playerArg.syncStatus.id);
};

const endpointPlayer = (playerArg: IBluesoundPairedPlayer, updatedAtArg: string | undefined): IBluesoundPlayerSnapshot => ({
  host: playerArg.ip,
  port: playerArg.port,
  syncStatus: {
    id: pairedPlayerId(playerArg),
    mac: pairedPlayerId(playerArg),
    name: pairedPlayerId(playerArg),
    brand: 'Bluesound',
  },
  status: { state: 'unknown' },
  presets: [],
  inputs: [],
  available: true,
  updatedAt: updatedAtArg,
});

const pairedPlayerFromId = (idArg: string | undefined): IBluesoundPairedPlayer | undefined => {
  if (!idArg) {
    return undefined;
  }
  const separator = idArg.lastIndexOf(':');
  if (separator <= 0) {
    return undefined;
  }
  const ip = idArg.slice(0, separator);
  const port = Number(idArg.slice(separator + 1));
  return Number.isFinite(port) ? { ip, port } : undefined;
};

const pairedPlayerId = (playerArg: IBluesoundPairedPlayer): string => `${playerArg.ip}:${playerArg.port}`;

const isGroupedFollower = (playerArg: IBluesoundPlayerSnapshot): boolean => Boolean(playerArg.syncStatus.leader);

const isRawCommandResponse = (valueArg: unknown): valueArg is IBluesoundRawCommandResponse => {
  return Boolean(valueArg && typeof valueArg === 'object' && ('status' in valueArg || 'body' in valueArg || 'data' in valueArg));
};

const formatHost = (hostArg: string): string => hostArg.includes(':') && !hostArg.startsWith('[') ? `[${hostArg}]` : hostArg;

const unescapeXml = (valueArg: string): string => {
  return valueArg
    .replace(/&apos;/g, "'")
    .replace(/&quot;/g, '"')
    .replace(/&gt;/g, '>')
    .replace(/&lt;/g, '<')
    .replace(/&amp;/g, '&');
};

const decodeURIComponentSafe = (valueArg: string): string => {
  try {
    return decodeURIComponent(valueArg);
  } catch {
    return valueArg;
  }
};
