import * as plugins from '../../plugins.js';
import type { IIntegrationEntity } from '../../core/types.js';
import type { IHeosGroup, IHeosMediaItem, IHeosMusicSource, IHeosNowPlayingMedia, IHeosPlayer, IHeosSnapshot } from './heos.types.js';

const heosAuxInputSourceId = 1027;

export class HeosMapper {
  public static toDevices(snapshotArg: IHeosSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.lastUpdated || new Date().toISOString();
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [{
      id: this.systemDeviceId(snapshotArg),
      integrationDomain: 'heos',
      name: this.systemName(snapshotArg),
      protocol: 'unknown',
      manufacturer: 'Denon',
      model: 'HEOS System',
      online: snapshotArg.players.some((playerArg) => playerArg.available !== false),
      features: [
        { id: 'players', capability: 'sensor', name: 'Players', readable: true, writable: false },
        { id: 'groups', capability: 'sensor', name: 'Groups', readable: true, writable: false },
        { id: 'sources', capability: 'media', name: 'Sources', readable: true, writable: false },
        { id: 'signed_in', capability: 'sensor', name: 'Signed in', readable: true, writable: false },
      ],
      state: [
        { featureId: 'players', value: snapshotArg.players.length, updatedAt },
        { featureId: 'groups', value: snapshotArg.groups?.length || 0, updatedAt },
        { featureId: 'sources', value: this.sourceList(snapshotArg).length, updatedAt },
        { featureId: 'signed_in', value: Boolean(snapshotArg.system.isSignedIn || snapshotArg.system.signedInUsername), updatedAt },
      ],
      metadata: {
        host: snapshotArg.system.currentHost || snapshotArg.system.host,
        signedInUsername: snapshotArg.system.signedInUsername,
        preferredHosts: snapshotArg.system.preferredHosts?.map((hostArg) => hostArg.ipAddress).filter(Boolean),
      },
    }];

    for (const player of snapshotArg.players) {
      const media = player.nowPlayingMedia;
      const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
        { id: 'playback', capability: 'media', name: 'Playback', 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: 'group', capability: 'media', name: 'Group', readable: true, writable: true },
      ];
      const state: plugins.shxInterfaces.data.IDeviceState[] = [
        { featureId: 'playback', value: this.mediaState(player), updatedAt },
        { featureId: 'volume', value: typeof player.volume === 'number' ? player.volume : null, updatedAt },
        { featureId: 'muted', value: typeof player.muted === 'boolean' ? player.muted : null, updatedAt },
        { featureId: 'source', value: this.currentSource(snapshotArg, media) || null, updatedAt },
        { featureId: 'group', value: player.groupId ?? null, updatedAt },
      ];
      if (media?.song || media?.station) {
        features.push({ id: 'current_title', capability: 'media', name: 'Current title', readable: true, writable: false });
        state.push({ featureId: 'current_title', value: this.mediaTitle(media) || null, updatedAt });
      }

      devices.push({
        id: this.playerDeviceId(player),
        integrationDomain: 'heos',
        name: player.name,
        protocol: 'unknown',
        manufacturer: this.manufacturer(player),
        model: this.model(player),
        online: player.available !== false,
        features,
        state,
        metadata: {
          playerId: player.playerId,
          serialNumber: player.serial,
          softwareVersion: player.version,
          ipAddress: player.ipAddress,
          network: player.network,
          groupId: player.groupId,
          viaDeviceId: this.systemDeviceId(snapshotArg),
        },
      });
    }

    return devices;
  }

  public static toEntities(snapshotArg: IHeosSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    for (const player of snapshotArg.players) {
      const media = player.nowPlayingMedia;
      const base = this.playerEntityBase(player);
      entities.push({
        id: `media_player.${base}`,
        uniqueId: `heos_${this.slug(String(player.playerId))}`,
        integrationDomain: 'heos',
        deviceId: this.playerDeviceId(player),
        platform: 'media_player',
        name: player.name,
        state: this.mediaState(player),
        attributes: {
          deviceClass: 'speaker',
          playerId: player.playerId,
          model: player.model,
          serialNumber: player.serial,
          ipAddress: player.ipAddress,
          volumeLevel: this.volumeLevel(player),
          volume: player.volume,
          isVolumeMuted: player.muted,
          source: this.currentSource(snapshotArg, media),
          sourceList: this.sourceList(snapshotArg),
          groupId: player.groupId,
          groupMembers: this.groupMembers(snapshotArg, player),
          groupRole: this.groupRole(snapshotArg, player),
          repeat: this.repeatMode(player.repeat),
          shuffle: player.shuffle,
          mediaContentType: 'music',
          mediaContentId: media?.mediaId,
          mediaTitle: this.mediaTitle(media),
          mediaArtist: media?.artist,
          mediaAlbumName: media?.album,
          mediaDuration: this.seconds(media?.duration),
          mediaPosition: media?.duration ? this.seconds(media.currentPosition) : undefined,
          mediaImageUrl: media?.imageUrl || undefined,
          mediaAlbumId: media?.albumId,
          mediaQueueId: media?.queueId,
          mediaSourceId: media?.sourceId,
          mediaStation: media?.station,
          mediaType: media?.type,
          supportedControls: media?.supportedControls,
          playbackError: player.playbackError,
        },
        available: player.available !== false,
      });

      entities.push({
        id: `sensor.${base}_heos_media`,
        uniqueId: `heos_${this.slug(String(player.playerId))}_media`,
        integrationDomain: 'heos',
        deviceId: this.playerDeviceId(player),
        platform: 'sensor',
        name: `${player.name} HEOS Media`,
        state: this.mediaTitle(media) || media?.station || 'None',
        attributes: {
          playerId: player.playerId,
          media,
          source: this.currentSource(snapshotArg, media),
        },
        available: player.available !== false,
      });
    }

    entities.push({
      id: `sensor.${this.slug(this.systemName(snapshotArg))}_sources`,
      uniqueId: `heos_${this.systemUniqueBase(snapshotArg)}_sources`,
      integrationDomain: 'heos',
      deviceId: this.systemDeviceId(snapshotArg),
      platform: 'sensor',
      name: `${this.systemName(snapshotArg)} Sources`,
      state: this.sourceList(snapshotArg).length,
      attributes: {
        sourceList: this.sourceList(snapshotArg),
        favorites: this.favoriteList(snapshotArg),
        inputSources: snapshotArg.inputSources || [],
        musicSources: snapshotArg.musicSources || [],
      },
      available: snapshotArg.players.some((playerArg) => playerArg.available !== false),
    });

    entities.push({
      id: `sensor.${this.slug(this.systemName(snapshotArg))}_groups`,
      uniqueId: `heos_${this.systemUniqueBase(snapshotArg)}_groups`,
      integrationDomain: 'heos',
      deviceId: this.systemDeviceId(snapshotArg),
      platform: 'sensor',
      name: `${this.systemName(snapshotArg)} Groups`,
      state: snapshotArg.groups?.length || 0,
      attributes: {
        groups: (snapshotArg.groups || []).map((groupArg) => ({
          ...groupArg,
          members: this.groupEntityIds(snapshotArg, groupArg),
        })),
      },
      available: snapshotArg.players.some((playerArg) => playerArg.available !== false),
    });

    return entities;
  }

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

  public static playerDeviceId(playerArg: IHeosPlayer): string {
    return `heos.player.${this.slug(String(playerArg.playerId))}`;
  }

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

  private static mediaState(playerArg: IHeosPlayer): string {
    if (playerArg.available === false) {
      return 'off';
    }
    const state = playerArg.state?.toLowerCase();
    if (state === 'play') {
      return 'playing';
    }
    if (state === 'pause') {
      return 'paused';
    }
    return 'idle';
  }

  private static currentSource(snapshotArg: IHeosSnapshot, mediaArg: IHeosNowPlayingMedia | undefined): string | undefined {
    if (!mediaArg) {
      return undefined;
    }
    if (mediaArg.sourceId === heosAuxInputSourceId) {
      const byStation = snapshotArg.inputSources?.find((sourceArg) => sourceArg.name === mediaArg.station);
      if (byStation) {
        return byStation.name;
      }
      const byMediaId = snapshotArg.inputSources?.find((sourceArg) => sourceArg.mediaId === mediaArg.mediaId);
      if (byMediaId) {
        return byMediaId.name;
      }
    }
    if (mediaArg.type === 'station') {
      const favorite = this.favoriteList(snapshotArg).find((favoriteArg) => favoriteArg.name === mediaArg.station || favoriteArg.mediaId === mediaArg.albumId);
      return favorite?.name;
    }
    return undefined;
  }

  private static sourceList(snapshotArg: IHeosSnapshot): string[] {
    if (snapshotArg.sourceList) {
      return [...snapshotArg.sourceList];
    }
    const names = [...this.favoriteList(snapshotArg).map((favoriteArg) => favoriteArg.name), ...(snapshotArg.inputSources || []).map((sourceArg) => sourceArg.name)];
    return [...new Set(names.filter(Boolean))];
  }

  private static favoriteList(snapshotArg: IHeosSnapshot): IHeosMediaItem[] {
    return Object.entries(snapshotArg.favorites || {})
      .sort(([leftArg], [rightArg]) => Number(leftArg) - Number(rightArg))
      .map(([, favoriteArg]) => favoriteArg);
  }

  private static groupMembers(snapshotArg: IHeosSnapshot, playerArg: IHeosPlayer): string[] | undefined {
    const group = (snapshotArg.groups || []).find((groupArg) => groupArg.groupId === playerArg.groupId);
    if (!group) {
      return undefined;
    }
    return this.groupEntityIds(snapshotArg, group);
  }

  private static groupEntityIds(snapshotArg: IHeosSnapshot, groupArg: IHeosGroup): string[] {
    const playerIds = [groupArg.leadPlayerId, ...groupArg.memberPlayerIds];
    return playerIds.map((playerIdArg) => {
      const player = snapshotArg.players.find((playerArg) => playerArg.playerId === playerIdArg);
      return player ? `media_player.${this.playerEntityBase(player)}` : undefined;
    }).filter((entityArg): entityArg is string => Boolean(entityArg));
  }

  private static groupRole(snapshotArg: IHeosSnapshot, playerArg: IHeosPlayer): string | undefined {
    const group = (snapshotArg.groups || []).find((groupArg) => groupArg.groupId === playerArg.groupId);
    if (!group) {
      return undefined;
    }
    return group.leadPlayerId === playerArg.playerId ? 'leader' : 'member';
  }

  private static mediaTitle(mediaArg: IHeosNowPlayingMedia | undefined): string | undefined {
    return mediaArg?.song || mediaArg?.station;
  }

  private static repeatMode(repeatArg: string | undefined): 'off' | 'one' | 'all' | undefined {
    if (!repeatArg) {
      return undefined;
    }
    if (repeatArg === 'on_one') {
      return 'one';
    }
    if (repeatArg === 'on_all') {
      return 'all';
    }
    return 'off';
  }

  private static volumeLevel(playerArg: IHeosPlayer): number | undefined {
    return typeof playerArg.volume === 'number' ? Math.max(0, Math.min(1, playerArg.volume / 100)) : undefined;
  }

  private static seconds(valueArg: number | undefined): number | undefined {
    return typeof valueArg === 'number' ? Math.floor(valueArg / 1000) : undefined;
  }

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

  private static systemDeviceId(snapshotArg: IHeosSnapshot): string {
    return `heos.system.${this.systemUniqueBase(snapshotArg)}`;
  }

  private static systemUniqueBase(snapshotArg: IHeosSnapshot): string {
    return this.slug(snapshotArg.system.host || snapshotArg.system.currentHost || snapshotArg.players[0]?.serial || 'heos');
  }

  private static systemName(snapshotArg: IHeosSnapshot): string {
    return snapshotArg.system.host ? 'HEOS System' : snapshotArg.players[0]?.name ? `${snapshotArg.players[0].name} HEOS System` : 'HEOS System';
  }

  private static manufacturer(playerArg: IHeosPlayer): string {
    const modelParts = playerArg.model.split(/\s+/, 2);
    return modelParts.length === 2 ? modelParts[0] : 'HEOS';
  }

  private static model(playerArg: IHeosPlayer): string {
    const modelParts = playerArg.model.split(/\s+/, 2);
    return modelParts.length === 2 ? modelParts[1] : playerArg.model;
  }
}
