import * as plugins from '../../plugins.js';
import type { IIntegrationEntity } from '../../core/types.js';
import type { ISnapcastClient, ISnapcastGroup, ISnapcastServerStatus, ISnapcastSnapshot, ISnapcastStream } from './snapcast.types.js';

export class SnapcastMapper {
  public static toDevices(snapshotArg: ISnapcastSnapshot | ISnapcastServerStatus): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const server = this.serverStatus(snapshotArg);
    const updatedAt = new Date().toISOString();
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [];

    for (const group of server.groups) {
      const stream = this.streamForGroup(server, group);
      devices.push(this.groupDevice(group, stream, updatedAt));
      for (const client of group.clients) {
        devices.push(this.clientDevice(client, group, stream, updatedAt));
      }
    }

    for (const stream of server.streams) {
      devices.push(this.streamDevice(stream, updatedAt));
    }

    return devices;
  }

  public static toEntities(snapshotArg: ISnapcastSnapshot | ISnapcastServerStatus): IIntegrationEntity[] {
    const server = this.serverStatus(snapshotArg);
    const entities: IIntegrationEntity[] = [];

    for (const group of server.groups) {
      const stream = this.streamForGroup(server, group);
      entities.push({
        id: `media_player.${this.slug(this.groupName(group))}_snapcast_group`,
        uniqueId: `snapcast_group_${this.slug(group.id)}`,
        integrationDomain: 'snapcast',
        deviceId: this.groupDeviceId(group),
        platform: 'media_player',
        name: `${this.groupName(group)} Snapcast Group`,
        state: this.playbackState(group, stream),
        attributes: {
          snapcastGroupId: group.id,
          muted: Boolean(group.muted),
          source: this.groupStreamId(group),
          sourceList: server.streams.map((streamArg) => streamArg.id),
          groupMembers: group.clients.map((clientArg) => `media_player.${this.slug(this.clientName(clientArg))}_snapcast_client`),
          clientIds: group.clients.map((clientArg) => clientArg.id),
          mediaTitle: stream?.metadata?.title,
          mediaArtist: this.joinArtist(stream?.metadata?.artist),
          mediaAlbum: stream?.metadata?.album,
          mediaImageUrl: stream?.metadata?.artUrl,
        },
        available: group.clients.some((clientArg) => clientArg.connected),
      });

      for (const client of group.clients) {
        entities.push({
          id: `media_player.${this.slug(this.clientName(client))}_snapcast_client`,
          uniqueId: `snapcast_client_${this.slug(client.id)}`,
          integrationDomain: 'snapcast',
          deviceId: this.clientDeviceId(client),
          platform: 'media_player',
          name: `${this.clientName(client)} Snapcast Client`,
          state: this.playbackState(group, stream, client),
          attributes: {
            snapcastClientId: client.id,
            snapcastGroupId: group.id,
            latency: client.config.latency,
            volumeLevel: typeof client.config.volume?.percent === 'number' ? client.config.volume.percent / 100 : undefined,
            volumePercent: client.config.volume?.percent,
            muted: client.config.volume?.muted,
            source: this.groupStreamId(group),
            sourceList: server.streams.map((streamArg) => streamArg.id),
            groupMembers: group.clients.map((clientArg) => `media_player.${this.slug(this.clientName(clientArg))}_snapcast_client`),
            hostName: client.host?.name,
            hostIp: client.host?.ip,
            hostMac: client.host?.mac,
            mediaTitle: stream?.metadata?.title,
            mediaArtist: this.joinArtist(stream?.metadata?.artist),
            mediaAlbum: stream?.metadata?.album,
            mediaImageUrl: stream?.metadata?.artUrl,
            mediaDuration: stream?.metadata?.duration,
            mediaPosition: typeof stream?.properties?.position === 'number' ? stream.properties.position : undefined,
          },
          available: client.connected,
        });
      }
    }

    for (const stream of server.streams) {
      entities.push({
        id: `sensor.${this.slug(this.streamName(stream))}_snapcast_stream`,
        uniqueId: `snapcast_stream_${this.slug(stream.id)}`,
        integrationDomain: 'snapcast',
        deviceId: this.streamDeviceId(stream),
        platform: 'sensor',
        name: `${this.streamName(stream)} Snapcast Stream`,
        state: stream.status || 'unknown',
        attributes: {
          snapcastStreamId: stream.id,
          uri: stream.uri?.raw,
          scheme: stream.uri?.scheme,
          metadata: stream.metadata,
          properties: stream.properties,
        },
        available: true,
      });
    }

    return entities;
  }

  public static clientDeviceId(clientArg: ISnapcastClient): string {
    return `snapcast.client.${this.slug(clientArg.id)}`;
  }

  public static groupDeviceId(groupArg: ISnapcastGroup): string {
    return `snapcast.group.${this.slug(groupArg.id)}`;
  }

  public static streamDeviceId(streamArg: ISnapcastStream): string {
    return `snapcast.stream.${this.slug(streamArg.id)}`;
  }

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

  private static clientDevice(clientArg: ISnapcastClient, groupArg: ISnapcastGroup, streamArg: ISnapcastStream | undefined, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    return {
      id: this.clientDeviceId(clientArg),
      integrationDomain: 'snapcast',
      name: this.clientName(clientArg),
      protocol: 'http',
      manufacturer: 'Snapcast',
      model: clientArg.snapclient?.name || 'Snapclient',
      online: clientArg.connected,
      features: [
        { id: 'playback', capability: 'media', name: 'Playback', readable: true, writable: false },
        { id: 'volume', capability: 'media', name: 'Volume', readable: true, writable: true, unit: '%' },
        { id: 'muted', capability: 'media', name: 'Muted', readable: true, writable: true },
        { id: 'latency', capability: 'media', name: 'Latency', readable: true, writable: true, unit: 'ms' },
        { id: 'stream', capability: 'media', name: 'Stream', readable: true, writable: true },
        { id: 'group', capability: 'media', name: 'Group', readable: true, writable: true },
      ],
      state: [
        { featureId: 'playback', value: this.playbackState(groupArg, streamArg, clientArg), updatedAt: updatedAtArg },
        { featureId: 'volume', value: clientArg.config.volume?.percent ?? null, updatedAt: updatedAtArg },
        { featureId: 'muted', value: clientArg.config.volume?.muted ?? null, updatedAt: updatedAtArg },
        { featureId: 'latency', value: clientArg.config.latency ?? null, updatedAt: updatedAtArg },
        { featureId: 'stream', value: this.groupStreamId(groupArg) || null, updatedAt: updatedAtArg },
        { featureId: 'group', value: groupArg.id, updatedAt: updatedAtArg },
      ],
      metadata: {
        snapcastClientId: clientArg.id,
        snapcastGroupId: groupArg.id,
        host: clientArg.host,
        snapclient: clientArg.snapclient,
        lastSeen: clientArg.lastSeen,
      },
    };
  }

  private static groupDevice(groupArg: ISnapcastGroup, streamArg: ISnapcastStream | undefined, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    return {
      id: this.groupDeviceId(groupArg),
      integrationDomain: 'snapcast',
      name: this.groupName(groupArg),
      protocol: 'http',
      manufacturer: 'Snapcast',
      model: 'Snapcast Group',
      online: groupArg.clients.some((clientArg) => clientArg.connected),
      features: [
        { id: 'playback', capability: 'media', name: 'Playback', readable: true, writable: false },
        { id: 'muted', capability: 'media', name: 'Muted', readable: true, writable: true },
        { id: 'stream', capability: 'media', name: 'Stream', readable: true, writable: true },
        { id: 'client_count', capability: 'sensor', name: 'Client count', readable: true, writable: false },
      ],
      state: [
        { featureId: 'playback', value: this.playbackState(groupArg, streamArg), updatedAt: updatedAtArg },
        { featureId: 'muted', value: Boolean(groupArg.muted), updatedAt: updatedAtArg },
        { featureId: 'stream', value: this.groupStreamId(groupArg) || null, updatedAt: updatedAtArg },
        { featureId: 'client_count', value: groupArg.clients.length, updatedAt: updatedAtArg },
      ],
      metadata: {
        snapcastGroupId: groupArg.id,
        clientIds: groupArg.clients.map((clientArg) => clientArg.id),
      },
    };
  }

  private static streamDevice(streamArg: ISnapcastStream, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    return {
      id: this.streamDeviceId(streamArg),
      integrationDomain: 'snapcast',
      name: this.streamName(streamArg),
      protocol: 'http',
      manufacturer: 'Snapcast',
      model: `${streamArg.uri?.scheme || 'audio'} stream`,
      online: true,
      features: [
        { id: 'status', capability: 'media', name: 'Status', readable: true, writable: false },
        { id: 'current_title', capability: 'media', name: 'Current title', readable: true, writable: false },
      ],
      state: [
        { featureId: 'status', value: streamArg.status || 'unknown', updatedAt: updatedAtArg },
        { featureId: 'current_title', value: streamArg.metadata?.title || null, updatedAt: updatedAtArg },
      ],
      metadata: {
        snapcastStreamId: streamArg.id,
        uri: streamArg.uri,
        metadata: streamArg.metadata,
        properties: streamArg.properties,
      },
    };
  }

  private static serverStatus(snapshotArg: ISnapcastSnapshot | ISnapcastServerStatus): ISnapcastServerStatus {
    return Array.isArray((snapshotArg as ISnapcastServerStatus).groups) ? snapshotArg as ISnapcastServerStatus : (snapshotArg as ISnapcastSnapshot).server;
  }

  private static streamForGroup(serverArg: ISnapcastServerStatus, groupArg: ISnapcastGroup): ISnapcastStream | undefined {
    const streamId = this.groupStreamId(groupArg);
    return serverArg.streams.find((streamArg) => streamArg.id === streamId);
  }

  private static playbackState(groupArg: ISnapcastGroup, streamArg?: ISnapcastStream, clientArg?: ISnapcastClient): string {
    if (clientArg && !clientArg.connected) {
      return 'off';
    }
    if (clientArg?.config.volume?.muted || groupArg.muted) {
      return 'idle';
    }
    if (streamArg?.status === 'playing') {
      return 'playing';
    }
    if (streamArg?.status === 'idle') {
      return 'idle';
    }
    return streamArg?.status || 'unknown';
  }

  private static clientName(clientArg: ISnapcastClient): string {
    return clientArg.config.name || clientArg.host?.name || clientArg.id;
  }

  private static groupName(groupArg: ISnapcastGroup): string {
    return groupArg.name || groupArg.clients.map((clientArg) => this.clientName(clientArg)).join(', ') || groupArg.id;
  }

  private static streamName(streamArg: ISnapcastStream): string {
    return streamArg.uri?.query?.name || streamArg.id;
  }

  private static groupStreamId(groupArg: ISnapcastGroup): string {
    return groupArg.stream_id || groupArg.streamId || '';
  }

  private static joinArtist(valueArg: string[] | string | undefined): string | undefined {
    return Array.isArray(valueArg) ? valueArg.join(', ') : valueArg;
  }
}
