import * as plugins from '../../plugins.js';
import { SimpleLocalClient, SimpleLocalMapper, type IServiceCallRequest, type IServiceCallResult, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot } from '../../core/index.js';
import type { ILutronConfig } from './lutron.types.js';
import { lutronProfile } from './lutron.types.js';

interface ILutronXmlNode {
  name: string;
  attributes: Record<string, string>;
  children: ILutronXmlNode[];
  text: string;
}

interface ILutronOutputRaw {
  integrationId: number;
  uuid?: string;
  name: string;
  areaName: string;
  outputType: string;
  wattage?: number;
  level?: number;
}

interface ILutronOccupancyRaw {
  integrationId: number;
  uuid?: string;
  areaName: string;
  groupNumber?: string;
}

interface ILutronButtonRaw {
  keypadIntegrationId: number;
  componentNumber: number;
  uuid?: string;
  name: string;
  areaName: string;
  keypadName: string;
  buttonType?: string;
}

interface ILutronRawSnapshot {
  guid?: string;
  projectName?: string;
  outputs: ILutronOutputRaw[];
  occupancyGroups: ILutronOccupancyRaw[];
  buttons: ILutronButtonRaw[];
  fetchedAt: string;
  stateError?: string;
}

interface ILutronTelnetSession {
  send(commandArg: string, responsePatternArg?: RegExp): Promise<string>;
  close(): void;
}

const keypadDeviceTypes = new Set([
  'HWI_SEETOUCH_KEYPAD',
  'SEETOUCH_KEYPAD',
  'INTERNATIONAL_SEETOUCH_KEYPAD',
  'SEETOUCH_TABLETOP_KEYPAD',
  'PICO_KEYPAD',
  'HYBRID_SEETOUCH_KEYPAD',
  'MAIN_REPEATER',
  'HOMEOWNER_KEYPAD',
  'PALLADIOM_KEYPAD',
  'HWI_SLIM',
  'GRAFIK_T_HYBRID_KEYPAD',
]);

const nonDimmableOutputTypes = new Set(['NON_DIM', 'NON_DIM_INC', 'NON_DIM_ELV', 'EXHAUST_FAN_TYPE', 'RELAY_LIGHTING', 'SWITCHED_MOTOR', 'MOTOR']);
const ledOptionMap: Record<string, number> = {
  off: 0,
  on: 1,
  slow_flash: 2,
  fast_flash: 3,
};

export class LutronClient extends SimpleLocalClient<ILutronConfig> {
  private liveSnapshot?: ISimpleLocalSnapshot;

  constructor(configArg: ILutronConfig) {
    super(lutronProfile, configArg);
    this.localConfig = configArg;
  }

  private readonly localConfig: ILutronConfig;

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

    if (this.localConfig.host || this.localConfig.url) {
      try {
        this.liveSnapshot = await this.fetchSnapshot();
      } catch (errorArg) {
        this.liveSnapshot = this.makeOfflineSnapshot(this.errorMessage(errorArg));
      }
      return SimpleLocalMapper.clone(this.liveSnapshot);
    }

    return super.getSnapshot(forceRefreshArg);
  }

  public async refresh(): Promise<IServiceCallResult> {
    this.liveSnapshot = undefined;
    const snapshot = await this.getSnapshot(true);
    const success = snapshot.online && !snapshot.error;
    return { success, error: success ? undefined : snapshot.error, data: snapshot };
  }

  public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (this.localConfig.client?.execute || this.localConfig.commandExecutor) {
      return super.execute(requestArg);
    }
    if (requestArg.service === 'refresh' || requestArg.service === 'status' || requestArg.service === 'snapshot') {
      const snapshot = await this.getSnapshot(requestArg.service === 'refresh');
      return { success: snapshot.online && !snapshot.error, error: snapshot.error, data: snapshot };
    }
    if (!this.canUseTelnet()) {
      return { success: false, error: 'Lutron RadioRA 2 live writes require host, username, password, and Telnet port 23 access; provide an injected client.execute() or commandExecutor for other transports.' };
    }

    try {
      const command = this.commandForService(requestArg);
      if (!command) {
        return { success: false, error: `Unsupported Lutron service: ${requestArg.domain}.${requestArg.service}` };
      }
      await this.withTelnet((sessionArg) => sessionArg.send(command));
      return { success: true, data: { command } };
    } catch (errorArg) {
      return { success: false, error: this.errorMessage(errorArg) };
    }
  }

  private async fetchSnapshot(): Promise<ISimpleLocalSnapshot> {
    const xml = await this.fetchXmlDatabase();
    const rawData = this.parseLutronXml(xml);
    if (this.canUseTelnet() && this.localConfig.queryStates !== false) {
      try {
        await this.fillOutputLevels(rawData.outputs);
      } catch (errorArg) {
        rawData.stateError = this.errorMessage(errorArg);
      }
    }
    return this.snapshotFromRaw(rawData);
  }

  private async fetchXmlDatabase(): Promise<string> {
    const url = this.xmlUrl();
    const response = await globalThis.fetch(url, {
      headers: { accept: 'application/xml, text/xml;q=0.9, */*;q=0.8' },
      signal: AbortSignal.timeout(this.localConfig.timeoutMs || 5000),
    });
    const text = await response.text();
    if (!response.ok) {
      throw new Error(`Lutron XML database request failed with HTTP ${response.status}: ${text.slice(0, 160)}`);
    }
    return text;
  }

  private snapshotFromRaw(rawDataArg: ILutronRawSnapshot): ISimpleLocalSnapshot {
    const entities: ISimpleLocalEntitySnapshot[] = [];
    for (const output of rawDataArg.outputs) {
      entities.push(this.entityFromOutput(output));
    }
    for (const occupancy of rawDataArg.occupancyGroups) {
      entities.push({
        id: `occupancy_${occupancy.integrationId}`,
        uniqueId: `${lutronProfile.domain}_${rawDataArg.guid || 'controller'}_occupancy_${occupancy.integrationId}`,
        name: `${occupancy.areaName} Occupancy`,
        platform: 'binary_sensor',
        state: null,
        available: rawDataArg.stateError === undefined,
        writable: false,
        deviceClass: 'occupancy',
        attributes: { integrationId: occupancy.integrationId, groupNumber: occupancy.groupNumber, uuid: occupancy.uuid },
      });
    }
    for (const button of rawDataArg.buttons) {
      entities.push({
        id: `button_${button.keypadIntegrationId}_${button.componentNumber}`,
        uniqueId: `${lutronProfile.domain}_${rawDataArg.guid || 'controller'}_button_${button.keypadIntegrationId}_${button.componentNumber}`,
        name: `${button.areaName} ${button.keypadName} ${button.name}`,
        platform: 'button',
        state: 'idle',
        available: true,
        writable: this.canUseTelnet(),
        attributes: {
          keypadIntegrationId: button.keypadIntegrationId,
          componentNumber: button.componentNumber,
          buttonType: button.buttonType,
          uuid: button.uuid,
        },
      });
    }

    return {
      device: {
        id: this.localConfig.uniqueId || rawDataArg.guid || this.localConfig.host || 'lutron',
        name: this.localConfig.name || rawDataArg.projectName || 'Lutron Main Repeater',
        manufacturer: 'Lutron',
        model: 'RadioRA 2 Main Repeater',
        serialNumber: rawDataArg.guid,
        host: this.localConfig.host || this.urlHost(),
        port: this.telnetPort(),
        protocol: 'telnet',
        configurationUrl: this.httpOrigin(),
        attributes: { guid: rawDataArg.guid, projectName: rawDataArg.projectName },
      },
      entities,
      online: true,
      updatedAt: rawDataArg.fetchedAt,
      source: 'http',
      rawData: rawDataArg,
      error: rawDataArg.stateError,
      capabilities: {
        localRead: true,
        localControl: this.canUseTelnet(),
        xmlDatabase: true,
        telnetState: this.canUseTelnet() && rawDataArg.stateError === undefined,
      },
    };
  }

  private entityFromOutput(outputArg: ILutronOutputRaw): ISimpleLocalEntitySnapshot {
    const platform = this.platformForOutput(outputArg.outputType);
    const level = outputArg.level;
    const state = platform === 'switch' || platform === 'light' || platform === 'fan'
      ? level === undefined ? null : level > 0
      : level ?? null;
    return {
      id: `output_${outputArg.integrationId}`,
      uniqueId: `${lutronProfile.domain}_output_${outputArg.uuid || outputArg.integrationId}`,
      name: `${outputArg.areaName} ${outputArg.name}`,
      platform,
      state,
      available: true,
      writable: this.canUseTelnet(),
      attributes: {
        integrationId: outputArg.integrationId,
        uuid: outputArg.uuid,
        outputType: outputArg.outputType,
        areaName: outputArg.areaName,
        level,
        wattage: outputArg.wattage,
      },
    };
  }

  private parseLutronXml(xmlArg: string): ILutronRawSnapshot {
    const root = this.parseXml(xmlArg);
    const projectRoot = root.children[0] || root;
    const guid = this.textOf(this.findFirst(projectRoot, 'GUID'));
    const occupancyByGroup = new Map<string, string | undefined>();
    for (const group of this.findChildren(this.findFirst(projectRoot, 'OccupancyGroups'), 'OccupancyGroup')) {
      const number = group.attributes.OccupancyGroupNumber;
      if (number) {
        occupancyByGroup.set(number, group.attributes.UUID);
      }
    }

    const areasRoot = this.findFirst(projectRoot, 'Areas');
    const topArea = this.findChildren(areasRoot, 'Area')[0];
    const projectName = topArea?.attributes.Name;
    const outputs: ILutronOutputRaw[] = [];
    const occupancyGroups: ILutronOccupancyRaw[] = [];
    const buttons: ILutronButtonRaw[] = [];
    const realAreasRoot = this.findFirst(topArea, 'Areas');
    const realAreas = realAreasRoot ? this.findChildren(realAreasRoot, 'Area') : this.findChildren(areasRoot, 'Area');

    for (const area of realAreas) {
      this.parseArea(area, outputs, occupancyGroups, buttons, occupancyByGroup);
    }

    return { guid, projectName, outputs, occupancyGroups, buttons, fetchedAt: new Date().toISOString() };
  }

  private parseArea(areaArg: ILutronXmlNode, outputsArg: ILutronOutputRaw[], occupancyGroupsArg: ILutronOccupancyRaw[], buttonsArg: ILutronButtonRaw[], occupancyByGroupArg: Map<string, string | undefined>): void {
    const areaName = areaArg.attributes.Name || 'Unknown Area';
    const areaIntegrationId = this.numberValue(areaArg.attributes.IntegrationID) || 0;
    const occupancyGroupNumber = areaArg.attributes.OccupancyGroupAssignedToID;
    if (occupancyGroupNumber) {
      occupancyGroupsArg.push({
        integrationId: areaIntegrationId,
        areaName,
        groupNumber: occupancyGroupNumber,
        uuid: occupancyByGroupArg.get(occupancyGroupNumber),
      });
    }

    for (const output of this.findChildren(this.findFirst(areaArg, 'Outputs'), 'Output')) {
      const integrationId = this.numberValue(output.attributes.IntegrationID);
      if (integrationId === undefined) {
        continue;
      }
      outputsArg.push({
        integrationId,
        uuid: output.attributes.UUID,
        name: output.attributes.Name || `Output ${integrationId}`,
        areaName,
        outputType: output.attributes.OutputType || 'Unknown',
        wattage: this.numberValue(output.attributes.Wattage),
      });
    }

    this.parseKeypads(areaArg, areaName, buttonsArg);

    for (const childArea of this.findChildren(this.findFirst(areaArg, 'Areas'), 'Area')) {
      this.parseArea(childArea, outputsArg, occupancyGroupsArg, buttonsArg, occupancyByGroupArg);
    }
  }

  private parseKeypads(areaArg: ILutronXmlNode, areaNameArg: string, buttonsArg: ILutronButtonRaw[]): void {
    const deviceGroups = this.findFirst(areaArg, 'DeviceGroups');
    if (!deviceGroups) {
      return;
    }
    for (const groupOrDevice of deviceGroups.children) {
      const devices = groupOrDevice.name === 'Device' ? [groupOrDevice] : this.findChildren(this.findFirst(groupOrDevice, 'Devices'), 'Device');
      for (const device of devices) {
        if (!keypadDeviceTypes.has(device.attributes.DeviceType || '')) {
          continue;
        }
        const keypadId = this.numberValue(device.attributes.IntegrationID);
        if (keypadId === undefined) {
          continue;
        }
        const keypadName = device.attributes.Name || groupOrDevice.attributes.Name || 'Keypad';
        for (const component of this.findChildren(this.findFirst(device, 'Components'), 'Component')) {
          if (component.attributes.ComponentType !== 'BUTTON') {
            continue;
          }
          const button = this.findFirst(component, 'Button');
          if (!button) {
            continue;
          }
          const componentNumber = this.numberValue(component.attributes.ComponentNumber);
          if (componentNumber === undefined) {
            continue;
          }
          const direction = button.attributes.Direction;
          const buttonType = button.attributes.ButtonType || 'Unknown';
          const engraving = button.attributes.Engraving || (buttonType.includes('RaiseLower') ? `Dimmer ${direction || ''}`.trim() : undefined) || 'Unknown Button';
          buttonsArg.push({
            keypadIntegrationId: keypadId,
            componentNumber,
            uuid: button.attributes.UUID,
            name: engraving,
            areaName: areaNameArg,
            keypadName,
            buttonType,
          });
        }
      }
    }
  }

  private async fillOutputLevels(outputsArg: ILutronOutputRaw[]): Promise<void> {
    if (!outputsArg.length) {
      return;
    }
    await this.withTelnet(async (sessionArg) => {
      for (const output of outputsArg) {
        const response = await sessionArg.send(`?OUTPUT,${output.integrationId},1`, new RegExp(`~OUTPUT,${output.integrationId},1,([0-9.]+)`));
        const match = response.match(new RegExp(`~OUTPUT,${output.integrationId},1,([0-9.]+)`));
        if (match) {
          output.level = Number(match[1]);
        }
      }
    });
  }

  private commandForService(requestArg: IServiceCallRequest): string | undefined {
    const data = requestArg.data || {};
    if (typeof data.command === 'string') {
      return data.command;
    }
    const integrationId = this.numberValue(data.integrationId ?? data.lutronIntegrationId ?? data.outputId ?? data.id);
    const keypadId = this.numberValue(data.keypadIntegrationId ?? data.keypadId);
    const componentNumber = this.numberValue(data.componentNumber ?? data.buttonNumber ?? data.ledNumber);

    if ((requestArg.domain === 'light' || requestArg.domain === 'switch' || requestArg.domain === 'fan' || requestArg.domain === 'cover') && integrationId !== undefined) {
      if (requestArg.service === 'turn_off' || requestArg.service === 'close_cover') {
        return `#OUTPUT,${integrationId},1,0`;
      }
      if (requestArg.service === 'turn_on' || requestArg.service === 'open_cover') {
        const level = this.outputLevelForService(requestArg);
        return `#OUTPUT,${integrationId},1,${level}`;
      }
      if (requestArg.service === 'set_cover_position') {
        const position = this.numberValue(data.position ?? data.POSITION) ?? 0;
        return `#OUTPUT,${integrationId},1,${Math.max(0, Math.min(100, position))}`;
      }
      if (requestArg.service === 'stop_cover') {
        return `#OUTPUT,${integrationId},4`;
      }
    }

    if ((requestArg.domain === 'button' || requestArg.service === 'press') && keypadId !== undefined && componentNumber !== undefined) {
      return `#DEVICE,${keypadId},${componentNumber},3`;
    }

    if (requestArg.domain === 'select' && requestArg.service === 'select_option' && keypadId !== undefined && componentNumber !== undefined) {
      const option = String(data.option || data.value || '').toLowerCase();
      const state = ledOptionMap[option];
      if (state !== undefined) {
        return `#DEVICE,${keypadId},${componentNumber},9,${state}`;
      }
    }

    return undefined;
  }

  private outputLevelForService(requestArg: IServiceCallRequest): number {
    const data = requestArg.data || {};
    const explicit = this.numberValue(data.level ?? data.position ?? data.percentage);
    if (explicit !== undefined) {
      return Math.max(0, Math.min(100, explicit));
    }
    const brightness = this.numberValue(data.brightness);
    if (brightness !== undefined) {
      return Math.max(0, Math.min(100, Math.round((brightness * 100) / 255)));
    }
    return requestArg.domain === 'fan' ? 67 : 100;
  }

  private async withTelnet<TValue>(handlerArg: (sessionArg: ILutronTelnetSession) => Promise<TValue>): Promise<TValue> {
    const session = await this.openTelnetSession();
    try {
      return await handlerArg(session);
    } finally {
      session.close();
    }
  }

  private async openTelnetSession(): Promise<ILutronTelnetSession> {
    const host = this.localConfig.host || this.urlHost();
    if (!host || !this.localConfig.username || !this.localConfig.password) {
      throw new Error('Lutron Telnet requires host, username, and password.');
    }
    const timeoutMs = this.localConfig.timeoutMs || 5000;
    const socket = plugins.net.createConnection({ host, port: this.telnetPort() });
    let buffer = '';
    let closed = false;
    const waiters: Array<() => void> = [];

    const notify = () => {
      for (const waiter of [...waiters]) {
        waiter();
      }
    };
    socket.on('data', (chunkArg: Buffer | string) => {
      buffer += this.decodeTelnetChunk(socket, Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(String(chunkArg)));
      notify();
    });
    socket.on('close', () => {
      closed = true;
      notify();
    });
    socket.on('error', () => {
      closed = true;
      notify();
    });

    await new Promise<void>((resolve, reject) => {
      const timer = setTimeout(() => reject(new Error(`Lutron Telnet connection to ${host}:${this.telnetPort()} timed out after ${timeoutMs}ms.`)), timeoutMs);
      socket.once('connect', () => {
        clearTimeout(timer);
        resolve();
      });
      socket.once('error', (errorArg) => {
        clearTimeout(timer);
        reject(errorArg);
      });
    });

    const waitFor = async (patternArg: RegExp): Promise<string> => {
      const started = Date.now();
      while (!patternArg.test(buffer)) {
        if (closed) {
          throw new Error(`Lutron Telnet connection closed while waiting for ${patternArg}.`);
        }
        const elapsed = Date.now() - started;
        if (elapsed >= timeoutMs) {
          throw new Error(`Lutron Telnet timed out waiting for ${patternArg}.`);
        }
        await new Promise<void>((resolve) => {
          const waiter = () => {
            const index = waiters.indexOf(waiter);
            if (index >= 0) {
              waiters.splice(index, 1);
            }
            resolve();
          };
          waiters.push(waiter);
          setTimeout(waiter, Math.min(50, timeoutMs - elapsed));
        });
      }
      return buffer;
    };

    await waitFor(/login:\s*$/i);
    socket.write(`${this.localConfig.username}\r\n`, 'ascii');
    await waitFor(/password:\s*$/i);
    socket.write(`${this.localConfig.password}\r\n`, 'ascii');
    const loginResponse = await waitFor(/([GQ]NET>|login:\s*$)/i);
    if (/login:\s*$/i.test(loginResponse)) {
      throw new Error('Lutron Telnet login failed.');
    }
    buffer = '';

    return {
      send: async (commandArg, responsePatternArg) => {
        buffer = '';
        socket.write(`${commandArg}\r\n`, 'ascii');
        if (!responsePatternArg) {
          await new Promise<void>((resolve) => setTimeout(resolve, 20));
          return '';
        }
        return waitFor(responsePatternArg);
      },
      close: () => socket.destroy(),
    };
  }

  private decodeTelnetChunk(socketArg: ReturnType<typeof plugins.net.createConnection>, chunkArg: Buffer): string {
    const bytes: number[] = [];
    for (let index = 0; index < chunkArg.length; index++) {
      const byte = chunkArg[index];
      if (byte === 255 && index + 2 < chunkArg.length) {
        const command = chunkArg[index + 1];
        const option = chunkArg[index + 2];
        if (command === 251 || command === 252) {
          socketArg.write(Buffer.from([255, 254, option]));
          index += 2;
          continue;
        }
        if (command === 253 || command === 254) {
          socketArg.write(Buffer.from([255, 252, option]));
          index += 2;
          continue;
        }
      }
      bytes.push(byte);
    }
    return Buffer.from(bytes).toString('ascii');
  }

  private platformForOutput(outputTypeArg: string): ISimpleLocalEntitySnapshot['platform'] {
    if (outputTypeArg === 'SYSTEM_SHADE' || outputTypeArg === 'MOTOR') {
      return 'cover';
    }
    if (outputTypeArg === 'CEILING_FAN_TYPE') {
      return 'fan';
    }
    return this.isDimmable(outputTypeArg) ? 'light' : 'switch';
  }

  private isDimmable(outputTypeArg: string): boolean {
    return !nonDimmableOutputTypes.has(outputTypeArg) && !outputTypeArg.startsWith('CCO_');
  }

  private parseXml(xmlArg: string): ILutronXmlNode {
    const root: ILutronXmlNode = { name: 'root', attributes: {}, children: [], text: '' };
    const stack: ILutronXmlNode[] = [root];
    const tagPattern = /<([^>]+)>/g;
    let cursor = 0;
    let match: RegExpExecArray | null;
    while ((match = tagPattern.exec(xmlArg))) {
      const text = xmlArg.slice(cursor, match.index);
      if (text.trim()) {
        stack[stack.length - 1].text += this.decodeXml(text.trim());
      }
      cursor = tagPattern.lastIndex;
      const raw = match[1].trim();
      if (!raw || raw.startsWith('?') || raw.startsWith('!')) {
        continue;
      }
      if (raw.startsWith('/')) {
        if (stack.length > 1) {
          stack.pop();
        }
        continue;
      }
      const selfClosing = raw.endsWith('/');
      const content = selfClosing ? raw.slice(0, -1).trim() : raw;
      const spaceIndex = content.search(/\s/);
      const name = spaceIndex >= 0 ? content.slice(0, spaceIndex) : content;
      const attributeText = spaceIndex >= 0 ? content.slice(spaceIndex + 1) : '';
      const node: ILutronXmlNode = { name, attributes: this.parseAttributes(attributeText), children: [], text: '' };
      stack[stack.length - 1].children.push(node);
      if (!selfClosing) {
        stack.push(node);
      }
    }
    return root;
  }

  private parseAttributes(attributeTextArg: string): Record<string, string> {
    const attributes: Record<string, string> = {};
    const attributePattern = /([A-Za-z_:][\w:.-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
    let match: RegExpExecArray | null;
    while ((match = attributePattern.exec(attributeTextArg))) {
      attributes[match[1]] = this.decodeXml(match[3] ?? match[4] ?? '');
    }
    return attributes;
  }

  private findFirst(nodeArg: ILutronXmlNode | undefined, nameArg: string): ILutronXmlNode | undefined {
    if (!nodeArg) {
      return undefined;
    }
    return nodeArg.children.find((childArg) => childArg.name === nameArg);
  }

  private findChildren(nodeArg: ILutronXmlNode | undefined, nameArg: string): ILutronXmlNode[] {
    return nodeArg?.children.filter((childArg) => childArg.name === nameArg) || [];
  }

  private textOf(nodeArg: ILutronXmlNode | undefined): string | undefined {
    const value = nodeArg?.text.trim();
    return value || undefined;
  }

  private decodeXml(valueArg: string): string {
    return valueArg
      .replace(/&quot;/g, '"')
      .replace(/&apos;/g, "'")
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')
      .replace(/&amp;/g, '&');
  }

  private xmlUrl(): string {
    if (this.localConfig.url) {
      return this.localConfig.url;
    }
    const port = this.httpPort();
    const portPart = port === 80 ? '' : `:${port}`;
    const path = this.localConfig.xmlPath || this.localConfig.path || '/DbXmlInfo.xml';
    return `http://${this.localConfig.host}${portPart}${path.startsWith('/') ? path : `/${path}`}`;
  }

  private httpOrigin(): string | undefined {
    if (this.localConfig.url) {
      const url = new URL(this.localConfig.url);
      return `${url.protocol}//${url.host}`;
    }
    if (!this.localConfig.host) {
      return undefined;
    }
    const port = this.httpPort();
    return `http://${this.localConfig.host}${port === 80 ? '' : `:${port}`}`;
  }

  private httpPort(): number {
    return this.localConfig.httpPort || (this.localConfig.transport === 'http' ? this.localConfig.port : undefined) || 80;
  }

  private telnetPort(): number {
    return this.localConfig.telnetPort || (this.localConfig.transport === 'tcp' ? this.localConfig.port : undefined) || 23;
  }

  private canUseTelnet(): boolean {
    return Boolean((this.localConfig.host || this.urlHost()) && this.localConfig.username && this.localConfig.password);
  }

  private urlHost(): string | undefined {
    return this.localConfig.url ? new URL(this.localConfig.url).hostname : undefined;
  }

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

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

  private errorMessage(errorArg: unknown): string {
    return errorArg instanceof Error ? errorArg.message : String(errorArg);
  }
}
