import * as plugins from '../../plugins.js';
import type { IIntegrationEntity } from '../../core/types.js';
import type { IMpdCurrentSong, IMpdOutput, IMpdSnapshot, IMpdStatus } from './mpd.types.js';

export class MpdMapper {
  public static toDevices(snapshotArg: IMpdSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [{
      id: this.serverDeviceId(snapshotArg),
      integrationDomain: 'mpd',
      name: this.serverName(snapshotArg),
      protocol: 'unknown',
      manufacturer: 'Music Player Daemon',
      model: 'MPD 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: 'source', capability: 'media', name: 'Playlist source', readable: true, writable: true },
        { id: 'current_title', capability: 'media', name: 'Current title', readable: true, writable: false },
        { id: 'playlist_length', capability: 'sensor', name: 'Playlist length', readable: true, writable: false },
      ],
      state: [
        { featureId: 'playback', value: this.mediaState(snapshotArg.status, snapshotArg.online), updatedAt },
        { featureId: 'volume', value: this.numberValue(snapshotArg.status.volume) ?? null, updatedAt },
        { featureId: 'source', value: snapshotArg.status.lastloadedplaylist || null, updatedAt },
        { featureId: 'current_title', value: this.mediaTitle(snapshotArg.currentSong) || null, updatedAt },
        { featureId: 'playlist_length', value: this.numberValue(snapshotArg.status.playlistlength) ?? null, updatedAt },
      ],
      metadata: {
        host: snapshotArg.server.host,
        port: snapshotArg.server.port,
        protocolVersion: snapshotArg.server.protocolVersion,
        source: snapshotArg.source,
      },
    }];

    for (const output of snapshotArg.outputs) {
      devices.push(this.outputDevice(snapshotArg, output, updatedAt));
    }
    return devices;
  }

  public static toEntities(snapshotArg: IMpdSnapshot): IIntegrationEntity[] {
    const serverName = this.serverName(snapshotArg);
    const uniqueBase = this.uniqueBase(snapshotArg);
    const song = snapshotArg.currentSong;
    const entities: IIntegrationEntity[] = [{
      id: `media_player.${this.slug(serverName)}`,
      uniqueId: `mpd_${uniqueBase}`,
      integrationDomain: 'mpd',
      deviceId: this.serverDeviceId(snapshotArg),
      platform: 'media_player',
      name: serverName,
      state: this.mediaState(snapshotArg.status, snapshotArg.online),
      attributes: {
        volumeLevel: this.volumeLevel(snapshotArg.status),
        mediaContentId: song?.file,
        mediaContentType: 'music',
        mediaDuration: this.duration(snapshotArg.status, song),
        mediaPosition: this.position(snapshotArg.status),
        mediaTitle: this.mediaTitle(song),
        mediaArtist: this.joinStrings(this.songValue(song, 'artist')),
        mediaAlbumName: this.firstString(this.songValue(song, 'album')),
        mediaAlbumArtist: this.joinStrings(this.songValue(song, 'albumartist')),
        source: snapshotArg.status.lastloadedplaylist,
        sourceList: snapshotArg.playlists.map((playlistArg) => playlistArg.playlist),
        repeat: this.repeatMode(snapshotArg.status),
        shuffle: this.booleanFlag(snapshotArg.status.random),
        audio: snapshotArg.status.audio,
        bitrate: this.numberValue(snapshotArg.status.bitrate),
        mpdSongId: snapshotArg.status.songid,
        mpdPlaylistVersion: snapshotArg.status.playlist,
        commands: snapshotArg.commands,
      },
      available: snapshotArg.online,
    }, {
      id: `sensor.${this.slug(serverName)}_mpd_status`,
      uniqueId: `mpd_${uniqueBase}_status`,
      integrationDomain: 'mpd',
      deviceId: this.serverDeviceId(snapshotArg),
      platform: 'sensor',
      name: `${serverName} MPD Status`,
      state: snapshotArg.status.state || 'unknown',
      attributes: {
        status: snapshotArg.status,
        stats: snapshotArg.stats,
        outputCount: snapshotArg.outputs.length,
        enabledOutputCount: snapshotArg.outputs.filter((outputArg) => this.outputEnabled(outputArg)).length,
      },
      available: snapshotArg.online,
    }, {
      id: `sensor.${this.slug(serverName)}_mpd_current_song`,
      uniqueId: `mpd_${uniqueBase}_current_song`,
      integrationDomain: 'mpd',
      deviceId: this.serverDeviceId(snapshotArg),
      platform: 'sensor',
      name: `${serverName} MPD Current Song`,
      state: this.mediaTitle(song) || 'None',
      attributes: {
        song,
        file: song?.file,
        artist: this.joinStrings(this.songValue(song, 'artist')),
        album: this.firstString(this.songValue(song, 'album')),
        duration: this.duration(snapshotArg.status, song),
      },
      available: snapshotArg.online,
    }, {
      id: `sensor.${this.slug(serverName)}_mpd_playlist_length`,
      uniqueId: `mpd_${uniqueBase}_playlist_length`,
      integrationDomain: 'mpd',
      deviceId: this.serverDeviceId(snapshotArg),
      platform: 'sensor',
      name: `${serverName} MPD Playlist Length`,
      state: this.numberValue(snapshotArg.status.playlistlength) ?? 0,
      attributes: {
        playlists: snapshotArg.playlists.map((playlistArg) => playlistArg.playlist),
        playlistVersion: snapshotArg.status.playlist,
      },
      available: snapshotArg.online,
    }];

    for (const output of snapshotArg.outputs) {
      entities.push({
        id: `switch.${this.slug(serverName)}_${this.slug(output.outputname)}_mpd_output`,
        uniqueId: `mpd_${uniqueBase}_output_${this.slug(String(output.outputid))}`,
        integrationDomain: 'mpd',
        deviceId: this.outputDeviceId(snapshotArg, output),
        platform: 'switch',
        name: `${output.outputname} MPD Output`,
        state: this.outputEnabled(output) ? 'on' : 'off',
        attributes: {
          mpdOutputId: output.outputid,
          mpdOutputName: output.outputname,
          plugin: output.plugin,
          attributes: output.attributes,
        },
        available: snapshotArg.online,
      });
    }

    return entities;
  }

  public static serverDeviceId(snapshotArg: IMpdSnapshot): string {
    return `mpd.server.${this.uniqueBase(snapshotArg)}`;
  }

  public static outputDeviceId(snapshotArg: IMpdSnapshot, outputArg: IMpdOutput): string {
    return `mpd.output.${this.uniqueBase(snapshotArg)}.${this.slug(outputArg.outputid !== undefined ? String(outputArg.outputid) : outputArg.outputname)}`;
  }

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

  private static outputDevice(snapshotArg: IMpdSnapshot, outputArg: IMpdOutput, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
    return {
      id: this.outputDeviceId(snapshotArg, outputArg),
      integrationDomain: 'mpd',
      name: outputArg.outputname,
      protocol: 'unknown',
      manufacturer: 'Music Player Daemon',
      model: outputArg.plugin || 'Audio Output',
      online: snapshotArg.online,
      features: [
        { id: 'enabled', capability: 'switch', name: 'Enabled', readable: true, writable: true },
      ],
      state: [
        { featureId: 'enabled', value: this.outputEnabled(outputArg), updatedAt: updatedAtArg },
      ],
      metadata: {
        mpdOutputId: outputArg.outputid,
        plugin: outputArg.plugin,
        attributes: outputArg.attributes,
      },
    };
  }

  private static mediaState(statusArg: IMpdStatus, onlineArg: boolean): string {
    if (!onlineArg) {
      return 'off';
    }
    if (statusArg.state === 'play') {
      return 'playing';
    }
    if (statusArg.state === 'pause') {
      return 'paused';
    }
    if (statusArg.state === 'stop') {
      return 'off';
    }
    return statusArg.state || 'unknown';
  }

  private static repeatMode(statusArg: IMpdStatus): 'off' | 'one' | 'all' {
    if (!this.booleanFlag(statusArg.repeat)) {
      return 'off';
    }
    return this.booleanFlag(statusArg.single) ? 'one' : 'all';
  }

  private static mediaTitle(songArg: IMpdCurrentSong | undefined): string | undefined {
    const name = this.firstString(this.songValue(songArg, 'name'));
    const title = this.firstString(this.songValue(songArg, 'title'));
    const file = this.firstString(this.songValue(songArg, 'file'));
    if (!name && !title) {
      return file ? file.split('/').pop() || file : undefined;
    }
    if (!name) {
      return title;
    }
    if (!title) {
      return name;
    }
    return `${name}: ${title}`;
  }

  private static duration(statusArg: IMpdStatus, songArg: IMpdCurrentSong | undefined): number | undefined {
    return this.numberValue(songArg?.duration)
      ?? this.numberValue(songArg?.time)
      ?? this.numberValue(statusArg.duration)
      ?? this.durationFromStatusTime(statusArg.time, 1);
  }

  private static position(statusArg: IMpdStatus): number | undefined {
    return this.numberValue(statusArg.elapsed) ?? this.durationFromStatusTime(statusArg.time, 0);
  }

  private static durationFromStatusTime(valueArg: unknown, indexArg: 0 | 1): number | undefined {
    if (typeof valueArg !== 'string' || !valueArg.includes(':')) {
      return undefined;
    }
    return this.numberValue(valueArg.split(':')[indexArg]);
  }

  private static volumeLevel(statusArg: IMpdStatus): number | undefined {
    const volume = this.numberValue(statusArg.volume);
    return volume === undefined || volume < 0 ? undefined : volume / 100;
  }

  private static outputEnabled(outputArg: IMpdOutput): boolean {
    return outputArg.outputenabled === true || outputArg.outputenabled === '1' || outputArg.outputenabled === 1;
  }

  private static booleanFlag(valueArg: unknown): boolean {
    return valueArg === true || valueArg === '1' || valueArg === 1;
  }

  private static 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;
  }

  private static songValue(songArg: IMpdCurrentSong | undefined, keyArg: string): string | string[] | number | undefined {
    if (!songArg) {
      return undefined;
    }
    const direct = songArg[keyArg];
    if (direct !== undefined) {
      return direct as string | string[] | number;
    }
    const lowerKey = keyArg.toLowerCase();
    for (const [key, value] of Object.entries(songArg)) {
      if (key.toLowerCase() === lowerKey) {
        return value as string | string[] | number;
      }
    }
    return undefined;
  }

  private static firstString(valueArg: string | string[] | number | undefined): string | undefined {
    if (Array.isArray(valueArg)) {
      return valueArg[0];
    }
    return valueArg === undefined ? undefined : String(valueArg);
  }

  private static joinStrings(valueArg: string | string[] | number | undefined): string | undefined {
    if (Array.isArray(valueArg)) {
      return valueArg.join(', ');
    }
    return valueArg === undefined ? undefined : String(valueArg);
  }

  private static serverName(snapshotArg: IMpdSnapshot): string {
    return snapshotArg.server.name || snapshotArg.server.host || 'Music Player Daemon';
  }

  private static uniqueBase(snapshotArg: IMpdSnapshot): string {
    return this.slug(snapshotArg.server.id || snapshotArg.server.host || this.serverName(snapshotArg));
  }
}
