import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
import type {
  IMadvrButtonRecord,
  IMadvrCommandRequest,
  IMadvrRatioState,
  IMadvrSelectRecord,
  IMadvrSignalState,
  IMadvrSnapshot,
  TMadvrPowerState,
} from './madvr.types.js';

const madvrDomain = 'madvr';
const madvrManufacturer = 'madVR';

interface ISensorDescription {
  key: string;
  name: string;
  value: (snapshotArg: IMadvrSnapshot) => unknown;
  deviceClass?: string;
  stateClass?: string;
  unit?: string;
  options?: string[];
  enabledDefault?: boolean;
}

interface IBinarySensorDescription {
  key: string;
  name: string;
  value: (snapshotArg: IMadvrSnapshot) => boolean;
  icon?: string;
}

const enumValue = (valueArg: unknown, optionsArg: string[]): string | null => {
  return typeof valueArg === 'string' && optionsArg.includes(valueArg) ? valueArg : null;
};

const signalOptions = ['2D', '3D'];
const colorSpaceOptions = ['RGB', '444', '422', '420'];
const bitDepthOptions = ['8bit', '10bit', '12bit'];
const colorimetryOptions = ['SDR', 'HDR10', 'HLG 601', 'PAL', '709', 'DCI', '2020'];
const blackLevelOptions = ['TV', 'PC'];
const aspectOptions = ['16:9', '4:3'];

const sensors: ISensorDescription[] = [
  { key: 'temp_gpu', name: 'GPU temperature', value: (snapshotArg) => validTemperature(snapshotArg.processor.temperatures?.gpu), deviceClass: 'temperature', stateClass: 'measurement', unit: 'C', enabledDefault: false },
  { key: 'temp_hdmi', name: 'HDMI temperature', value: (snapshotArg) => validTemperature(snapshotArg.processor.temperatures?.hdmi), deviceClass: 'temperature', stateClass: 'measurement', unit: 'C', enabledDefault: false },
  { key: 'temp_cpu', name: 'CPU temperature', value: (snapshotArg) => validTemperature(snapshotArg.processor.temperatures?.cpu), deviceClass: 'temperature', stateClass: 'measurement', unit: 'C', enabledDefault: false },
  { key: 'temp_mainboard', name: 'Mainboard temperature', value: (snapshotArg) => validTemperature(snapshotArg.processor.temperatures?.mainboard), deviceClass: 'temperature', stateClass: 'measurement', unit: 'C', enabledDefault: false },
  { key: 'incoming_res', name: 'Incoming resolution', value: (snapshotArg) => snapshotArg.processor.incoming?.resolution ?? null },
  { key: 'incoming_signal_type', name: 'Incoming signal type', value: (snapshotArg) => enumValue(snapshotArg.processor.incoming?.signalType, signalOptions), deviceClass: 'enum', options: signalOptions, enabledDefault: false },
  { key: 'incoming_frame_rate', name: 'Incoming frame rate', value: (snapshotArg) => snapshotArg.processor.incoming?.frameRate ?? null },
  { key: 'incoming_color_space', name: 'Incoming color space', value: (snapshotArg) => enumValue(snapshotArg.processor.incoming?.colorSpace, colorSpaceOptions), deviceClass: 'enum', options: colorSpaceOptions },
  { key: 'incoming_bit_depth', name: 'Incoming bit depth', value: (snapshotArg) => enumValue(snapshotArg.processor.incoming?.bitDepth, bitDepthOptions), deviceClass: 'enum', options: bitDepthOptions },
  { key: 'incoming_colorimetry', name: 'Incoming colorimetry', value: (snapshotArg) => enumValue(snapshotArg.processor.incoming?.colorimetry, colorimetryOptions), deviceClass: 'enum', options: colorimetryOptions },
  { key: 'incoming_black_levels', name: 'Incoming black levels', value: (snapshotArg) => enumValue(snapshotArg.processor.incoming?.blackLevels, blackLevelOptions), deviceClass: 'enum', options: blackLevelOptions },
  { key: 'incoming_aspect_ratio', name: 'Incoming aspect ratio', value: (snapshotArg) => enumValue(snapshotArg.processor.incoming?.aspectRatio, aspectOptions), deviceClass: 'enum', options: aspectOptions, enabledDefault: false },
  { key: 'outgoing_res', name: 'Outgoing resolution', value: (snapshotArg) => snapshotArg.processor.outgoing?.resolution ?? null },
  { key: 'outgoing_signal_type', name: 'Outgoing signal type', value: (snapshotArg) => enumValue(snapshotArg.processor.outgoing?.signalType, signalOptions), deviceClass: 'enum', options: signalOptions, enabledDefault: false },
  { key: 'outgoing_frame_rate', name: 'Outgoing frame rate', value: (snapshotArg) => snapshotArg.processor.outgoing?.frameRate ?? null },
  { key: 'outgoing_color_space', name: 'Outgoing color space', value: (snapshotArg) => enumValue(snapshotArg.processor.outgoing?.colorSpace, colorSpaceOptions), deviceClass: 'enum', options: colorSpaceOptions },
  { key: 'outgoing_bit_depth', name: 'Outgoing bit depth', value: (snapshotArg) => enumValue(snapshotArg.processor.outgoing?.bitDepth, bitDepthOptions), deviceClass: 'enum', options: bitDepthOptions },
  { key: 'outgoing_colorimetry', name: 'Outgoing colorimetry', value: (snapshotArg) => enumValue(snapshotArg.processor.outgoing?.colorimetry, colorimetryOptions), deviceClass: 'enum', options: colorimetryOptions },
  { key: 'outgoing_black_levels', name: 'Outgoing black levels', value: (snapshotArg) => enumValue(snapshotArg.processor.outgoing?.blackLevels, blackLevelOptions), deviceClass: 'enum', options: blackLevelOptions },
  { key: 'aspect_res', name: 'Aspect resolution', value: (snapshotArg) => snapshotArg.display.aspect?.resolution ?? null, enabledDefault: false },
  { key: 'aspect_dec', name: 'Aspect decimal', value: (snapshotArg) => snapshotArg.display.aspect?.decimal ?? null },
  { key: 'aspect_int', name: 'Aspect integer', value: (snapshotArg) => snapshotArg.display.aspect?.integer ?? null, enabledDefault: false },
  { key: 'aspect_name', name: 'Aspect name', value: (snapshotArg) => snapshotArg.display.aspect?.name ?? null, enabledDefault: false },
  { key: 'masking_res', name: 'Masking resolution', value: (snapshotArg) => snapshotArg.display.masking?.resolution ?? null, enabledDefault: false },
  { key: 'masking_dec', name: 'Masking decimal', value: (snapshotArg) => snapshotArg.display.masking?.decimal ?? null },
  { key: 'masking_int', name: 'Masking integer', value: (snapshotArg) => snapshotArg.display.masking?.integer ?? null, enabledDefault: false },
];

const binarySensors: IBinarySensorDescription[] = [
  { key: 'power_state', name: 'Power state', value: (snapshotArg) => snapshotArg.processor.isOn === true, icon: 'mdi:power' },
  { key: 'signal_state', name: 'Signal state', value: (snapshotArg) => snapshotArg.processor.isSignal === true, icon: 'mdi:signal' },
  { key: 'hdr_flag', name: 'HDR flag', value: (snapshotArg) => snapshotArg.display.hdrFlag === true, icon: 'mdi:hdr' },
  { key: 'outgoing_hdr_flag', name: 'Outgoing HDR flag', value: (snapshotArg) => snapshotArg.display.outgoingHdrFlag === true, icon: 'mdi:hdr' },
];

export class MadvrMapper {
  public static toDevices(snapshotArg: IMadvrSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'power', capability: 'media', name: 'Power', readable: true, writable: true },
      { id: 'signal', capability: 'sensor', name: 'Signal', readable: true, writable: false },
      { id: 'incoming_res', capability: 'media', name: 'Incoming resolution', readable: true, writable: false },
      { id: 'outgoing_res', capability: 'media', name: 'Outgoing resolution', readable: true, writable: false },
      { id: 'aspect_ratio', capability: 'sensor', name: 'Aspect ratio', readable: true, writable: false },
      { id: 'masking_ratio', capability: 'sensor', name: 'Masking ratio', readable: true, writable: false },
      { id: 'remote_command', capability: 'media', name: 'Remote command', readable: false, writable: true },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'power', value: this.powerState(snapshotArg), updatedAt },
      { featureId: 'signal', value: snapshotArg.processor.isSignal ?? null, updatedAt },
      { featureId: 'incoming_res', value: snapshotArg.processor.incoming?.resolution ?? null, updatedAt },
      { featureId: 'outgoing_res', value: snapshotArg.processor.outgoing?.resolution ?? null, updatedAt },
      { featureId: 'aspect_ratio', value: this.ratioLabel(snapshotArg.display.aspect), updatedAt },
      { featureId: 'masking_ratio', value: this.ratioLabel(snapshotArg.display.masking), updatedAt },
    ];

    for (const sensor of sensors) {
      if (!features.some((featureArg) => featureArg.id === sensor.key)) {
        features.push({ id: sensor.key, capability: 'sensor', name: sensor.name, readable: true, writable: false, unit: sensor.unit });
        state.push({ featureId: sensor.key, value: this.deviceStateValue(sensor.value(snapshotArg)), updatedAt });
      }
    }
    for (const button of snapshotArg.buttons || []) {
      features.push({ id: `button_${button.key}`, capability: 'media', name: button.name, readable: false, writable: true });
    }
    for (const select of snapshotArg.selects || []) {
      features.push({ id: `select_${select.key}`, capability: 'media', name: select.name, readable: true, writable: true });
      state.push({ featureId: `select_${select.key}`, value: select.current ?? null, updatedAt });
    }

    return [{
      id: this.deviceId(snapshotArg),
      integrationDomain: madvrDomain,
      name: this.deviceName(snapshotArg),
      protocol: 'unknown',
      manufacturer: snapshotArg.deviceInfo.manufacturer || madvrManufacturer,
      model: snapshotArg.deviceInfo.model || 'Envy',
      online: this.available(snapshotArg) && this.powerState(snapshotArg) !== 'off',
      features,
      state,
      metadata: this.cleanAttributes({
        host: snapshotArg.deviceInfo.host,
        port: snapshotArg.deviceInfo.port,
        macAddress: snapshotArg.deviceInfo.macAddress,
        serialNumber: snapshotArg.deviceInfo.serialNumber,
        firmwareVersion: snapshotArg.deviceInfo.firmwareVersion,
        processor: snapshotArg.processor,
        projector: snapshotArg.projector,
        display: snapshotArg.display,
      }),
    }];
  }

  public static toEntities(snapshotArg: IMadvrSnapshot): IIntegrationEntity[] {
    const entities: IIntegrationEntity[] = [];
    const usedIds = new Map<string, number>();
    const deviceId = this.deviceId(snapshotArg);
    const name = this.deviceName(snapshotArg);
    const available = this.available(snapshotArg);

    entities.push(this.entity('media_player', name, deviceId, this.uniqueId(snapshotArg, 'remote'), this.mediaState(snapshotArg), usedIds, {
      deviceClass: 'receiver',
      power: this.powerState(snapshotArg),
      isOn: snapshotArg.processor.isOn,
      isSignal: snapshotArg.processor.isSignal,
      incoming: snapshotArg.processor.incoming,
      outgoing: snapshotArg.processor.outgoing,
      temperatures: snapshotArg.processor.temperatures,
      aspect: snapshotArg.display.aspect,
      masking: snapshotArg.display.masking,
      hdrFlag: snapshotArg.display.hdrFlag,
      outgoingHdrFlag: snapshotArg.display.outgoingHdrFlag,
      profileName: snapshotArg.processor.profileName || snapshotArg.projector?.profileName,
      profileNumber: snapshotArg.processor.profileNumber || snapshotArg.projector?.profileNumber,
      macAddress: snapshotArg.deviceInfo.macAddress,
      source: snapshotArg.processor.incoming?.resolution,
      mediaTitle: this.mediaTitle(snapshotArg),
      assumedState: true,
      lastError: snapshotArg.processor.lastError,
    }, available));

    for (const sensor of sensors) {
      entities.push(this.entity('sensor', `${name} ${sensor.name}`, deviceId, this.uniqueId(snapshotArg, sensor.key), sensor.value(snapshotArg) ?? null, usedIds, {
        deviceClass: sensor.deviceClass,
        stateClass: sensor.stateClass,
        unit: sensor.unit,
        options: sensor.options,
        enabledDefault: sensor.enabledDefault,
      }, available));
    }

    for (const binarySensor of binarySensors) {
      entities.push(this.entity('binary_sensor', `${name} ${binarySensor.name}`, deviceId, this.uniqueId(snapshotArg, binarySensor.key), binarySensor.value(snapshotArg), usedIds, {
        icon: binarySensor.icon,
      }, available));
    }

    for (const button of snapshotArg.buttons || []) {
      entities.push(this.buttonEntity(snapshotArg, button, deviceId, name, usedIds, available));
    }

    for (const select of snapshotArg.selects || []) {
      entities.push(this.selectEntity(snapshotArg, select, deviceId, name, usedIds, available));
    }

    return entities;
  }

  public static findTargetEntity(snapshotArg: IMadvrSnapshot, requestArg: IServiceCallRequest): IIntegrationEntity | undefined {
    const entities = this.toEntities(snapshotArg);
    if (requestArg.target.entityId) {
      return entities.find((entityArg) => entityArg.id === requestArg.target.entityId || entityArg.uniqueId === requestArg.target.entityId);
    }
    if (requestArg.target.deviceId) {
      return entities.find((entityArg) => entityArg.deviceId === requestArg.target.deviceId && entityArg.platform === requestArg.domain)
        || entities.find((entityArg) => entityArg.deviceId === requestArg.target.deviceId && entityArg.attributes?.writable === true);
    }
    return entities.find((entityArg) => entityArg.platform === requestArg.domain);
  }

  public static commandFromWritableEntity(entityArg: IIntegrationEntity, requestArg: IServiceCallRequest): IMadvrCommandRequest | undefined {
    if (entityArg.platform === 'button' && requestArg.service === 'press' && isCommandRequest(entityArg.attributes?.madvrCommand)) {
      return entityArg.attributes.madvrCommand;
    }
    if (entityArg.platform === 'select' && requestArg.service === 'select_option') {
      const option = typeof requestArg.data?.option === 'string' ? requestArg.data.option : undefined;
      if (!option) {
        return undefined;
      }
      const commands = entityArg.attributes?.madvrSelectCommands;
      if (isCommandMap(commands) && isCommandRequest(commands[option])) {
        return commands[option];
      }
      if (isCommandRequest(entityArg.attributes?.madvrCommand)) {
        return { ...entityArg.attributes.madvrCommand, args: [...(entityArg.attributes.madvrCommand.args || []), option] };
      }
    }
    return undefined;
  }

  public static deviceId(snapshotArg: IMadvrSnapshot): string {
    return `madvr.device.${this.slug(this.identity(snapshotArg))}`;
  }

  public static uniqueId(snapshotArg: IMadvrSnapshot, keyArg: string): string {
    return `madvr_${this.slug(this.identity(snapshotArg))}_${this.slug(keyArg)}`;
  }

  private static buttonEntity(snapshotArg: IMadvrSnapshot, buttonArg: IMadvrButtonRecord, deviceIdArg: string, nameArg: string, usedIdsArg: Map<string, number>, availableArg: boolean): IIntegrationEntity {
    return this.entity('button', `${nameArg} ${buttonArg.name}`, deviceIdArg, this.uniqueId(snapshotArg, `button_${buttonArg.key}`), 'idle', usedIdsArg, {
      writable: true,
      entityCategory: buttonArg.entityCategory,
      icon: buttonArg.icon,
      madvrCommand: normalizeEntityCommand(buttonArg.command),
    }, availableArg && buttonArg.available !== false);
  }

  private static selectEntity(snapshotArg: IMadvrSnapshot, selectArg: IMadvrSelectRecord, deviceIdArg: string, nameArg: string, usedIdsArg: Map<string, number>, availableArg: boolean): IIntegrationEntity {
    return this.entity('select', `${nameArg} ${selectArg.name}`, deviceIdArg, this.uniqueId(snapshotArg, `select_${selectArg.key}`), selectArg.current ?? 'unknown', usedIdsArg, {
      writable: true,
      entityCategory: selectArg.entityCategory,
      options: selectArg.options,
      madvrCommand: selectArg.command ? normalizeEntityCommand(selectArg.command) : undefined,
      madvrSelectCommands: selectArg.commands ? Object.fromEntries(Object.entries(selectArg.commands).map(([keyArg, commandArg]) => [keyArg, normalizeEntityCommand(commandArg)])) : undefined,
    }, availableArg && selectArg.available !== false);
  }

  private static entity(platformArg: TEntityPlatform, nameArg: string, deviceIdArg: string, uniqueIdArg: string, stateArg: unknown, usedIdsArg: Map<string, number>, attributesArg: Record<string, unknown>, availableArg: boolean): IIntegrationEntity {
    const baseId = `${platformArg}.${this.slug(nameArg) || this.slug(uniqueIdArg)}`;
    const seen = usedIdsArg.get(baseId) || 0;
    usedIdsArg.set(baseId, seen + 1);
    return {
      id: seen ? `${baseId}_${seen + 1}` : baseId,
      uniqueId: uniqueIdArg,
      integrationDomain: madvrDomain,
      deviceId: deviceIdArg,
      platform: platformArg,
      name: nameArg,
      state: stateArg,
      attributes: this.cleanAttributes(attributesArg),
      available: availableArg,
    };
  }

  private static mediaState(snapshotArg: IMadvrSnapshot): string {
    if (!this.available(snapshotArg)) {
      return 'unavailable';
    }
    const power = this.powerState(snapshotArg);
    if (power === 'off') {
      return 'off';
    }
    if (power === 'unknown') {
      return 'unknown';
    }
    return snapshotArg.processor.isSignal === false ? 'idle' : 'on';
  }

  private static powerState(snapshotArg: IMadvrSnapshot): TMadvrPowerState {
    if (snapshotArg.processor.power) {
      return snapshotArg.processor.power;
    }
    if (snapshotArg.processor.isOn === true) {
      return 'on';
    }
    if (snapshotArg.processor.isOn === false) {
      return 'off';
    }
    return 'unknown';
  }

  private static available(snapshotArg: IMadvrSnapshot): boolean {
    return snapshotArg.available !== false;
  }

  private static mediaTitle(snapshotArg: IMadvrSnapshot): string | undefined {
    const incoming = this.signalLabel(snapshotArg.processor.incoming);
    const outgoing = this.signalLabel(snapshotArg.processor.outgoing);
    if (incoming && outgoing) {
      return `${incoming} to ${outgoing}`;
    }
    return incoming || outgoing;
  }

  private static signalLabel(signalArg: IMadvrSignalState | undefined): string | undefined {
    if (!signalArg?.resolution) {
      return undefined;
    }
    return [signalArg.resolution, signalArg.frameRate, signalArg.colorimetry].filter(Boolean).join(' ');
  }

  private static ratioLabel(ratioArg: IMadvrRatioState | undefined): string | null {
    if (!ratioArg) {
      return null;
    }
    return ratioArg.name || ratioArg.resolution || (typeof ratioArg.decimal === 'number' ? String(ratioArg.decimal) : null);
  }

  private static identity(snapshotArg: IMadvrSnapshot): string {
    return snapshotArg.deviceInfo.macAddress
      || snapshotArg.deviceInfo.serialNumber
      || snapshotArg.deviceInfo.host
      || snapshotArg.deviceInfo.name
      || 'madvr-envy';
  }

  private static deviceName(snapshotArg: IMadvrSnapshot): string {
    return snapshotArg.deviceInfo.name || snapshotArg.deviceInfo.model || 'madVR Envy';
  }

  private static deviceStateValue(valueArg: unknown): plugins.shxInterfaces.data.TDeviceStateValue {
    if (typeof valueArg === 'string' || typeof valueArg === 'number' || typeof valueArg === 'boolean' || valueArg === null || this.isRecord(valueArg)) {
      return valueArg;
    }
    return valueArg === undefined ? null : String(valueArg);
  }

  private static cleanAttributes(attributesArg: Record<string, unknown>): Record<string, unknown> {
    return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  }

  private static isRecord(valueArg: unknown): valueArg is Record<string, unknown> {
    return typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
  }

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

const validTemperature = (valueArg: number | undefined): number | null => {
  return typeof valueArg === 'number' && Number.isFinite(valueArg) && valueArg > 0 ? valueArg : null;
};

const normalizeEntityCommand = (valueArg: unknown): IMadvrCommandRequest | undefined => {
  if (typeof valueArg === 'string') {
    const [command, ...args] = valueArg.split(',').map((partArg) => partArg.trim()).filter(Boolean);
    return command ? { command, args } : undefined;
  }
  if (Array.isArray(valueArg)) {
    const [command, ...args] = valueArg.filter((itemArg): itemArg is string => typeof itemArg === 'string' && Boolean(itemArg));
    return command ? { command, args } : undefined;
  }
  if (isCommandRequest(valueArg)) {
    return valueArg;
  }
  return undefined;
};

const isCommandRequest = (valueArg: unknown): valueArg is IMadvrCommandRequest => {
  return typeof valueArg === 'object' && valueArg !== null && typeof (valueArg as IMadvrCommandRequest).command === 'string';
};

const isCommandMap = (valueArg: unknown): valueArg is Record<string, IMadvrCommandRequest> => {
  return typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
};
