import * as plugins from '../../plugins.js';
import type { IIntegrationEntity, IServiceCallRequest } from '../../core/types.js';
import type {
  IPrusaLinkCommandRequest,
  IPrusaLinkConfig,
  IPrusaLinkJobInfo,
  IPrusaLinkJobSnapshot,
  IPrusaLinkPrinterInfo,
  IPrusaLinkPrinterSnapshotInfo,
  IPrusaLinkPrinterStatus,
  IPrusaLinkRawData,
  IPrusaLinkSnapshot,
  IPrusaLinkTelemetrySnapshot,
  IPrusaLinkVersionInfo,
  TPrusaLinkCommandAction,
  TPrusaLinkPrinterState,
  TPrusaLinkSnapshotSource,
} from './prusalink.types.js';
import { prusaLinkDefaultPort, prusaLinkDomain } from './prusalink.types.js';

interface IPrusaLinkSnapshotOptions {
  config: IPrusaLinkConfig;
  rawData?: Partial<IPrusaLinkRawData>;
  online?: boolean;
  source?: TPrusaLinkSnapshotSource;
  error?: string;
}

interface IPrusaLinkSensorDefinition {
  key: string;
  name: string;
  value: unknown;
  platform?: IIntegrationEntity['platform'];
  unit?: string;
  deviceClass?: string;
  stateClass?: string;
  entityCategory?: string;
  available?: boolean;
  attributes?: Record<string, unknown>;
}

export class PrusaLinkMapper {
  public static toSnapshot(optionsArg: IPrusaLinkSnapshotOptions): IPrusaLinkSnapshot {
    if (optionsArg.config.snapshot && !optionsArg.rawData) {
      return this.normalizeSnapshot(this.clone(optionsArg.config.snapshot), optionsArg.config, optionsArg.source || 'snapshot');
    }

    const rawData = this.rawData(optionsArg.config, optionsArg.rawData);
    const online = optionsArg.online ?? optionsArg.config.online ?? this.hasRawData(rawData);
    const updatedAt = new Date().toISOString();
    const snapshot: IPrusaLinkSnapshot = {
      printer: this.printerInfo(optionsArg.config, rawData),
      telemetry: this.telemetry(rawData.status, rawData.legacyStatus),
      job: this.job(rawData.job, updatedAt),
      rawData: this.hasRawData(rawData) ? this.clone(rawData) : undefined,
      online,
      updatedAt,
      source: optionsArg.source || (this.hasRawData(rawData) ? 'manual' : 'runtime'),
      error: optionsArg.error,
    };
    return this.normalizeSnapshot(snapshot, optionsArg.config, snapshot.source || 'runtime');
  }

  public static toDevices(snapshotArg: IPrusaLinkSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
    const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
    const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
      { id: 'connection', capability: 'sensor', name: 'Connection', readable: true, writable: false },
      { id: 'printer_state', capability: 'sensor', name: 'Printer state', readable: true, writable: false },
      { id: 'progress', capability: 'sensor', name: 'Progress', readable: true, writable: false, unit: '%' },
      { id: 'filename', capability: 'sensor', name: 'Filename', readable: true, writable: false },
      { id: 'nozzle_temperature', capability: 'sensor', name: 'Nozzle temperature', readable: true, writable: false, unit: '°C' },
      { id: 'heatbed_temperature', capability: 'sensor', name: 'Heatbed temperature', readable: true, writable: false, unit: '°C' },
      { id: 'print_speed', capability: 'sensor', name: 'Print speed', readable: true, writable: false, unit: '%' },
      { id: 'material', capability: 'sensor', name: 'Material', readable: true, writable: false },
      { id: 'mmu', capability: 'sensor', name: 'MMU', readable: true, writable: false },
      { id: 'pause_job', capability: 'switch', name: 'Pause job', readable: false, writable: true },
      { id: 'resume_job', capability: 'switch', name: 'Resume job', readable: false, writable: true },
      { id: 'cancel_job', capability: 'switch', name: 'Cancel job', readable: false, writable: true },
    ];
    const state: plugins.shxInterfaces.data.IDeviceState[] = [
      { featureId: 'connection', value: snapshotArg.online ? 'online' : 'offline', updatedAt },
      { featureId: 'printer_state', value: snapshotArg.telemetry.state, updatedAt },
      { featureId: 'progress', value: snapshotArg.job.progress ?? null, updatedAt },
      { featureId: 'filename', value: snapshotArg.job.filename ?? null, updatedAt },
      { featureId: 'nozzle_temperature', value: snapshotArg.telemetry.tempNozzle ?? null, updatedAt },
      { featureId: 'heatbed_temperature', value: snapshotArg.telemetry.tempBed ?? null, updatedAt },
      { featureId: 'print_speed', value: snapshotArg.telemetry.printSpeed ?? null, updatedAt },
      { featureId: 'material', value: snapshotArg.telemetry.material ?? null, updatedAt },
      { featureId: 'mmu', value: snapshotArg.printer.mmu ?? null, updatedAt },
    ];

    return [{
      id: this.printerDeviceId(snapshotArg),
      integrationDomain: prusaLinkDomain,
      name: this.printerName(snapshotArg),
      protocol: snapshotArg.printer.host || snapshotArg.printer.baseUrl ? 'http' : 'unknown',
      manufacturer: snapshotArg.printer.manufacturer || 'Prusa',
      model: snapshotArg.printer.model || 'PrusaLink printer',
      online: snapshotArg.online,
      features,
      state,
      metadata: this.cleanAttributes({
        serialNumber: snapshotArg.printer.serialNumber,
        firmware: snapshotArg.printer.firmware,
        apiVersion: snapshotArg.printer.apiVersion,
        linkVersion: snapshotArg.printer.linkVersion,
        hostname: snapshotArg.printer.hostname,
        location: snapshotArg.printer.location,
        host: snapshotArg.printer.host,
        port: snapshotArg.printer.port,
        baseUrl: snapshotArg.printer.baseUrl,
        source: snapshotArg.source,
        thumbnailPath: snapshotArg.job.thumbnailPath,
        activeCamera: snapshotArg.printer.activeCamera,
        error: snapshotArg.error,
      }),
    }];
  }

  public static toEntities(snapshotArg: IPrusaLinkSnapshot): IIntegrationEntity[] {
    const deviceId = this.printerDeviceId(snapshotArg);
    const uniqueBase = this.uniqueBase(snapshotArg);
    const baseName = this.printerName(snapshotArg);
    const entities: IIntegrationEntity[] = [];
    const telemetry = snapshotArg.telemetry;
    const job = snapshotArg.job;

    entities.push(this.entity(snapshotArg, {
      key: 'printer_state',
      name: `${baseName} Printer State`,
      value: telemetry.state,
      deviceClass: 'enum',
      attributes: {
        options: ['idle', 'busy', 'printing', 'paused', 'finished', 'stopped', 'error', 'attention', 'ready'],
        statusPrinter: telemetry.statusPrinter,
        statusConnect: telemetry.statusConnect,
        source: snapshotArg.source,
        error: snapshotArg.error,
      },
    }, deviceId, uniqueBase));

    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'heatbed_temperature', name: `${baseName} Heatbed Temperature`, value: telemetry.tempBed, unit: '°C', deviceClass: 'temperature', stateClass: 'measurement' });
    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'nozzle_temperature', name: `${baseName} Nozzle Temperature`, value: telemetry.tempNozzle, unit: '°C', deviceClass: 'temperature', stateClass: 'measurement' });
    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'heatbed_target_temperature', name: `${baseName} Heatbed Target Temperature`, value: telemetry.targetBed, unit: '°C', deviceClass: 'temperature', stateClass: 'measurement' });
    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'nozzle_target_temperature', name: `${baseName} Nozzle Target Temperature`, value: telemetry.targetNozzle, unit: '°C', deviceClass: 'temperature', stateClass: 'measurement' });
    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'z_height', name: `${baseName} Z-Height`, value: telemetry.axisZ, unit: 'mm', deviceClass: 'distance', stateClass: 'measurement' });
    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'print_speed', name: `${baseName} Print Speed`, value: telemetry.printSpeed, unit: '%' });
    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'print_flow', name: `${baseName} Print Flow`, value: telemetry.printFlow, unit: '%' });
    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'fan_hotend', name: `${baseName} Hotend Fan`, value: telemetry.fanHotend, unit: 'rpm' });
    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'fan_print', name: `${baseName} Print Fan`, value: telemetry.fanPrint, unit: 'rpm' });
    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'material', name: `${baseName} Material`, value: telemetry.material });

    if (job.active) {
      this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'progress', name: `${baseName} Progress`, value: job.progress, unit: '%' });
      this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'filename', name: `${baseName} Filename`, value: job.filename, attributes: { path: job.filePath, displayPath: job.displayPath, thumbnailPath: job.thumbnailPath } });
      this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'print_start', name: `${baseName} Print Start`, value: job.startedAt, deviceClass: 'timestamp' });
      this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'print_finish', name: `${baseName} Print Finish`, value: job.finishesAt, deviceClass: 'timestamp' });
    }

    this.pushEntity(entities, snapshotArg, deviceId, uniqueBase, { key: 'nozzle_diameter', name: `${baseName} Nozzle Diameter`, value: snapshotArg.printer.nozzleDiameter, unit: 'mm', deviceClass: 'distance' });
    if (snapshotArg.printer.mmu !== undefined) {
      entities.push(this.entity(snapshotArg, { key: 'mmu', name: `${baseName} MMU`, platform: 'binary_sensor', value: snapshotArg.printer.mmu ? 'on' : 'off' }, deviceId, uniqueBase));
    }

    entities.push(this.button(snapshotArg, 'cancel', 'Cancel Job', this.buttonAvailable(snapshotArg, 'cancel'), deviceId, uniqueBase));
    entities.push(this.button(snapshotArg, 'pause', 'Pause Job', this.buttonAvailable(snapshotArg, 'pause'), deviceId, uniqueBase));
    entities.push(this.button(snapshotArg, 'resume', 'Resume Job', this.buttonAvailable(snapshotArg, 'resume'), deviceId, uniqueBase));

    return entities;
  }

  public static commandForService(snapshotArg: IPrusaLinkSnapshot, requestArg: IServiceCallRequest): IPrusaLinkCommandRequest | undefined {
    const action = this.actionForService(snapshotArg, requestArg);
    if (!action) {
      return undefined;
    }

    const jobId = this.numberValue(requestArg.data?.job_id ?? requestArg.data?.jobId) ?? snapshotArg.job.id;
    if (action !== 'restart' && jobId === undefined) {
      return undefined;
    }

    const request: IPrusaLinkCommandRequest = {
      action,
      jobId,
      service: requestArg.service,
      target: requestArg.target,
      data: requestArg.data,
      host: snapshotArg.printer.host || snapshotArg.printer.baseUrl,
    };
    if (action === 'cancel') {
      return { ...request, method: 'DELETE', path: `/api/v1/job/${jobId}` };
    }
    if (action === 'pause') {
      return { ...request, method: 'PUT', path: `/api/v1/job/${jobId}/pause` };
    }
    if (action === 'resume') {
      return { ...request, method: 'PUT', path: `/api/v1/job/${jobId}/resume` };
    }
    return request;
  }

  public static printerDeviceId(snapshotArg: IPrusaLinkSnapshot): string {
    return `${prusaLinkDomain}.printer.${this.uniqueBase(snapshotArg)}`;
  }

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

  public static snapshotId(snapshotArg: IPrusaLinkSnapshot): string | undefined {
    return snapshotArg.printer.id || snapshotArg.printer.serialNumber || snapshotArg.printer.hostname || snapshotArg.printer.host;
  }

  private static normalizeSnapshot(snapshotArg: IPrusaLinkSnapshot, configArg: IPrusaLinkConfig, sourceArg: TPrusaLinkSnapshotSource): IPrusaLinkSnapshot {
    const rawData = this.rawData(configArg, snapshotArg.rawData);
    const derivedPrinter = this.hasRawData(rawData) ? this.printerInfo(configArg, rawData) : undefined;
    const derivedTelemetry = this.hasRawData(rawData) ? this.telemetry(rawData.status, rawData.legacyStatus) : undefined;
    const derivedJob = this.hasRawData(rawData) ? this.job(rawData.job, snapshotArg.updatedAt || new Date().toISOString()) : undefined;
    const printer = {
      ...derivedPrinter,
      ...snapshotArg.printer,
    };
    const parsed = this.parseEndpoint(configArg.host);
    printer.host = printer.host || parsed?.host || configArg.host;
    printer.port = printer.port || parsed?.port || (printer.host ? configArg.port || prusaLinkDefaultPort : configArg.port);
    printer.ssl = printer.ssl ?? parsed?.ssl ?? configArg.ssl ?? configArg.tls;
    printer.baseUrl = printer.baseUrl || this.baseUrl(configArg, printer.host, printer.port, printer.ssl);
    printer.manufacturer = printer.manufacturer || configArg.manufacturer || 'Prusa';
    printer.name = printer.name || configArg.name || printer.hostname || printer.host || 'PrusaLink';
    printer.model = printer.model || configArg.model || 'PrusaLink printer';
    printer.id = printer.id || configArg.uniqueId || printer.serialNumber || printer.hostname || (printer.host ? `${printer.host}:${printer.port || prusaLinkDefaultPort}` : undefined) || printer.name;

    return {
      ...snapshotArg,
      printer,
      telemetry: { ...derivedTelemetry, ...snapshotArg.telemetry, state: snapshotArg.telemetry.state || derivedTelemetry?.state || 'unknown' },
      job: { ...derivedJob, ...snapshotArg.job, active: snapshotArg.job.active ?? derivedJob?.active ?? false },
      rawData: this.hasRawData(rawData) ? this.clone(rawData) : snapshotArg.rawData,
      online: snapshotArg.online,
      updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
      source: snapshotArg.source || sourceArg,
    };
  }

  private static rawData(configArg: IPrusaLinkConfig, rawDataArg?: Partial<IPrusaLinkRawData>): Partial<IPrusaLinkRawData> {
    return this.cleanAttributes({
      ...configArg.rawData,
      ...rawDataArg,
      version: rawDataArg?.version || configArg.version || configArg.rawData?.version,
      status: rawDataArg?.status || configArg.status || configArg.rawData?.status,
      legacyStatus: rawDataArg?.legacyStatus || configArg.legacyStatus || configArg.rawData?.legacyStatus,
      job: rawDataArg?.job || configArg.job || configArg.rawData?.job,
      info: rawDataArg?.info || configArg.info || configArg.rawData?.info,
    });
  }

  private static printerInfo(configArg: IPrusaLinkConfig, rawDataArg: Partial<IPrusaLinkRawData>): IPrusaLinkPrinterSnapshotInfo {
    const version = rawDataArg.version || {};
    const info = rawDataArg.info || {};
    const parsed = this.parseEndpoint(configArg.host);
    const host = parsed?.host || configArg.host;
    const port = configArg.port || parsed?.port || this.numberValue(info.port) || (host ? prusaLinkDefaultPort : undefined);
    const ssl = configArg.ssl ?? configArg.tls ?? parsed?.ssl;
    const hostname = this.stringValue(info.hostname) || this.stringValue(version.hostname);
    const serialNumber = this.stringValue(info.serial);
    const name = configArg.name || this.stringValue(info.name) || hostname || this.stringValue(version.text) || host || 'PrusaLink';
    return this.cleanAttributes({
      id: configArg.uniqueId || serialNumber || hostname || (host ? `${host}:${port || prusaLinkDefaultPort}` : undefined) || name,
      name,
      manufacturer: configArg.manufacturer || 'Prusa',
      model: configArg.model || this.stringValue(version.printer) || 'PrusaLink printer',
      serialNumber,
      firmware: this.stringValue(version.firmware),
      apiVersion: this.stringValue(version.api),
      linkVersion: this.stringValue(version.version),
      sdk: typeof version.sdk === 'string' ? version.sdk : version.sdk === null ? null : undefined,
      hostname,
      location: this.stringValue(info.location),
      host,
      port,
      baseUrl: this.baseUrl(configArg, host, port, ssl),
      ssl,
      nozzleDiameter: this.numberValue(info.nozzle_diameter),
      minExtrusionTemp: this.numberValue(info.min_extrusion_temp),
      mmu: this.booleanValue(info.mmu),
      sdReady: this.booleanValue(info.sd_ready),
      activeCamera: this.booleanValue(info.active_camera),
      farmMode: this.booleanValue(info.farm_mode),
    });
  }

  private static telemetry(statusArg: IPrusaLinkPrinterStatus | undefined, legacyStatusArg: IPrusaLinkRawData['legacyStatus']): IPrusaLinkTelemetrySnapshot {
    const printer = statusArg?.printer || {};
    return this.cleanAttributes({
      state: this.stateValue(printer.state),
      tempNozzle: this.numberValue(printer.temp_nozzle ?? printer['temp-nozzle']),
      targetNozzle: this.numberValue(printer.target_nozzle ?? printer['target-nozzle']),
      tempBed: this.numberValue(printer.temp_bed ?? printer['temp-bed']),
      targetBed: this.numberValue(printer.target_bed ?? printer['target-bed']),
      axisX: this.numberValue(printer.axis_x ?? printer['axis-x']),
      axisY: this.numberValue(printer.axis_y ?? printer['axis-y']),
      axisZ: this.numberValue(printer.axis_z ?? printer['axis-z']),
      printFlow: this.numberValue(printer.flow),
      printSpeed: this.numberValue(printer.speed),
      fanHotend: this.numberValue(printer.fan_hotend ?? printer['fan-hotend']),
      fanPrint: this.numberValue(printer.fan_print ?? printer['fan-print']),
      material: this.stringValue(legacyStatusArg?.telemetry?.material),
      statusPrinter: this.recordValue(printer.status_printer),
      statusConnect: this.recordValue(printer.status_connect),
    });
  }

  private static job(jobArg: IPrusaLinkJobInfo | undefined, updatedAtArg: string): IPrusaLinkJobSnapshot {
    const job = jobArg || {};
    const state = this.stateValue(job.state);
    const progress = this.numberValue(job.progress);
    const file = this.recordValue(job.file) as IPrusaLinkJobInfo['file'];
    const refs = file?.refs || undefined;
    const timePrinting = this.numberValue(job.time_printing);
    const timeRemaining = this.numberValue(job.time_remaining);
    const now = Date.parse(updatedAtArg);
    const active = state !== 'idle' && (progress !== undefined || Boolean(file) || timePrinting !== undefined || timeRemaining !== undefined);
    return this.cleanAttributes({
      id: this.numberValue(job.id),
      state: this.stringValue(job.state) ? state : undefined,
      progress,
      filename: this.stringValue(file?.display_name) || this.stringValue(file?.name),
      filePath: this.stringValue(file?.path),
      displayPath: this.stringValue(file?.display_path),
      thumbnailPath: this.stringValue(refs?.thumbnail),
      downloadPath: this.stringValue(refs?.download),
      iconPath: this.stringValue(refs?.icon),
      fileSize: this.numberValue(file?.size),
      timeRemainingSeconds: timeRemaining,
      timePrintingSeconds: timePrinting,
      startedAt: active && timePrinting !== undefined ? new Date(now - timePrinting * 1000).toISOString() : undefined,
      finishesAt: active && timeRemaining !== undefined ? new Date(now + timeRemaining * 1000).toISOString() : undefined,
      inaccurateEstimates: this.booleanValue(job.inaccurate_estimates),
      serialPrint: this.booleanValue(job.serial_print),
      active,
      rawFile: file || undefined,
    });
  }

  private static pushEntity(entitiesArg: IIntegrationEntity[], snapshotArg: IPrusaLinkSnapshot, deviceIdArg: string, uniqueBaseArg: string, definitionArg: IPrusaLinkSensorDefinition): void {
    if (definitionArg.value === undefined || definitionArg.value === null) {
      return;
    }
    entitiesArg.push(this.entity(snapshotArg, definitionArg, deviceIdArg, uniqueBaseArg));
  }

  private static entity(snapshotArg: IPrusaLinkSnapshot, definitionArg: IPrusaLinkSensorDefinition, deviceIdArg: string, uniqueBaseArg: string): IIntegrationEntity {
    const platform = definitionArg.platform || 'sensor';
    return {
      id: `${platform}.${this.slug(definitionArg.name)}`,
      uniqueId: `${prusaLinkDomain}_${uniqueBaseArg}_${this.slug(definitionArg.key)}`,
      integrationDomain: prusaLinkDomain,
      deviceId: deviceIdArg,
      platform,
      name: definitionArg.name,
      state: definitionArg.value,
      attributes: this.cleanAttributes({
        key: definitionArg.key,
        unitOfMeasurement: definitionArg.unit,
        deviceClass: definitionArg.deviceClass,
        stateClass: definitionArg.stateClass,
        entityCategory: definitionArg.entityCategory,
        ...definitionArg.attributes,
      }),
      available: definitionArg.available ?? (snapshotArg.online && definitionArg.value !== undefined && definitionArg.value !== null),
    };
  }

  private static button(snapshotArg: IPrusaLinkSnapshot, actionArg: TPrusaLinkCommandAction, nameArg: string, availableArg: boolean, deviceIdArg: string, uniqueBaseArg: string): IIntegrationEntity {
    return this.entity(snapshotArg, {
      key: `${actionArg}_job`,
      name: `${this.printerName(snapshotArg)} ${nameArg}`,
      platform: 'button',
      value: 'idle',
      available: availableArg,
      attributes: {
        action: actionArg,
        jobId: snapshotArg.job.id,
      },
    }, deviceIdArg, uniqueBaseArg);
  }

  private static actionForService(snapshotArg: IPrusaLinkSnapshot, requestArg: IServiceCallRequest): TPrusaLinkCommandAction | undefined {
    if (requestArg.domain === prusaLinkDomain) {
      const service = requestArg.service.replace(/_job$/, '');
      if (service === 'pause' || service === 'resume' || service === 'cancel' || service === 'restart') {
        return service;
      }
      return undefined;
    }

    if (requestArg.domain !== 'button' || requestArg.service !== 'press') {
      return undefined;
    }

    const entityId = requestArg.target.entityId;
    if (!entityId) {
      return undefined;
    }
    for (const action of ['pause', 'resume', 'cancel'] as const) {
      const entity = this.button(snapshotArg, action, `${this.title(action)} Job`, true, this.printerDeviceId(snapshotArg), this.uniqueBase(snapshotArg));
      if (entity.id === entityId || entity.uniqueId === entityId) {
        return action;
      }
    }
    return undefined;
  }

  private static buttonAvailable(snapshotArg: IPrusaLinkSnapshot, actionArg: TPrusaLinkCommandAction): boolean {
    if (!snapshotArg.online || snapshotArg.job.id === undefined) {
      return false;
    }
    const state = snapshotArg.telemetry.state;
    if (actionArg === 'cancel') {
      return state === 'printing' || state === 'paused';
    }
    if (actionArg === 'pause') {
      return state === 'printing';
    }
    if (actionArg === 'resume') {
      return state === 'paused';
    }
    return false;
  }

  private static printerName(snapshotArg: IPrusaLinkSnapshot): string {
    return snapshotArg.printer.name || snapshotArg.printer.hostname || snapshotArg.printer.host || 'PrusaLink';
  }

  private static uniqueBase(snapshotArg: IPrusaLinkSnapshot): string {
    return this.slug(snapshotArg.printer.serialNumber || snapshotArg.printer.id || snapshotArg.printer.hostname || snapshotArg.printer.host || this.printerName(snapshotArg));
  }

  private static hasRawData(rawDataArg: Partial<IPrusaLinkRawData>): boolean {
    return Boolean(rawDataArg.version || rawDataArg.status || rawDataArg.legacyStatus || rawDataArg.job || rawDataArg.info);
  }

  private static stateValue(valueArg: unknown): TPrusaLinkPrinterState {
    const state = this.stringValue(valueArg)?.toLowerCase();
    return state || 'unknown';
  }

  private static baseUrl(configArg: IPrusaLinkConfig, hostArg?: string, portArg?: number, sslArg?: boolean): string | undefined {
    const parsed = this.parseEndpoint(configArg.host);
    if (parsed?.baseUrl) {
      return parsed.baseUrl;
    }
    if (!hostArg) {
      return undefined;
    }
    const ssl = sslArg ?? configArg.ssl ?? configArg.tls ?? false;
    const protocol = ssl ? 'https' : 'http';
    const port = portArg && portArg !== (ssl ? 443 : 80) ? `:${portArg}` : '';
    return `${protocol}://${this.hostForUrl(hostArg)}${port}`;
  }

  private static parseEndpoint(valueArg: string | undefined): { host: string; port?: number; ssl: boolean; baseUrl: string } | undefined {
    if (!valueArg || !/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg)) {
      return undefined;
    }
    try {
      const url = new URL(valueArg);
      const defaultPort = url.protocol === 'https:' ? 443 : 80;
      return {
        host: url.hostname,
        port: url.port ? Number(url.port) : defaultPort,
        ssl: url.protocol === 'https:',
        baseUrl: `${url.protocol}//${url.host}`,
      };
    } catch {
      return undefined;
    }
  }

  private static hostForUrl(hostArg: string): string {
    return hostArg.includes(':') && !hostArg.startsWith('[') ? `[${hostArg}]` : hostArg;
  }

  private static recordValue(valueArg: unknown): Record<string, unknown> | undefined {
    return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : undefined;
  }

  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 stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
  }

  private static booleanValue(valueArg: unknown): boolean | undefined {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'string') {
      if (['true', 'yes', 'on', '1'].includes(valueArg.toLowerCase())) {
        return true;
      }
      if (['false', 'no', 'off', '0'].includes(valueArg.toLowerCase())) {
        return false;
      }
    }
    return undefined;
  }

  private static title(valueArg: string): string {
    return `${valueArg.charAt(0).toUpperCase()}${valueArg.slice(1)}`;
  }

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

  private static clone<TValue>(valueArg: TValue): TValue {
    return JSON.parse(JSON.stringify(valueArg)) as TValue;
  }
}

export const prusaLinkApiVersionSupported = (versionArg: IPrusaLinkVersionInfo | undefined): boolean => {
  if (!versionArg) {
    return true;
  }
  if (compareVersions(stringValue(versionArg.api), '2.0.0') >= 0) {
    return true;
  }
  const original = stringValue(versionArg.original);
  const server = stringValue(versionArg.server);
  return Boolean(original?.startsWith('PrusaLink I3MK3') || original?.startsWith('PrusaLink I3MK2')) && compareVersions(server, '0.7.2') >= 0;
};

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

const compareVersions = (valueArg: string | undefined, minimumArg: string): number => {
  if (!valueArg) {
    return -1;
  }
  const valueParts = valueArg.match(/\d+(?:\.\d+)*/)?.[0]?.split('.').map((partArg) => Number(partArg)) || [];
  const minimumParts = minimumArg.split('.').map((partArg) => Number(partArg));
  const length = Math.max(valueParts.length, minimumParts.length);
  for (let index = 0; index < length; index++) {
    const value = valueParts[index] || 0;
    const minimum = minimumParts[index] || 0;
    if (value > minimum) {
      return 1;
    }
    if (value < minimum) {
      return -1;
    }
  }
  return 0;
};
