import type * as shxInterfaces from '@smarthome.exchange/interfaces';
import { SimpleLocalMapper, type IIntegrationEntity, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot, type ISimpleLocalSnapshotOptions, type TSimpleLocalRawData } from '../../core/index.js';
import type { IHdfuryConfig } from './hdfury.types.js';
import { hdfuryDefaultPort, hdfuryProfile } from './hdfury.types.js';

export class HdfuryMapper {
  public static toSnapshot(optionsArg: Omit<ISimpleLocalSnapshotOptions<IHdfuryConfig>, 'profile'>): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({
      ...optionsArg,
      profile: hdfuryProfile,
      rawData: this.normalizeRawData(optionsArg.config, optionsArg.rawData),
    });
  }

  public static toSnapshotFromRaw(configArg: IHdfuryConfig, rawDataArg: TSimpleLocalRawData): ISimpleLocalSnapshot {
    return this.toSnapshot({ config: configArg, rawData: rawDataArg, online: true, source: 'manual' });
  }

  public static toDevices(snapshotArg: ISimpleLocalSnapshot): shxInterfaces.data.IDeviceDefinition[] {
    return SimpleLocalMapper.toDevices(hdfuryProfile, snapshotArg);
  }

  public static toEntities(snapshotArg: ISimpleLocalSnapshot): IIntegrationEntity[] {
    return SimpleLocalMapper.toEntities(hdfuryProfile, snapshotArg);
  }

  public static slug(valueArg: unknown): string {
    return SimpleLocalMapper.slug(valueArg);
  }

  private static normalizeRawData(configArg: IHdfuryConfig, rawDataArg: unknown): unknown {
    if (!isRecord(rawDataArg) || ('device' in rawDataArg && 'entities' in rawDataArg)) {
      return rawDataArg;
    }

    const board = recordValue(rawDataArg.board) || {};
    const info = recordValue(rawDataArg.info) || {};
    const deviceConfig = recordValue(rawDataArg.config) || {};
    const hasHdfuryShape = Object.keys(board).length > 0 || Object.keys(info).some((keyArg) => keyArg.toUpperCase() === keyArg) || 'macaddr' in deviceConfig || 'opmode' in info;
    if (!hasHdfuryShape) {
      return rawDataArg;
    }

    const host = configArg.host || stringValue(rawDataArg.host) || stringValue(board.host);
    const port = configArg.port || numberValue(rawDataArg.port) || (host ? hdfuryDefaultPort : undefined);
    const hostname = stringValue(board.hostname) || configArg.name || hdfuryProfile.defaultName;
    const serial = stringValue(board.serial);
    const entities: ISimpleLocalEntitySnapshot[] = [
      ...hdfurySensors(info),
      ...hdfurySwitches(deviceConfig),
      ...hdfuryNumbers(deviceConfig),
      ...hdfurySelects(info),
      ...hdfuryButtons(),
    ];

    return {
      device: {
        id: configArg.uniqueId || serial || stringValue(deviceConfig.macaddr) || (host ? `${host}:${port || ''}` : undefined) || hostname,
        name: hostname,
        manufacturer: 'HDFury',
        model: stringValue(board.model) || hostname?.split('-')[0] || hdfuryProfile.model,
        serialNumber: serial,
        host,
        port,
        protocol: 'http',
        configurationUrl: host ? `http://${host}${port && port !== 80 ? `:${port}` : ''}` : undefined,
        attributes: {
          firmwareVersion: stringValue(board.version)?.replace(/^FW:\s*/, ''),
          hardwareVersion: stringValue(board.pcbv),
          macAddress: stringValue(deviceConfig.macaddr),
        },
      },
      entities: entities.length ? entities : [{ id: 'online', name: 'Online', platform: 'binary_sensor', state: true }],
      online: configArg.online ?? true,
      updatedAt: new Date().toISOString(),
      source: 'manual',
      rawData: rawDataArg,
    } satisfies ISimpleLocalSnapshot;
  }
}

const hdfurySensorNames: Record<string, string> = {
  AUD0: 'Audio TX0',
  AUD1: 'Audio TX1',
  AUDOUT: 'Audio output',
  EARCRX: 'eARC/ARC status',
  EDIDA0: 'EDID TXA0',
  EDIDA1: 'EDID TXA1',
  EDIDA2: 'EDID AUDA',
  RX0: 'Input RX0',
  RX1: 'Input RX1',
  SINK0: 'EDID TX0',
  SINK1: 'EDID TX1',
  SINK2: 'EDID AUD',
  TX0: 'Output TX0',
  TX1: 'Output TX1',
};

const hdfurySwitchNames: Record<string, string> = {
  autosw: 'Auto switch inputs',
  cec: 'CEC',
  cec0en: 'CEC RX0',
  cec1en: 'CEC RX1',
  cec2en: 'CEC RX2',
  cec3en: 'CEC RX3',
  htpcmode0: 'HTPC mode RX0',
  htpcmode1: 'HTPC mode RX1',
  htpcmode2: 'HTPC mode RX2',
  htpcmode3: 'HTPC mode RX3',
  iractive: 'Infrared',
  mutetx0: 'Mute audio TX0',
  mutetx1: 'Mute audio TX1',
  oled: 'OLED display',
  relay: 'Relay',
  tx0plus5: 'TX0 force +5V',
  tx1plus5: 'TX1 force +5V',
};

const hdfuryNumberNames: Record<string, { name: string; unit: string; deviceClass: string }> = {
  earcunmutecnt: { name: 'eARC unmute delay', unit: 'ms', deviceClass: 'duration' },
  oledfade: { name: 'OLED fade timer', unit: 's', deviceClass: 'duration' },
  reboottimer: { name: 'Restart timer', unit: 'h', deviceClass: 'duration' },
  unmutecnt: { name: 'Unmute delay', unit: 'ms', deviceClass: 'duration' },
};

const hdfurySelectNames: Record<string, { name: string; options: string[] }> = {
  opmode: { name: 'Operation mode', options: ['0', '1', '2', '3', '4', '5'] },
  portseltx0: { name: 'Port select TX0', options: ['0', '1', '2', '3', '4'] },
  portseltx1: { name: 'Port select TX1', options: ['0', '1', '2', '3', '4'] },
};

const hdfurySensors = (infoArg: Record<string, unknown>): ISimpleLocalEntitySnapshot[] => Object.entries(hdfurySensorNames)
  .filter(([keyArg]) => infoArg[keyArg] !== undefined)
  .map(([keyArg, nameArg]) => ({ id: keyArg.toLowerCase(), name: nameArg, platform: 'sensor', state: infoArg[keyArg], attributes: { hdfuryKey: keyArg } }));

const hdfurySwitches = (configArg: Record<string, unknown>): ISimpleLocalEntitySnapshot[] => Object.entries(hdfurySwitchNames)
  .filter(([keyArg]) => configArg[keyArg] !== undefined)
  .map(([keyArg, nameArg]) => ({ id: keyArg, name: nameArg, platform: 'switch', state: booleanValue(configArg[keyArg]) ?? configArg[keyArg], writable: true, attributes: { hdfuryKey: keyArg } }));

const hdfuryNumbers = (configArg: Record<string, unknown>): ISimpleLocalEntitySnapshot[] => Object.entries(hdfuryNumberNames)
  .filter(([keyArg]) => configArg[keyArg] !== undefined)
  .map(([keyArg, descriptionArg]) => ({ id: keyArg, name: descriptionArg.name, platform: 'number', state: numberValue(configArg[keyArg]) ?? configArg[keyArg], unit: descriptionArg.unit, deviceClass: descriptionArg.deviceClass, writable: true, attributes: { hdfuryKey: keyArg } }));

const hdfurySelects = (infoArg: Record<string, unknown>): ISimpleLocalEntitySnapshot[] => Object.entries(hdfurySelectNames)
  .filter(([keyArg]) => infoArg[keyArg] !== undefined)
  .map(([keyArg, descriptionArg]) => ({ id: keyArg, name: descriptionArg.name, platform: 'select', state: String(infoArg[keyArg]), writable: true, attributes: { hdfuryKey: keyArg, options: descriptionArg.options } }));

const hdfuryButtons = (): ISimpleLocalEntitySnapshot[] => [
  { id: 'reboot', name: 'Restart', platform: 'button', state: 'available', writable: true, deviceClass: 'restart' },
  { id: 'issue_hotplug', name: 'Issue hotplug', platform: 'button', state: 'available', writable: true },
];

const recordValue = (valueArg: unknown): Record<string, unknown> | undefined => isRecord(valueArg) ? valueArg : undefined;

const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> => Boolean(valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg));

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const numberValue = (valueArg: unknown): number | undefined => {
  const value = typeof valueArg === 'number' ? valueArg : typeof valueArg === 'string' ? Number(valueArg) : undefined;
  return value !== undefined && Number.isFinite(value) ? value : undefined;
};

const booleanValue = (valueArg: unknown): boolean | undefined => {
  if (typeof valueArg === 'boolean') {
    return valueArg;
  }
  if (typeof valueArg === 'number') {
    return valueArg !== 0;
  }
  const value = stringValue(valueArg)?.toLowerCase();
  if (!value) {
    return undefined;
  }
  if (['1', 'true', 'yes', 'on', 'enabled'].includes(value)) {
    return true;
  }
  if (['0', 'false', 'no', 'off', 'disabled'].includes(value)) {
    return false;
  }
  return undefined;
};
