import * as plugins from '../../plugins.js';
import type { IIntegrationEntity } from '../../core/types.js';
import type { IForkedDaapdOutput, IForkedDaapdQueueItem, IForkedDaapdSnapshot } from './forked_daapd.types.js';
import { forkedDaapdDefaultMaxPlaylists, forkedDaapdSourceNameClear, forkedDaapdSourceNameDefault } from './forked_daapd.types.js';

export class ForkedDaapdMapper {
  public static toDevices(snapshotArg: IForkedDaapdSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const activeOutputs = snapshotArg.outputs.filter((outputArg) => outputArg.selected).length;
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [{
      id: this.serverDeviceId(snapshotArg),
      integrationDomain: 'forked_daapd',
      name: this.serverName(snapshotArg),
      protocol: 'http',
      manufacturer: 'OwnTone',
      model: 'OwnTone Server',
      online: snapshotArg.online,
      features: [
        { id: 'playback', capability: 'media', name: 'Playback', readable: true, writable: true },
        { id: 'volume', capability: 'media', name: 'Volume', readable: true, writable: true, unit: '%' },
        { id: 'active_outputs', capability: 'media', name: 'Active outputs', readable: true, writable: true },
        { id: 'queue_count', capability: 'media', name: 'Queue count', readable: true, writable: true },
        { id: 'current_title', capability: 'media', name: 'Current title', readable: true, writable: false },
      ],
      state: [
        { featureId: 'playback', value: this.mediaState(snapshotArg), updatedAt },
        { featureId: 'volume', value: snapshotArg.player.volume, updatedAt },
        { featureId: 'active_outputs', value: activeOutputs, updatedAt },
        { featureId: 'queue_count', value: snapshotArg.queue.count, updatedAt },
        { featureId: 'current_title', value: this.mediaTitle(snapshotArg) || null, updatedAt },
      ],
      metadata: {
        uniqueId: snapshotArg.server.id,
        host: snapshotArg.server.host,
        port: snapshotArg.server.port,
        websocketPort: snapshotArg.server.websocketPort,
        source: snapshotArg.source,
      },
    }];

    for (const output of snapshotArg.outputs) {
      devices.push({
        id: this.outputDeviceId(snapshotArg, output),
        integrationDomain: 'forked_daapd',
        name: output.name,
        protocol: 'http',
        manufacturer: 'OwnTone',
        model: output.type || 'Audio Output',
        online: snapshotArg.online,
        features: [
          { id: 'selected', capability: 'media', name: 'Selected', readable: true, writable: true },
          { id: 'volume', capability: 'media', name: 'Volume', readable: true, writable: true, unit: '%' },
        ],
        state: [
          { featureId: 'selected', value: output.selected, updatedAt },
          { featureId: 'volume', value: output.volume, updatedAt },
        ],
        metadata: {
          serverId: snapshotArg.server.id,
          outputId: output.id,
          outputType: output.type,
          requiresAuth: output.requires_auth,
          needsAuthKey: output.needs_auth_key,
        },
      });
    }
    return devices;
  }

  public static toEntities(snapshotArg: IForkedDaapdSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    const track = this.trackInfo(snapshotArg);
    entities.push({
      id: this.serverEntityId(snapshotArg),
      uniqueId: `forked_daapd_${this.slug(snapshotArg.server.id)}_server`,
      integrationDomain: 'forked_daapd',
      deviceId: this.serverDeviceId(snapshotArg),
      platform: 'media_player',
      name: `${this.serverName(snapshotArg)} server`,
      state: this.mediaState(snapshotArg),
      attributes: {
        deviceClass: 'speaker',
        volumeLevel: this.volumeLevel(snapshotArg.player.volume),
        volume: snapshotArg.player.volume,
        isVolumeMuted: snapshotArg.player.volume === 0,
        repeat: snapshotArg.player.repeat,
        shuffle: snapshotArg.player.shuffle,
        source: this.currentSource(snapshotArg),
        sourceList: this.sourceList(snapshotArg),
        mediaContentId: snapshotArg.player.item_id,
        mediaContentType: track?.media_kind,
        mediaDuration: this.seconds(snapshotArg.player.item_length_ms),
        mediaPosition: this.seconds(snapshotArg.player.item_progress_ms),
        mediaTitle: this.mediaTitle(snapshotArg),
        mediaArtist: track?.artist,
        mediaAlbumName: this.mediaAlbumName(track),
        mediaAlbumArtist: track?.album_artist || track?.albumartist,
        mediaTrack: track?.track_number,
        mediaImageUrl: this.mediaImageUrl(snapshotArg, track),
        queueCount: snapshotArg.queue.count,
        outputs: snapshotArg.outputs.map((outputArg) => ({ id: outputArg.id, name: outputArg.name, selected: outputArg.selected, volume: outputArg.volume, type: outputArg.type })),
      },
      available: snapshotArg.online,
    });

    for (const output of snapshotArg.outputs) {
      entities.push({
        id: this.outputEntityId(snapshotArg, output),
        uniqueId: `forked_daapd_${this.slug(snapshotArg.server.id)}_output_${this.slug(output.id)}`,
        integrationDomain: 'forked_daapd',
        deviceId: this.outputDeviceId(snapshotArg, output),
        platform: 'media_player',
        name: `${this.serverName(snapshotArg)} output (${output.name})`,
        state: output.selected ? 'on' : 'off',
        attributes: {
          outputId: output.id,
          outputType: output.type,
          volumeLevel: this.volumeLevel(output.volume),
          volume: output.volume,
          isVolumeMuted: output.volume === 0,
          hasPassword: output.has_password,
          requiresAuth: output.requires_auth,
          needsAuthKey: output.needs_auth_key,
          format: output.format,
          supportedFormats: output.supported_formats,
        },
        available: snapshotArg.online,
      });
    }

    entities.push({
      id: `sensor.${this.serverEntityBase(snapshotArg)}_queue`,
      uniqueId: `forked_daapd_${this.slug(snapshotArg.server.id)}_queue`,
      integrationDomain: 'forked_daapd',
      deviceId: this.serverDeviceId(snapshotArg),
      platform: 'sensor',
      name: `${this.serverName(snapshotArg)} queue`,
      state: snapshotArg.queue.count,
      attributes: {
        version: snapshotArg.queue.version,
        items: snapshotArg.queue.items,
      },
      available: snapshotArg.online,
    });
    return entities;
  }

  public static entityTarget(snapshotArg: IForkedDaapdSnapshot, entityIdArg: string): { kind: 'server' } | { kind: 'output'; outputId: string } | undefined {
    if (entityIdArg === this.serverEntityId(snapshotArg)) {
      return { kind: 'server' };
    }
    const output = snapshotArg.outputs.find((outputArg) => this.outputEntityId(snapshotArg, outputArg) === entityIdArg);
    return output ? { kind: 'output', outputId: output.id } : undefined;
  }

  public static deviceTarget(snapshotArg: IForkedDaapdSnapshot, deviceIdArg: string): { kind: 'server' } | { kind: 'output'; outputId: string } | undefined {
    if (deviceIdArg === this.serverDeviceId(snapshotArg)) {
      return { kind: 'server' };
    }
    const output = snapshotArg.outputs.find((outputArg) => this.outputDeviceId(snapshotArg, outputArg) === deviceIdArg);
    return output ? { kind: 'output', outputId: output.id } : undefined;
  }

  public static serverEntityId(snapshotArg: IForkedDaapdSnapshot): string {
    return `media_player.${this.serverEntityBase(snapshotArg)}`;
  }

  public static outputEntityId(snapshotArg: IForkedDaapdSnapshot, outputArg: IForkedDaapdOutput): string {
    return `media_player.${this.serverEntityBase(snapshotArg)}_${this.slug(outputArg.name || outputArg.id)}`;
  }

  public static serverDeviceId(snapshotArg: IForkedDaapdSnapshot): string {
    return `forked_daapd.server.${this.slug(snapshotArg.server.id || snapshotArg.server.name)}`;
  }

  public static outputDeviceId(snapshotArg: IForkedDaapdSnapshot, outputArg: IForkedDaapdOutput): string {
    return `forked_daapd.output.${this.slug(snapshotArg.server.id)}.${this.slug(outputArg.id)}`;
  }

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

  public static sourceList(snapshotArg: IForkedDaapdSnapshot): string[] {
    return Object.keys(this.sourceUris(snapshotArg));
  }

  public static sourceUris(snapshotArg: IForkedDaapdSnapshot): Record<string, string | undefined> {
    const sources: Record<string, string | undefined> = {
      [forkedDaapdSourceNameClear]: undefined,
      [forkedDaapdSourceNameDefault]: undefined,
    };
    for (const pipe of snapshotArg.library.pipes) {
      const title = this.stringValue(pipe.title) || this.stringValue(pipe.name);
      if (title && pipe.uri) {
        sources[`${title} (pipe)`] = pipe.uri;
      }
    }
    for (const playlist of snapshotArg.library.playlists.slice(0, snapshotArg.maxPlaylists || forkedDaapdDefaultMaxPlaylists)) {
      const name = this.stringValue(playlist.name) || this.stringValue(playlist.title);
      if (name && playlist.uri) {
        sources[`${name} (playlist)`] = playlist.uri;
      }
    }
    return sources;
  }

  private static serverEntityBase(snapshotArg: IForkedDaapdSnapshot): string {
    return `${this.slug(this.serverName(snapshotArg))}_owntone`;
  }

  private static serverName(snapshotArg: IForkedDaapdSnapshot): string {
    return snapshotArg.server.name || snapshotArg.server.libraryName || snapshotArg.server.host || 'OwnTone';
  }

  private static mediaState(snapshotArg: IForkedDaapdSnapshot): string {
    if (!snapshotArg.online) {
      return 'off';
    }
    if (snapshotArg.player.state === 'play') {
      return 'playing';
    }
    if (snapshotArg.player.state === 'pause') {
      return 'paused';
    }
    if (!snapshotArg.outputs.some((outputArg) => outputArg.selected)) {
      return 'off';
    }
    if (snapshotArg.player.state === 'stop') {
      return 'idle';
    }
    return snapshotArg.player.state || 'unknown';
  }

  private static currentSource(snapshotArg: IForkedDaapdSnapshot): string {
    const first = snapshotArg.queue.items[0];
    if (first?.data_kind === 'pipe' && first.title) {
      return `${first.title} (pipe)`;
    }
    return forkedDaapdSourceNameDefault;
  }

  private static trackInfo(snapshotArg: IForkedDaapdSnapshot): IForkedDaapdQueueItem | undefined {
    return snapshotArg.queue.items.find((itemArg) => String(itemArg.id) === String(snapshotArg.player.item_id));
  }

  private static mediaTitle(snapshotArg: IForkedDaapdSnapshot): string | undefined {
    const track = this.trackInfo(snapshotArg);
    if (track?.data_kind === 'url') {
      return track.album;
    }
    return track?.title;
  }

  private static mediaAlbumName(trackArg: IForkedDaapdQueueItem | undefined): string | undefined {
    if (trackArg?.data_kind === 'url') {
      return trackArg.title;
    }
    return trackArg?.album;
  }

  private static mediaImageUrl(snapshotArg: IForkedDaapdSnapshot, trackArg: IForkedDaapdQueueItem | undefined): string | undefined {
    if (!trackArg?.artwork_url) {
      return undefined;
    }
    if (/^https?:\/\//i.test(trackArg.artwork_url)) {
      return trackArg.artwork_url;
    }
    if (!snapshotArg.server.host) {
      return trackArg.artwork_url;
    }
    return `http://${snapshotArg.server.host}:${snapshotArg.server.port || 3689}${trackArg.artwork_url.startsWith('/') ? trackArg.artwork_url : `/${trackArg.artwork_url}`}`;
  }

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

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

  private static stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
  }
}
