import * as plugins from '../../plugins.js';
import type { IIntegrationEntity } from '../../core/types.js';
import type { ISqueezeboxFavorite, ISqueezeboxPlayer, ISqueezeboxServerInfo, ISqueezeboxSnapshot, ISqueezeboxSyncGroup, ISqueezeboxTrack } from './squeezebox.types.js';

export class SqueezeboxMapper {
  public static toDevices(snapshotArg: ISqueezeboxSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [{
      id: this.serverDeviceId(snapshotArg),
      integrationDomain: 'squeezebox',
      name: this.serverName(snapshotArg.server),
      protocol: 'http',
      manufacturer: snapshotArg.server.manufacturer || 'Lyrion',
      model: snapshotArg.server.model || 'Lyrion Music Server',
      online: snapshotArg.online,
      features: [
        { id: 'players', capability: 'sensor', name: 'Players', readable: true, writable: false },
        { id: 'favorites', capability: 'media', name: 'Favorites', readable: true, writable: false },
        { id: 'sync_groups', capability: 'media', name: 'Sync groups', readable: true, writable: true },
        { id: 'library_songs', capability: 'sensor', name: 'Library songs', readable: true, writable: false },
        { id: 'rescan', capability: 'sensor', name: 'Library rescan', readable: true, writable: false },
      ],
      state: [
        { featureId: 'players', value: snapshotArg.server.playerCount ?? snapshotArg.players.length, updatedAt },
        { featureId: 'favorites', value: snapshotArg.favorites?.length || 0, updatedAt },
        { featureId: 'sync_groups', value: this.syncGroups(snapshotArg).length, updatedAt },
        { featureId: 'library_songs', value: snapshotArg.server.stats?.totalSongs ?? null, updatedAt },
        { featureId: 'rescan', value: snapshotArg.server.rescan ?? null, updatedAt },
      ],
      metadata: {
        uuid: snapshotArg.server.uuid,
        mac: snapshotArg.server.mac,
        version: snapshotArg.server.version,
        host: snapshotArg.server.host,
        port: snapshotArg.server.port,
        source: snapshotArg.source,
      },
    }];

    for (const player of snapshotArg.players) {
      devices.push({
        id: this.playerDeviceId(player),
        integrationDomain: 'squeezebox',
        name: player.name,
        protocol: 'http',
        manufacturer: player.manufacturer || player.creator || 'Logitech',
        model: player.model || 'Squeezebox Player',
        online: this.playerAvailable(player),
        features: [
          { id: 'playback', capability: 'media', name: 'Playback', readable: true, writable: true },
          { id: 'power', capability: 'switch', name: 'Power', readable: true, writable: true },
          { id: 'volume', capability: 'media', name: 'Volume', readable: true, writable: true, unit: '%' },
          { id: 'muted', capability: 'media', name: 'Muted', readable: true, writable: true },
          { id: 'source', capability: 'media', name: 'Source', readable: true, writable: true },
          { id: 'sync_group', capability: 'media', name: 'Sync group', readable: true, writable: true },
          { id: 'current_title', capability: 'media', name: 'Current title', readable: true, writable: false },
        ],
        state: [
          { featureId: 'playback', value: this.mediaState(player), updatedAt },
          { featureId: 'power', value: player.power ?? null, updatedAt },
          { featureId: 'volume', value: this.volumePercent(player) ?? null, updatedAt },
          { featureId: 'muted', value: player.muting ?? null, updatedAt },
          { featureId: 'source', value: this.currentSource(snapshotArg, player) || null, updatedAt },
          { featureId: 'sync_group', value: this.groupForPlayer(snapshotArg, player)?.id || null, updatedAt },
          { featureId: 'current_title', value: this.mediaTitle(player) || null, updatedAt },
        ],
        metadata: {
          playerId: player.playerId,
          uuid: player.uuid,
          firmware: player.firmware,
          ipAddress: player.ipAddress,
          connected: player.connected,
          modelType: player.modelType,
          viaDeviceId: this.serverDeviceId(snapshotArg),
        },
      });
    }

    return devices;
  }

  public static toEntities(snapshotArg: ISqueezeboxSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    const sourceList = this.sourceList(snapshotArg);
    for (const player of snapshotArg.players) {
      const base = this.playerEntityBase(player);
      const group = this.groupForPlayer(snapshotArg, player);
      const available = this.playerAvailable(player);
      entities.push({
        id: this.playerEntityId(player),
        uniqueId: `squeezebox_${this.slug(player.playerId)}`,
        integrationDomain: 'squeezebox',
        deviceId: this.playerDeviceId(player),
        platform: 'media_player',
        name: player.name,
        state: this.mediaState(player),
        attributes: {
          deviceClass: 'speaker',
          playerId: player.playerId,
          uuid: player.uuid,
          model: player.model,
          modelType: player.modelType,
          firmware: player.firmware,
          ipAddress: player.ipAddress,
          connected: player.connected,
          power: player.power,
          volumeLevel: this.volumeLevel(player),
          volume: this.volumePercent(player),
          isVolumeMuted: player.muting,
          source: this.currentSource(snapshotArg, player),
          sourceList,
          repeat: this.repeatMode(player),
          shuffle: player.shuffle === 'song',
          shuffleMode: player.shuffle,
          mediaContentId: player.url,
          mediaContentType: player.playlist && player.playlist.length > 1 ? 'playlist' : 'music',
          mediaDuration: player.duration,
          mediaPosition: player.time,
          mediaImageUrl: player.imageUrl,
          mediaTitle: player.title,
          mediaChannel: player.remoteTitle,
          mediaArtist: player.artist,
          mediaAlbumName: player.album,
          currentIndex: player.currentIndex,
          playlist: player.playlist,
          syncGroupId: group?.id,
          groupMembers: this.groupMembers(snapshotArg, player),
          alarmsEnabled: player.alarmsEnabled,
          alarmNext: player.alarmNext,
        },
        available,
      });

      entities.push({
        id: `sensor.${base}_squeezebox_media`,
        uniqueId: `squeezebox_${this.slug(player.playerId)}_media`,
        integrationDomain: 'squeezebox',
        deviceId: this.playerDeviceId(player),
        platform: 'sensor',
        name: `${player.name} Squeezebox Media`,
        state: this.mediaTitle(player) || 'None',
        attributes: {
          playerId: player.playerId,
          url: player.url,
          title: player.title,
          remoteTitle: player.remoteTitle,
          artist: player.artist,
          album: player.album,
          playlist: player.playlist,
        },
        available,
      });

      if (player.alarms?.length) {
        entities.push({
          id: `sensor.${base}_squeezebox_alarms`,
          uniqueId: `squeezebox_${this.slug(player.playerId)}_alarms`,
          integrationDomain: 'squeezebox',
          deviceId: this.playerDeviceId(player),
          platform: 'sensor',
          name: `${player.name} Squeezebox Alarms`,
          state: player.alarms.length,
          attributes: { playerId: player.playerId, alarms: player.alarms },
          available,
        });
      }
    }

    entities.push({
      id: `sensor.${this.slug(this.serverName(snapshotArg.server))}_favorites`,
      uniqueId: `squeezebox_${this.serverUniqueBase(snapshotArg)}_favorites`,
      integrationDomain: 'squeezebox',
      deviceId: this.serverDeviceId(snapshotArg),
      platform: 'sensor',
      name: `${this.serverName(snapshotArg.server)} Favorites`,
      state: snapshotArg.favorites?.length || 0,
      attributes: {
        favorites: snapshotArg.favorites || [],
        sourceList,
      },
      available: snapshotArg.online,
    });

    entities.push({
      id: `sensor.${this.slug(this.serverName(snapshotArg.server))}_sync_groups`,
      uniqueId: `squeezebox_${this.serverUniqueBase(snapshotArg)}_sync_groups`,
      integrationDomain: 'squeezebox',
      deviceId: this.serverDeviceId(snapshotArg),
      platform: 'sensor',
      name: `${this.serverName(snapshotArg.server)} Sync Groups`,
      state: this.syncGroups(snapshotArg).length,
      attributes: {
        syncGroups: this.syncGroups(snapshotArg).map((groupArg) => ({
          ...groupArg,
          members: groupArg.playerIds.map((playerIdArg) => {
            const player = snapshotArg.players.find((itemArg) => itemArg.playerId === playerIdArg);
            return player ? this.playerEntityId(player) : undefined;
          }).filter((valueArg): valueArg is string => Boolean(valueArg)),
        })),
      },
      available: snapshotArg.online,
    });

    entities.push({
      id: `sensor.${this.slug(this.serverName(snapshotArg.server))}_server_status`,
      uniqueId: `squeezebox_${this.serverUniqueBase(snapshotArg)}_server_status`,
      integrationDomain: 'squeezebox',
      deviceId: this.serverDeviceId(snapshotArg),
      platform: 'sensor',
      name: `${this.serverName(snapshotArg.server)} Server Status`,
      state: snapshotArg.online ? 'online' : 'offline',
      attributes: {
        version: snapshotArg.server.version,
        playerCount: snapshotArg.server.playerCount ?? snapshotArg.players.length,
        otherPlayerCount: snapshotArg.server.otherPlayerCount,
        stats: snapshotArg.server.stats,
        rescan: snapshotArg.server.rescan,
        needsRestart: snapshotArg.server.needsRestart,
        error: snapshotArg.error,
      },
      available: true,
    });

    if (snapshotArg.server.rescan !== undefined) {
      entities.push({
        id: `binary_sensor.${this.slug(this.serverName(snapshotArg.server))}_library_rescan`,
        uniqueId: `squeezebox_${this.serverUniqueBase(snapshotArg)}_library_rescan`,
        integrationDomain: 'squeezebox',
        deviceId: this.serverDeviceId(snapshotArg),
        platform: 'binary_sensor',
        name: `${this.serverName(snapshotArg.server)} Library Rescan`,
        state: snapshotArg.server.rescan ? 'on' : 'off',
        attributes: {},
        available: snapshotArg.online,
      });
    }

    return entities;
  }

  public static entityPlayerId(snapshotArg: ISqueezeboxSnapshot, entityIdArg: string): string | undefined {
    const entity = this.toEntities(snapshotArg).find((entityArg) => entityArg.id === entityIdArg);
    const playerId = entity?.attributes?.playerId;
    return typeof playerId === 'string' ? playerId : undefined;
  }

  public static playerEntityId(playerArg: ISqueezeboxPlayer): string {
    return `media_player.${this.playerEntityBase(playerArg)}`;
  }

  public static playerDeviceId(playerArg: ISqueezeboxPlayer): string {
    return `squeezebox.player.${this.slug(playerArg.playerId)}`;
  }

  public static serverDeviceId(snapshotArg: ISqueezeboxSnapshot): string {
    return `squeezebox.server.${this.serverUniqueBase(snapshotArg)}`;
  }

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

  private static playerAvailable(playerArg: ISqueezeboxPlayer): boolean {
    return playerArg.available !== false && playerArg.connected !== false;
  }

  private static mediaState(playerArg: ISqueezeboxPlayer): string {
    if (!this.playerAvailable(playerArg) || playerArg.power === false) {
      return 'off';
    }
    if (playerArg.mode === 'play') {
      return 'playing';
    }
    if (playerArg.mode === 'pause') {
      return 'paused';
    }
    if (playerArg.mode === 'stop') {
      return 'idle';
    }
    return playerArg.mode || 'unknown';
  }

  private static repeatMode(playerArg: ISqueezeboxPlayer): 'off' | 'one' | 'all' | undefined {
    if (!playerArg.repeat) {
      return undefined;
    }
    if (playerArg.repeat === 'song') {
      return 'one';
    }
    if (playerArg.repeat === 'playlist') {
      return 'all';
    }
    return 'off';
  }

  private static mediaTitle(playerArg: ISqueezeboxPlayer): string | undefined {
    return playerArg.title || playerArg.remoteTitle || playerArg.playlist?.[playerArg.currentIndex || 0]?.title;
  }

  private static volumePercent(playerArg: ISqueezeboxPlayer): number | undefined {
    return typeof playerArg.volume === 'number' ? Math.max(0, Math.min(100, Math.round(playerArg.volume))) : undefined;
  }

  private static volumeLevel(playerArg: ISqueezeboxPlayer): number | undefined {
    const volume = this.volumePercent(playerArg);
    return typeof volume === 'number' ? volume / 100 : undefined;
  }

  private static currentSource(snapshotArg: ISqueezeboxSnapshot, playerArg: ISqueezeboxPlayer): string | undefined {
    if (playerArg.source) {
      return playerArg.source;
    }
    if (playerArg.url) {
      const favorite = (snapshotArg.favorites || []).find((favoriteArg) => favoriteArg.url === playerArg.url || favoriteArg.itemId === playerArg.url);
      if (favorite) {
        return favorite.name;
      }
      if (sourceLike(playerArg.url)) {
        return playerArg.url;
      }
    }
    return undefined;
  }

  private static sourceList(snapshotArg: ISqueezeboxSnapshot): string[] {
    const values = [
      ...(snapshotArg.favorites || []).map((favoriteArg) => favoriteArg.name),
      ...snapshotArg.players.map((playerArg) => sourceLike(playerArg.url) ? playerArg.url : undefined),
    ].filter((valueArg): valueArg is string => Boolean(valueArg));
    return [...new Set(values)];
  }

  private static groupForPlayer(snapshotArg: ISqueezeboxSnapshot, playerArg: ISqueezeboxPlayer): ISqueezeboxSyncGroup | undefined {
    return this.syncGroups(snapshotArg).find((groupArg) => groupArg.playerIds.includes(playerArg.playerId));
  }

  private static groupMembers(snapshotArg: ISqueezeboxSnapshot, playerArg: ISqueezeboxPlayer): string[] | undefined {
    const group = this.groupForPlayer(snapshotArg, playerArg);
    if (!group) {
      return playerArg.syncGroup?.length ? playerArg.syncGroup.map((playerIdArg) => this.playerById(snapshotArg, playerIdArg)).filter((valueArg): valueArg is ISqueezeboxPlayer => Boolean(valueArg)).map((itemArg) => this.playerEntityId(itemArg)) : undefined;
    }
    return group.playerIds.map((playerIdArg) => this.playerById(snapshotArg, playerIdArg)).filter((valueArg): valueArg is ISqueezeboxPlayer => Boolean(valueArg)).map((itemArg) => this.playerEntityId(itemArg));
  }

  private static playerById(snapshotArg: ISqueezeboxSnapshot, playerIdArg: string): ISqueezeboxPlayer | undefined {
    return snapshotArg.players.find((playerArg) => playerArg.playerId === playerIdArg);
  }

  private static syncGroups(snapshotArg: ISqueezeboxSnapshot): ISqueezeboxSyncGroup[] {
    if (snapshotArg.syncGroups?.length) {
      return snapshotArg.syncGroups;
    }
    const groups = new Map<string, ISqueezeboxSyncGroup>();
    for (const player of snapshotArg.players) {
      const ids = [player.playerId, ...(player.syncGroup || [])].filter(Boolean).sort();
      if (ids.length < 2) {
        continue;
      }
      const key = ids.join(',');
      if (!groups.has(key)) {
        groups.set(key, { id: `sync_${this.slug(key)}`, playerIds: ids, leaderPlayerId: ids[0] });
      }
    }
    return [...groups.values()];
  }

  private static playerEntityBase(playerArg: ISqueezeboxPlayer): string {
    return this.slug(playerArg.name || playerArg.playerId);
  }

  private static serverUniqueBase(snapshotArg: ISqueezeboxSnapshot): string {
    return this.slug(snapshotArg.server.uuid || snapshotArg.server.id || snapshotArg.server.host || this.serverName(snapshotArg.server));
  }

  private static serverName(serverArg: ISqueezeboxServerInfo): string {
    return serverArg.name || serverArg.libraryName || serverArg.host || 'Lyrion Music Server';
  }
}

const sourceLike = (valueArg: string | undefined): valueArg is string => Boolean(valueArg && /^(source|wavin|spotify|loop):/i.test(valueArg));
