import { SimpleLocalClient, SimpleLocalMapper, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot, type IServiceCallRequest, type IServiceCallResult, type TSimpleLocalRawData } from '../../core/index.js';
import type { IPalazzettiConfig } from './palazzetti.types.js';
import { palazzettiDefaultName, palazzettiDefaultPort, palazzettiProfile } from './palazzetti.types.js';

interface IPalazzettiEndpoint {
  host: string;
  port: number;
  baseUrl: string;
}

interface IPalazzettiApiPayload extends Record<string, unknown> {
  SUCCESS?: boolean;
  DATA?: Record<string, unknown>;
}

interface IPalazzettiLiveData extends Record<string, unknown> {
  connected: boolean;
  properties: Record<string, unknown>;
  attributes: Record<string, unknown>;
  fetchedAt: string;
}

export class PalazzettiClient extends SimpleLocalClient<IPalazzettiConfig> {
  private cachedSnapshot?: ISimpleLocalSnapshot;
  private liveData?: IPalazzettiLiveData;

  constructor(private readonly configArg: IPalazzettiConfig) {
    super(palazzettiProfile, configArg);
  }

  public override async getSnapshot(forceRefreshArg = false): Promise<ISimpleLocalSnapshot> {
    if (!forceRefreshArg && this.cachedSnapshot) {
      return SimpleLocalMapper.clone(this.cachedSnapshot);
    }

    if (this.shouldUseNativeHttp(forceRefreshArg)) {
      try {
        this.cachedSnapshot = await this.fetchLiveSnapshot();
      } catch (errorArg) {
        this.cachedSnapshot = this.createOfflineSnapshot(errorMessage(errorArg));
      }
      return SimpleLocalMapper.clone(this.cachedSnapshot);
    }

    const snapshot = await super.getSnapshot(forceRefreshArg);
    this.cachedSnapshot = SimpleLocalMapper.toSnapshot({
      profile: palazzettiProfile,
      config: this.configArg,
      rawData: (snapshot.rawData as TSimpleLocalRawData | undefined) ?? snapshot,
      online: snapshot.online,
      source: snapshot.source,
      error: snapshot.error,
    });
    return SimpleLocalMapper.clone(this.cachedSnapshot);
  }

  public override async refresh(): Promise<IServiceCallResult> {
    this.cachedSnapshot = undefined;
    const liveCapable = Boolean(this.endpoint()) || Boolean(this.configArg.client || this.configArg.snapshotProvider);
    const snapshot = await this.getSnapshot(liveCapable);
    const success = snapshot.online && !snapshot.error && (liveCapable || this.hasManualData());
    return { success, error: success ? undefined : snapshot.error || 'Palazzetti refresh requires a local HTTP host, injected client, snapshotProvider, snapshot, entities, or rawData.', data: snapshot };
  }

  public override async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (this.configArg.client?.execute || this.configArg.commandExecutor || !this.endpoint()) {
      return super.execute(requestArg);
    }

    try {
      const payload = await this.executeNative(requestArg);
      this.cachedSnapshot = undefined;
      return payload.SUCCESS === false
        ? { success: false, error: `Palazzetti command ${requestArg.domain}.${requestArg.service} returned SUCCESS=false.`, data: payload }
        : { success: true, data: payload };
    } catch (errorArg) {
      return { success: false, error: errorMessage(errorArg) };
    }
  }

  public override async destroy(): Promise<void> {
    await this.configArg.client?.destroy?.();
  }

  private shouldUseNativeHttp(forceRefreshArg: boolean): boolean {
    if (!this.endpoint() || this.configArg.client || this.configArg.snapshotProvider) {
      return false;
    }
    if (!forceRefreshArg && this.configArg.snapshot) {
      return false;
    }
    if (!forceRefreshArg && this.hasManualData()) {
      return false;
    }
    return true;
  }

  private async fetchLiveSnapshot(): Promise<ISimpleLocalSnapshot> {
    const propertiesPayload = await this.command('GET STDT');
    if (!propertiesPayload.SUCCESS) {
      throw new Error('Palazzetti GET STDT returned SUCCESS=false.');
    }
    const statePayload = await this.command('GET ALLS');
    if (!statePayload.SUCCESS) {
      throw new Error('Palazzetti GET ALLS returned SUCCESS=false.');
    }

    this.liveData = {
      connected: true,
      properties: recordValue(propertiesPayload.DATA),
      attributes: recordValue(statePayload.DATA),
      fetchedAt: new Date().toISOString(),
    };
    return this.snapshotFromLiveData(this.requireEndpoint(), this.liveData);
  }

  private async executeNative(requestArg: IServiceCallRequest): Promise<IPalazzettiApiPayload> {
    const data = requestArg.data || {};
    if (requestArg.service === 'set_hvac_mode') {
      const mode = normalizedString(data.hvac_mode ?? data.hvacMode ?? data.mode);
      if (mode === 'heat') {
        await this.ensureOnOffAvailable();
        return this.command('CMD ON');
      }
      if (mode === 'off') {
        await this.ensureOnOffAvailable();
        return this.command('CMD OFF');
      }
      throw new Error('Palazzetti set_hvac_mode requires data.hvac_mode set to heat or off.');
    }
    if (requestArg.service === 'turn_on' || requestArg.service === 'turn_off') {
      await this.ensureOnOffAvailable();
      return this.command(requestArg.service === 'turn_on' ? 'CMD ON' : 'CMD OFF');
    }
    if (requestArg.service === 'set_temperature') {
      const temperature = numberValue(data.temperature ?? data.target_temperature ?? data.targetTemperature ?? data.value);
      if (temperature === undefined) {
        throw new Error('Palazzetti set_temperature requires data.temperature.');
      }
      await this.ensureTemperatureInRange(temperature);
      return this.command('SET SETP', Math.round(temperature));
    }
    if (requestArg.service === 'set_fan_mode') {
      const fanMode = normalizedString(data.fan_mode ?? data.fanMode ?? data.mode ?? data.value);
      if (!fanMode) {
        throw new Error('Palazzetti set_fan_mode requires data.fan_mode.');
      }
      return this.setFanMode(fanMode);
    }
    if (requestArg.service === 'set_value') {
      return this.setNumberValue(requestArg);
    }
    if (requestArg.service === 'press') {
      return this.command('SET SLNT 1');
    }
    throw new Error(`Unsupported Palazzetti service: ${requestArg.domain}.${requestArg.service}`);
  }

  private async setFanMode(fanModeArg: string): Promise<IPalazzettiApiPayload> {
    if (fanModeArg === 'silent') {
      return this.command('SET SLNT 1');
    }
    if (fanModeArg === 'high') {
      await this.ensureFanAvailable('main', 6);
      return this.command('SET RFAN', 6);
    }
    if (fanModeArg === 'auto') {
      await this.ensureFanAvailable('main', 7);
      return this.command('SET RFAN', 7);
    }
    const speed = numberValue(fanModeArg);
    if (speed === undefined) {
      throw new Error(`Unsupported Palazzetti fan mode: ${fanModeArg}`);
    }
    await this.ensureFanAvailable('main', speed);
    return this.command('SET RFAN', speed);
  }

  private async setNumberValue(requestArg: IServiceCallRequest): Promise<IPalazzettiApiPayload> {
    const data = requestArg.data || {};
    const value = numberValue(data.value ?? data.native_value ?? data.nativeValue ?? data.power ?? data.power_mode ?? data.fan_speed ?? data.fanSpeed);
    if (value === undefined) {
      throw new Error('Palazzetti set_value requires data.value.');
    }
    const fan = this.fanFromRequest(requestArg);
    if (fan) {
      await this.ensureFanAvailable(fan, value);
      return this.command(fan === 'left' ? 'SET FN3L' : fan === 'right' ? 'SET FN4L' : 'SET RFAN', Math.round(value));
    }
    if (value < 1 || value > 5) {
      throw new Error(`Palazzetti combustion power ${value} is out of range 1..5.`);
    }
    return this.command('SET POWR', Math.round(value));
  }

  private async command(commandArg: string, parameterArg?: string | number): Promise<IPalazzettiApiPayload> {
    const endpoint = this.requireEndpoint();
    const commandAndParameter = parameterArg === undefined ? commandArg : `${commandArg} ${parameterArg}`;
    const url = new URL('/cgi-bin/sendmsg.lua', endpoint.baseUrl);
    url.searchParams.set('cmd', commandAndParameter);

    const timeoutMs = this.configArg.timeoutMs || 10000;
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const response = await globalThis.fetch(url, {
        headers: { accept: 'application/json, text/plain, */*' },
        signal: controller.signal,
      });
      const text = await response.text();
      if (!response.ok) {
        throw new Error(`Palazzetti command ${commandAndParameter} failed with HTTP ${response.status}: ${text.slice(0, 160)}`);
      }
      const parsed = JSON.parse(text) as unknown;
      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
        throw new Error(`Palazzetti command ${commandAndParameter} returned invalid JSON.`);
      }
      return parsed as IPalazzettiApiPayload;
    } finally {
      clearTimeout(timer);
    }
  }

  private snapshotFromLiveData(endpointArg: IPalazzettiEndpoint, dataArg: IPalazzettiLiveData): ISimpleLocalSnapshot {
    const properties = dataArg.properties;
    const attributes = dataArg.attributes;
    const macAddress = stringValue(properties.MAC) || stringValue(properties.EMAC) || stringValue(properties.WMAC) || this.configArg.macAddress;
    const deviceId = this.configArg.uniqueId || this.configArg.deviceId || macAddress || `${endpointArg.host}:${endpointArg.port}`;
    return {
      device: {
        id: deviceId,
        name: this.configArg.name || stringValue(properties.LABEL) || palazzettiDefaultName,
        manufacturer: 'Palazzetti',
        model: 'Palazzetti Connection Box',
        serialNumber: macAddress,
        host: endpointArg.host,
        port: endpointArg.port,
        protocol: 'http',
        configurationUrl: endpointArg.baseUrl,
        attributes: cleanAttributes({
          swVersion: stringValue(properties.plzbridge),
          hwVersion: stringValue(properties.SYSTEM),
          stoveType: numberValue(properties.STOVETYPE),
        }),
      },
      entities: entitiesFromLiveData(dataArg),
      online: true,
      updatedAt: dataArg.fetchedAt,
      source: 'http',
      rawData: dataArg,
      capabilities: {
        localRead: true,
        localControl: true,
        palazzettiSendmsgApi: true,
      },
    };
  }

  private async currentLiveData(): Promise<IPalazzettiLiveData> {
    if (this.liveData) {
      return this.liveData;
    }
    await this.fetchLiveSnapshot();
    if (!this.liveData) {
      throw new Error('Palazzetti state is unavailable.');
    }
    return this.liveData;
  }

  private async ensureOnOffAvailable(): Promise<void> {
    const data = await this.currentLiveData();
    if (!hasOnOffSwitch(data.properties, data.attributes)) {
      throw new Error('Palazzetti on/off switch is not available for the current stove state.');
    }
  }

  private async ensureTemperatureInRange(valueArg: number): Promise<void> {
    const data = await this.currentLiveData();
    const min = numberValue(data.properties.SPLMIN) ?? 0;
    const max = numberValue(data.properties.SPLMAX) ?? 99;
    if (valueArg < min || valueArg > max) {
      throw new Error(`Palazzetti target temperature ${valueArg} is out of range ${min}..${max}.`);
    }
  }

  private async ensureFanAvailable(fanArg: 'main' | 'left' | 'right', speedArg: number): Promise<void> {
    const data = await this.currentLiveData();
    if (!hasFan(data.properties, fanArg)) {
      throw new Error(`Palazzetti ${fanArg} fan is not available.`);
    }
    const [min, max] = fanLimits(data.attributes, fanArg);
    const highAllowed = fanArg === 'main' && speedArg === 6 && hasFanHigh(data.properties);
    const autoAllowed = fanArg === 'main' && speedArg === 7 && hasFanAuto(data.properties);
    if (!highAllowed && !autoAllowed && (speedArg < min || speedArg > max)) {
      throw new Error(`Palazzetti ${fanArg} fan speed ${speedArg} is out of range ${min}..${max}.`);
    }
  }

  private fanFromRequest(requestArg: IServiceCallRequest): 'main' | 'left' | 'right' | undefined {
    const data = requestArg.data || {};
    const fan = normalizedString(data.fan ?? data.fan_type ?? data.fanType);
    if (fan === 'main' || fan === 'left' || fan === 'right') {
      return fan;
    }
    const entity = normalizedString(requestArg.target.entityId);
    if (entity?.includes('fan_left')) {
      return 'left';
    }
    if (entity?.includes('fan_right')) {
      return 'right';
    }
    if (entity?.includes('fan_main')) {
      return 'main';
    }
    return undefined;
  }

  private requireEndpoint(): IPalazzettiEndpoint {
    const endpoint = this.endpoint();
    if (!endpoint) {
      throw new Error('Palazzetti local HTTP API requires config.host or config.url.');
    }
    return endpoint;
  }

  private endpoint(): IPalazzettiEndpoint | undefined {
    const rawUrl = stringValue(this.configArg.url) || stringValue(this.configArg.host);
    if (!rawUrl) {
      return undefined;
    }

    try {
      const url = new URL(rawUrl.includes('://') ? rawUrl : `http://${rawUrl}`);
      if (url.protocol !== 'http:') {
        return undefined;
      }
      return endpointFromParts(url.hostname, this.configArg.port || numberValue(url.port) || palazzettiDefaultPort);
    } catch {
      return endpointFromParts(rawUrl, this.configArg.port || palazzettiDefaultPort);
    }
  }

  private hasManualData(): boolean {
    return this.configArg.rawData !== undefined || this.configArg.rawText !== undefined || Boolean(this.configArg.entities?.length);
  }

  private createOfflineSnapshot(errorArg: string): ISimpleLocalSnapshot {
    return SimpleLocalMapper.toSnapshot({ profile: palazzettiProfile, config: this.configArg, online: false, source: 'runtime', error: errorArg });
  }
}

const statusToHa: Record<number, string> = {
  0: 'off', 1: 'off_timer', 2: 'test_fire', 3: 'heatup', 4: 'fueling', 5: 'ign_test', 6: 'burning', 7: 'burning_mod', 8: 'unknown', 9: 'cool_fluid', 10: 'fire_stop', 11: 'clean_fire', 12: 'cooling', 50: 'cleanup', 51: 'ecomode', 241: 'chimney_alarm', 243: 'grate_error', 244: 'pellet_water_error', 245: 't05_error', 247: 'hatch_door_open', 248: 'pressure_error', 249: 'main_probe_failure', 250: 'flue_probe_failure', 252: 'exhaust_temp_high', 253: 'pellet_finished', 501: 'off', 502: 'fueling', 503: 'ign_test', 504: 'burning', 505: 'firewood_finished', 506: 'cooling', 507: 'clean_fire', 1000: 'general_error', 1001: 'general_error', 1239: 'door_open', 1240: 'temp_too_high', 1241: 'cleaning_warning', 1243: 'fuel_error', 1244: 'pellet_water_error', 1245: 't05_error', 1247: 'hatch_door_open', 1248: 'pressure_error', 1249: 'main_probe_failure', 1250: 'flue_probe_failure', 1252: 'exhaust_temp_high', 1253: 'pellet_finished', 1508: 'general_error',
};

const heatingStatuses = new Set([2, 3, 4, 5, 6, 7, 502, 503, 504]);
const offStatuses = new Set([0, 1]);
const temperatureProbes = ['T1', 'T2', 'T3', 'T4', 'T5'];

const entitiesFromLiveData = (dataArg: IPalazzettiLiveData): ISimpleLocalEntitySnapshot[] => {
  const properties = dataArg.properties;
  const attributes = dataArg.attributes;
  const statusCode = numberValue(attributes.LSTATUS) ?? 0;
  const status = statusToHa[statusCode] || String(statusCode);
  const entities: ISimpleLocalEntitySnapshot[] = [
    {
      id: 'climate',
      name: 'Climate',
      platform: 'climate',
      state: isOn(attributes) ? 'heat' : 'off',
      available: dataArg.connected,
      writable: true,
      attributes: cleanAttributes({
        currentTemperature: currentTemperature(properties, attributes),
        targetTemperature: numberValue(attributes.SETP),
        hvacAction: heatingStatuses.has(statusCode) ? 'heating' : 'idle',
        fanMode: fanMode(attributes),
        fanModes: fanModes(properties, attributes),
        minTemp: numberValue(properties.SPLMIN),
        maxTemp: numberValue(properties.SPLMAX),
        status,
      }),
    },
    sensor('status', 'Status', status, undefined, 'enum', undefined, { statusCode }),
    sensor('pellet_quantity', 'Pellet Quantity', numberValue(attributes.PQT) ?? 0, 'kg', 'weight', 'measurement'),
    numberEntity('combustion_power', 'Combustion Power', numberValue(attributes.PWR) ?? 0, { min: 1, max: 5 }),
  ];

  if (hasPelletLevel(properties)) {
    entities.push(sensor('pellet_level', 'Pellet Level', numberValue(attributes.PLEVEL) ?? 0, 'cm', 'distance', 'measurement'));
  }
  for (const definition of temperatureDefinitions(properties, attributes)) {
    entities.push(sensor(definition.id, definition.name, numberValue(attributes[definition.property]) ?? 0, 'degC', 'temperature', 'measurement', { property: definition.property }));
  }
  if (hasFan(properties, 'left')) {
    const [min, max] = fanLimits(attributes, 'left');
    entities.push(numberEntity('fan_left_speed', 'Left Fan Speed', numberValue(attributes.F3L) ?? 0, { fan: 'left', min, max }));
  }
  if (hasFan(properties, 'right')) {
    const [min, max] = fanLimits(attributes, 'right');
    entities.push(numberEntity('fan_right_speed', 'Right Fan Speed', numberValue(attributes.F4L) ?? 0, { fan: 'right', min, max }));
  }
  if (hasFanSilent(properties)) {
    entities.push({ id: 'silent', name: 'Silent', platform: 'button', state: false, available: dataArg.connected, writable: true, attributes: { command: 'SET SLNT 1' } });
  }
  return entities;
};

const sensor = (idArg: string, nameArg: string, stateArg: unknown, unitArg?: string, deviceClassArg?: string, stateClassArg?: string, attributesArg?: Record<string, unknown>): ISimpleLocalEntitySnapshot => ({
  id: idArg,
  name: nameArg,
  platform: 'sensor',
  state: stateArg ?? null,
  unit: unitArg,
  deviceClass: deviceClassArg,
  stateClass: stateClassArg,
  available: true,
  writable: false,
  attributes: cleanAttributes({ source: 'palazzetti-sendmsg', ...attributesArg }),
});

const numberEntity = (idArg: string, nameArg: string, stateArg: unknown, attributesArg: Record<string, unknown>): ISimpleLocalEntitySnapshot => ({
  id: idArg,
  name: nameArg,
  platform: 'number',
  state: stateArg ?? null,
  available: true,
  writable: true,
  attributes: cleanAttributes({ source: 'palazzetti-sendmsg', ...attributesArg }),
});

const temperatureDefinitions = (propertiesArg: Record<string, unknown>, attributesArg: Record<string, unknown>): Array<{ id: string; name: string; property: string }> => {
  const definitions: Array<{ id: string; name: string; property: string }> = [];
  const mainProbe = temperatureProbes[mainTemperatureProbeIndex(propertiesArg)] || 'T1';
  definitions.push({ id: mainTemperatureId(propertiesArg), name: mainTemperatureName(propertiesArg), property: mainProbe });
  if (hasAirOutletTemperature(propertiesArg) || numberValue(attributesArg.T4) !== 0) {
    definitions.push({ id: 'air_outlet_temperature', name: 'Air Outlet Temperature', property: 'T4' });
  }
  if (hasWoodCombustionTemperature(propertiesArg) || numberValue(attributesArg.T3) !== 0) {
    definitions.push({ id: 'wood_combustion_temperature', name: 'Wood Combustion Temperature', property: 'T3' });
  }
  if (isHydro(propertiesArg)) {
    definitions.push({ id: 't1_hydro', name: 'Hydro Temperature 1', property: 'T1' });
    definitions.push({ id: 't2_hydro', name: 'Hydro Temperature 2', property: 'T2' });
  }
  return definitions;
};

const mainTemperatureProbeIndex = (propertiesArg: Record<string, unknown>): number => {
  if (isHydro(propertiesArg)) {
    if (numberValue(propertiesArg.UICONFIG) === 1) {
      return 1;
    }
    if (numberValue(propertiesArg.UICONFIG) === 10) {
      return 4;
    }
  }
  return numberValue(propertiesArg.MAINTPROBE) ?? 0;
};

const mainTemperatureId = (propertiesArg: Record<string, unknown>): string => {
  if (isHydro(propertiesArg)) {
    if (numberValue(propertiesArg.UICONFIG) === 1) {
      return 'return_water_temperature';
    }
    if ([3, 4].includes(numberValue(propertiesArg.UICONFIG) ?? -1)) {
      return 'tank_water_temperature';
    }
  }
  return 'room_temperature';
};

const mainTemperatureName = (propertiesArg: Record<string, unknown>): string => mainTemperatureId(propertiesArg).replace(/_/g, ' ').replace(/\b\w/g, (charArg) => charArg.toUpperCase());

const currentTemperature = (propertiesArg: Record<string, unknown>, attributesArg: Record<string, unknown>): number | undefined => numberValue(attributesArg[temperatureProbes[mainTemperatureProbeIndex(propertiesArg)] || 'T1']);

const fanMode = (attributesArg: Record<string, unknown>): string => {
  const value = numberValue(attributesArg.F2L) ?? 0;
  if (value === 6) {
    return 'high';
  }
  if (value === 7) {
    return 'auto';
  }
  return String(value);
};

const fanModes = (propertiesArg: Record<string, unknown>, attributesArg: Record<string, unknown>): string[] => {
  const [min, max] = fanLimits(attributesArg, 'main');
  const modes: string[] = [];
  for (let index = min; index <= max; index++) {
    modes.push(String(index));
  }
  if (hasFanHigh(propertiesArg)) {
    modes.push('high');
  }
  if (hasFanAuto(propertiesArg)) {
    modes.push('auto');
  }
  return modes;
};

const fanLimits = (attributesArg: Record<string, unknown>, fanArg: 'main' | 'left' | 'right'): [number, number] => {
  const limits = Array.isArray(attributesArg.FANLMINMAX) ? attributesArg.FANLMINMAX : [];
  const offset = fanArg === 'left' ? 2 : fanArg === 'right' ? 4 : 0;
  return [numberValue(limits[offset]) ?? 0, numberValue(limits[offset + 1]) ?? 5];
};

const hasOnOffSwitch = (propertiesArg: Record<string, unknown>, attributesArg: Record<string, unknown>): boolean => ![7, 8].includes(numberValue(propertiesArg.STOVETYPE) ?? -1) && [0, 1, 6, 7, 9, 11, 12, 51, 501, 504, 505, 506, 507].includes(numberValue(attributesArg.LSTATUS) ?? -1);
const isOn = (attributesArg: Record<string, unknown>): boolean => !offStatuses.has(numberValue(attributesArg.LSTATUS) ?? 0);
const isHydro = (propertiesArg: Record<string, unknown>): boolean => [2, 4, 6].includes(numberValue(propertiesArg.STOVETYPE) ?? -1);
const hasAirOutletTemperature = (propertiesArg: Record<string, unknown>): boolean => [7, 8].includes(numberValue(propertiesArg.STOVETYPE) ?? -1) && (numberValue(propertiesArg.FAN2TYPE) ?? 0) > 1;
const hasWoodCombustionTemperature = (propertiesArg: Record<string, unknown>): boolean => [7, 8].includes(numberValue(propertiesArg.STOVETYPE) ?? -1);
const hasPelletLevel = (propertiesArg: Record<string, unknown>): boolean => [1, 2].includes(numberValue(propertiesArg.PSENSTYPE) ?? -1);
const hasFanSilent = (propertiesArg: Record<string, unknown>): boolean => (numberValue(propertiesArg.FAN2TYPE) ?? 0) > 2;
const hasFanHigh = (propertiesArg: Record<string, unknown>): boolean => numberValue(propertiesArg.FAN2MODE) === 3;
const hasFanAuto = (propertiesArg: Record<string, unknown>): boolean => [2, 3].includes(numberValue(propertiesArg.FAN2MODE) ?? -1);
const hasFan = (propertiesArg: Record<string, unknown>, fanArg: 'main' | 'left' | 'right'): boolean => {
  const fanType = numberValue(propertiesArg.FAN2TYPE) ?? 0;
  if (fanArg === 'left') {
    return fanType > 3;
  }
  if (fanArg === 'right') {
    return fanType > 2;
  }
  return fanType > 1;
};

const endpointFromParts = (hostArg: string, portArg: number): IPalazzettiEndpoint => {
  const portPart = portArg === 80 ? '' : `:${portArg}`;
  return { host: hostArg, port: portArg, baseUrl: `http://${hostArg}${portPart}` };
};

const cleanAttributes = (attributesArg: Record<string, unknown>): Record<string, unknown> | undefined => {
  const clean = Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
  return Object.keys(clean).length ? clean : undefined;
};

const recordValue = (valueArg: unknown): Record<string, unknown> => valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Record<string, unknown> : {};
const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
const normalizedString = (valueArg: unknown): string | undefined => stringValue(valueArg)?.toLowerCase();
const numberValue = (valueArg: unknown): number | undefined => {
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return valueArg;
  }
  if (typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg))) {
    return Number(valueArg);
  }
  return undefined;
};
const errorMessage = (errorArg: unknown): string => errorArg instanceof Error ? errorArg.message : String(errorArg);
