import * as plugins from '../../plugins.js';
import { SimpleLocalClient, SimpleLocalMapper, type IServiceCallRequest, type IServiceCallResult, type ISimpleLocalEntitySnapshot, type ISimpleLocalSnapshot } from '../../core/index.js';
import { JvcProjectorMapper } from './jvc_projector.mapper.js';
import type { IJvcProjectorConfig } from './jvc_projector.types.js';
import { jvcProjectorProfile } from './jvc_projector.types.js';

export class JvcProjectorClientError extends Error {
  constructor(messageArg: string) {
    super(messageArg);
    this.name = 'JvcProjectorClientError';
  }
}

type TJvcProjectorCommandKey = 'ModelName' | 'MacAddress' | 'Version' | 'Power' | 'Input' | 'Signal' | 'LightTime' | 'Remote' | 'LowLatencyMode' | 'EShift';

interface IJvcProjectorEndpoint {
  host: string;
  port: number;
}

interface IJvcProjectorCommandDefinition {
  code: string;
  read?: Record<string, string>;
  write?: Record<string, string>;
  readTransform?: (valueArg: string) => string;
  operationTimeoutMs?: number;
}

const pjOk = Buffer.from('PJ_OK', 'latin1');
const pjNg = Buffer.from('PJ_NG', 'latin1');
const pjReq = Buffer.from('PJREQ', 'latin1');
const pjAck = Buffer.from('PJACK', 'latin1');
const pjNak = Buffer.from('PJNAK', 'latin1');
const jvcUnitId = Buffer.from([0x89, 0x01]);
const headOp = Buffer.concat([Buffer.from('!', 'latin1'), jvcUnitId]);
const headRef = Buffer.concat([Buffer.from('?', 'latin1'), jvcUnitId]);
const headRes = Buffer.concat([Buffer.from('@', 'latin1'), jvcUnitId]);
const headAck = Buffer.concat([Buffer.from([0x06]), jvcUnitId]);
const jvcEnd = Buffer.from('\n', 'latin1');
const authSalt = 'JVCKWPJ';

const onPowerStates = new Set(['on', 'warming']);
const jvcReadWrite = (valuesArg: Record<string, string>): { read: Record<string, string>; write: Record<string, string> } => ({ read: valuesArg, write: valuesArg });

const remoteCommandMap: Record<string, string> = {
  '732F': 'ok',
  '732E': 'menu',
  '7303': 'back',
  '731D': 'hide',
  '7301': 'up',
  '7302': 'down',
  '7336': 'left',
  '7334': 'right',
  '7374': 'info',
  '7308': 'input',
  '7370': 'hdmi1',
  '7371': 'hdmi2',
  '73D4': 'setting-memory',
  '7330': 'lens-control',
  '73F4': 'picture-mode',
  '7388': 'color-profile',
  '73F5': 'gamma-settings',
  '738A': 'cmd',
  '73F0': 'mpc',
  '7373': 'advanced-menu',
  '73D8': 'mode-1',
  '73D9': 'mode-2',
  '73DA': 'mode-3',
  '73E5': 'mode-4',
  '73E6': 'mode-5',
  '73E7': 'mode-6',
  '73E8': 'mode-7',
  '73E9': 'mode-8',
  '73EA': 'mode-9',
  '73EB': 'mode-10',
  '7320': 'lens-aperture',
  '7372': 'picture-adjust',
  '73C5': 'anamorphic',
  '7375': 'gamma',
  '7376': 'color-temp',
  '73D6': '3d-format',
  '736A': 'natural',
  '7368': 'cinema',
};

const jvcCommandDefinitions: Record<TJvcProjectorCommandKey, IJvcProjectorCommandDefinition> = {
  ModelName: {
    code: 'MD',
    readTransform: (valueArg: string) => valueArg.replace(/ILAFPJ\W+/g, ''),
  },
  MacAddress: {
    code: 'LSMA',
    readTransform: (valueArg: string) => valueArg.replace(/ /g, '-').replace(/-+/g, '-'),
  },
  Version: {
    code: 'IFSV',
    readTransform: (valueArg: string) => valueArg,
  },
  Power: {
    code: 'PW',
    read: { '0': 'standby', '1': 'on', '2': 'cooling', '3': 'warming', '4': 'error' },
    write: { '0': 'off', '1': 'on' },
  },
  Input: {
    code: 'IP',
    ...jvcReadWrite({ '6': 'hdmi1', '7': 'hdmi2' }),
    operationTimeoutMs: 10000,
  },
  Signal: {
    code: 'SC',
    read: { '0': 'none', '1': 'signal' },
  },
  LightTime: {
    code: 'IFLT',
    readTransform: (valueArg: string) => String(Number.parseInt(valueArg, 16)),
  },
  Remote: {
    code: 'RC',
    write: remoteCommandMap,
  },
  LowLatencyMode: {
    code: 'PMLL',
    ...jvcReadWrite({ '0': 'off', '1': 'on' }),
  },
  EShift: {
    code: 'PMUS',
    ...jvcReadWrite({ '0': 'off', '1': 'on' }),
  },
};

export class JvcProjectorClient extends SimpleLocalClient<IJvcProjectorConfig> {
  private liveSnapshot?: ISimpleLocalSnapshot;

  constructor(private readonly configArg: IJvcProjectorConfig) {
    super(jvcProjectorProfile, configArg);
  }

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

    if (this.configArg.client || this.configArg.snapshotProvider || (!forceRefreshArg && this.configArg.snapshot)) {
      return super.getSnapshot(forceRefreshArg);
    }

    if (this.endpoint() && this.configArg.transport !== 'snapshot') {
      try {
        this.liveSnapshot = await this.fetchSnapshot();
      } catch (errorArg) {
        this.liveSnapshot = this.createOfflineSnapshot(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 && snapshot.source !== 'runtime';
    return { success, error: success ? undefined : snapshot.error, data: snapshot };
  }

  public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
    if (this.configArg.client?.execute || this.configArg.commandExecutor) {
      return super.execute(requestArg);
    }
    if (!this.endpoint() || this.configArg.transport === 'snapshot') {
      return super.execute(requestArg);
    }
    if (this.configArg.useTls) {
      return { success: false, error: 'JVC Projector TLS is not implemented: Home Assistant pyjvcprojector uses plain local TCP on port 20554.' };
    }

    const session = new JvcProjectorTcpSession(this.endpoint()!, this.configArg.password, this.timeoutMs());
    try {
      await session.connect();
      const data = await this.executeNativeRequest(session, requestArg);
      this.liveSnapshot = undefined;
      return { success: true, data };
    } catch (errorArg) {
      return { success: false, error: errorMessage(errorArg) };
    } finally {
      session.close();
    }
  }

  private async fetchSnapshot(): Promise<ISimpleLocalSnapshot> {
    if (this.configArg.useTls) {
      throw new JvcProjectorClientError('JVC Projector TLS is not implemented: Home Assistant pyjvcprojector uses plain local TCP on port 20554.');
    }

    const endpoint = this.endpoint();
    if (!endpoint) {
      throw new JvcProjectorClientError('JVC Projector TCP polling requires config.host or tcp:// config.url.');
    }

    const state: Record<string, string | undefined> = {};
    const session = new JvcProjectorTcpSession(endpoint, this.configArg.password, this.timeoutMs());
    try {
      await session.connect();
      state.ModelName = await optional(() => session.reference('ModelName')) || stringValue(this.configArg.modelName) || stringValue(this.configArg.model);
      state.MacAddress = await optional(() => session.reference('MacAddress')) || this.configArg.macAddress;
      state.Version = await optional(() => session.reference('Version'));
      state.Power = await session.reference('Power');
      if (state.Power === 'on') {
        state.Signal = await optional(() => session.reference('Signal'));
        state.Input = await optional(() => session.reference('Input'));
        state.LightTime = await optional(() => session.reference('LightTime'));
      } else {
        state.Signal = 'none';
      }
    } finally {
      session.close();
    }

    const uniqueBase = this.configArg.uniqueId || this.configArg.deviceId || state.MacAddress || `${endpoint.host}:${endpoint.port}`;
    const deviceName = this.configArg.name || (state.ModelName ? `JVC Projector ${state.ModelName}` : jvcProjectorProfile.defaultName);
    const entities: ISimpleLocalEntitySnapshot[] = [
      {
        id: 'power',
        uniqueId: `${SimpleLocalMapper.slug(uniqueBase)}_power`,
        name: 'Power',
        platform: 'binary_sensor' as const,
        state: onPowerStates.has(state.Power || ''),
        available: true,
        writable: true,
        attributes: { power: state.Power },
      },
      {
        id: 'power_state',
        uniqueId: `${SimpleLocalMapper.slug(uniqueBase)}_power_state`,
        name: 'Power State',
        platform: 'sensor' as const,
        state: state.Power || 'unknown',
        available: true,
        attributes: { deviceClass: 'enum' },
      },
      {
        id: 'model_name',
        uniqueId: `${SimpleLocalMapper.slug(uniqueBase)}_model_name`,
        name: 'Model Name',
        platform: 'sensor' as const,
        state: state.ModelName || 'unknown',
        available: Boolean(state.ModelName),
      },
      {
        id: 'signal',
        uniqueId: `${SimpleLocalMapper.slug(uniqueBase)}_signal`,
        name: 'Signal',
        platform: 'sensor' as const,
        state: state.Signal || 'unknown',
        available: Boolean(state.Signal),
      },
    ];

    if (state.Input) {
      entities.push({
        id: 'input',
        uniqueId: `${SimpleLocalMapper.slug(uniqueBase)}_input`,
        name: 'Input',
        platform: 'select' as const,
        state: state.Input,
        available: true,
        writable: true,
        attributes: { options: Object.values(jvcCommandDefinitions.Input.write || {}) },
      });
    }
    if (state.LightTime) {
      entities.push({
        id: 'light_time',
        uniqueId: `${SimpleLocalMapper.slug(uniqueBase)}_light_time`,
        name: 'Light Time',
        platform: 'sensor' as const,
        state: Number(state.LightTime),
        available: true,
        attributes: { unit: 'h' },
      });
    }
    if (state.Version) {
      entities.push({
        id: 'software_version',
        uniqueId: `${SimpleLocalMapper.slug(uniqueBase)}_software_version`,
        name: 'Software Version',
        platform: 'sensor' as const,
        state: state.Version,
        available: true,
      });
    }

    const rawData = {
      device: {
        id: uniqueBase,
        name: deviceName,
        manufacturer: 'JVC',
        model: state.ModelName || jvcProjectorProfile.model,
        serialNumber: state.MacAddress,
        host: endpoint.host,
        port: endpoint.port,
        protocol: 'tcp' as const,
        configurationUrl: `tcp://${endpoint.host}:${endpoint.port}`,
      },
      state,
      entities,
      fetchedAt: new Date().toISOString(),
    };

    return {
      ...JvcProjectorMapper.toSnapshot({ config: this.configArg, rawData, online: true, source: 'tcp' }),
      capabilities: {
        localRead: true,
        localControl: true,
        liveControlRequiresExecutor: false,
        nativeCommands: Object.keys(jvcCommandDefinitions),
      },
    };
  }

  private async executeNativeRequest(sessionArg: JvcProjectorTcpSession, requestArg: IServiceCallRequest): Promise<unknown> {
    if (requestArg.service === 'send_command' || requestArg.service === 'press') {
      const commands = remoteCommandsFromRequest(requestArg);
      if (!commands.length) {
        throw new JvcProjectorClientError('JVC Projector remote control requires data.command, data.commands, or data.button.');
      }
      for (const command of commands) {
        await sessionArg.operation('Remote', normalizeRemoteCommand(command));
      }
      return { commands };
    }

    if (requestArg.service === 'select_option') {
      const option = stringValue(requestArg.data?.option) || stringValue(requestArg.data?.value);
      if (!option) {
        throw new JvcProjectorClientError('JVC Projector select_option requires data.option.');
      }
      const command = commandKeyFromRequest(requestArg) || 'Input';
      await sessionArg.operation(command, option);
      return { command, option };
    }

    if (requestArg.service === 'turn_on' || requestArg.service === 'turn_off') {
      const command = commandKeyFromRequest(requestArg) || 'Power';
      const value = requestArg.service === 'turn_on' ? 'on' : command === 'Power' ? 'off' : 'off';
      await sessionArg.operation(command, value);
      return { command, value };
    }

    if (requestArg.service === 'toggle') {
      const command = commandKeyFromRequest(requestArg) || 'Power';
      const current = await sessionArg.reference(command);
      const value = current === 'on' ? 'off' : 'on';
      await sessionArg.operation(command, value);
      return { command, previous: current, value };
    }

    throw new JvcProjectorClientError(`Unsupported JVC Projector service: ${requestArg.service}`);
  }

  private endpoint(): IJvcProjectorEndpoint | undefined {
    if (this.configArg.url) {
      try {
        const url = new URL(this.configArg.url);
        if (url.protocol !== 'tcp:') {
          return undefined;
        }
        return { host: url.hostname, port: url.port ? Number(url.port) : jvcProjectorProfile.defaultPort || 20554 };
      } catch {
        return undefined;
      }
    }
    if (!this.configArg.host) {
      return undefined;
    }
    return { host: this.configArg.host, port: this.configArg.port || jvcProjectorProfile.defaultPort || 20554 };
  }

  private timeoutMs(): number {
    return this.configArg.timeoutMs || 5000;
  }

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

class JvcProjectorTcpSession {
  private socket?: plugins.net.Socket;
  private buffer: Buffer<ArrayBufferLike> = Buffer.alloc(0);

  constructor(private readonly endpointArg: IJvcProjectorEndpoint, private readonly passwordArg: unknown, private readonly timeoutMsArg: number) {}

  public async connect(): Promise<void> {
    this.socket = plugins.net.createConnection({ host: this.endpointArg.host, port: this.endpointArg.port });
    await new Promise<void>((resolve, reject) => {
      const timer = setTimeout(() => reject(new JvcProjectorClientError(`JVC Projector TCP connect to ${this.endpointArg.host}:${this.endpointArg.port} timed out after ${this.timeoutMsArg}ms.`)), this.timeoutMsArg);
      const cleanup = () => {
        clearTimeout(timer);
        this.socket?.off('connect', onConnect);
        this.socket?.off('error', onError);
      };
      const onConnect = () => {
        cleanup();
        resolve();
      };
      const onError = (errorArg: Error) => {
        cleanup();
        reject(errorArg);
      };
      this.socket!.once('connect', onConnect);
      this.socket!.once('error', onError);
    });

    const greeting = await this.readBytes(pjOk.length, this.timeoutMsArg, 'handshake init');
    if (greeting.equals(pjNg)) {
      throw new JvcProjectorClientError('JVC Projector reported PJ_NG during handshake.');
    }
    if (!greeting.equals(pjOk)) {
      throw new JvcProjectorClientError(`JVC Projector invalid handshake greeting: ${greeting.toString('latin1')}`);
    }

    await this.write(Buffer.concat([pjReq, this.authSuffix(false)]));
    let auth = await this.readBytes(pjAck.length, this.timeoutMsArg, 'handshake ack');
    if (auth.equals(pjNak) && this.password()) {
      await this.write(Buffer.concat([pjReq, this.authSuffix(true)]));
      auth = await this.readBytes(pjAck.length, this.timeoutMsArg, 'SHA256 handshake ack');
    }
    if (auth.equals(pjNak)) {
      throw new JvcProjectorClientError('JVC Projector authentication failed.');
    }
    if (!auth.equals(pjAck)) {
      throw new JvcProjectorClientError(`JVC Projector invalid handshake ack: ${auth.toString('latin1')}`);
    }
  }

  public async reference(commandArg: TJvcProjectorCommandKey): Promise<string> {
    const definition = jvcCommandDefinitions[commandArg];
    await this.send(definition, undefined);
    const line = await this.readLine(this.timeoutMsArg, `${commandArg} reference response`);
    const codePrefix = definition.code.slice(0, 2);
    const expected = Buffer.concat([headRes, Buffer.from(codePrefix, 'latin1')]);
    if (!line.subarray(0, expected.length).equals(expected)) {
      throw new JvcProjectorClientError(`JVC Projector invalid ${commandArg} response header: ${line.toString('hex')}`);
    }
    const rawValue = line.subarray(expected.length, line.length - 1).toString('latin1');
    return decodeJvcValue(commandArg, rawValue);
  }

  public async operation(commandArg: TJvcProjectorCommandKey, valueArg: string): Promise<void> {
    const definition = jvcCommandDefinitions[commandArg];
    if (!definition.write) {
      throw new JvcProjectorClientError(`JVC Projector command ${commandArg} does not support writes.`);
    }
    await this.send(definition, encodeJvcValue(commandArg, valueArg));
  }

  public close(): void {
    this.socket?.removeAllListeners();
    this.socket?.destroy();
    this.socket = undefined;
  }

  private async send(definitionArg: IJvcProjectorCommandDefinition, valueArg: string | undefined): Promise<void> {
    const code = Buffer.from(definitionArg.code, 'latin1');
    const payload = valueArg === undefined
      ? Buffer.concat([headRef, code, jvcEnd])
      : Buffer.concat([headOp, code, Buffer.from(valueArg, 'latin1'), jvcEnd]);
    await this.write(payload);

    const ack = await this.readLine(definitionArg.operationTimeoutMs || this.timeoutMsArg, `${definitionArg.code} ack`);
    const expected = Buffer.concat([headAck, Buffer.from(definitionArg.code.slice(0, 2), 'latin1')]);
    if (!ack.subarray(0, expected.length).equals(expected)) {
      throw new JvcProjectorClientError(`JVC Projector invalid ack for ${definitionArg.code}: ${ack.toString('hex')}`);
    }
  }

  private async write(dataArg: Buffer): Promise<void> {
    if (!this.socket) {
      throw new JvcProjectorClientError('JVC Projector TCP socket is not connected.');
    }
    await new Promise<void>((resolve, reject) => {
      this.socket!.write(dataArg, (errorArg?: Error | null) => errorArg ? reject(errorArg) : resolve());
    });
  }

  private async readBytes(lengthArg: number, timeoutMsArg: number, labelArg: string): Promise<Buffer<ArrayBufferLike>> {
    return this.readUntil((bufferArg) => {
      if (bufferArg.length < lengthArg) {
        return undefined;
      }
      return { data: bufferArg.subarray(0, lengthArg), rest: bufferArg.subarray(lengthArg) };
    }, timeoutMsArg, labelArg);
  }

  private async readLine(timeoutMsArg: number, labelArg: string): Promise<Buffer<ArrayBufferLike>> {
    return this.readUntil((bufferArg) => {
      const newlineIndex = bufferArg.indexOf(0x0a);
      if (newlineIndex < 0) {
        return undefined;
      }
      return { data: bufferArg.subarray(0, newlineIndex + 1), rest: bufferArg.subarray(newlineIndex + 1) };
    }, timeoutMsArg, labelArg);
  }

  private async readUntil(selectorArg: (bufferArg: Buffer<ArrayBufferLike>) => { data: Buffer<ArrayBufferLike>; rest: Buffer<ArrayBufferLike> } | undefined, timeoutMsArg: number, labelArg: string): Promise<Buffer<ArrayBufferLike>> {
    if (!this.socket) {
      throw new JvcProjectorClientError('JVC Projector TCP socket is not connected.');
    }
    return new Promise<Buffer>((resolve, reject) => {
      const cleanup = () => {
        clearTimeout(timer);
        this.socket?.off('data', onData);
        this.socket?.off('error', onError);
        this.socket?.off('close', onClose);
      };
      const tryResolve = () => {
        const selected = selectorArg(this.buffer);
        if (!selected) {
          return;
        }
        this.buffer = selected.rest;
        cleanup();
        resolve(selected.data);
      };
      const onData = (chunkArg: Buffer<ArrayBufferLike>) => {
        this.buffer = Buffer.concat([this.buffer, chunkArg]);
        tryResolve();
      };
      const onError = (errorArg: Error) => {
        cleanup();
        reject(errorArg);
      };
      const onClose = () => {
        cleanup();
        reject(new JvcProjectorClientError(`JVC Projector TCP socket closed while waiting for ${labelArg}.`));
      };
      const timer = setTimeout(() => {
        cleanup();
        reject(new JvcProjectorClientError(`JVC Projector ${labelArg} timed out after ${timeoutMsArg}ms.`));
      }, timeoutMsArg);

      this.socket!.on('data', onData);
      this.socket!.once('error', onError);
      this.socket!.once('close', onClose);
      tryResolve();
    });
  }

  private authSuffix(hashArg: boolean): Buffer {
    const password = this.password();
    if (!password) {
      return Buffer.alloc(0);
    }
    if (hashArg) {
      return Buffer.concat([Buffer.from('_', 'latin1'), Buffer.from(plugins.crypto.createHash('sha256').update(`${password}${authSalt}`).digest('hex'), 'latin1')]);
    }
    const passwordBuffer = Buffer.alloc(Math.max(16, Buffer.byteLength(password)));
    passwordBuffer.write(password, 'utf8');
    return Buffer.concat([Buffer.from('_', 'latin1'), passwordBuffer]);
  }

  private password(): string | undefined {
    return typeof this.passwordArg === 'string' && this.passwordArg.length ? this.passwordArg : undefined;
  }
}

const decodeJvcValue = (commandArg: TJvcProjectorCommandKey, rawValueArg: string): string => {
  const definition = jvcCommandDefinitions[commandArg];
  if (definition.readTransform) {
    return definition.readTransform(rawValueArg);
  }
  if (definition.read?.[rawValueArg] !== undefined) {
    return definition.read[rawValueArg];
  }
  throw new JvcProjectorClientError(`JVC Projector command ${commandArg} returned unmapped value '${rawValueArg}'.`);
};

const encodeJvcValue = (commandArg: TJvcProjectorCommandKey, valueArg: string): string => {
  const normalized = commandArg === 'Remote' ? normalizeRemoteCommand(valueArg) : valueArg;
  const definition = jvcCommandDefinitions[commandArg];
  for (const [raw, value] of Object.entries(definition.write || {})) {
    if (value === normalized) {
      return raw;
    }
  }
  throw new JvcProjectorClientError(`JVC Projector command ${commandArg} does not support value '${valueArg}'.`);
};

const commandKeyFromRequest = (requestArg: IServiceCallRequest): TJvcProjectorCommandKey | undefined => {
  const explicit = stringValue(requestArg.data?.command) || stringValue(requestArg.data?.key) || stringValue(requestArg.data?.entity);
  const haystack = `${explicit || ''} ${requestArg.target.entityId || ''} ${requestArg.target.deviceId || ''}`.toLowerCase();
  if (haystack.includes('low_latency')) {
    return 'LowLatencyMode';
  }
  if (haystack.includes('eshift') || haystack.includes('e_shift')) {
    return 'EShift';
  }
  if (haystack.includes('input')) {
    return 'Input';
  }
  if (haystack.includes('power')) {
    return 'Power';
  }
  return undefined;
};

const remoteCommandsFromRequest = (requestArg: IServiceCallRequest): string[] => {
  const value = requestArg.data?.command ?? requestArg.data?.commands ?? requestArg.data?.button;
  if (Array.isArray(value)) {
    return value.map((itemArg) => String(itemArg));
  }
  return value === undefined ? [] : [String(value)];
};

const normalizeRemoteCommand = (valueArg: string): string => {
  const renamed: Record<string, string> = {
    anamo: 'anamorphic',
    lens_ap: 'lens-aperture',
  };
  const normalized = (renamed[valueArg] || valueArg).replace(/_/g, '-').toLowerCase();
  if (!Object.values(remoteCommandMap).includes(normalized)) {
    throw new JvcProjectorClientError(`${valueArg} is not a known JVC Projector remote command.`);
  }
  return normalized;
};

const optional = async (callbackArg: () => Promise<string>): Promise<string | undefined> => {
  try {
    return await callbackArg();
  } catch {
    return undefined;
  }
};

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
const errorMessage = (errorArg: unknown): string => errorArg instanceof Error ? errorArg.message : String(errorArg);
